Skip to content

Commit 9b7f62a

Browse files
Closure self-reference WIP
1 parent 4ce00aa commit 9b7f62a

18 files changed

+811
-576
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
--TEST--
2+
Testing Closure self-reference functionality: Basic
3+
--FILE--
4+
<?php
5+
6+
$factorial = function(int $num) as $factorial {
7+
if($num > 1) {
8+
return $num + $factorial($num - 1);
9+
}
10+
return $num;
11+
};
12+
13+
echo "4! = ", $factorial(4), PHP_EOL;
14+
15+
$dynamic = function(bool $root = false) as $test use(&$dynamic) {
16+
if ($root) {
17+
$dynamic = function() {
18+
return "Hello";
19+
};
20+
return $test();
21+
} else {
22+
return "Hi";
23+
}
24+
};
25+
26+
echo $dynamic(true), PHP_EOL;
27+
echo $dynamic(true), PHP_EOL;
28+
29+
echo (function() as $fn : string {
30+
echo get_class($fn), PHP_EOL;
31+
return "Bye";
32+
})(), PHP_EOL;
33+
34+
?>
35+
--EXPECTF--
36+
4! = 10
37+
Hi
38+
Hello
39+
Closure
40+
Bye
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Closure self-reference can't reuse static variable name
3+
--FILE--
4+
<?php
5+
6+
function() as $a use($a) {};
7+
8+
?>
9+
--EXPECTF--
10+
Fatal error: Cannot use lexical variable $a as self reference in %s on line %d
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Closure self-reference can't reuse parameter variable name
3+
--FILE--
4+
<?php
5+
6+
function(int $a) as $a {};
7+
8+
?>
9+
--EXPECTF--
10+
Fatal error: Cannot use lexical variable $a as self reference in %s on line %d
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Closure self-reference can't use this as a variable name
3+
--FILE--
4+
<?php
5+
6+
function() as $this {};
7+
8+
?>
9+
--EXPECTF--
10+
Fatal error: Cannot use $this as self reference in %s on line %d
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
Testing Closure self-reference functionality: Generator
3+
--FILE--
4+
<?php
5+
6+
$fn = function(int $max) as $fn {
7+
yield from range($max, 0, -1);
8+
if ($max > 1) {
9+
yield from $fn($max - 1);
10+
}
11+
};
12+
print_r(iterator_to_array($fn(2), false));
13+
14+
?>
15+
--EXPECT--
16+
Array
17+
(
18+
[0] => 2
19+
[1] => 1
20+
[2] => 0
21+
[3] => 1
22+
[4] => 0
23+
)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Testing Closure self-reference functionality: Closure instances
3+
--FILE--
4+
<?php
5+
6+
$map = new WeakMap();
7+
8+
$fn = function() as $fn use ($map) {
9+
var_dump(isset($map[$fn]));
10+
};
11+
$map[$fn] = 1;
12+
$fn();
13+
$fn->call(new class {});
14+
$fn();
15+
16+
var_dump(count($map));
17+
unset($fn);
18+
var_dump(count($map));
19+
20+
?>
21+
--EXPECT--
22+
bool(true)
23+
bool(false)
24+
bool(true)
25+
int(1)
26+
int(0)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--TEST--
2+
Testing Closure self-reference functionality: This binding
3+
--FILE--
4+
<?php
5+
6+
$closure = null;
7+
$fn = function(bool $root = false, bool $set = false) as $fn use(&$closure) : void {
8+
echo $this::A, PHP_EOL;
9+
if ($set) {
10+
$closure = $fn;
11+
}
12+
if ($root) {
13+
$fn();
14+
}
15+
return;
16+
};
17+
$fn = $fn->bindTo(new class{const A="bind";});
18+
$fn(true, true);
19+
$fn->call(new class{const A="call";}, true, true);
20+
$fn(true, false);
21+
$closure(true, false);
22+
23+
?>
24+
--EXPECTF--
25+
bind
26+
bind
27+
call
28+
call
29+
bind
30+
bind
31+
call
32+
call

