1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

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 GH-20717.
This commit is contained in:
Arnaud Le Blanc
2025-12-16 13:11:43 +01:00
parent e4098da58a
commit 5472cac806
11 changed files with 191 additions and 18 deletions

View File

@@ -0,0 +1,10 @@
--TEST--
First class callable error: more than one argument
--FILE--
<?php
foo(1, ...);
?>
--EXPECTF--
Fatal error: Cannot create a Closure for call expression with more than one argument, or non-variadic placeholders in %s on line %d

View File

@@ -0,0 +1,10 @@
--TEST--
First class callable error: non-variadic placeholder
--FILE--
<?php
foo(?);
?>
--EXPECTF--
Fatal error: Cannot create a Closure for call expression with more than one argument, or non-variadic placeholders in %s on line %d

View File

@@ -54,13 +54,14 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_znode(const znode *node) {
return (zend_ast *) ast;
}
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_fcc(void) {
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_fcc(zend_ast *args) {
zend_ast_fcc *ast;
ast = zend_ast_alloc(sizeof(zend_ast_fcc));
ast->kind = ZEND_AST_CALLABLE_CONVERT;
ast->attr = 0;
ast->lineno = CG(zend_lineno);
ast->args = args;
ZEND_MAP_PTR_INIT(ast->fptr, NULL);
return (zend_ast *) ast;
@@ -157,6 +158,12 @@ ZEND_API zend_ast *zend_ast_create_decl(
return (zend_ast *) ast;
}
static bool zend_ast_is_placeholder_arg(zend_ast *arg) {
return arg->kind == ZEND_AST_PLACEHOLDER_ARG
|| (arg->kind == ZEND_AST_NAMED_ARG
&& arg->child[1]->kind == ZEND_AST_PLACEHOLDER_ARG);
}
#if ZEND_AST_SPEC
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_0(zend_ast_kind kind) {
zend_ast *ast;
@@ -400,6 +407,30 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_2(zend_ast_kind kind, zen
return ast;
}
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_arg_list_0(zend_ast_kind kind) {
return zend_ast_create_list(0, kind);
}
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_arg_list_1(zend_ast_kind kind, zend_ast *arg) {
zend_ast *list = zend_ast_create_list(1, kind, arg);
if (zend_ast_is_placeholder_arg(arg)) {
return zend_ast_create_fcc(list);
}
return list;
}
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_arg_list_2(zend_ast_kind kind, zend_ast *arg1, zend_ast *arg2) {
zend_ast *list = zend_ast_create_list(2, kind, arg1, arg2);
if (zend_ast_is_placeholder_arg(arg1) || zend_ast_is_placeholder_arg(arg2)) {
return zend_ast_create_fcc(list);
}
return list;
}
#else
static zend_ast *zend_ast_create_from_va_list(zend_ast_kind kind, zend_ast_attr attr, va_list va) {
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
return ast;
}
ZEND_API zend_ast *zend_ast_create_arg_list(uint32_t init_children, zend_ast_kind kind, ...) {
zend_ast *ast;
zend_ast_list *list;
bool has_placeholders = false;
ast = zend_ast_alloc(zend_ast_list_size(4));
list = (zend_ast_list *) ast;
list->kind = kind;
list->attr = 0;
list->lineno = CG(zend_lineno);
list->children = 0;
{
va_list va;
uint32_t i;
va_start(va, kind);
for (i = 0; i < init_children; ++i) {
zend_ast *child = va_arg(va, zend_ast *);
ast = zend_ast_list_add(ast, child);
uint32_t lineno = zend_ast_get_lineno(child);
if (lineno < ast->lineno) {
ast->lineno = lineno;
}
has_placeholders = has_placeholders || zend_ast_is_placeholder_arg(child);
}
va_end(va);
}
if (has_placeholders) {
return zend_ast_create_fcc(list);
}
return ast;
}
#endif
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
return (zend_ast *) list;
}
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_arg_list_add(zend_ast *list, zend_ast *arg)
{
if (list->kind == ZEND_AST_CALLABLE_CONVERT) {
zend_ast_fcc *fcc_ast = (zend_ast_fcc*)list;
fcc_ast->args = zend_ast_list_add(fcc_ast->args, arg);
return (zend_ast*)fcc_ast;
}
ZEND_ASSERT(list->kind == ZEND_AST_ARG_LIST);
if (zend_ast_is_placeholder_arg(arg)) {
return zend_ast_create_fcc(zend_ast_list_add(list, arg));
}
return zend_ast_list_add(list, arg);
}
static zend_result zend_ast_add_array_element(const zval *result, zval *offset, zval *expr)
{
if (Z_TYPE_P(offset) == IS_UNDEF) {
@@ -1060,6 +1143,15 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
case ZEND_AST_CALL: {
ZEND_ASSERT(ast->child[1]->kind == ZEND_AST_CALLABLE_CONVERT);
zend_ast_fcc *fcc_ast = (zend_ast_fcc*)ast->child[1];
zend_ast_list *args = zend_ast_get_list(fcc_ast->args);
ZEND_ASSERT(args->children > 0);
if (args->children != 1 || args->child[0]->attr != ZEND_PLACEHOLDER_VARIADIC) {
/* TODO: PFAs */
zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations");
return FAILURE;
}
fptr = ZEND_MAP_PTR_GET(fcc_ast->fptr);
if (!fptr) {
@@ -1087,6 +1179,14 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
ZEND_ASSERT(ast->child[2]->kind == ZEND_AST_CALLABLE_CONVERT);
zend_ast_fcc *fcc_ast = (zend_ast_fcc*)ast->child[2];
zend_ast_list *args = zend_ast_get_list(fcc_ast->args);
ZEND_ASSERT(args->children > 0);
if (args->children != 1 || args->child[0]->attr != ZEND_PLACEHOLDER_VARIADIC) {
/* TODO: PFAs */
zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations");
return FAILURE;
}
zend_class_entry *ce = zend_ast_fetch_class(ast->child[0], scope);
if (!ce) {
return FAILURE;
@@ -1243,7 +1343,8 @@ static size_t ZEND_FASTCALL zend_ast_tree_size(zend_ast *ast)
} else if (ast->kind == ZEND_AST_OP_ARRAY) {
size = sizeof(zend_ast_op_array);
} else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) {
size = sizeof(zend_ast_fcc);
zend_ast *args_ast = ((zend_ast_fcc*)ast)->args;
size = sizeof(zend_ast_fcc) + zend_ast_tree_size(args_ast);
} else if (zend_ast_is_list(ast)) {
uint32_t i;
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)
new->lineno = old->lineno;
ZEND_MAP_PTR_INIT(new->fptr, ZEND_MAP_PTR(old->fptr));
buf = (void*)((char*)buf + sizeof(zend_ast_fcc));
new->args = buf;
buf = zend_ast_tree_copy(old->args, buf);
} else if (zend_ast_is_decl(ast)) {
/* Not implemented. */
ZEND_UNREACHABLE();
@@ -1403,6 +1506,11 @@ tail_call:
zend_ast_destroy(decl->child[3]);
ast = decl->child[4];
goto tail_call;
} else if (EXPECTED(ast->kind == ZEND_AST_CALLABLE_CONVERT)) {
zend_ast_fcc *fcc_ast = (zend_ast_fcc*) ast;
ast = fcc_ast->args;
goto tail_call;
}
}
@@ -2299,6 +2407,13 @@ simple_list:
EMPTY_SWITCH_DEFAULT_CASE();
}
break;
case ZEND_AST_PLACEHOLDER_ARG:
if (ast->attr == ZEND_PLACEHOLDER_VARIADIC) {
APPEND_STR("...");
} else {
APPEND_STR("?");
}
break;
/* 1 child node */
case ZEND_AST_VAR:
@@ -2445,9 +2560,11 @@ simple_list:
zend_ast_export_ex(str, ast->child[1], 0, indent);
smart_str_appendc(str, ')');
break;
case ZEND_AST_CALLABLE_CONVERT:
smart_str_appends(str, "...");
break;
case ZEND_AST_CALLABLE_CONVERT: {
zend_ast_fcc *fcc_ast = (zend_ast_fcc*)ast;
ast = fcc_ast->args;
goto simple_list;
}
case ZEND_AST_CLASS_CONST:
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
smart_str_appends(str, "::");

View File

@@ -76,6 +76,7 @@ enum _zend_ast_kind {
ZEND_AST_TYPE,
ZEND_AST_CONSTANT_CLASS,
ZEND_AST_CALLABLE_CONVERT,
ZEND_AST_PLACEHOLDER_ARG,
/* 1 child node */
ZEND_AST_VAR = 1 << ZEND_AST_NUM_CHILDREN_SHIFT,
@@ -229,10 +230,12 @@ typedef struct _zend_ast_decl {
zend_ast *child[5];
} zend_ast_decl;
// TODO: rename
typedef struct _zend_ast_fcc {
zend_ast_kind kind; /* Type of the node (ZEND_AST_* enum constant) */
zend_ast_attr attr; /* Additional attribute, use depending on node type */
uint32_t lineno; /* Line number */
zend_ast *args;
ZEND_MAP_PTR_DEF(zend_function *, fptr);
} zend_ast_fcc;
@@ -307,27 +310,39 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_0(zend_ast_kind kind);
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_1(zend_ast_kind kind, zend_ast *child);
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_2(zend_ast_kind kind, zend_ast *child1, zend_ast *child2);
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_arg_list_0(zend_ast_kind kind);
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_arg_list_1(zend_ast_kind kind, zend_ast *child);
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_arg_list_2(zend_ast_kind kind, zend_ast *child1, zend_ast *child2);
# define zend_ast_create(...) \
ZEND_AST_SPEC_CALL(zend_ast_create, __VA_ARGS__)
# define zend_ast_create_ex(...) \
ZEND_AST_SPEC_CALL_EX(zend_ast_create_ex, __VA_ARGS__)
# define zend_ast_create_list(init_children, ...) \
ZEND_AST_SPEC_CALL(zend_ast_create_list, __VA_ARGS__)
# define zend_ast_create_arg_list(init_children, ...) \
ZEND_AST_SPEC_CALL(zend_ast_create_arg_list, __VA_ARGS__)
#else
ZEND_API zend_ast *zend_ast_create(zend_ast_kind kind, ...);
ZEND_API zend_ast *zend_ast_create_ex(zend_ast_kind kind, zend_ast_attr attr, ...);
ZEND_API zend_ast *zend_ast_create_list(uint32_t init_children, zend_ast_kind kind, ...);
ZEND_API zend_ast *zend_ast_create_arg_list(uint32_t init_children, zend_ast_kind kind, ...);
#endif
ZEND_ATTRIBUTE_NODISCARD ZEND_API zend_ast * ZEND_FASTCALL zend_ast_list_add(zend_ast *list, zend_ast *op);
/* Like zend_ast_list_add(), but wraps the list into a ZEND_AST_CALLABLE_CONVERT
* if any arg is a ZEND_AST_PLACEHOLDER_ARG. list can be a zend_ast_list, or a
* zend_ast_fcc. */
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_arg_list_add(zend_ast *list, zend_ast *arg);
ZEND_API zend_ast *zend_ast_create_decl(
zend_ast_kind kind, uint32_t flags, uint32_t start_lineno, zend_string *doc_comment,
zend_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2, zend_ast *child3, zend_ast *child4
);
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_fcc(void);
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_fcc(zend_ast *args);
typedef struct {
bool had_side_effects;

View File

@@ -3948,6 +3948,11 @@ static bool zend_compile_call_common(znode *result, zend_ast *args_ast, const ze
zend_error_noreturn(E_COMPILE_ERROR, "Cannot create Closure for new expression");
}
zend_ast_list *args = zend_ast_get_list(((zend_ast_fcc*)args_ast)->args);
if (args->children != 1 || args->child[0]->attr != ZEND_PLACEHOLDER_VARIADIC) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot create a Closure for call expression with more than one argument, or non-variadic placeholders");
}
if (opcode == ZEND_INIT_FCALL) {
opline->op1.num = zend_vm_calc_used_stack(0, fbc);
}

View File

@@ -1236,6 +1236,9 @@ static zend_always_inline bool zend_check_arg_send_type(const zend_function *zf,
#define ZEND_IS_BINARY_ASSIGN_OP_OPCODE(opcode) \
(((opcode) >= ZEND_ADD) && ((opcode) <= ZEND_POW))
/* PFAs/FCCs */
#define ZEND_PLACEHOLDER_VARIADIC (1<<0)
/* Pseudo-opcodes that are used only temporarily during compilation */
#define ZEND_GOTO 253
#define ZEND_BRK 254

View File

@@ -901,16 +901,15 @@ return_type:
;
argument_list:
'(' ')' { $$ = zend_ast_create_list(0, ZEND_AST_ARG_LIST); }
'(' ')' { $$ = zend_ast_create_arg_list(0, ZEND_AST_ARG_LIST); }
| '(' non_empty_argument_list possible_comma ')' { $$ = $2; }
| '(' T_ELLIPSIS ')' { $$ = zend_ast_create_fcc(); }
;
non_empty_argument_list:
argument
{ $$ = zend_ast_create_list(1, ZEND_AST_ARG_LIST, $1); }
{ $$ = zend_ast_create_arg_list(1, ZEND_AST_ARG_LIST, $1); }
| non_empty_argument_list ',' argument
{ $$ = zend_ast_list_add($1, $3); }
{ $$ = zend_ast_arg_list_add($1, $3); }
;
/* `clone_argument_list` is necessary to resolve a parser ambiguity (shift-reduce conflict)
@@ -923,25 +922,31 @@ non_empty_argument_list:
* syntax.
*/
clone_argument_list:
'(' ')' { $$ = zend_ast_create_list(0, ZEND_AST_ARG_LIST); }
'(' ')' { $$ = zend_ast_create_arg_list(0, ZEND_AST_ARG_LIST); }
| '(' non_empty_clone_argument_list possible_comma ')' { $$ = $2; }
| '(' expr ',' ')' { $$ = zend_ast_create_list(1, ZEND_AST_ARG_LIST, $2); }
| '(' T_ELLIPSIS ')' { $$ = zend_ast_create_fcc(); }
| '(' expr ',' ')' { $$ = zend_ast_create_arg_list(1, ZEND_AST_ARG_LIST, $2); }
;
non_empty_clone_argument_list:
expr ',' argument
{ $$ = zend_ast_create_list(2, ZEND_AST_ARG_LIST, $1, $3); }
{ $$ = zend_ast_create_arg_list(2, ZEND_AST_ARG_LIST, $1, $3); }
| argument_no_expr
{ $$ = zend_ast_create_list(1, ZEND_AST_ARG_LIST, $1); }
{ $$ = zend_ast_create_arg_list(1, ZEND_AST_ARG_LIST, $1); }
| non_empty_clone_argument_list ',' argument
{ $$ = zend_ast_list_add($1, $3); }
{ $$ = zend_ast_arg_list_add($1, $3); }
;
argument_no_expr:
identifier ':' expr
{ $$ = zend_ast_create(ZEND_AST_NAMED_ARG, $1, $3); }
| T_ELLIPSIS expr { $$ = zend_ast_create(ZEND_AST_UNPACK, $2); }
| T_ELLIPSIS
{ $$ = zend_ast_create_ex(ZEND_AST_PLACEHOLDER_ARG, ZEND_PLACEHOLDER_VARIADIC); }
| '?'
{ $$ = zend_ast_create(ZEND_AST_PLACEHOLDER_ARG); }
| identifier ':' '?'
{ $$ = zend_ast_create(ZEND_AST_NAMED_ARG, $1, zend_ast_create(ZEND_AST_PLACEHOLDER_ARG)); }
| T_ELLIPSIS expr
{ $$ = zend_ast_create(ZEND_AST_UNPACK, $2); }
;
argument:

View File

@@ -638,6 +638,9 @@ struct _zend_ast_ref {
#define _IS_BOOL 18
#define _IS_NUMBER 19
/* used for PFAs/FCCs */
#define _IS_PLACEHOLDER 20
/* guard flags */
#define ZEND_GUARD_PROPERTY_GET (1<<0)
#define ZEND_GUARD_PROPERTY_SET (1<<1)

View File

@@ -384,6 +384,7 @@ static void zend_file_cache_serialize_ast(zend_ast *ast,
} else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) {
zend_ast_fcc *fcc = (zend_ast_fcc*)ast;
ZEND_MAP_PTR_INIT(fcc->fptr, NULL);
zend_file_cache_serialize_ast(fcc->args, script, info, buf);
} else if (zend_ast_is_decl(ast)) {
/* Not implemented. */
ZEND_UNREACHABLE();
@@ -1304,6 +1305,7 @@ static void zend_file_cache_unserialize_ast(zend_ast *ast,
} else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) {
zend_ast_fcc *fcc = (zend_ast_fcc*)ast;
ZEND_MAP_PTR_NEW(fcc->fptr);
zend_file_cache_unserialize_ast(fcc->args, script, buf);
} else if (zend_ast_is_decl(ast)) {
/* Not implemented. */
ZEND_UNREACHABLE();
@@ -2109,7 +2111,7 @@ void zend_file_cache_invalidate(zend_string *full_path)
if (ZCG(accel_directives).file_cache_read_only) {
return;
}
char *filename;
filename = zend_file_cache_get_bin_file_path(full_path);

View File

@@ -197,6 +197,7 @@ static zend_ast *zend_persist_ast(zend_ast *ast)
node = (zend_ast *) copy;
} else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) {
zend_ast_fcc *copy = zend_shared_memdup(ast, sizeof(zend_ast_fcc));
copy->args = zend_persist_ast(copy->args);
node = (zend_ast *) copy;
} else if (zend_ast_is_decl(ast)) {
/* Not implemented. */

View File

@@ -92,7 +92,9 @@ static void zend_persist_ast_calc(zend_ast *ast)
ZVAL_PTR(&z, zend_ast_get_op_array(ast)->op_array);
zend_persist_op_array_calc(&z);
} else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) {
zend_ast_fcc *fcc_ast = (zend_ast_fcc*)ast;
ADD_SIZE(sizeof(zend_ast_fcc));
zend_persist_ast_calc(fcc_ast->args);
} else if (zend_ast_is_decl(ast)) {
/* Not implemented. */
ZEND_UNREACHABLE();