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

Support Closures in constant expressions (#16458)

RFC: https://wiki.php.net/rfc/closures_in_const_expr

Co-authored-by: Volker Dusch <volker@tideways-gmbh.com>
Co-authored-by: Ilija Tovilo <ilija.tovilo@me.com>
Co-authored-by: Arthur Kurbidaev <artkurbidaev@gmail.com>
This commit is contained in:
Tim Düsterhus
2024-12-02 18:25:43 +01:00
committed by GitHub
parent 3b517e0825
commit f6a0bb4d04
24 changed files with 577 additions and 26 deletions

2
NEWS
View File

@@ -10,6 +10,8 @@ PHP NEWS
. Fixed bug GH-16665 (\array and \callable should not be usable in
class_alias). (nielsdos)
. Added PHP_BUILD_DATE constant. (cmb)
. Added support for Closures in constant expressions. (timwolla,
Volker Dusch)
- Curl:
. Added curl_multi_get_handles(). (timwolla)

View File

@@ -44,6 +44,10 @@ PHP 8.5 UPGRADE NOTES
2. New Features
========================================
- Core:
. Added support for Closures in constant expressions.
RFC: https://wiki.php.net/rfc/closures_in_const_expr
- DOM:
. Added Dom\Element::$outerHTML.

View File

@@ -0,0 +1,57 @@
--TEST--
Allow defining closures in attributes
--EXTENSIONS--
reflection
--FILE--
<?php
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
class Attr {
public function __construct(public Closure $value) {
$value('foo');
}
}
#[Attr(static function () { })]
#[Attr(static function (...$args) {
var_dump($args);
})]
class C {}
foreach ((new ReflectionClass(C::class))->getAttributes() as $reflectionAttribute) {
var_dump($reflectionAttribute->newInstance());
}
?>
--EXPECTF--
object(Attr)#%d (1) {
["value"]=>
object(Closure)#%d (3) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
string(%d) "%s"
["line"]=>
int(%d)
}
}
array(1) {
[0]=>
string(3) "foo"
}
object(Attr)#%d (1) {
["value"]=>
object(Closure)#%d (4) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
string(%d) "%s"
["line"]=>
int(%d)
["parameter"]=>
array(1) {
["$args"]=>
string(10) "<optional>"
}
}
}

View File

@@ -0,0 +1,41 @@
--TEST--
AST printing for closures in attributes
--FILE--
<?php
// Do not use `false &&` to fully evaluate the function / class definition.
try {
\assert(
!
#[Attr(static function ($foo) {
echo $foo;
})]
function () { }
);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
\assert(
!
new #[Attr(static function ($foo) {
echo $foo;
})]
class {}
);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
assert(!#[Attr(static function ($foo) {
echo $foo;
})] function () {
})
assert(!new #[Attr(static function ($foo) {
echo $foo;
})] class {
})

View File

@@ -0,0 +1,28 @@
--TEST--
Closure in attribute may access private variables
--EXTENSIONS--
reflection
--FILE--
<?php
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
class Attr {
public function __construct(public Closure $value) {}
}
#[Attr(static function (C $c) {
echo $c->secret, PHP_EOL;
})]
class C {
public function __construct(
private string $secret,
) {}
}
foreach ((new ReflectionClass(C::class))->getAttributes() as $reflectionAttribute) {
($reflectionAttribute->newInstance()->value)(new C('secret'));
}
?>
--EXPECT--
secret

View File

@@ -0,0 +1,35 @@
--TEST--
Closure in attribute may not access unrelated private variables
--EXTENSIONS--
reflection
--FILE--
<?php
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
class Attr {
public function __construct(public Closure $value) {}
}
#[Attr(static function (E $e) {
echo $e->secret, PHP_EOL;
})]
class C {
}
class E {
public function __construct(
private string $secret,
) {}
}
foreach ((new ReflectionClass(C::class))->getAttributes() as $reflectionAttribute) {
($reflectionAttribute->newInstance()->value)(new E('secret'));
}
?>
--EXPECTF--
Fatal error: Uncaught Error: Cannot access private property E::$secret in %s:%d
Stack trace:
#0 %s(%d): C::{closure:%s:%d}(Object(E))
#1 {main}
thrown in %s on line %d

