Commit 5472cac806a for php.net
commit 5472cac806a60f1c30cd473b3dabea3b205d5d1b
Author: Arnaud Le Blanc <arnaud.lb@gmail.com>
Date: Tue Dec 16 13:11:43 2025 +0100
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.
diff --git a/Zend/tests/first_class_callable/first_class_callable_non_unary_error.phpt b/Zend/tests/first_class_callable/first_class_callable_non_unary_error.phpt
new file mode 100644
index 00000000000..74e36a9ad0d
--- /dev/null
+++ b/Zend/tests/first_class_callable/first_class_callable_non_unary_error.phpt
@@ -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
diff --git a/Zend/tests/first_class_callable/first_class_callable_non_variadic_error.phpt b/Zend/tests/first_class_callable/first_class_callable_non_variadic_error.phpt
new file mode 100644
index 00000000000..efbd13b7593
--- /dev/null
+++ b/Zend/tests/first_class_callable/first_class_callable_non_variadic_error.phpt
@@ -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
diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c
index 30bd4a9c05d..2cd2ab40b04 100644
--- a/Zend/zend_ast.c
+++ b/Zend/zend_ast.c
@@ -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 @@ ZEND_API void ZEND_FASTCALL zend_ast_destroy(zend_ast *ast)
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 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
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 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
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, "::");
diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h
index fb48b187252..1c6d6f82fad 100644
--- a/Zend/zend_ast.h
+++ b/Zend/zend_ast.h
@@ -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;
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index 0cb4ee3740f..203201bcc12 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -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);
}
diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h
index d2a3b47bf92..6075ae3d1e5 100644
--- a/Zend/zend_compile.h
+++ b/Zend/zend_compile.h
@@ -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
diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y
index e4d61006fe1..882a1763641 100644
--- a/Zend/zend_language_parser.y
+++ b/Zend/zend_language_parser.y
@@ -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:
diff --git a/Zend/zend_types.h b/Zend/zend_types.h
index 43aa2aa86a0..22dbfa9be87 100644
--- a/Zend/zend_types.h
+++ b/Zend/zend_types.h
@@ -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)
diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c
index d430f4833d3..fb6196abb93 100644
--- a/ext/opcache/zend_file_cache.c
+++ b/ext/opcache/zend_file_cache.c
@@ -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);
diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c
index ef69cceb025..9bc2496837c 100644
--- a/ext/opcache/zend_persist.c
+++ b/ext/opcache/zend_persist.c
@@ -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. */
diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c
index c638d66619d..0b0ff51d0d4 100644
--- a/ext/opcache/zend_persist_calc.c
+++ b/ext/opcache/zend_persist_calc.c
@@ -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();