Skip to content

Commit cdedf1d

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

13 files changed

+717
-568
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 a parameter name 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 a parameter name 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 static variable in %s on line %d

Zend/zend_ast.c

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,7 +1165,8 @@ ZEND_API void ZEND_FASTCALL zend_ast_destroy(zend_ast *ast)
11651165
zend_ast_destroy(decl->child[1]);
11661166
zend_ast_destroy(decl->child[2]);
11671167
zend_ast_destroy(decl->child[3]);
1168-
ast = decl->child[4];
1168+
zend_ast_destroy(decl->child[4]);
1169+
ast = decl->child[5];
11691170
goto tail_call;
11701171
}
11711172
}
@@ -1765,9 +1766,9 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
17651766
case ZEND_AST_ARROW_FUNC:
17661767
case ZEND_AST_METHOD:
17671768
decl = (zend_ast_decl *) ast;
1768-
if (decl->child[4]) {
1769+
if (decl->child[5]) {
17691770
bool newlines = !(ast->kind == ZEND_AST_CLOSURE || ast->kind == ZEND_AST_ARROW_FUNC);
1770-
zend_ast_export_attributes(str, decl->child[4], indent, newlines);
1771+
zend_ast_export_attributes(str, decl->child[5], indent, newlines);
17711772
}
17721773

17731774
zend_ast_export_visibility(str, decl->flags);
@@ -2572,7 +2573,7 @@ zend_ast * ZEND_FASTCALL zend_ast_with_attributes(zend_ast *ast, zend_ast *attr)
25722573
case ZEND_AST_CLOSURE:
25732574
case ZEND_AST_METHOD:
25742575
case ZEND_AST_ARROW_FUNC:
2575-
((zend_ast_decl *) ast)->child[4] = attr;
2576+
((zend_ast_decl *) ast)->child[5] = attr;
25762577
break;
25772578
case ZEND_AST_CLASS:
25782579
((zend_ast_decl *) ast)->child[3] = attr;

Zend/zend_ast.h

Lines changed: 1 addition & 1 deletion
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);

Zend/zend_compile.c

Lines changed: 37 additions & 2 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 a 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 static variable");
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[4];
74367467
bool is_method = decl->kind == ZEND_AST_METHOD;
74377468
zend_string *lcname;
74387469

@@ -7476,14 +7507,14 @@ static void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel)
74767507

74777508
CG(active_op_array) = op_array;
74787509

7479-
if (decl->child[4]) {
7510+
if (decl->child[5]) {
74807511
int target = ZEND_ATTRIBUTE_TARGET_FUNCTION;
74817512

74827513
if (is_method) {
74837514
target = ZEND_ATTRIBUTE_TARGET_METHOD;
74847515
}
74857516

7486-
zend_compile_attributes(&op_array->attributes, decl->child[4], 0, target, 0);
7517+
zend_compile_attributes(&op_array->attributes, decl->child[5], 0, target, 0);
74877518
}
74887519

74897520
/* Do not leak the class scope into free standing functions, even if they are dynamically
@@ -7520,6 +7551,10 @@ 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+
}
7557+
75237558
if (ast->kind == ZEND_AST_ARROW_FUNC) {
75247559
bool needs_return = true;
75257560
if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {

Zend/zend_language_parser.y

Lines changed: 9 additions & 4 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
@@ -1258,11 +1258,11 @@ 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, $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,
@@ -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; }

Zend/zend_vm_def.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9476,6 +9476,20 @@ ZEND_VM_HANDLER(202, ZEND_CALLABLE_CONVERT, UNUSED, UNUSED)
94769476
ZEND_VM_NEXT_OPCODE();
94779477
}
94789478

9479+
ZEND_VM_HANDLER(203, ZEND_BIND_SELF_REFERENCE, CV, UNUSED, REF)
9480+
{
9481+
USE_OPLINE
9482+
zval *variable_ptr;
9483+
9484+
variable_ptr = GET_OP1_ZVAL_PTR_PTR_UNDEF(BP_VAR_W);
9485+
9486+
zend_closure_from_frame(variable_ptr, execute_data);
9487+
9488+
Z_ADDREF_P(variable_ptr);
9489+
9490+
ZEND_VM_NEXT_OPCODE();
9491+
}
9492+
94799493
ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_JMP, (OP_JMP_ADDR(op, op->op1) > op), ZEND_JMP_FORWARD, JMP_ADDR, ANY)
94809494
{
94819495
USE_OPLINE

0 commit comments

Comments
 (0)