Zend/zend_ast.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_class_const_or_name(zend_ast *
111111

112112
ZEND_API zend_ast *zend_ast_create_decl(
113113
zend_ast_kind kind, uint32_t flags, uint32_t start_lineno, zend_string *doc_comment,
114-
zend_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2, zend_ast *child3, zend_ast *child4
114+
zend_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2, zend_ast *child3, zend_ast *child4, zend_ast *child5
115115
) {
116116
zend_ast_decl *ast;
117117

@@ -128,6 +128,7 @@ ZEND_API zend_ast *zend_ast_create_decl(
128128
ast->child[2] = child2;
129129
ast->child[3] = child3;
130130
ast->child[4] = child4;
131+
ast->child[5] = child5;
131132

132133
return (zend_ast *) ast;
133134
}
@@ -1165,7 +1166,8 @@ ZEND_API void ZEND_FASTCALL zend_ast_destroy(zend_ast *ast)
11651166
zend_ast_destroy(decl->child[1]);
11661167
zend_ast_destroy(decl->child[2]);
11671168
zend_ast_destroy(decl->child[3]);
1168-
ast = decl->child[4];
1169+
zend_ast_destroy(decl->child[4]);
1170+
ast = decl->child[5];
11691171
goto tail_call;
11701172
}
11711173
}

Zend/zend_ast.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ typedef struct _zend_ast_decl {
210210
uint32_t flags;
211211
zend_string *doc_comment;
212212
zend_string *name;
213-
zend_ast *child[5];
213+
zend_ast *child[6];
214214
} zend_ast_decl;
215215

