Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support first-class callables in const-expressions #17213

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions Zend/tests/closure_const_expr/fcc/attributes.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
--TEST--
Allow defining FCC in attributes
--EXTENSIONS--
reflection
--FILE--
<?php

#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
class Attr {
public function __construct(public Closure $value) {
var_dump($value('abc'));
}
}

#[Attr(strrev(...))]
#[Attr(strlen(...))]
class C {}

foreach ((new ReflectionClass(C::class))->getAttributes() as $reflectionAttribute) {
var_dump($reflectionAttribute->newInstance());
}

?>
--EXPECTF--
string(3) "cba"
object(Attr)#%d (1) {
["value"]=>
object(Closure)#%d (2) {
["function"]=>
string(6) "strrev"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
}
int(3)
object(Attr)#%d (1) {
["value"]=>
object(Closure)#%d (2) {
["function"]=>
string(6) "strlen"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
}
33 changes: 33 additions & 0 deletions Zend/tests/closure_const_expr/fcc/attributes_ast_printing.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
--TEST--
AST printing for FCC in attributes
--FILE--
<?php

// Do not use `false &&` to fully evaluate the function / class definition.

try {
\assert(
!
#[Attr(strrev(...))]
function () { }
);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
\assert(
!
new #[Attr(strrev(...))]
class {}
);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

?>
--EXPECT--
assert(!#[Attr(strrev(...))] function () {
})
assert(!new #[Attr(strrev(...))] class {
})
28 changes: 28 additions & 0 deletions Zend/tests/closure_const_expr/fcc/attributes_scope_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
--TEST--
FCC in attribute may access private methods
--EXTENSIONS--
reflection
--FILE--
<?php

#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
class Attr {
public function __construct(public Closure $value) {}
}

#[Attr(C::myMethod(...))]
class C {
private static function myMethod(string $foo) {
echo "Called ", __METHOD__, PHP_EOL;
var_dump($foo);
}
}

foreach ((new ReflectionClass(C::class))->getAttributes() as $reflectionAttribute) {
($reflectionAttribute->newInstance()->value)('abc');
}

?>
--EXPECT--
Called C::myMethod
string(3) "abc"
34 changes: 34 additions & 0 deletions Zend/tests/closure_const_expr/fcc/attributes_scope_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
--TEST--
FCC in attribute may not access unrelated private methods
--EXTENSIONS--
reflection
--FILE--
<?php

#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
class Attr {
public function __construct(public Closure $value) {}
}

class E {
private static function myMethod(string $foo) {
echo "Called ", __METHOD__, PHP_EOL;
var_dump($foo);
}
}

#[Attr(E::myMethod(...))]
class C {
}

foreach ((new ReflectionClass(C::class))->getAttributes() as $reflectionAttribute) {
($reflectionAttribute->newInstance()->value)('abc');
}

?>
--EXPECTF--
Fatal error: Uncaught Error: Call to private method E::myMethod() from scope C in %s:%d
Stack trace:
#0 %s(%d): ReflectionAttribute->newInstance()
#1 {main}
thrown in %s on line %d
31 changes: 31 additions & 0 deletions Zend/tests/closure_const_expr/fcc/autoload.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
Errors if the function does not exist.
--FILE--
<?php

spl_autoload_register(static function ($class) {
echo "Autoloading {$class}", PHP_EOL;
eval(
<<<'EOT'
class AutoloadedClass {
public static function withStaticMethod() {
echo "Called ", __METHOD__, PHP_EOL;
}
}
EOT
);
});

const Closure = AutoloadedClass::withStaticMethod(...);

var_dump(Closure);
(Closure)();

?>
--EXPECTF--
Autoloading AutoloadedClass
object(Closure)#%d (1) {
["function"]=>
string(16) "withStaticMethod"
}
Called AutoloadedClass::withStaticMethod
22 changes: 22 additions & 0 deletions Zend/tests/closure_const_expr/fcc/basic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
Allow defining FCC in const expressions.
--FILE--
<?php

const Closure = strrev(...);

var_dump(Closure);
var_dump((Closure)("abc"));

?>
--EXPECTF--
object(Closure)#%d (2) {
["function"]=>
string(%d) "%s"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
string(3) "cba"
24 changes: 24 additions & 0 deletions Zend/tests/closure_const_expr/fcc/class_const.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
Allow defining FCC in class constants.
--FILE--
<?php

