Skip to content

Commit 5472cac

Browse files
committed
Support PFA syntax
RFC: https://wiki.php.net/rfc/partial_function_application_v2 For FCCs, the parser generates a normal function call AST node, the but argument list is a ZEND_AST_CALLABLE_CONVERT / zend_ast_fcc node. We extend this for PFAs so that zend_ast_fcc can represent arguments. * Support PFA syntax in grammar * Update zend_ast_fcc so that arguments can be represented * Support serialization of zend_ast_fcc arguments in SHM / file cache * Introduce zend_ast_arg_list_add(): Same as zend_ast_list_add(), but wraps the list in a ZEND_AST_CALLABLE_CONVERT when adding any placeholder argument. Technically the arg list wrapping is not required, but it results in simpler code later as it will be very convenient in the compiler (determines whether a function calls is a PFA/FCC), and for PFA-in-const-expr support. It also allows to unify FCCs and PFAs in the grammar. Closes phpGH-20717.
1 parent e4098da commit 5472cac

File tree

11 files changed

+191
-18
lines changed

11 files changed

+191
-18
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
First class callable error: more than one argument
3+
--FILE--
4+
<?php
5+
6+
foo(1, ...);
7+
8+
?>
9+
--EXPECTF--
10+
Fatal error: Cannot create a Closure for call expression with more than one argument, or non-variadic placeholders 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+
First class callable error: non-variadic placeholder
3+
--FILE--
4+
<?php
5+
6+
foo(?);
7+
8+
?>
9+
--EXPECTF--
10+
Fatal error: Cannot create a Closure for call expression with more than one argument, or non-variadic placeholders in %s on line %d

Zend/zend_ast.c

Lines changed: 122 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,14 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_znode(const znode *node) {
5454
return (zend_ast *) ast;
5555
}
5656