216216
typedef void (*zend_ast_process_t)(zend_ast *ast);
@@ -294,7 +294,7 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_list_add(zend_ast *list, zend_ast *op
294294

295295
ZEND_API zend_ast *zend_ast_create_decl(
296296
zend_ast_kind kind, uint32_t flags, uint32_t start_lineno, zend_string *doc_comment,
297-
zend_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2, zend_ast *child3, zend_ast *child4
297+
zend_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2, zend_ast *child3, zend_ast *child4, zend_ast *child5
298298
);
299299

300300
typedef struct {

Zend/zend_closures.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ ZEND_METHOD(Closure, call)
149149
ZVAL_UNDEF(&closure_result);
150150
fci.retval = &closure_result;
151151

152-
if (closure->func.common.fn_flags & ZEND_ACC_GENERATOR) {
152+
if (closure->func.common.fn_flags & (ZEND_ACC_GENERATOR | ZEND_ACC_SELF_REFERENCE)) {
153153
zval new_closure;
154154
zend_create_closure(&new_closure, &closure->func, newclass, closure->called_scope, newthis);
155155
closure = (zend_closure *) Z_OBJ(new_closure);

Zend/zend_compile.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7272,6 +7272,36 @@ static void zend_compile_closure_uses(zend_ast *ast) /* {{{ */
72727272
}
72737273
/* }}} */
72747274

7275+
static void zend_compile_closure_self_reference(zend_ast *ast) /* {{{ */
7276+
{
7277+
zend_op_array* op_array = CG(active_op_array);
7278+
7279+
zend_string *var_name = zend_ast_get_str(ast);
7280+
7281+
CG(zend_lineno) = zend_ast_get_lineno(ast);
7282+
7283+
{
7284+
int i;
7285+
for (i = 0; i < op_array->last_var; i++) {
7286+
if (zend_string_equals(op_array->vars[i], var_name)) {
7287+
zend_error_noreturn(E_COMPILE_ERROR,
7288+
"Cannot use lexical variable $%s as self reference", ZSTR_VAL(var_name));
7289+
}
7290+
}
7291+
}
7292+
7293+
if (zend_string_equals_literal(var_name, "this")) {
7294+
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use $this as self reference");
7295+
}
7296+
7297+
zend_op *opline;
7298+
7299+
opline = zend_emit_op(NULL, ZEND_BIND_SELF_REFERENCE, NULL, NULL);
7300+
opline->op1_type = IS_CV;
7301+
opline->op1.var = lookup_cv(var_name);
7302+
}
7303+
/* }}} */
7304+
72757305
static void zend_compile_implicit_closure_uses(closure_info *info)
72767306
{
72777307
zend_string *var_name;
@@ -7433,6 +7463,7 @@ static void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel)
74337463
zend_ast *uses_ast = decl->child[1];
74347464
zend_ast *stmt_ast = decl->child[2];
74357465
zend_ast *return_type_ast = decl->child[3];
7466+
zend_ast *as_ast = decl->child[5];
74367467
bool is_method = decl->kind == ZEND_AST_METHOD;
74377468
zend_string *lcname;
74387469

@@ -7520,6 +7551,11 @@ static void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel)
75207551
zend_compile_closure_uses(uses_ast);
75217552
}
75227553

7554+
if (as_ast) {
7555+
zend_compile_closure_self_reference(as_ast);
7556+
op_array->fn_flags |= ZEND_ACC_SELF_REFERENCE;
7557+
}
7558+
75237559
if (ast->kind == ZEND_AST_ARROW_FUNC) {
75247560
bool needs_return = true;
75257561
if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {

Zend/zend_compile.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ typedef struct _zend_oparray_context {
304304
/* Class cannot be serialized or unserialized | | | */
305305
#define ZEND_ACC_NOT_SERIALIZABLE (1 << 29) /* X | | | */
306306
/* | | | */
307-
/* Function Flags (unused: 28-30) | | | */
307+
/* Function Flags (unused: 29-30) | | | */
308308
/* ============== | | | */
309309
/* | | | */
310310
/* deprecation flag | | | */
@@ -364,6 +364,9 @@ typedef struct _zend_oparray_context {
364364
#define ZEND_ACC_COMPILE_TIME_EVAL (1 << 27) /* | X | | */
365365
/* | | | */
366366
/* op_array uses strict mode types | | | */
367+
#define ZEND_ACC_SELF_REFERENCE (1 << 28) /* | X | | */
368+
/* | | | */
369+
/* op_array uses strict mode types | | | */
367370
#define ZEND_ACC_STRICT_TYPES (1U << 31) /* | X | | */
368371

369372

Zend/zend_language_parser.y

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
269269
%type <ast> non_empty_parameter_list argument_list non_empty_argument_list property_list
270270
%type <ast> class_const_list first_class_const_decl class_const_decl class_name_list trait_adaptations method_body non_empty_for_exprs
271271
%type <ast> ctor_arguments alt_if_stmt_without_else trait_adaptation_list lexical_vars
272-
%type <ast> lexical_var_list encaps_list
272+
%type <ast> lexical_var_list encaps_list closure_self_reference
273273
%type <ast> array_pair non_empty_array_pair_list array_pair_list possible_array_pair
274274
%type <ast> isset_variable type return_type type_expr type_without_static
275275
%type <ast> identifier type_expr_without_static union_type_without_static_element union_type_without_static intersection_type_without_static
@@ -573,7 +573,7 @@ function_declaration_statement:
573573
function returns_ref function_name backup_doc_comment '(' parameter_list ')' return_type
574574
backup_fn_flags '{' inner_statement_list '}' backup_fn_flags
575575
{ $$ = zend_ast_create_decl(ZEND_AST_FUNC_DECL, $2 | $13, $1, $4,
576-
zend_ast_get_str($3), $6, NULL, $11, $8, NULL); CG(extra_fn_flags) = $9; }
576+
zend_ast_get_str($3), $6, NULL, $11, $8, NULL, NULL); CG(extra_fn_flags) = $9; }
577577
;
578578

579579
is_reference:
@@ -589,10 +589,10 @@ is_variadic:
589589
class_declaration_statement:
590590
class_modifiers T_CLASS { $<num>$ = CG(zend_lineno); }
591591
T_STRING extends_from implements_list backup_doc_comment '{' class_statement_list '}'
592-
{ $$ = zend_ast_create_decl(ZEND_AST_CLASS, $1, $<num>3, $7, zend_ast_get_str($4), $5, $6, $9, NULL, NULL); }
592+
{ $$ = zend_ast_create_decl(ZEND_AST_CLASS, $1, $<num>3, $7, zend_ast_get_str($4), $5, $6, $9, NULL, NULL, NULL); }
593593
| T_CLASS { $<num>$ = CG(zend_lineno); }
594594
T_STRING extends_from implements_list backup_doc_comment '{' class_statement_list '}'
595-
{ $$ = zend_ast_create_decl(ZEND_AST_CLASS, 0, $<num>2, $6, zend_ast_get_str($3), $4, $5, $8, NULL, NULL); }
595+
{ $$ = zend_ast_create_decl(ZEND_AST_CLASS, 0, $<num>2, $6, zend_ast_get_str($3), $4, $5, $8, NULL, NULL, NULL); }
596596
;
597597

598598
class_modifiers:
@@ -622,19 +622,19 @@ class_modifier:
622622
trait_declaration_statement:
623623
T_TRAIT { $<num>$ = CG(zend_lineno); }
624624
T_STRING backup_doc_comment '{' class_statement_list '}'
625-
{ $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_TRAIT, $<num>2, $4, zend_ast_get_str($3), NULL, NULL, $6, NULL, NULL); }
625+
{ $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_TRAIT, $<num>2, $4, zend_ast_get_str($3), NULL, NULL, $6, NULL, NULL, NULL); }
626626
;
627627

628628
interface_declaration_statement:
629629
T_INTERFACE { $<num>$ = CG(zend_lineno); }
630630
T_STRING interface_extends_list backup_doc_comment '{' class_statement_list '}'
631-
{ $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_INTERFACE, $<num>2, $5, zend_ast_get_str($3), NULL, $4, $7, NULL, NULL); }
631+
{ $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_INTERFACE, $<num>2, $5, zend_ast_get_str($3), NULL, $4, $7, NULL, NULL, NULL); }
632632
;
633633