View File

@@ -0,0 +1,23 @@
--TEST--
Allow defining Closures in const expressions.
--FILE--
<?php
const Closure = static function () {
echo "called", PHP_EOL;
};
var_dump(Closure);
(Closure)();
?>
--EXPECTF--
object(Closure)#%d (3) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
string(%d) "%s"
["line"]=>
int(3)
}
called

View File

@@ -0,0 +1,25 @@
--TEST--
Allow defining Closures in class constants.
--FILE--
<?php
class C {
const Closure = static function () {
echo "called", PHP_EOL;
};
}
var_dump(C::Closure);
(C::Closure)();
?>
--EXPECTF--
object(Closure)#%d (3) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
string(%d) "%s"
["line"]=>
int(4)
}
called

View File

@@ -0,0 +1,41 @@
--TEST--
Allow defining Closures wrapped in an array in const expressions.
--FILE--
<?php
const Closure = [static function () {
echo "called", PHP_EOL;
}, static function () {
echo "also called", PHP_EOL;
}];
var_dump(Closure);
foreach (Closure as $closure) {
$closure();
}
?>
--EXPECTF--
array(2) {
[0]=>
object(Closure)#%d (3) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
string(%d) "%s"
["line"]=>
int(3)
}
[1]=>
object(Closure)#%d (3) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
string(%d) "%s"
["line"]=>
int(5)
}
}
called
also called

View File

@@ -0,0 +1,33 @@
--TEST--
Allow defining Closures passed as constructor arguments in const expressions.
--FILE--
<?php
class Dummy {
public function __construct(
public Closure $c,
) {}
}
const Closure = new Dummy(static function () {
echo "called", PHP_EOL;
});
var_dump(Closure);
(Closure->c)();
?>
--EXPECTF--
object(Dummy)#%d (1) {
["c"]=>
object(Closure)#%d (3) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
string(%d) "%s"
["line"]=>
int(9)
}
}
called

View File

@@ -0,0 +1,22 @@
--TEST--
Closures in default argument
--FILE--
<?php
function test(
Closure $name = static function () {
echo "default", PHP_EOL;
},
) {
$name();
}
test();
test(function () {
echo "explicit", PHP_EOL;
});
?>
--EXPECT--
default
explicit

View File

@@ -0,0 +1,18 @@
--TEST--
Disallows using non-static closures.
--FILE--
<?php
class C {
public Closure $d = function () {
var_dump($this);
};
}
$foo = new C();
var_dump($foo->d);
($foo->d)();
?>
--EXPECTF--
Fatal error: Closures in constant expressions must be static in %s on line %d

View File

@@ -0,0 +1,17 @@
--TEST--
Disallows using variables.
--FILE--
<?php
$foo = "bar";
const Closure = static function () use ($foo) {
echo $foo, PHP_EOL;
};
var_dump(Closure);
(Closure)();
?>
--EXPECTF--
Fatal error: Cannot use(...) variables in constant expression in %s on line %d

View File

@@ -0,0 +1,27 @@
--TEST--
Closure in property initializer
--FILE--
<?php
class C {
public Closure $d = static function () {
echo "called", PHP_EOL;
};
}
$c = new C();
var_dump($c->d);
($c->d)();
?>
--EXPECTF--
object(Closure)#%d (3) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
string(%d) "%s"
["line"]=>
int(4)
}
called

View File

@@ -0,0 +1,22 @@
--TEST--
Closure in property initializer may access private variables
--FILE--
<?php
class C {
public Closure $d = static function (C $c) {
echo $c->secret, PHP_EOL;
};
public function __construct(
private string $secret,
) {}
}
$c = new C('secret');
($c->d)($c);
?>
--EXPECTF--
secret

View File

