Skip to content

Commit 9c29baf

Browse files
committed
Allow defining Closures in const-expr
Most notably this allows to use Closures as Attribute parameters.
1 parent 097edc8 commit 9c29baf

11 files changed

+261
-1
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
--TEST--
2+
Allow defining closures in attributes
3+
--EXTENSIONS--
4+
reflection
5+
--FILE--
6+
<?php
7+
8+
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
9+
class Attr {
10+
public function __construct(public Closure $value) {
11+
$value('foo');
12+
}
13+
}
14+
15+
#[Attr(function () {
16+
17+
})]
18+
#[Attr(function (...$args) {
19+
var_dump($args);
20+
})]
21+
class C {}
22+
23+
foreach ((new ReflectionClass(C::class))->getAttributes() as $reflectionAttribute) {
24+
var_dump($reflectionAttribute->newInstance());
25+
}
26+
27+
?>
28+
--EXPECTF--
29+
object(Attr)#%d (1) {
30+
["value"]=>
31+
object(Closure)#%d (3) {
32+
["name"]=>
33+
string(%d) "{closure:%s:%d}"
34+
["file"]=>
35+
string(%d) "%s"
36+
["line"]=>
37+
int(%d)
38+
}
39+
}
40+
array(1) {
41+
[0]=>
42+
string(3) "foo"
43+
}
44+
object(Attr)#%d (1) {
45+
["value"]=>
46+
object(Closure)#%d (4) {
47+
["name"]=>
48+
string(%d) "{closure:%s:%d}"
49+
["file"]=>
50+
string(%d) "%s"
51+
["line"]=>
52+
int(%d)
53+
["parameter"]=>
54+
array(1) {
55+
["$args"]=>
56+
string(10) "<optional>"
57+
}
58+
}
59+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
Allow defining Closures in const expressions.
3+
--FILE--
4+
<?php
5+
6+
const Closure = function () {
7+
echo "called", PHP_EOL;
8+
};
9+
10+
var_dump(Closure);
11+
(Closure)();
12+
13+
?>
14+
--EXPECTF--
15+
object(Closure)#%d (3) {
16+
["name"]=>
17+
string(%d) "{closure:%s:%d}"
18+
["file"]=>
19+
string(%d) "%s"
20+
["line"]=>
21+
int(3)
22+
}
23+
called
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Allow defining Closures in class constants.
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
const Closure = function () {
8+
echo "called", PHP_EOL;
9+
};
10+
}
11+
12+
var_dump(C::Closure);
13+
(C::Closure)();
14+
15+
?>
16+
--EXPECTF--
17+
object(Closure)#%d (3) {
18+
["name"]=>
19+
string(%d) "{closure:%s:%d}"
20+
["file"]=>
21+
string(%d) "%s"
22+
["line"]=>
23+
int(4)
24+
}
25+
called
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
Closures in default argument
3+
--FILE--
4+
<?php
5+
6+
function test(
7+
Closure $name = function () {
8+
echo "default", PHP_EOL;
9+
},
10+
) {
11+
$name();
12+
}
13+
14+
test();
15+
test(function () {
16+
echo "explicit", PHP_EOL;
17+
});
18+
19+
?>
20+
--EXPECT--
21+
default
22+
explicit
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
Disallows using variables.
3+
--FILE--
4+
<?php
5+
6+
$foo = "bar";
7+
8+
const Closure = function () use ($foo) {
9+
echo $foo, PHP_EOL;
10+
};
11+
12+
var_dump(Closure);
13+
(Closure)();
14+
15+
?>
16+
--EXPECTF--
17+
Fatal error: Cannot use(...) variables in constant expression in %s on line %d
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
Closure in property initializer
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public Closure $d = function () {
8+
echo "called", PHP_EOL;
9+
};
10+
}
11+
12+
$c = new C();
13+
var_dump($c->d);
14+
($c->d)();
15+
16+
17+
?>
18+
--EXPECTF--
19+
object(Closure)#%d (3) {
20+
["name"]=>
21+
string(%d) "{closure:%s:%d}"
22+
["file"]=>
23+
string(%d) "%s"
24+
["line"]=>
25+
int(4)
26+
}
27+
called
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
Closure in static initializer
3+
--FILE--
4+
<?php
5+
6+
function foo() {
7+
static $closure = function () {
8+
echo "called", PHP_EOL;
9+
};
10+
11+
var_dump($closure);
12+
$closure();
13+
}
14+
15+
foo();
16+
17+
?>
18+
--EXPECTF--
19+
object(Closure)#%d (3) {
20+
["name"]=>
21+
string(17) "{closure:foo():4}"
22+
["file"]=>
23+
string(%d) "%s"
24+
["line"]=>
25+
int(4)
26+
}
27+
called
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Closure in static property initializer
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public static Closure $d = function () {
8+
echo "called", PHP_EOL;
9+
};
10+
}
11+
12+
var_dump(C::$d);
13+
(C::$d)();
14+
15+
16+
?>
17+
--EXPECTF--
18+
object(Closure)#%d (3) {
19+
["name"]=>
20+
string(%d) "{closure:%s:%d}"
21+
["file"]=>
22+
string(%d) "%s"
23+
["line"]=>
24+
int(4)
25+
}
26+
called