634634
enum_declaration_statement:
635635
T_ENUM { $<num>$ = CG(zend_lineno); }
636636
T_STRING enum_backing_type implements_list backup_doc_comment '{' class_statement_list '}'
637-
{ $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_ENUM|ZEND_ACC_FINAL, $<num>2, $6, zend_ast_get_str($3), NULL, $5, $8, NULL, $4); }
637+
{ $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_ENUM|ZEND_ACC_FINAL, $<num>2, $6, zend_ast_get_str($3), NULL, $5, $8, NULL, $4, NULL); }
638638
;
639639

640640
enum_backing_type:
@@ -945,7 +945,7 @@ attributed_class_statement:
945945
| method_modifiers function returns_ref identifier backup_doc_comment '(' parameter_list ')'
946946
return_type backup_fn_flags method_body backup_fn_flags
947947
{ $$ = zend_ast_create_decl(ZEND_AST_METHOD, $3 | $1 | $12, $2, $5,
948-
zend_ast_get_str($4), $7, NULL, $11, $9, NULL); CG(extra_fn_flags) = $10; }
948+
zend_ast_get_str($4), $7, NULL, $11, $9, NULL, NULL); CG(extra_fn_flags) = $10; }
949949
| enum_case { $$ = $1; }
950950
;
951951

@@ -1116,7 +1116,7 @@ anonymous_class:
11161116
extends_from implements_list backup_doc_comment '{' class_statement_list '}' {
11171117
zend_ast *decl = zend_ast_create_decl(
11181118
ZEND_AST_CLASS, ZEND_ACC_ANON_CLASS | $1, $<num>3, $7, NULL,
1119-
$5, $6, $9, NULL, NULL);
1119+
$5, $6, $9, NULL, NULL, NULL);
11201120
$$ = zend_ast_create(ZEND_AST_NEW, decl, $4);
11211121
}
11221122
;
@@ -1258,15 +1258,15 @@ expr:
12581258

12591259

12601260
inline_function:
1261-
function returns_ref backup_doc_comment '(' parameter_list ')' lexical_vars return_type
1261+
function returns_ref backup_doc_comment '(' parameter_list ')' closure_self_reference lexical_vars return_type
12621262
backup_fn_flags '{' inner_statement_list '}' backup_fn_flags
1263-
{ $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $2 | $13, $1, $3,
1263+
{ $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $2 | $14, $1, $3,
12641264
ZSTR_INIT_LITERAL("{closure}", 0),
1265-
$5, $7, $11, $8, NULL); CG(extra_fn_flags) = $9; }
1265+
$5, $8, $12, $9, NULL, $7); CG(extra_fn_flags) = $10; }
12661266
| fn returns_ref backup_doc_comment '(' parameter_list ')' return_type
12671267
T_DOUBLE_ARROW backup_fn_flags backup_lex_pos expr backup_fn_flags
12681268
{ $$ = zend_ast_create_decl(ZEND_AST_ARROW_FUNC, $2 | $12, $1, $3,
1269-
ZSTR_INIT_LITERAL("{closure}", 0), $5, NULL, $11, $7, NULL);
1269+
ZSTR_INIT_LITERAL("{closure}", 0), $5, NULL, $11, $7, NULL, NULL);
12701270
CG(extra_fn_flags) = $9; }
12711271
;
12721272

@@ -1295,6 +1295,11 @@ returns_ref:
12951295
| ampersand { $$ = ZEND_ACC_RETURN_REFERENCE; }
12961296
;
12971297

1298+
closure_self_reference:
1299+
%empty { $$ = NULL; }
1300+
| T_AS T_VARIABLE { $$ = $2; }
1301+
;
1302+
12981303
lexical_vars:
12991304
%empty { $$ = NULL; }
13001305
| T_USE '(' lexical_var_list possible_comma ')' { $$ = $3; }

0 commit comments

Comments
 (0)