class C {
const Closure = strrev(...);
}

var_dump(C::Closure);
var_dump((C::Closure)("abc"));

?>
--EXPECTF--
object(Closure)#%d (2) {
["function"]=>
string(6) "strrev"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
string(3) "cba"
39 changes: 39 additions & 0 deletions Zend/tests/closure_const_expr/fcc/complex_array.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
--TEST--
Allow defining FCC wrapped in an array in const expressions.
--FILE--
<?php

const Closure = [strrev(...), strlen(...)];

var_dump(Closure);

foreach (Closure as $closure) {
var_dump($closure("abc"));
}

?>
--EXPECTF--
array(2) {
[0]=>
object(Closure)#%d (2) {
["function"]=>
string(6) "strrev"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
[1]=>
object(Closure)#%d (2) {
["function"]=>
string(6) "strlen"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
}
string(3) "cba"
int(3)
18 changes: 18 additions & 0 deletions Zend/tests/closure_const_expr/fcc/default_args.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--TEST--
FCC in default argument
--FILE--
<?php

function test(
Closure $name = strrev(...)
) {
var_dump($name("abc"));
}

test();
test(strlen(...));

?>
--EXPECT--
string(3) "cba"
int(3)
12 changes: 12 additions & 0 deletions Zend/tests/closure_const_expr/fcc/error_dynamic_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
FCC in initializer errors for FCC on variable.
--FILE--
<?php

const Closure = $foo(...);

var_dump(Closure);

?>
--EXPECTF--
Fatal error: Cannot use dynamic function name in constant expression in %s on line %d
12 changes: 12 additions & 0 deletions Zend/tests/closure_const_expr/fcc/error_dynamic_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
FCC in initializer errors for FCC on Closure.
--FILE--
<?php

const Closure = (static function () { })(...);

var_dump(Closure);

?>
--EXPECTF--
Fatal error: Cannot use dynamic function name in constant expression in %s on line %d
14 changes: 14 additions & 0 deletions Zend/tests/closure_const_expr/fcc/error_dynamic_003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
FCC in initializer errors for FCC on Constant.
--FILE--
<?php

const Name = 'strrev';

const Closure = (Name)(...);

var_dump(Closure);

?>
--EXPECTF--
Fatal error: Cannot use dynamic function name in constant expression in %s on line %d
20 changes: 20 additions & 0 deletions Zend/tests/closure_const_expr/fcc/error_instance_call.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
FCC in initializer errors for FCC on instance call.
--FILE--
<?php

class Foo {
public function myMethod(string $foo) {
echo "Called ", __METHOD__, PHP_EOL;
var_dump($foo);
}
}

const Closure = (new Foo())->myMethod(...);

var_dump(Closure);
(Closure)("abc");

?>
--EXPECTF--
Fatal error: Constant expression contains invalid operations in %s on line %d
15 changes: 15 additions & 0 deletions Zend/tests/closure_const_expr/fcc/error_unknown_class.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
FCC in initializer errors for missing class.
--FILE--
<?php

const Closure = ThisClassNotDoesExist::thisMethodIsNotRelevant(...);

var_dump(Closure);

?>
--EXPECTF--
Fatal error: Uncaught Error: Class "ThisClassNotDoesExist" not found in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d
15 changes: 15 additions & 0 deletions Zend/tests/closure_const_expr/fcc/error_unknown_function.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
FCC in initializer errors for missing function.
--FILE--
<?php

const Closure = this_function_does_not_exist(...);

var_dump(Closure);

?>
--EXPECTF--
Fatal error: Uncaught Error: Call to undefined function this_function_does_not_exist() in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d
17 changes: 17 additions & 0 deletions Zend/tests/closure_const_expr/fcc/error_unknown_method.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
FCC in initializer errors for missing method.
--FILE--
<?php

class ThisClassDoesExist { }

const Closure = ThisClassDoesExist::thisMethodDoesNotExist(...);

var_dump(Closure);

?>
--EXPECTF--
Fatal error: Uncaught Error: Call to undefined method ThisClassDoesExist::thisMethodDoesNotExist() in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d
Loading
Loading