parent
b6d0488ea6
commit
c94be9c6a9
14 changed files with 478 additions and 30 deletions
@ -0,0 +1,32 @@ |
||||
--TEST-- |
||||
anonymous class stores constructor state and uses it in methods |
||||
--FILE-- |
||||
<?php |
||||
|
||||
abstract class AnonStateVisitor |
||||
{ |
||||
abstract public function visit(string $name): string; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$prefixes = ['node' => 'Node', 'leaf' => 'Leaf']; |
||||
|
||||
$visitor = new class($prefixes) extends AnonStateVisitor { |
||||
public function __construct(private array $prefixes) |
||||
{ |
||||
} |
||||
|
||||
public function visit(string $name): string |
||||
{ |
||||
return ($this->prefixes[$name] ?? 'Unknown') . ':' . $name; |
||||
} |
||||
}; |
||||
|
||||
var_dump($visitor->visit('node')); |
||||
var_dump($visitor->visit('missing')); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
string(9) "Node:node" |
||||
string(15) "Unknown:missing" |
||||
@ -0,0 +1,40 @@ |
||||
--TEST-- |
||||
function attributes are available through reflection |
||||
--FILE-- |
||||
<?php |
||||
|
||||
#[Attribute(Attribute::TARGET_FUNCTION)] |
||||
class AotFunctionMeta |
||||
{ |
||||
public function __construct(public string $name, public int $priority = 0) |
||||
{ |
||||
} |
||||
} |
||||
|
||||
#[AotFunctionMeta('handler', 10)] |
||||
function attributed_function(): string |
||||
{ |
||||
return 'ok'; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$func = new ReflectionFunction('attributed_function'); |
||||
$attrs = $func->getAttributes(AotFunctionMeta::class); |
||||
|
||||
var_dump($attrs[0]->getName()); |
||||
var_dump($attrs[0]->getArguments()); |
||||
var_dump($attrs[0]->newInstance()->name); |
||||
var_dump(attributed_function()); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
string(15) "AotFunctionMeta" |
||||
array(2) { |
||||
[0]=> |
||||
string(7) "handler" |
||||
[1]=> |
||||
int(10) |
||||
} |
||||
string(7) "handler" |
||||
string(2) "ok" |
||||
@ -0,0 +1,52 @@ |
||||
--TEST-- |
||||
function parameter and property attributes are available through reflection |
||||
--FILE-- |
||||
<?php |
||||
|
||||
#[Attribute(Attribute::TARGET_PARAMETER | Attribute::TARGET_PROPERTY)] |
||||
class AotMeta |
||||
{ |
||||
public function __construct(public string $name, public int $order = 0) |
||||
{ |
||||
} |
||||
} |
||||
|
||||
class AttributeHolder |
||||
{ |
||||
#[AotMeta('property', 1)] |
||||
public string $value = 'ok'; |
||||
} |
||||
|
||||
function attributed_parameter(#[AotMeta('parameter', 2)] string $value): string |
||||
{ |
||||
return $value; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$func = new ReflectionFunction('attributed_parameter'); |
||||
$paramAttrs = $func->getParameters()[0]->getAttributes(AotMeta::class); |
||||
var_dump($paramAttrs[0]->getName()); |
||||
var_dump($paramAttrs[0]->getArguments()); |
||||
|
||||
$prop = new ReflectionProperty(AttributeHolder::class, 'value'); |
||||
$propAttrs = $prop->getAttributes(AotMeta::class); |
||||
var_dump($propAttrs[0]->getName()); |
||||
var_dump($propAttrs[0]->getArguments()); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
string(7) "AotMeta" |
||||
array(2) { |
||||
[0]=> |
||||
string(9) "parameter" |
||||
[1]=> |
||||
int(2) |
||||
} |
||||
string(7) "AotMeta" |
||||
array(2) { |
||||
[0]=> |
||||
string(8) "property" |
||||
[1]=> |
||||
int(1) |
||||
} |
||||
@ -0,0 +1,27 @@ |
||||
--TEST-- |
||||
unset evaluates array dimension expressions left to right |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function unset_key(string $key): string |
||||
{ |
||||
echo "unset-key:$key\n"; |
||||
return $key; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$items = ['a' => 1, 'b' => 2, 'c' => 3]; |
||||
|
||||
unset($items[unset_key('a')], $items[unset_key('c')]); |
||||
|
||||
var_dump($items); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
unset-key:a |
||||
unset-key:c |
||||
array(1) { |
||||
["b"]=> |
||||
int(2) |
||||
} |
||||
@ -0,0 +1,43 @@ |
||||
--TEST-- |
||||
dynamic new with unpacked constructor args then method call preserves order |
||||
--FILE-- |
||||
<?php |
||||
|
||||
class DynamicNewOrder |
||||
{ |
||||
public function __construct(public string $name) |
||||
{ |
||||
echo "ctor:$name\n"; |
||||
} |
||||
|
||||
public function run(string $suffix): string |
||||
{ |
||||
echo "run:$suffix\n"; |
||||
return $this->name . ':' . $suffix; |
||||
} |
||||
} |
||||
|
||||
function make_new_args(): array |
||||
{ |
||||
echo "new-args\n"; |
||||
return ['object']; |
||||
} |
||||
|
||||
function make_call_arg(): string |
||||
{ |
||||
echo "call-arg\n"; |
||||
return 'method'; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$class = DynamicNewOrder::class; |
||||
var_dump((new $class(...make_new_args()))->run(make_call_arg())); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
new-args |
||||
ctor:object |
||||
call-arg |
||||
run:method |
||||
string(13) "object:method" |
||||
@ -0,0 +1,36 @@ |
||||
--TEST-- |
||||
isset and empty evaluate array dimension expressions in order |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function dim_key(string $key): string |
||||
{ |
||||
echo "key:$key\n"; |
||||
return $key; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$data = [ |
||||
'user' => ['name' => 'Alice'], |
||||
'zero' => 0, |
||||
]; |
||||
|
||||
var_dump(isset($data[dim_key('user')][dim_key('name')])); |
||||
var_dump(isset($data[dim_key('missing')][dim_key('nested')])); |
||||
var_dump(empty($data[dim_key('zero')])); |
||||
var_dump(empty($data[dim_key('missing')][dim_key('empty-nested')])); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
key:user |
||||
key:name |
||||
bool(true) |
||||
key:missing |
||||
key:nested |
||||
bool(false) |
||||
key:zero |
||||
bool(true) |
||||
key:missing |
||||
key:empty-nested |
||||
bool(true) |
||||
@ -0,0 +1,29 @@ |
||||
--TEST-- |
||||
finally runs for return inside catch without changing return value |
||||
--FILE-- |
||||
<?php |
||||
|
||||
class FinallyCatchReturnException extends Exception {} |
||||
|
||||
function catch_finally_return(): string |
||||
{ |
||||
$state = "before"; |
||||
try { |
||||
throw new FinallyCatchReturnException("failure"); |
||||
} catch (FinallyCatchReturnException $e) { |
||||
$state = "catch:" . $e->getMessage(); |
||||
return $state; |
||||
} finally { |
||||
echo "finally:$state\n"; |
||||
$state = "finally"; |
||||
} |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
var_dump(catch_finally_return()); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
finally:catch:failure |
||||
string(13) "catch:failure" |
||||
@ -0,0 +1,33 @@ |
||||
--TEST-- |
||||
finally runs for return nested in if/else branches without changing return value |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function nested_finally_return(int $value): string |
||||
{ |
||||
$state = "start"; |
||||
try { |
||||
if ($value > 0) { |
||||
$state .= ":positive"; |
||||
return $state; |
||||
} else { |
||||
$state .= ":negative"; |
||||
return $state; |
||||
} |
||||
} finally { |
||||
echo "finally:$state\n"; |
||||
$state .= ":finally"; |
||||
} |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
var_dump(nested_finally_return(1)); |
||||
var_dump(nested_finally_return(-1)); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
finally:start:positive |
||||
string(14) "start:positive" |
||||
finally:start:negative |
||||
string(14) "start:negative" |
||||
@ -0,0 +1,45 @@ |
||||
--TEST-- |
||||
finally side effects run before return and throw leave the frame |
||||
--FILE-- |
||||
<?php |
||||
|
||||
class FinallySideEffectException extends Exception {} |
||||
|
||||
function finally_return_case(): string |
||||
{ |
||||
try { |
||||
echo "try-return\n"; |
||||
return "returned"; |
||||
} finally { |
||||
echo "finally-return\n"; |
||||
} |
||||
} |
||||
|
||||
function finally_throw_case(): void |
||||
{ |
||||
try { |
||||
echo "try-throw\n"; |
||||
throw new FinallySideEffectException("thrown"); |
||||
} finally { |
||||
echo "finally-throw\n"; |
||||
} |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
var_dump(finally_return_case()); |
||||
|
||||
try { |
||||
finally_throw_case(); |
||||
} catch (FinallySideEffectException $e) { |
||||
var_dump($e->getMessage()); |
||||
} |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
try-return |
||||
finally-return |
||||
string(8) "returned" |
||||
try-throw |
||||
finally-throw |
||||
string(6) "thrown" |
||||
@ -0,0 +1,31 @@ |
||||
--TEST-- |
||||
array_push with unpacked values mutates first argument once |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function make_push_values(): array |
||||
{ |
||||
echo "make-values\n"; |
||||
return [2, 3]; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$items = [1]; |
||||
$count = array_push($items, ...make_push_values()); |
||||
|
||||
var_dump($count); |
||||
var_dump($items); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
make-values |
||||
int(3) |
||||
array(3) { |
||||
[0]=> |
||||
int(1) |
||||
[1]=> |
||||
int(2) |
||||
[2]=> |
||||
int(3) |
||||
} |
||||
@ -0,0 +1,24 @@ |
||||
--TEST-- |
||||
preg_match output array can be reused through references |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function main(): void |
||||
{ |
||||
preg_match('/(foo)(bar)/', 'foobar', $matches); |
||||
|
||||
$first =& $matches[1]; |
||||
$first = strtoupper($first); |
||||
|
||||
var_dump($matches); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
array(3) { |
||||
[0]=> |
||||
string(6) "foobar" |
||||
[1]=> |
||||
&string(3) "FOO" |
||||
[2]=> |
||||
string(3) "bar" |
||||
} |
||||
Loading…
Reference in new issue