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

Allow defining Closures in const-expr #16458

Draft
wants to merge 3 commits 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
57 changes: 57 additions & 0 deletions Zend/tests/closure_const_expr/attributes.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
--TEST--
Allow defining closures in attributes
--EXTENSIONS--
reflection
--FILE--
<?php

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

#[Attr(static function () { })]
#[Attr(static function (...$args) {
var_dump($args);
})]
class C {}

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

?>
--EXPECTF--
object(Attr)#%d (1) {
["value"]=>
object(Closure)#%d (3) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
string(%d) "%s"
["line"]=>
int(%d)
}
}
array(1) {
[0]=>
string(3) "foo"
}
object(Attr)#%d (1) {
["value"]=>
object(Closure)#%d (4) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
string(%d) "%s"
["line"]=>
int(%d)
["parameter"]=>
array(1) {
["$args"]=>
string(10) "<optional>"
}
}
}
28 changes: 28 additions & 0 deletions Zend/tests/closure_const_expr/attributes_scope_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
--TEST--
Closure in attribute may access private variables
--EXTENSIONS--
reflection
--FILE--
<?php

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

#[Attr(static function (C $c) {
echo $c->secret, PHP_EOL;
})]
class C {
public function __construct(
private string $secret,
) {}
}

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

?>
--EXPECT--
secret
35 changes: 35 additions & 0 deletions Zend/tests/closure_const_expr/attributes_scope_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
--TEST--
Closure in attribute may not access unrelated private variables
--EXTENSIONS--
reflection
--FILE--
<?php

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

#[Attr(static function (E $e) {
echo $e->secret, PHP_EOL;
})]
class C {
}

class E {
public function __construct(
private string $secret,
) {}
}

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

?>
--EXPECTF--
Fatal error: Uncaught Error: Cannot access private property E::$secret in %s:%d
Stack trace:
#0 %s(%d): C::{closure:%s:%d}(Object(E))
#1 {main}
thrown in %s on line %d
23 changes: 23 additions & 0 deletions Zend/tests/closure_const_expr/basic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--TEST--
Allow defining Closures in const expressions.
--FILE--
<?php

const Closure = static function () {
echo "called", PHP_EOL;
};

var_dump(Closure);
(Closure)();

?>
--EXPECTF--
object(Closure)#%d (3) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
string(%d) "%s"
["line"]=>
int(3)
}
called
25 changes: 25 additions & 0 deletions Zend/tests/closure_const_expr/class_const.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
--TEST--
Allow defining Closures in class constants.
--FILE--
<?php

class C {
const Closure = static function () {
echo "called", PHP_EOL;
};
}

var_dump(C::Closure);
(C::Closure)();

?>
--EXPECTF--
object(Closure)#%d (3) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
string(%d) "%s"
["line"]=>
int(4)
}
called
22 changes: 22 additions & 0 deletions Zend/tests/closure_const_expr/default_args.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
Closures in default argument
--FILE--
<?php

function test(
Closure $name = static function () {
echo "default", PHP_EOL;
},
) {
$name();
}

test();
test(function () {
echo "explicit", PHP_EOL;
});

?>
--EXPECT--
default
explicit
17 changes: 17 additions & 0 deletions Zend/tests/closure_const_expr/disallows_non_static.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Disallows using variables.
--FILE--
<?php

$foo = "bar";

const Closure = function () {
echo $foo, PHP_EOL;
};

var_dump(Closure);
(Closure)();

?>
--EXPECTF--
Fatal error: Closures in constant expressions must be "static" in %s on line %d
17 changes: 17 additions & 0 deletions Zend/tests/closure_const_expr/disallows_using_variables.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Disallows using variables.
--FILE--
<?php

$foo = "bar";

const Closure = static function () use ($foo) {
echo $foo, PHP_EOL;
};

var_dump(Closure);
(Closure)();

?>
--EXPECTF--
Fatal error: Cannot "use(...)" variables in constant expression in %s on line %d
27 changes: 27 additions & 0 deletions Zend/tests/closure_const_expr/property_initializer.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
Closure in property initializer
--FILE--
<?php

class C {
public Closure $d = static function () {
echo "called", PHP_EOL;
};
}

$c = new C();
var_dump($c->d);
($c->d)();


?>
--EXPECTF--
object(Closure)#%d (3) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
string(%d) "%s"
["line"]=>
int(4)
}
called
22 changes: 22 additions & 0 deletions Zend/tests/closure_const_expr/property_initializer_scope_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
Closure in property initializer may access private variables
--FILE--
<?php

class C {
public Closure $d = static function (C $c) {
echo $c->secret, PHP_EOL;
};

public function __construct(
private string $secret,
) {}
}

$c = new C('secret');
($c->d)($c);


?>
--EXPECTF--
secret
30 changes: 30 additions & 0 deletions Zend/tests/closure_const_expr/property_initializer_scope_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
--TEST--
Closure in property initializer may not access unrelated private variables
--FILE--
<?php

class C {
public Closure $d = static function (E $e) {
echo $e->secret, PHP_EOL;
};


}

class E {
public function __construct(
private string $secret,
) {}
}

$c = new C();
($c->d)(new E('secret'));


?>
--EXPECTF--
Fatal error: Uncaught Error: Cannot access private property E::$secret in %s:%d
Stack trace:
#0 %s(%d): C::{closure:%s:%d}(Object(E))
#1 {main}
thrown in %s on line %d
27 changes: 27 additions & 0 deletions Zend/tests/closure_const_expr/static_initalizer.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
Closure in static initializer
--FILE--
<?php

function foo() {
static $closure = static function () {
echo "called", PHP_EOL;
};

var_dump($closure);
$closure();
}

foo();

?>
--EXPECTF--
object(Closure)#%d (3) {
["name"]=>
string(17) "{closure:foo():4}"
["file"]=>
string(%d) "%s"
["line"]=>
int(4)
}
called
26 changes: 26 additions & 0 deletions Zend/tests/closure_const_expr/static_property_initializer.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
Closure in static property initializer
--FILE--
<?php

class C {
public static Closure $d = static function () {
echo "called", PHP_EOL;
};
}

var_dump(C::$d);
(C::$d)();


?>
--EXPECTF--
object(Closure)#%d (3) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
string(%d) "%s"
["line"]=>
int(4)
}
called
11 changes: 11 additions & 0 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,8 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_ex(
return r;
}

#include "Zend/zend_closures.h"

ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
zval *result,
zend_ast *ast,
Expand Down Expand Up @@ -989,6 +991,15 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
}
return SUCCESS;
}
case ZEND_AST_CLOSURE_CONSTEXPR:
{
zend_ast *child = ast->child[0];
zval *z = zend_ast_get_zval(child);
zend_function *func = Z_PTR_P(z);

zend_create_closure(result, func, scope, scope, NULL);
return SUCCESS;
}
case ZEND_AST_PROP:
case ZEND_AST_NULLSAFE_PROP:
{
Expand Down
Loading
Loading