57-
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_fcc(void) {
57+
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_fcc(zend_ast *args) {
5858
zend_ast_fcc *ast;
5959

6060
ast = zend_ast_alloc(sizeof(zend_ast_fcc));
6161
ast->kind = ZEND_AST_CALLABLE_CONVERT;
6262
ast->attr = 0;
6363
ast->lineno = CG(zend_lineno);
64+
ast->args = args;
6465
ZEND_MAP_PTR_INIT(ast->fptr, NULL);
6566

6667
return (zend_ast *) ast;
@@ -157,6 +158,12 @@ ZEND_API zend_ast *zend_ast_create_decl(
157158
return (zend_ast *) ast;
158159
}
159160

161+
static bool zend_ast_is_placeholder_arg(zend_ast *arg) {
162+
return arg->kind == ZEND_AST_PLACEHOLDER_ARG
163+
|| (arg->kind == ZEND_AST_NAMED_ARG
164+
&& arg->child[1]->kind == ZEND_AST_PLACEHOLDER_ARG);
165+
}
166+
160167
#if ZEND_AST_SPEC
161168
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_0(zend_ast_kind kind) {
162169
zend_ast *ast;
@@ -400,6 +407,30 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_2(zend_ast_kind kind, zen
400407

401408
return ast;
402409
}
410+
411+
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_arg_list_0(zend_ast_kind kind) {
412+
return zend_ast_create_list(0, kind);
413+
}
414+
415+
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_arg_list_1(zend_ast_kind kind, zend_ast *arg) {
416+
zend_ast *list = zend_ast_create_list(1, kind, arg);
417+
418+
if (zend_ast_is_placeholder_arg(arg)) {
419+
return zend_ast_create_fcc(list);
420+
}
421+
422+
return list;
423+
}
424+
425+
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_arg_list_2(zend_ast_kind kind, zend_ast *arg1, zend_ast *arg2) {
426+
zend_ast *list = zend_ast_create_list(2, kind, arg1, arg2);
427+
428+
if (zend_ast_is_placeholder_arg(arg1) || zend_ast_is_placeholder_arg(arg2)) {
429+
return zend_ast_create_fcc(list);
430+
}
431+
432+
return list;
433+
}
403434
#else
404435
static zend_ast *zend_ast_create_from_va_list(zend_ast_kind kind, zend_ast_attr attr, va_list va) {
405436
uint32_t i, children = kind >> ZEND_AST_NUM_CHILDREN_SHIFT;
@@ -479,6 +510,41 @@ ZEND_API zend_ast *zend_ast_create_list(uint32_t init_children, zend_ast_kind ki
479510

480511
return ast;
481512
}
513+
514+
ZEND_API zend_ast *zend_ast_create_arg_list(uint32_t init_children, zend_ast_kind kind, ...) {
515+
zend_ast *ast;
516+
zend_ast_list *list;
517+
bool has_placeholders = false;
518+
519+
ast = zend_ast_alloc(zend_ast_list_size(4));
520+
list = (zend_ast_list *) ast;
521+
list->kind = kind;
522+
list->attr = 0;
523+
list->lineno = CG(zend_lineno);
524+
list->children = 0;
525+
526+
{
527+
va_list va;
528+
uint32_t i;
529+
va_start(va, kind);
530+
for (i = 0; i < init_children; ++i) {
531+
zend_ast *child = va_arg(va, zend_ast *);
532+
ast = zend_ast_list_add(ast, child);
533+
uint32_t lineno = zend_ast_get_lineno(child);
534+
if (lineno < ast->lineno) {
535+
ast->lineno = lineno;
536+
}
537+
has_placeholders = has_placeholders || zend_ast_is_placeholder_arg(child);
538+
}
539+
va_end(va);
540+
}
541+
542+
if (has_placeholders) {
543+
return zend_ast_create_fcc(list);
544+
}
545+
546+
return ast;
547+
}
482548
#endif
483549

484550
zend_ast *zend_ast_create_concat_op(zend_ast *op0, zend_ast *op1) {
@@ -508,6 +574,23 @@ ZEND_ATTRIBUTE_NODISCARD ZEND_API zend_ast * ZEND_FASTCALL zend_ast_list_add(zen
508574
return (zend_ast *) list;
509575
}
510576

577+
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_arg_list_add(zend_ast *list, zend_ast *arg)
578+
{
579+
if (list->kind == ZEND_AST_CALLABLE_CONVERT) {
580+
zend_ast_fcc *fcc_ast = (zend_ast_fcc*)list;
581+
fcc_ast->args = zend_ast_list_add(fcc_ast->args, arg);
582+
return (zend_ast*)fcc_ast;
583+
}
584+
585+
ZEND_ASSERT(list->kind == ZEND_AST_ARG_LIST);
586+
587+
if (zend_ast_is_placeholder_arg(arg)) {
588+
return zend_ast_create_fcc(zend_ast_list_add(list, arg));
589+
}
590+
591+
return zend_ast_list_add(list, arg);
592+
}
593+
511594
static zend_result zend_ast_add_array_element(const zval *result, zval *offset, zval *expr)
512595
{
513596
if (Z_TYPE_P(offset) == IS_UNDEF) {
@@ -1060,6 +1143,15 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
10601143
case ZEND_AST_CALL: {
10611144
ZEND_ASSERT(ast->child[1]->kind == ZEND_AST_CALLABLE_CONVERT);
10621145
zend_ast_fcc *fcc_ast = (zend_ast_fcc*)ast->child[1];
1146+
1147+
zend_ast_list *args = zend_ast_get_list(fcc_ast->args);
1148+
ZEND_ASSERT(args->children > 0);
1149+
if (args->children != 1 || args->child[0]->attr != ZEND_PLACEHOLDER_VARIADIC) {
1150+
/* TODO: PFAs */
1151+
zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations");
1152+
return FAILURE;
1153+
}
1154+
10631155
fptr = ZEND_MAP_PTR_GET(fcc_ast->fptr);
10641156

10651157
if (!fptr) {
@@ -1087,6 +1179,14 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
10871179
ZEND_ASSERT(ast->child[2]->kind == ZEND_AST_CALLABLE_CONVERT);
10881180
zend_ast_fcc *fcc_ast = (zend_ast_fcc*)ast->child[2];
10891181

1182+
zend_ast_list *args = zend_ast_get_list(fcc_ast->args);
1183+
ZEND_ASSERT(args->children > 0);
1184+
if (args->children != 1 || args->child[0]->attr != ZEND_PLACEHOLDER_VARIADIC) {
1185+
/* TODO: PFAs */
1186+
zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations");
1187+
return FAILURE;
1188+
}
1189+
10901190
zend_class_entry *ce = zend_ast_fetch_class(ast->child[0], scope);
10911191
if (!ce) {
10921192
return FAILURE;
@@ -1243,7 +1343,8 @@ static size_t ZEND_FASTCALL zend_ast_tree_size(zend_ast *ast)
12431343
} else if (ast->kind == ZEND_AST_OP_ARRAY) {
12441344
size = sizeof(zend_ast_op_array);
12451345
} else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) {
1246-
size = sizeof(zend_ast_fcc);
1346+
zend_ast *args_ast = ((zend_ast_fcc*)ast)->args;
1347+
size = sizeof(zend_ast_fcc) + zend_ast_tree_size(args_ast);
12471348
} else if (zend_ast_is_list(ast)) {
12481349
uint32_t i;
12491350
const zend_ast_list *list = zend_ast_get_list(ast);
@@ -1320,6 +1421,8 @@ static void* ZEND_FASTCALL zend_ast_tree_copy(zend_ast *ast, void *buf)
13201421
new->lineno = old->lineno;
13211422
ZEND_MAP_PTR_INIT(new->fptr, ZEND_MAP_PTR(old->fptr));
13221423
buf = (void*)((char*)buf + sizeof(zend_ast_fcc));
1424+
new->args = buf;
1425+
buf = zend_ast_tree_copy(old->args, buf);
13231426
} else if (zend_ast_is_decl(ast)) {
13241427
/* Not implemented. */
13251428
ZEND_UNREACHABLE();
@@ -1403,6 +1506,11 @@ ZEND_API void ZEND_FASTCALL zend_ast_destroy(zend_ast *ast)
14031506
zend_ast_destroy(decl->child[3]);
14041507
ast = decl->child[4];
14051508
goto tail_call;
1509+
} else if (EXPECTED(ast->kind == ZEND_AST_CALLABLE_CONVERT)) {
1510+
zend_ast_fcc *fcc_ast = (zend_ast_fcc*) ast;
1511+
1512+
ast = fcc_ast->args;
1513+
goto tail_call;
14061514
}
14071515
}
14081516

@@ -2299,6 +2407,13 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
22992407
EMPTY_SWITCH_DEFAULT_CASE();
23002408
}
23012409
break;
2410+
case ZEND_AST_PLACEHOLDER_ARG:
2411+
if (ast->attr == ZEND_PLACEHOLDER_VARIADIC) {
2412+
APPEND_STR("...");
2413+
} else {
2414+
APPEND_STR("?");
2415+
}
2416+
break;
23022417

23032418
/* 1 child node */
23042419
case ZEND_AST_VAR:
@@ -2445,9 +2560,11 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
24452560
zend_ast_export_ex(str, ast->child[1], 0, indent);
24462561
smart_str_appendc(str, ')');
24472562
break;
2448-
case ZEND_AST_CALLABLE_CONVERT:
2449-
smart_str_appends(str, "...");
2450-
break;
2563+
case ZEND_AST_CALLABLE_CONVERT: {
2564+
zend_ast_fcc *fcc_ast = (zend_ast_fcc*)ast;
2565+
ast = fcc_ast->args;
2566+
goto simple_list;
2567+
}
24512568
case ZEND_AST_CLASS_CONST:
24522569
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
24532570
smart_str_appends(str, "::");

Zend/zend_ast.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ enum _zend_ast_kind {
7676
ZEND_AST_TYPE,
7777
ZEND_AST_CONSTANT_CLASS,
7878
ZEND_AST_CALLABLE_CONVERT,
79+
ZEND_AST_PLACEHOLDER_ARG,
7980

8081
/* 1 child node */
8182
ZEND_AST_VAR = 1 << ZEND_AST_NUM_CHILDREN_SHIFT,
@@ -229,10 +230,12 @@ typedef struct _zend_ast_decl {
229230
zend_ast *child[5];
230231
} zend_ast_decl;
231232

233+
// TODO: rename
232234
typedef struct _zend_ast_fcc {
233235
zend_ast_kind kind; /* Type of the node (ZEND_AST_* enum constant) */
234236
zend_ast_attr attr; /* Additional attribute, use depending on node type */
235237
uint32_t lineno; /* Line number */
238+
zend_ast *args;
236239
ZEND_MAP_PTR_DEF(zend_function *, fptr);
237240
} zend_ast_fcc;
238241

@@ -307,27 +310,39 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_0(zend_ast_kind kind);
307310
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_1(zend_ast_kind kind, zend_ast *child);
308311
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_2(zend_ast_kind kind, zend_ast *child1, zend_ast *child2);
309312

313+
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_arg_list_0(zend_ast_kind kind);
314+
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_arg_list_1(zend_ast_kind kind, zend_ast *child);
315+
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_arg_list_2(zend_ast_kind kind, zend_ast *child1, zend_ast *child2);
316+
310317
# define zend_ast_create(...) \
311318
ZEND_AST_SPEC_CALL(zend_ast_create, __VA_ARGS__)
312319
# define zend_ast_create_ex(...) \
313320
ZEND_AST_SPEC_CALL_EX(zend_ast_create_ex, __VA_ARGS__)
314321
# define zend_ast_create_list(init_children, ...) \
315322
ZEND_AST_SPEC_CALL(zend_ast_create_list, __VA_ARGS__)
323+
# define zend_ast_create_arg_list(init_children, ...) \
324+
ZEND_AST_SPEC_CALL(zend_ast_create_arg_list, __VA_ARGS__)
316325

317326
#else
318327
ZEND_API zend_ast *zend_ast_create(zend_ast_kind kind, ...);
319328
ZEND_API zend_ast *zend_ast_create_ex(zend_ast_kind kind, zend_ast_attr attr, ...);
320329
ZEND_API zend_ast *zend_ast_create_list(uint32_t init_children, zend_ast_kind kind, ...);
330+
ZEND_API zend_ast *zend_ast_create_arg_list(uint32_t init_children, zend_ast_kind kind, ...);
321331
#endif
322332

323333
ZEND_ATTRIBUTE_NODISCARD ZEND_API zend_ast * ZEND_FASTCALL zend_ast_list_add(zend_ast *list, zend_ast *op);
324334

335+
/* Like zend_ast_list_add(), but wraps the list into a ZEND_AST_CALLABLE_CONVERT
336+
* if any arg is a ZEND_AST_PLACEHOLDER_ARG. list can be a zend_ast_list, or a
337+
* zend_ast_fcc. */
338+
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_arg_list_add(zend_ast *list, zend_ast *arg);
339+
325340
ZEND_API zend_ast *zend_ast_create_decl(
326341
zend_ast_kind kind, uint32_t flags, uint32_t start_lineno, zend_string *doc_comment,
327342
zend_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2, zend_ast *child3, zend_ast *child4
328343
);
329344

330-
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_fcc(void);
345+
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_fcc(zend_ast *args);
331346

332347
typedef struct {
333348
bool had_side_effects;

Zend/zend_compile.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3948,6 +3948,11 @@ static bool zend_compile_call_common(znode *result, zend_ast *args_ast, const ze
39483948
zend_error_noreturn(E_COMPILE_ERROR, "Cannot create Closure for new expression");
39493949
}
39503950

3951+
zend_ast_list *args = zend_ast_get_list(((zend_ast_fcc*)args_ast)->args);
3952+
if (args->children != 1 || args->child[0]->attr != ZEND_PLACEHOLDER_VARIADIC) {
3953+
zend_error_noreturn(E_COMPILE_ERROR, "Cannot create a Closure for call expression with more than one argument, or non-variadic placeholders");
3954+
}
3955+
39513956
if (opcode == ZEND_INIT_FCALL) {
39523957
opline->op1.num = zend_vm_calc_used_stack(0, fbc);
39533958
}

Zend/zend_compile.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,6 +1236,9 @@ static zend_always_inline bool zend_check_arg_send_type(const zend_function *zf,
12361236
#define ZEND_IS_BINARY_ASSIGN_OP_OPCODE(opcode) \
12371237
(((opcode) >= ZEND_ADD) && ((opcode) <= ZEND_POW))
12381238

1239+
/* PFAs/FCCs */
1240+
#define ZEND_PLACEHOLDER_VARIADIC (1<<0)
1241+
12391242
/* Pseudo-opcodes that are used only temporarily during compilation */
12401243
#define ZEND_GOTO 253
12411244
#define ZEND_BRK 254

Zend/zend_language_parser.y

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -901,16 +901,15 @@ return_type:
901901
;
902902

903903
argument_list:
904-
'(' ')' { $$ = zend_ast_create_list(0, ZEND_AST_ARG_LIST); }
904+
'(' ')' { $$ = zend_ast_create_arg_list(0, ZEND_AST_ARG_LIST); }
905905
| '(' non_empty_argument_list possible_comma ')' { $$ = $2; }
906-
| '(' T_ELLIPSIS ')' { $$ = zend_ast_create_fcc(); }
907906
;
908907

909908
non_empty_argument_list:
910909
argument
911-
{ $$ = zend_ast_create_list(1, ZEND_AST_ARG_LIST, $1); }
910+
{ $$ = zend_ast_create_arg_list(1, ZEND_AST_ARG_LIST, $1); }
912911
| non_empty_argument_list ',' argument
913-
{ $$ = zend_ast_list_add($1, $3); }
912+
{ $$ = zend_ast_arg_list_add($1, $3); }
914913
;
915914

916915
/* `clone_argument_list` is necessary to resolve a parser ambiguity (shift-reduce conflict)
@@ -923,25 +922,31 @@ non_empty_argument_list:
923922
* syntax.
924923
*/
925924
clone_argument_list:
926-
'(' ')' { $$ = zend_ast_create_list(0, ZEND_AST_ARG_LIST); }
925+
'(' ')' { $$ = zend_ast_create_arg_list(0, ZEND_AST_ARG_LIST); }
927926
| '(' non_empty_clone_argument_list possible_comma ')' { $$ = $2; }
928-
| '(' expr ',' ')' { $$ = zend_ast_create_list(1, ZEND_AST_ARG_LIST, $2); }
929-
| '(' T_ELLIPSIS ')' { $$ = zend_ast_create_fcc(); }
927+
| '(' expr ',' ')' { $$ = zend_ast_create_arg_list(1, ZEND_AST_ARG_LIST, $2); }
930928
;
931929

932930
non_empty_clone_argument_list:
933931
expr ',' argument
934-
{ $$ = zend_ast_create_list(2, ZEND_AST_ARG_LIST, $1, $3); }
932+
{ $$ = zend_ast_create_arg_list(2, ZEND_AST_ARG_LIST, $1, $3); }
935933
| argument_no_expr
936-
{ $$ = zend_ast_create_list(1, ZEND_AST_ARG_LIST, $1); }
934+
{ $$ = zend_ast_create_arg_list(1, ZEND_AST_ARG_LIST, $1); }
937935
| non_empty_clone_argument_list ',' argument
938-
{ $$ = zend_ast_list_add($1, $3); }
936+
{ $$ = zend_ast_arg_list_add($1, $3); }
939937
;
940938

941939
argument_no_expr:
942940
identifier ':' expr
943941
{ $$ = zend_ast_create(ZEND_AST_NAMED_ARG, $1, $3); }
944-
| T_ELLIPSIS expr { $$ = zend_ast_create(ZEND_AST_UNPACK, $2); }
942+
| T_ELLIPSIS
943+
{ $$ = zend_ast_create_ex(ZEND_AST_PLACEHOLDER_ARG, ZEND_PLACEHOLDER_VARIADIC); }
944+
| '?'
945+
{ $$ = zend_ast_create(ZEND_AST_PLACEHOLDER_ARG); }
946+
| identifier ':' '?'
947+
{ $$ = zend_ast_create(ZEND_AST_NAMED_ARG, $1, zend_ast_create(ZEND_AST_PLACEHOLDER_ARG)); }
948+
| T_ELLIPSIS expr
949+
{ $$ = zend_ast_create(ZEND_AST_UNPACK, $2); }
945950
;
946951

947952
argument:

Zend/zend_types.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,9 @@ struct _zend_ast_ref {
638638
#define _IS_BOOL 18
639639
#define _IS_NUMBER 19
640640

641+
/* used for PFAs/FCCs */
642+
#define _IS_PLACEHOLDER 20
643+
641644
/* guard flags */
642645
#define ZEND_GUARD_PROPERTY_GET (1<<0)
643646
#define ZEND_GUARD_PROPERTY_SET (1<<1)

0 commit comments

Comments
 (0)