Zend/zend_ast.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,8 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_ex(
564564
return r;
565565
}
566566

567+
#include "Zend/zend_closures.h"
568+
567569
ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
568570
zval *result,
569571
zend_ast *ast,
@@ -989,6 +991,14 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
989991
}
990992
return SUCCESS;
991993
}
994+
case ZEND_AST_CLOSURE_CONSTEXPR:
995+
{
996+
zend_ast *child = ast->child[0];
997+
zval *z = zend_ast_get_zval(child);
998+
999+
zend_create_closure(result, Z_PTR_P(z), NULL, NULL, NULL);
1000+
return SUCCESS;
1001+
}
9921002
case ZEND_AST_PROP:
9931003
case ZEND_AST_NULLSAFE_PROP:
9941004
{

Zend/zend_ast.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ enum _zend_ast_kind {
111111
ZEND_AST_CONTINUE,
112112
ZEND_AST_PROPERTY_HOOK_SHORT_BODY,
113113

114+
ZEND_AST_CLOSURE_CONSTEXPR,
115+
114116
/* 2 child nodes */
115117
ZEND_AST_DIM = 2 << ZEND_AST_NUM_CHILDREN_SHIFT,
116118
ZEND_AST_PROP,

Zend/zend_compile.c

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11030,7 +11030,8 @@ static bool zend_is_allowed_in_const_expr(zend_ast_kind kind) /* {{{ */
1103011030
|| kind == ZEND_AST_CONST_ENUM_INIT
1103111031
|| kind == ZEND_AST_NEW || kind == ZEND_AST_ARG_LIST
1103211032
|| kind == ZEND_AST_NAMED_ARG
11033-
|| kind == ZEND_AST_PROP || kind == ZEND_AST_NULLSAFE_PROP;
11033+
|| kind == ZEND_AST_PROP || kind == ZEND_AST_NULLSAFE_PROP
11034+
|| kind == ZEND_AST_CLOSURE;
1103411035
}
1103511036
/* }}} */
1103611037

@@ -11164,6 +11165,24 @@ static void zend_compile_const_expr_new(zend_ast **ast_ptr)
1116411165
class_ast->attr = fetch_type << ZEND_CONST_EXPR_NEW_FETCH_TYPE_SHIFT;
1116511166
}
1116611167

11168+
static void zend_compile_const_expr_closure(zend_ast **ast_ptr)
11169+
{
11170+
zend_ast_decl *closure_ast = (zend_ast_decl *) *ast_ptr;
11171+
zend_ast *uses_ast = closure_ast->child[1];
11172+
if (uses_ast) {
11173+
zend_error_noreturn(E_COMPILE_ERROR,
11174+
"Cannot use(...) variables in constant expression");
11175+
}
11176+
11177+
znode node;
11178+
zend_op_array *op = zend_compile_func_decl(&node, *ast_ptr, 1);
11179+
11180+
zend_ast_destroy(*ast_ptr);
11181+
zval z;
11182+
ZVAL_PTR(&z, op);
11183+
*ast_ptr = zend_ast_create(ZEND_AST_CLOSURE_CONSTEXPR, zend_ast_create_zval(&z));
11184+
}
11185+
1116711186
static void zend_compile_const_expr_args(zend_ast **ast_ptr)
1116811187
{
1116911188
zend_ast_list *list = zend_ast_get_list(*ast_ptr);
@@ -11226,6 +11245,9 @@ static void zend_compile_const_expr(zend_ast **ast_ptr, void *context) /* {{{ */
1122611245
case ZEND_AST_ARG_LIST:
1122711246
zend_compile_const_expr_args(ast_ptr);
1122811247
break;
11248+
case ZEND_AST_CLOSURE:
11249+
zend_compile_const_expr_closure(ast_ptr);
11250+
break;
1122911251
}
1123011252

1123111253
zend_ast_apply(ast, zend_compile_const_expr, context);

0 commit comments

Comments
 (0)