@@ -0,0 +1,30 @@
--TEST--
Closure in property initializer may not access unrelated private variables
--FILE--
<?php
class C {
public Closure $d = static function (E $e) {
echo $e->secret, PHP_EOL;
};
}
class E {
public function __construct(
private string $secret,
) {}
}
$c = new C();
($c->d)(new E('secret'));
?>
--EXPECTF--
Fatal error: Uncaught Error: Cannot access private property E::$secret in %s:%d
Stack trace:
#0 %s(%d): C::{closure:%s:%d}(Object(E))
#1 {main}
thrown in %s on line %d

View File

@@ -0,0 +1,27 @@
--TEST--
Closure in static initializer
--FILE--
<?php
function foo() {
static $closure = static function () {
echo "called", PHP_EOL;
};
var_dump($closure);
$closure();
}
foo();
?>
--EXPECTF--
object(Closure)#%d (3) {
["name"]=>
string(17) "{closure:foo():4}"
["file"]=>
string(%d) "%s"
["line"]=>
int(4)
}
called

View File

@@ -0,0 +1,26 @@
--TEST--
Closure in static property initializer
--FILE--
<?php
class C {
public static Closure $d = static function () {
echo "called", PHP_EOL;
};
}
var_dump(C::$d);
(C::$d)();
?>
--EXPECTF--
object(Closure)#%d (3) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
string(%d) "%s"
["line"]=>
int(4)
}
called

View File

@@ -23,6 +23,7 @@
#include "zend_language_parser.h"
#include "zend_smart_str.h"
#include "zend_exceptions.h"
#include "zend_closures.h"
#include "zend_constants.h"
#include "zend_enum.h"
@@ -989,6 +990,13 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
}
return SUCCESS;
}
case ZEND_AST_OP_ARRAY:
{
zend_function *func = Z_PTR_P(&((zend_ast_zval*)(ast))->val);
zend_create_closure(result, func, scope, scope, NULL);
return SUCCESS;
}
case ZEND_AST_PROP:
case ZEND_AST_NULLSAFE_PROP:
{
@@ -1068,7 +1076,7 @@ static size_t ZEND_FASTCALL zend_ast_tree_size(zend_ast *ast)
{
size_t size;
if (ast->kind == ZEND_AST_ZVAL || ast->kind == ZEND_AST_CONSTANT) {
if (ast->kind == ZEND_AST_ZVAL || ast->kind == ZEND_AST_CONSTANT || ast->kind == ZEND_AST_OP_ARRAY) {
size = sizeof(zend_ast_zval);
} else if (zend_ast_is_list(ast)) {
uint32_t i;
@@ -1126,6 +1134,13 @@ static void* ZEND_FASTCALL zend_ast_tree_copy(zend_ast *ast, void *buf)
new->child[i] = NULL;
}
}
} else if (ast->kind == ZEND_AST_OP_ARRAY) {
zend_ast_zval *new = (zend_ast_zval*)buf;
new->kind = ZEND_AST_OP_ARRAY;
new->attr = ast->attr;
ZVAL_COPY(&new->val, &((zend_ast_zval *) ast)->val);
Z_LINENO(new->val) = zend_ast_get_lineno(ast);
buf = (void*)((char*)buf + sizeof(zend_ast_zval));
} else {
uint32_t i, children = zend_ast_get_num_children(ast);
zend_ast *new = (zend_ast*)buf;
@@ -1189,6 +1204,8 @@ tail_call:
}
} else if (EXPECTED(ast->kind == ZEND_AST_CONSTANT)) {
zend_string_release_ex(zend_ast_get_constant_name(ast), 0);
} else if (EXPECTED(ast->kind == ZEND_AST_OP_ARRAY)) {
ZEND_ASSERT(!Z_REFCOUNTED(((zend_ast_zval*)(ast))->val));
} else if (EXPECTED(ast->kind >= ZEND_AST_FUNC_DECL)) {
zend_ast_decl *decl = (zend_ast_decl *) ast;

View File

@@ -35,6 +35,7 @@ enum _zend_ast_kind {
/* special nodes */
ZEND_AST_ZVAL = 1 << ZEND_AST_SPECIAL_SHIFT,
ZEND_AST_CONSTANT,
ZEND_AST_OP_ARRAY,
ZEND_AST_ZNODE,
/* declaration nodes */
@@ -362,7 +363,7 @@ static zend_always_inline uint32_t zend_ast_get_lineno(zend_ast *ast) {
if (ast->kind == ZEND_AST_ZVAL) {
zval *zv = zend_ast_get_zval(ast);
return Z_LINENO_P(zv);
} else if (ast->kind == ZEND_AST_CONSTANT) {
} else if (ast->kind == ZEND_AST_CONSTANT || ast->kind == ZEND_AST_OP_ARRAY) {
zval *zv = &((zend_ast_zval *) ast)->val;
return Z_LINENO_P(zv);
} else {

View File

@@ -8138,7 +8138,13 @@ static uint32_t zend_add_dynamic_func_def(zend_op_array *def) {
return def_offset;
}
static zend_string *zend_begin_func_decl(znode *result, zend_op_array *op_array, zend_ast_decl *decl, bool toplevel) /* {{{ */
enum func_decl_level {
FUNC_DECL_LEVEL_TOPLEVEL,
FUNC_DECL_LEVEL_NESTED,
FUNC_DECL_LEVEL_CONSTEXPR,
};
static zend_string *zend_begin_func_decl(znode *result, zend_op_array *op_array, zend_ast_decl *decl, enum func_decl_level level) /* {{{ */
{
zend_string *unqualified_name, *name, *lcname;
zend_op *opline;
@@ -8208,25 +8214,34 @@ static zend_string *zend_begin_func_decl(znode *result, zend_op_array *op_array,
}
zend_register_seen_symbol(lcname, ZEND_SYMBOL_FUNCTION);
if (!toplevel) {
uint32_t func_ref = zend_add_dynamic_func_def(op_array);
if (op_array->fn_flags & ZEND_ACC_CLOSURE) {
opline = zend_emit_op_tmp(result, ZEND_DECLARE_LAMBDA_FUNCTION, NULL, NULL);
opline->op2.num = func_ref;
} else {
opline = get_next_op();
opline->opcode = ZEND_DECLARE_FUNCTION;
opline->op1_type = IS_CONST;
LITERAL_STR(opline->op1, zend_string_copy(lcname));
opline->op2.num = func_ref;
switch (level) {
case FUNC_DECL_LEVEL_CONSTEXPR:
zend_add_dynamic_func_def(op_array);
break;
case FUNC_DECL_LEVEL_NESTED: {
uint32_t func_ref = zend_add_dynamic_func_def(op_array);
if (op_array->fn_flags & ZEND_ACC_CLOSURE) {
opline = zend_emit_op_tmp(result, ZEND_DECLARE_LAMBDA_FUNCTION, NULL, NULL);
opline->op2.num = func_ref;
} else {
opline = get_next_op();
opline->opcode = ZEND_DECLARE_FUNCTION;
opline->op1_type = IS_CONST;
LITERAL_STR(opline->op1, zend_string_copy(lcname));
opline->op2.num = func_ref;
}
break;
}
case FUNC_DECL_LEVEL_TOPLEVEL:
/* Nothing to do. */
break;
}
return lcname;
}
/* }}} */
static zend_op_array *zend_compile_func_decl_ex(
znode *result, zend_ast *ast, bool toplevel,
znode *result, zend_ast *ast, enum func_decl_level level,
const zend_property_info *property_info,
zend_property_hook_kind hook_kind
) {
@@ -8272,7 +8287,7 @@ static zend_op_array *zend_compile_func_decl_ex(
bool has_body = stmt_ast != NULL;
lcname = zend_begin_method_decl(op_array, decl->name, has_body);
} else {
lcname = zend_begin_func_decl(result, op_array, decl, toplevel);
lcname = zend_begin_func_decl(result, op_array, decl, level);
if (decl->kind == ZEND_AST_ARROW_FUNC) {
find_implicit_binds(&info, params_ast, stmt_ast);
compile_implicit_lexical_binds(&info, result, op_array);
@@ -8320,7 +8335,7 @@ static zend_op_array *zend_compile_func_decl_ex(
CG(active_class_entry) = NULL;
}
if (toplevel) {
if (level == FUNC_DECL_LEVEL_TOPLEVEL) {
op_array->fn_flags |= ZEND_ACC_TOP_LEVEL;
}
@@ -8367,7 +8382,7 @@ static zend_op_array *zend_compile_func_decl_ex(
CG(zend_lineno) = decl->start_lineno;
zend_check_magic_method_implementation(
CG(active_class_entry), (zend_function *) op_array, lcname, E_COMPILE_ERROR);
} else if (toplevel) {
} else if (level == FUNC_DECL_LEVEL_TOPLEVEL) {
/* Only register the function after a successful compile */
if (UNEXPECTED(zend_hash_add_ptr(CG(function_table), lcname, op_array) == NULL)) {
CG(zend_lineno) = decl->start_lineno;
@@ -8387,7 +8402,7 @@ static zend_op_array *zend_compile_func_decl_ex(
/* Pop the loop variable stack separator */
zend_stack_del_top(&CG(loop_var_stack));
if (toplevel) {
if (level == FUNC_DECL_LEVEL_TOPLEVEL) {
zend_observer_function_declared_notify(op_array, lcname);
}
@@ -8401,9 +8416,9 @@ static zend_op_array *zend_compile_func_decl_ex(
return op_array;
}
static zend_op_array *zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel)
static zend_op_array *zend_compile_func_decl(znode *result, zend_ast *ast, enum func_decl_level level)
{
return zend_compile_func_decl_ex(result, ast, toplevel, /* property_info */ NULL, (zend_property_hook_kind)-1);
return zend_compile_func_decl_ex(result, ast, level, /* property_info */ NULL, (zend_property_hook_kind)-1);
}
zend_property_hook_kind zend_get_property_hook_kind_from_name(zend_string *name) {
@@ -8542,7 +8557,7 @@ static void zend_compile_property_hooks(
hook->name = zend_strpprintf(0, "$%s::%s", ZSTR_VAL(prop_name), ZSTR_VAL(name));
zend_function *func = (zend_function *) zend_compile_func_decl_ex(
NULL, (zend_ast *) hook, /* toplevel */ false, prop_info, hook_kind);
NULL, (zend_ast *) hook, FUNC_DECL_LEVEL_NESTED, prop_info, hook_kind);
func->common.prop_info = prop_info;
@@ -11049,7 +11064,8 @@ static bool zend_is_allowed_in_const_expr(zend_ast_kind kind) /* {{{ */
|| kind == ZEND_AST_CONST_ENUM_INIT
|| kind == ZEND_AST_NEW || kind == ZEND_AST_ARG_LIST
|| kind == ZEND_AST_NAMED_ARG
|| kind == ZEND_AST_PROP || kind == ZEND_AST_NULLSAFE_PROP;
|| kind == ZEND_AST_PROP || kind == ZEND_AST_NULLSAFE_PROP
|| kind == ZEND_AST_CLOSURE;
}
/* }}} */
@@ -11183,6 +11199,29 @@ static void zend_compile_const_expr_new(zend_ast **ast_ptr)
class_ast->attr = fetch_type << ZEND_CONST_EXPR_NEW_FETCH_TYPE_SHIFT;
}
static void zend_compile_const_expr_closure(zend_ast **ast_ptr)
{
zend_ast_decl *closure_ast = (zend_ast_decl *) *ast_ptr;
zend_ast *uses_ast = closure_ast->child[1];
if (!(closure_ast->flags & ZEND_ACC_STATIC)) {
zend_error_noreturn(E_COMPILE_ERROR,
"Closures in constant expressions must be static");
}
if (uses_ast) {
zend_error_noreturn(E_COMPILE_ERROR,
"Cannot use(...) variables in constant expression");
}
znode node;
zend_op_array *op = zend_compile_func_decl(&node, *ast_ptr, FUNC_DECL_LEVEL_CONSTEXPR);
zend_ast_destroy(*ast_ptr);
zval z;
ZVAL_PTR(&z, op);
*ast_ptr = zend_ast_create_zval(&z);
(*ast_ptr)->kind = ZEND_AST_OP_ARRAY;
}
static void zend_compile_const_expr_args(zend_ast **ast_ptr)
{
zend_ast_list *list = zend_ast_get_list(*ast_ptr);
@@ -11245,6 +11284,9 @@ static void zend_compile_const_expr(zend_ast **ast_ptr, void *context) /* {{{ */
case ZEND_AST_ARG_LIST:
zend_compile_const_expr_args(ast_ptr);
break;
case ZEND_AST_CLOSURE:
zend_compile_const_expr_closure(ast_ptr);
break;
}
zend_ast_apply(ast, zend_compile_const_expr, context);
@@ -11287,7 +11329,7 @@ void zend_compile_top_stmt(zend_ast *ast) /* {{{ */
if (ast->kind == ZEND_AST_FUNC_DECL) {
CG(zend_lineno) = ast->lineno;
zend_compile_func_decl(NULL, ast, 1);
zend_compile_func_decl(NULL, ast, FUNC_DECL_LEVEL_TOPLEVEL);
CG(zend_lineno) = ((zend_ast_decl *) ast)->end_lineno;
} else if (ast->kind == ZEND_AST_CLASS) {
CG(zend_lineno) = ast->lineno;
@@ -11369,7 +11411,7 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */
break;
case ZEND_AST_FUNC_DECL:
case ZEND_AST_METHOD:
zend_compile_func_decl(NULL, ast, 0);
zend_compile_func_decl(NULL, ast, FUNC_DECL_LEVEL_NESTED);
break;
case ZEND_AST_ENUM_CASE:
zend_compile_enum_case(ast);
@@ -11546,7 +11588,7 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */
return;
case ZEND_AST_CLOSURE:
case ZEND_AST_ARROW_FUNC:
zend_compile_func_decl(result, ast, 0);
zend_compile_func_decl(result, ast, FUNC_DECL_LEVEL_NESTED);
return;
case ZEND_AST_THROW:
zend_compile_throw(result, ast);

View File

@@ -363,6 +363,9 @@ static void zend_file_cache_serialize_ast(zend_ast *ast,
zend_file_cache_serialize_ast(tmp, script, info, buf);
}
}
} else if (ast->kind == ZEND_AST_OP_ARRAY) {
/* The op_array itself will be serialized as part of the dynamic_func_defs. */
SERIALIZE_PTR(Z_PTR(((zend_ast_zval*)ast)->val));
} else {
uint32_t children = zend_ast_get_num_children(ast);
for (i = 0; i < children; i++) {
@@ -1242,6 +1245,9 @@ static void zend_file_cache_unserialize_ast(zend_ast *ast,
zend_file_cache_unserialize_ast(list->child[i], script, buf);
}
}
} else if (ast->kind == ZEND_AST_OP_ARRAY) {
/* The op_array itself will be unserialized as part of the dynamic_func_defs. */
UNSERIALIZE_PTR(Z_PTR(((zend_ast_zval*)ast)->val));
} else {
uint32_t children = zend_ast_get_num_children(ast);
for (i = 0; i < children; i++) {

View File

@@ -188,6 +188,10 @@ static zend_ast *zend_persist_ast(zend_ast *ast)
}
}
node = (zend_ast *) copy;
} else if (ast->kind == ZEND_AST_OP_ARRAY) {
zend_ast_zval *copy = zend_shared_memdup(ast, sizeof(zend_ast_zval));
zend_persist_op_array(&copy->val);
node = (zend_ast *) copy;
} else {
uint32_t children = zend_ast_get_num_children(ast);
node = zend_shared_memdup(ast, zend_ast_size(children));

View File

@@ -86,6 +86,9 @@ static void zend_persist_ast_calc(zend_ast *ast)
zend_persist_ast_calc(list->child[i]);
}
}
} else if (ast->kind == ZEND_AST_OP_ARRAY) {
ADD_SIZE(sizeof(zend_ast_zval));
zend_persist_op_array_calc(&((zend_ast_zval*)(ast))->val);
} else {
uint32_t children = zend_ast_get_num_children(ast);
ADD_SIZE(zend_ast_size(children));