/*
   +----------------------------------------------------------------------+
   | Zend Engine                                                          |
   +----------------------------------------------------------------------+
   | Copyright © Zend Technologies Ltd., a subsidiary company of          |
   |     Perforce Software, Inc., and Contributors.                       |
   +----------------------------------------------------------------------+
   | This source file is subject to the Modified BSD License that is      |
   | bundled with this package in the file LICENSE, and is available      |
   | through the World Wide Web at <https://www.php.net/license/>.        |
   |                                                                      |
   | SPDX-License-Identifier: BSD-3-Clause                                |
   +----------------------------------------------------------------------+
   | Authors: Bob Weinand <bwoebi@php.net>                                |
   |          Dmitry Stogov <dmitry@php.net>                              |
   +----------------------------------------------------------------------+
*/

#include "zend_ast.h"
#include "zend_API.h"
#include "zend_operators.h"
#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"

ZEND_API zend_ast_process_t zend_ast_process = NULL;

static inline void *zend_ast_alloc(size_t size) {
	return zend_arena_alloc(&CG(ast_arena), size);
}

static inline void *zend_ast_realloc(const void *old, size_t old_size, size_t new_size) {
	void *new = zend_ast_alloc(new_size);
	memcpy(new, old, old_size);
	return new;
}

static inline size_t zend_ast_list_size(uint32_t children) {
	return sizeof(zend_ast_list) - sizeof(zend_ast *) + sizeof(zend_ast *) * children;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_znode(const znode *node) {
	zend_ast_znode *ast;

	ast = zend_ast_alloc(sizeof(zend_ast_znode));
	ast->kind = ZEND_AST_ZNODE;
	ast->attr = 0;
	ast->lineno = CG(zend_lineno);
	ast->node = *node;
	return (zend_ast *) ast;
}

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;
}

static zend_always_inline zend_ast * zend_ast_create_zval_int(const zval *zv, uint32_t attr, uint32_t lineno) {
	zend_ast_zval *ast;

	ast = zend_ast_alloc(sizeof(zend_ast_zval));
	ast->kind = ZEND_AST_ZVAL;
	ast->attr = attr;
	ZVAL_COPY_VALUE(&ast->val, zv);
	Z_LINENO(ast->val) = lineno;
	return (zend_ast *) ast;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_zval_with_lineno(const zval *zv, uint32_t lineno) {
	return zend_ast_create_zval_int(zv, 0, lineno);
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_zval_ex(const zval *zv, zend_ast_attr attr) {
	return zend_ast_create_zval_int(zv, attr, CG(zend_lineno));
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_zval(const zval *zv) {
	return zend_ast_create_zval_int(zv, 0, CG(zend_lineno));
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_zval_from_str(zend_string *str) {
	zval zv;
	ZVAL_STR(&zv, str);
	return zend_ast_create_zval_int(&zv, 0, CG(zend_lineno));
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_zval_from_long(zend_long lval) {
	zval zv;
	ZVAL_LONG(&zv, lval);
	return zend_ast_create_zval_int(&zv, 0, CG(zend_lineno));
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_constant(zend_string *name, zend_ast_attr attr) {
	zend_ast_zval *ast;

	ast = zend_ast_alloc(sizeof(zend_ast_zval));
	ast->kind = ZEND_AST_CONSTANT;
	ast->attr = attr;
	ZVAL_STR(&ast->val, name);
	Z_LINENO(ast->val) = CG(zend_lineno);
	return (zend_ast *) ast;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_op_array(zend_op_array *op_array) {
	zend_ast_op_array *ast;

	ast = zend_ast_alloc(sizeof(zend_ast_op_array));
	ast->kind = ZEND_AST_OP_ARRAY;
	ast->attr = 0;
	ast->lineno = CG(zend_lineno);
	ast->op_array = op_array;

	return (zend_ast *) ast;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_class_const_or_name(zend_ast *class_name, zend_ast *name) {
	zend_string *name_str = zend_ast_get_str(name);
	if (zend_string_equals_ci(name_str, ZSTR_KNOWN(ZEND_STR_CLASS))) {
		zend_string_release(name_str);
		return zend_ast_create(ZEND_AST_CLASS_NAME, class_name);
	} else {
		return zend_ast_create(ZEND_AST_CLASS_CONST, class_name, name);
	}
}

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_ast_decl *ast;

	ast = zend_ast_alloc(sizeof(zend_ast_decl));
	ast->kind = kind;
	ast->attr = 0;
	ast->start_lineno = start_lineno;
	ast->end_lineno = CG(zend_lineno);
	ast->flags = flags;
	ast->doc_comment = doc_comment;
	ast->name = name;
	ast->child[0] = child0;
	ast->child[1] = child1;
	ast->child[2] = child2;
	ast->child[3] = child3;
	ast->child[4] = child4;

	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;

	ZEND_ASSERT(kind >> ZEND_AST_NUM_CHILDREN_SHIFT == 0);
	ast = zend_ast_alloc(zend_ast_size(0));
	ast->kind = kind;
	ast->attr = 0;
	ast->lineno = CG(zend_lineno);

	return ast;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_1(zend_ast_kind kind, zend_ast *child) {
	zend_ast *ast;
	uint32_t lineno;

	ZEND_ASSERT(kind >> ZEND_AST_NUM_CHILDREN_SHIFT == 1);
	ast = zend_ast_alloc(zend_ast_size(1));
	ast->kind = kind;
	ast->attr = 0;
	ast->child[0] = child;
	if (child) {
		lineno = zend_ast_get_lineno(child);
	} else {
		lineno = CG(zend_lineno);
	}
	ast->lineno = lineno;

	return ast;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_2(zend_ast_kind kind, zend_ast *child1, zend_ast *child2) {
	zend_ast *ast;
	uint32_t lineno;

	ZEND_ASSERT(kind >> ZEND_AST_NUM_CHILDREN_SHIFT == 2);
	ast = zend_ast_alloc(zend_ast_size(2));
	ast->kind = kind;
	ast->attr = 0;
	ast->child[0] = child1;
	ast->child[1] = child2;
	if (child1) {
		lineno = zend_ast_get_lineno(child1);
	} else if (child2) {
		lineno = zend_ast_get_lineno(child2);
	} else {
		lineno = CG(zend_lineno);
	}
	ast->lineno = lineno;

	return ast;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_3(zend_ast_kind kind, zend_ast *child1, zend_ast *child2, zend_ast *child3) {
	zend_ast *ast;
	uint32_t lineno;

	ZEND_ASSERT(kind >> ZEND_AST_NUM_CHILDREN_SHIFT == 3);
	ast = zend_ast_alloc(zend_ast_size(3));
	ast->kind = kind;
	ast->attr = 0;
	ast->child[0] = child1;
	ast->child[1] = child2;
	ast->child[2] = child3;
	if (child1) {
		lineno = zend_ast_get_lineno(child1);
	} else if (child2) {
		lineno = zend_ast_get_lineno(child2);
	} else if (child3) {
		lineno = zend_ast_get_lineno(child3);
	} else {
		lineno = CG(zend_lineno);
	}
	ast->lineno = lineno;

	return ast;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_4(zend_ast_kind kind, zend_ast *child1, zend_ast *child2, zend_ast *child3, zend_ast *child4) {
	zend_ast *ast;
	uint32_t lineno;

	ZEND_ASSERT(kind >> ZEND_AST_NUM_CHILDREN_SHIFT == 4);
	ast = zend_ast_alloc(zend_ast_size(4));
	ast->kind = kind;
	ast->attr = 0;
	ast->child[0] = child1;
	ast->child[1] = child2;
	ast->child[2] = child3;
	ast->child[3] = child4;
	if (child1) {
		lineno = zend_ast_get_lineno(child1);
	} else if (child2) {
		lineno = zend_ast_get_lineno(child2);
	} else if (child3) {
		lineno = zend_ast_get_lineno(child3);
	} else if (child4) {
		lineno = zend_ast_get_lineno(child4);
	} else {
		lineno = CG(zend_lineno);
	}
	ast->lineno = lineno;

	return ast;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_5(zend_ast_kind kind, zend_ast *child1, zend_ast *child2, zend_ast *child3, zend_ast *child4, zend_ast *child5) {
	zend_ast *ast;
	uint32_t lineno;

	ZEND_ASSERT(kind >> ZEND_AST_NUM_CHILDREN_SHIFT == 5);
	ast = zend_ast_alloc(zend_ast_size(5));
	ast->kind = kind;
	ast->attr = 0;
	ast->child[0] = child1;
	ast->child[1] = child2;
	ast->child[2] = child3;
	ast->child[3] = child4;
	ast->child[4] = child5;
	if (child1) {
		lineno = zend_ast_get_lineno(child1);
	} else if (child2) {
		lineno = zend_ast_get_lineno(child2);
	} else if (child3) {
		lineno = zend_ast_get_lineno(child3);
	} else if (child4) {
		lineno = zend_ast_get_lineno(child4);
	} else if (child5) {
		lineno = zend_ast_get_lineno(child5);
	} else {
		lineno = CG(zend_lineno);
	}
	ast->lineno = lineno;

	return ast;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_va(
		zend_ast_kind kind, zend_ast_attr attr, va_list *va) {
	uint32_t lineno = (uint32_t)-1;
	uint32_t children = kind >> ZEND_AST_NUM_CHILDREN_SHIFT;
	zend_ast *ast = zend_ast_alloc(zend_ast_size(children));
	ast->kind = kind;
	ast->attr = attr;
	for (uint32_t i = 0; i < children; i++) {
		ast->child[i] = va_arg(*va, zend_ast *);
		if (lineno == (uint32_t)-1 && ast->child[i]) {
			lineno = zend_ast_get_lineno(ast->child[i]);
		}
	}
	if (lineno == (uint32_t)-1) {
		lineno = CG(zend_lineno);
	}
	ast->lineno = lineno;
	return ast;
}

ZEND_API zend_ast * zend_ast_create_n(unsigned kind, ...) {
	va_list va;
	va_start(va, kind);
	zend_ast *ast = zend_ast_create_va(kind, 0, &va);
	va_end(va);
	return ast;
}

ZEND_API zend_ast * zend_ast_create_ex_n(
		zend_ast_kind kind, unsigned attr, ...) {
	va_list va;
	va_start(va, attr);
	zend_ast *ast = zend_ast_create_va(kind, attr, &va);
	va_end(va);
	return ast;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_0(zend_ast_kind kind) {
	zend_ast *ast;
	zend_ast_list *list;

	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;

	return ast;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_1(zend_ast_kind kind, zend_ast *child) {
	zend_ast *ast;
	zend_ast_list *list;
	uint32_t lineno;

	ast = zend_ast_alloc(zend_ast_list_size(4));
	list = (zend_ast_list *) ast;
	list->kind = kind;
	list->attr = 0;
	list->children = 1;
	list->child[0] = child;
	if (child) {
		lineno = zend_ast_get_lineno(child);
		if (lineno > CG(zend_lineno)) {
			lineno = CG(zend_lineno);
		}
	} else {
		lineno = CG(zend_lineno);
	}
	list->lineno = lineno;

	return ast;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_2(zend_ast_kind kind, zend_ast *child1, zend_ast *child2) {
	zend_ast *ast;
	zend_ast_list *list;
	uint32_t lineno;

	ast = zend_ast_alloc(zend_ast_list_size(4));
	list = (zend_ast_list *) ast;
	list->kind = kind;
	list->attr = 0;
	list->children = 2;
	list->child[0] = child1;
	list->child[1] = child2;
	if (child1) {
		lineno = zend_ast_get_lineno(child1);
		if (lineno > CG(zend_lineno)) {
			lineno = CG(zend_lineno);
		}
	} else if (child2) {
		lineno = zend_ast_get_lineno(child2);
		if (lineno > CG(zend_lineno)) {
			lineno = CG(zend_lineno);
		}
	} else {
		list->children = 0;
		lineno = CG(zend_lineno);
	}
	list->lineno = lineno;

	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;
	zend_ast *ast;

	ast = zend_ast_alloc(zend_ast_size(children));
	ast->kind = kind;
	ast->attr = attr;
	ast->lineno = (uint32_t) -1;

	for (i = 0; i < children; ++i) {
		ast->child[i] = va_arg(va, zend_ast *);
		if (ast->child[i] != NULL) {
			uint32_t lineno = zend_ast_get_lineno(ast->child[i]);
			if (lineno < ast->lineno) {
				ast->lineno = lineno;
			}
		}
	}

	if (ast->lineno == UINT_MAX) {
		ast->lineno = CG(zend_lineno);
	}

	return ast;
}

ZEND_API zend_ast *zend_ast_create_ex(zend_ast_kind kind, zend_ast_attr attr, ...) {
	va_list va;
	zend_ast *ast;

	va_start(va, attr);
	ast = zend_ast_create_from_va_list(kind, attr, va);
	va_end(va);

	return ast;
}

ZEND_API zend_ast *zend_ast_create(zend_ast_kind kind, ...) {
	va_list va;
	zend_ast *ast;

	va_start(va, kind);
	ast = zend_ast_create_from_va_list(kind, 0, va);
	va_end(va);

	return ast;
}

ZEND_API zend_ast *zend_ast_create_list(uint32_t init_children, zend_ast_kind kind, ...) {
	zend_ast *ast;
	zend_ast_list *list;

	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);
			if (child != NULL) {
				uint32_t lineno = zend_ast_get_lineno(child);
				if (lineno < ast->lineno) {
					ast->lineno = lineno;
				}
			}
		}
		va_end(va);
	}

	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) {
	if (op0->kind == ZEND_AST_ZVAL && op1->kind == ZEND_AST_ZVAL) {
		zval *zv0 = zend_ast_get_zval(op0);
		zval *zv1 = zend_ast_get_zval(op1);
		if (!zend_binary_op_produces_error(ZEND_CONCAT, zv0, zv1) &&
				concat_function(zv0, zv0, zv1) == SUCCESS) {
			zval_ptr_dtor_nogc(zv1);
			return zend_ast_create_zval(zv0);
		}
	}
	return zend_ast_create_binary_op(ZEND_CONCAT, op0, op1);
}

static inline bool is_power_of_two(uint32_t n) {
	return ((n != 0) && (n == (n & (~n + 1))));
}

ZEND_ATTRIBUTE_NODISCARD ZEND_API zend_ast * ZEND_FASTCALL zend_ast_list_add(zend_ast *ast, zend_ast *op) {
	zend_ast_list *list = zend_ast_get_list(ast);
	if (list->children >= 4 && is_power_of_two(list->children)) {
			list = zend_ast_realloc(list,
			zend_ast_list_size(list->children), zend_ast_list_size(list->children * 2));
	}
	list->child[list->children++] = op;
	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) {
		if (!zend_hash_next_index_insert(Z_ARRVAL_P(result), expr)) {
			zend_throw_error(NULL,
				"Cannot add element to the array as the next element is already occupied");
			return FAILURE;
		}
		return SUCCESS;
	}

	if (array_set_zval_key(Z_ARRVAL_P(result), offset, expr) == FAILURE) {
		return FAILURE;
	}

	zval_ptr_dtor_nogc(offset);
	zval_ptr_dtor_nogc(expr);
	return SUCCESS;
}

static zend_result zend_ast_add_unpacked_element(const zval *result, const zval *expr) {
	if (EXPECTED(Z_TYPE_P(expr) == IS_ARRAY)) {
		const HashTable *ht = Z_ARRVAL_P(expr);
		zval *val;
		zend_string *key;

		ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, val) {
			if (key) {
				zend_hash_update(Z_ARRVAL_P(result), key, val);
			} else {
				if (!zend_hash_next_index_insert(Z_ARRVAL_P(result), val)) {
					zend_throw_error(NULL,
						"Cannot add element to the array as the next element is already occupied");
					return FAILURE;
				}
			}
			Z_TRY_ADDREF_P(val);
		} ZEND_HASH_FOREACH_END();
		return SUCCESS;
	}

	zend_throw_error(NULL, "Only arrays can be unpacked in constant expression");
	return FAILURE;
}

static zend_class_entry *zend_ast_fetch_class(zend_ast *ast, zend_class_entry *scope)
{
	return zend_fetch_class_with_scope(zend_ast_get_str(ast), (ast->attr >> ZEND_CONST_EXPR_NEW_FETCH_TYPE_SHIFT) | ZEND_FETCH_CLASS_EXCEPTION, scope);
}

static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
	zval *result,
	zend_ast *ast,
	zend_class_entry *scope,
	bool *short_circuited_ptr,
	zend_ast_evaluate_ctx *ctx
);

ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_ex(
	zval *result,
	zend_ast *ast,
	zend_class_entry *scope,
	bool *short_circuited_ptr,
	zend_ast_evaluate_ctx *ctx
) {
	zend_string *previous_filename;
	zend_long previous_lineno;
	if (scope) {
		previous_filename = EG(filename_override);
		previous_lineno = EG(lineno_override);
		EG(filename_override) = scope->info.user.filename;
		EG(lineno_override) = zend_ast_get_lineno(ast);
	}
	zend_result r = zend_ast_evaluate_inner(result, ast, scope, short_circuited_ptr, ctx);
	if (scope) {
		EG(filename_override) = previous_filename;
		EG(lineno_override) = previous_lineno;
	}
	return r;
}

static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
	zval *result,
	zend_ast *ast,
	zend_class_entry *scope,
	bool *short_circuited_ptr,
	zend_ast_evaluate_ctx *ctx
) {
	zval op1, op2;
	zend_result ret = SUCCESS;
	bool short_circuited;
	*short_circuited_ptr = false;

	switch (ast->kind) {
		case ZEND_AST_BINARY_OP:
			if (UNEXPECTED(zend_ast_evaluate_ex(&op1, ast->child[0], scope, &short_circuited, ctx) != SUCCESS)) {
				ret = FAILURE;
			} else if (UNEXPECTED(zend_ast_evaluate_ex(&op2, ast->child[1], scope, &short_circuited, ctx) != SUCCESS)) {
				zval_ptr_dtor_nogc(&op1);
				ret = FAILURE;
			} else {
				binary_op_type op = get_binary_op(ast->attr);
				op(result, &op1, &op2);
				zval_ptr_dtor_nogc(&op1);
				zval_ptr_dtor_nogc(&op2);
				ret = EG(exception) ? FAILURE : SUCCESS;
			}
			break;
		case ZEND_AST_GREATER:
		case ZEND_AST_GREATER_EQUAL:
			if (UNEXPECTED(zend_ast_evaluate_ex(&op1, ast->child[0], scope, &short_circuited, ctx) != SUCCESS)) {
				ret = FAILURE;
			} else if (UNEXPECTED(zend_ast_evaluate_ex(&op2, ast->child[1], scope, &short_circuited, ctx) != SUCCESS)) {
				zval_ptr_dtor_nogc(&op1);
				ret = FAILURE;
			} else {
				/* op1 > op2 is the same as op2 < op1 */
				binary_op_type op = ast->kind == ZEND_AST_GREATER
					? is_smaller_function : is_smaller_or_equal_function;
				op(result, &op2, &op1);
				zval_ptr_dtor_nogc(&op1);
				zval_ptr_dtor_nogc(&op2);
				ret = EG(exception) ? FAILURE : SUCCESS;
			}
			break;
		case ZEND_AST_UNARY_OP:
			if (UNEXPECTED(zend_ast_evaluate_ex(&op1, ast->child[0], scope, &short_circuited, ctx) != SUCCESS)) {
				ret = FAILURE;
			} else {
				unary_op_type op = get_unary_op(ast->attr);
				ret = op(result, &op1);
				zval_ptr_dtor_nogc(&op1);
			}
			break;
		case ZEND_AST_ZVAL:
		{
			zval *zv = zend_ast_get_zval(ast);

			ZVAL_COPY(result, zv);
			break;
		}
		case ZEND_AST_CONSTANT:
		{
			zend_string *name = zend_ast_get_constant_name(ast);
			zval *zv = zend_get_constant_ex(name, scope, ast->attr);

			if (UNEXPECTED(zv == NULL)) {
				ZVAL_UNDEF(result);
				return FAILURE;
			}
			ZVAL_COPY_OR_DUP(result, zv);
			break;
		}
		case ZEND_AST_CONSTANT_CLASS:
			if (scope) {
				ZVAL_STR_COPY(result, scope->name);
			} else {
				ZVAL_EMPTY_STRING(result);
			}
			break;
		case ZEND_AST_CLASS_NAME:
			if (!scope) {
				zend_throw_error(NULL, "Cannot use \"self\" when no class scope is active");
				return FAILURE;
			}
			if (ast->attr == ZEND_FETCH_CLASS_SELF) {
				ZVAL_STR_COPY(result, scope->name);
			} else if (ast->attr == ZEND_FETCH_CLASS_PARENT) {
				if (!scope->parent) {
					zend_throw_error(NULL,
						"Cannot use \"parent\" when current class scope has no parent");
					return FAILURE;
				}
				ZVAL_STR_COPY(result, scope->parent->name);
			} else {
				ZEND_ASSERT(0 && "Should have errored during compilation");
			}
			break;
		case ZEND_AST_AND:
			if (UNEXPECTED(zend_ast_evaluate_ex(&op1, ast->child[0], scope, &short_circuited, ctx) != SUCCESS)) {
				ret = FAILURE;
				break;
			}
			if (zend_is_true(&op1)) {
				if (UNEXPECTED(zend_ast_evaluate_ex(&op2, ast->child[1], scope, &short_circuited, ctx) != SUCCESS)) {
					zval_ptr_dtor_nogc(&op1);
					ret = FAILURE;
					break;
				}
				ZVAL_BOOL(result, zend_is_true(&op2));
				zval_ptr_dtor_nogc(&op2);
			} else {
				ZVAL_FALSE(result);
			}
			zval_ptr_dtor_nogc(&op1);
			break;
		case ZEND_AST_CAST:
			if (UNEXPECTED(zend_ast_evaluate_ex(&op1, ast->child[0], scope, &short_circuited, ctx) != SUCCESS)) {
				ret = FAILURE;
				break;
			}
			if (ast->attr == Z_TYPE(op1)) {
				ZVAL_COPY_VALUE(result, &op1);
			} else {
				switch (ast->attr) {
					case _IS_BOOL:
						ZVAL_BOOL(result, zend_is_true(&op1));
						break;
					case IS_LONG:
						ZVAL_LONG(result, zval_get_long_func(&op1, false));
						break;
					case IS_DOUBLE:
						ZVAL_DOUBLE(result, zval_get_double_func(&op1));
						break;
					case IS_STRING:
						ZVAL_STR(result, zval_get_string_func(&op1));
						break;
					case IS_ARRAY:
						zend_cast_zval_to_array(result, &op1, IS_VAR);
						break;
					case IS_OBJECT:
						zend_cast_zval_to_object(result, &op1, IS_VAR);
						break;
					default: ZEND_UNREACHABLE();
				}
				zval_ptr_dtor_nogc(&op1);
				if (UNEXPECTED(EG(exception))) {
					ret = FAILURE;
				}
			}
			break;
		case ZEND_AST_OR:
			if (UNEXPECTED(zend_ast_evaluate_ex(&op1, ast->child[0], scope, &short_circuited, ctx) != SUCCESS)) {
				ret = FAILURE;
				break;
			}
			if (zend_is_true(&op1)) {
				ZVAL_TRUE(result);
			} else {
				if (UNEXPECTED(zend_ast_evaluate_ex(&op2, ast->child[1], scope, &short_circuited, ctx) != SUCCESS)) {
					zval_ptr_dtor_nogc(&op1);
					ret = FAILURE;
					break;
				}
				ZVAL_BOOL(result, zend_is_true(&op2));
				zval_ptr_dtor_nogc(&op2);
			}
			zval_ptr_dtor_nogc(&op1);
			break;
		case ZEND_AST_CONDITIONAL:
			if (UNEXPECTED(zend_ast_evaluate_ex(&op1, ast->child[0], scope, &short_circuited, ctx) != SUCCESS)) {
				ret = FAILURE;
				break;
			}
			if (zend_is_true(&op1)) {
				if (!ast->child[1]) {
					*result = op1;
				} else {
					if (UNEXPECTED(zend_ast_evaluate_ex(result, ast->child[1], scope, &short_circuited, ctx) != SUCCESS)) {
						zval_ptr_dtor_nogc(&op1);
						ret = FAILURE;
						break;
					}
					zval_ptr_dtor_nogc(&op1);
				}
			} else {
				if (UNEXPECTED(zend_ast_evaluate_ex(result, ast->child[2], scope, &short_circuited, ctx) != SUCCESS)) {
					zval_ptr_dtor_nogc(&op1);
					ret = FAILURE;
					break;
				}
				zval_ptr_dtor_nogc(&op1);
			}
			break;
		case ZEND_AST_COALESCE:
			if (UNEXPECTED(zend_ast_evaluate_ex(&op1, ast->child[0], scope, &short_circuited, ctx) != SUCCESS)) {
				ret = FAILURE;
				break;
			}
			if (Z_TYPE(op1) > IS_NULL) {
				*result = op1;
			} else {
				if (UNEXPECTED(zend_ast_evaluate_ex(result, ast->child[1], scope, &short_circuited, ctx) != SUCCESS)) {
					zval_ptr_dtor_nogc(&op1);
					ret = FAILURE;
					break;
				}
				zval_ptr_dtor_nogc(&op1);
			}
			break;
		case ZEND_AST_UNARY_PLUS:
			if (UNEXPECTED(zend_ast_evaluate_ex(&op2, ast->child[0], scope, &short_circuited, ctx) != SUCCESS)) {
				ret = FAILURE;
			} else {
				ZVAL_LONG(&op1, 0);
				ret = add_function(result, &op1, &op2);
				zval_ptr_dtor_nogc(&op2);
			}
			break;
		case ZEND_AST_UNARY_MINUS:
			if (UNEXPECTED(zend_ast_evaluate_ex(&op2, ast->child[0], scope, &short_circuited, ctx) != SUCCESS)) {
				ret = FAILURE;
			} else {
				ZVAL_LONG(&op1, -1);
				ret = mul_function(result, &op1, &op2);
				zval_ptr_dtor_nogc(&op2);
			}
			break;
		case ZEND_AST_ARRAY:
			{
				uint32_t i;
				zend_ast_list *list = zend_ast_get_list(ast);

				if (!list->children) {
					ZVAL_EMPTY_ARRAY(result);
					break;
				}
				array_init(result);
				for (i = 0; i < list->children; i++) {
					zend_ast *elem = list->child[i];
					if (elem->kind == ZEND_AST_UNPACK) {
						if (UNEXPECTED(zend_ast_evaluate_ex(&op1, elem->child[0], scope, &short_circuited, ctx) != SUCCESS)) {
							zval_ptr_dtor_nogc(result);
							return FAILURE;
						}
						if (UNEXPECTED(zend_ast_add_unpacked_element(result, &op1) != SUCCESS)) {
							zval_ptr_dtor_nogc(&op1);
							zval_ptr_dtor_nogc(result);
							return FAILURE;
						}
						zval_ptr_dtor_nogc(&op1);
						continue;
					}
					if (elem->child[1]) {
						if (UNEXPECTED(zend_ast_evaluate_ex(&op1, elem->child[1], scope, &short_circuited, ctx) != SUCCESS)) {
							zval_ptr_dtor_nogc(result);
							return FAILURE;
						}
					} else {
						ZVAL_UNDEF(&op1);
					}
					if (UNEXPECTED(zend_ast_evaluate_ex(&op2, elem->child[0], scope, &short_circuited, ctx) != SUCCESS)) {
						zval_ptr_dtor_nogc(&op1);
						zval_ptr_dtor_nogc(result);
						return FAILURE;
					}
					if (UNEXPECTED(zend_ast_add_array_element(result, &op1, &op2) != SUCCESS)) {
						zval_ptr_dtor_nogc(&op1);
						zval_ptr_dtor_nogc(&op2);
						zval_ptr_dtor_nogc(result);
						return FAILURE;
					}
				}
			}
			break;
		case ZEND_AST_DIM:
			if (ast->child[1] == NULL) {
				zend_error_noreturn(E_COMPILE_ERROR, "Cannot use [] for reading");
			}

			if (UNEXPECTED(zend_ast_evaluate_ex(&op1, ast->child[0], scope, &short_circuited, ctx) != SUCCESS)) {
				ret = FAILURE;
				break;
			}
			if (short_circuited) {
				*short_circuited_ptr = true;
				ZVAL_NULL(result);
				return SUCCESS;
			}

			// DIM on objects is disallowed because it allows executing arbitrary expressions
			if (Z_TYPE(op1) == IS_OBJECT) {
				zval_ptr_dtor_nogc(&op1);
				zend_throw_error(NULL, "Cannot use [] on objects in constant expression");
				ret = FAILURE;
				break;
			}

			if (UNEXPECTED(zend_ast_evaluate_ex(&op2, ast->child[1], scope, &short_circuited, ctx) != SUCCESS)) {
				zval_ptr_dtor_nogc(&op1);
				ret = FAILURE;
				break;
			}

			zend_fetch_dimension_const(result, &op1, &op2, (ast->attr & ZEND_DIM_IS) ? BP_VAR_IS : BP_VAR_R);

			zval_ptr_dtor_nogc(&op1);
			zval_ptr_dtor_nogc(&op2);
			if (UNEXPECTED(EG(exception))) {
				return FAILURE;
			}

			break;
		case ZEND_AST_CONST_ENUM_INIT:
		{
			// Preloading will attempt to resolve constants but objects can't be stored in shm
			// Aborting here to store the const AST instead
			if (CG(in_compilation)) {
				return FAILURE;
			}

			zend_ast *class_name_ast = ast->child[0];
			zend_string *class_name = zend_ast_get_str(class_name_ast);

			zend_ast *case_id_ast = ast->child[1];
			int case_id = (int)Z_LVAL_P(zend_ast_get_zval(case_id_ast));

			zend_ast *case_name_ast = ast->child[2];
			zend_string *case_name = zend_ast_get_str(case_name_ast);

			zend_ast *case_value_ast = ast->child[3];

			zval case_value_zv;
			ZVAL_UNDEF(&case_value_zv);
			if (case_value_ast != NULL) {
				if (UNEXPECTED(zend_ast_evaluate_ex(&case_value_zv, case_value_ast, scope, &short_circuited, ctx) != SUCCESS)) {
					return FAILURE;
				}
			}

			zend_class_entry *ce = zend_lookup_class(class_name);
			zend_enum_new(result, ce, case_id, case_name, case_value_ast != NULL ? &case_value_zv : NULL);
			zval_ptr_dtor_nogc(&case_value_zv);
			break;
		}
		case ZEND_AST_CLASS_CONST:
		{
			zend_string *class_name = zend_ast_get_str(ast->child[0]);
			if (UNEXPECTED(zend_ast_evaluate_ex(&op2, ast->child[1], scope, &short_circuited, ctx) != SUCCESS)) {
				return FAILURE;
			}
			if (UNEXPECTED(Z_TYPE(op2) != IS_STRING)) {
				zend_invalid_class_constant_type_error(Z_TYPE(op2));
				zval_ptr_dtor_nogc(&op2);
				return FAILURE;
			}
			zend_string *const_name = Z_STR(op2);

			zend_string *previous_filename;
			zend_long previous_lineno;
			if (scope) {
				previous_filename = EG(filename_override);
				previous_lineno = EG(lineno_override);
				EG(filename_override) = scope->info.user.filename;
				EG(lineno_override) = zend_ast_get_lineno(ast);
			}
			zval *zv = zend_get_class_constant_ex(class_name, const_name, scope, ast->attr);
			if (scope) {
				EG(filename_override) = previous_filename;
				EG(lineno_override) = previous_lineno;
			}

			if (UNEXPECTED(zv == NULL)) {
				ZVAL_UNDEF(result);
				zval_ptr_dtor_nogc(&op2);
				return FAILURE;
			}
			ZVAL_COPY_OR_DUP(result, zv);
			zval_ptr_dtor_nogc(&op2);
			break;
		}
		case ZEND_AST_NEW:
		{
			zend_class_entry *ce = zend_ast_fetch_class(ast->child[0], scope);
			if (!ce) {
				return FAILURE;
			}

			if (object_init_ex(result, ce) != SUCCESS) {
				return FAILURE;
			}

			/* Even if there is no constructor, the object can have cause side-effects in various ways (__toString(), __get(), __isset(), etc). */
			ctx->had_side_effects = true;

			zend_ast_list *args_ast = zend_ast_get_list(ast->child[1]);
			if (args_ast->attr) {
				/* Has named arguments. */
				HashTable *args = zend_new_array(args_ast->children);
				for (uint32_t i = 0; i < args_ast->children; i++) {
					zend_ast *arg_ast = args_ast->child[i];
					zend_string *name = NULL;
					zval arg;
					if (arg_ast->kind == ZEND_AST_NAMED_ARG) {
						name = zend_ast_get_str(arg_ast->child[0]);
						arg_ast = arg_ast->child[1];
					}
					if (zend_ast_evaluate_ex(&arg, arg_ast, scope, &short_circuited, ctx) == FAILURE) {
						zend_array_destroy(args);
						zval_ptr_dtor(result);
						return FAILURE;
					}
					if (name) {
						if (!zend_hash_add(args, name, &arg)) {
							zend_throw_error(NULL,
								"Named parameter $%s overwrites previous argument",
								ZSTR_VAL(name));
							zend_array_destroy(args);
							zval_ptr_dtor(result);
							return FAILURE;
						}
					} else {
						zend_hash_next_index_insert(args, &arg);
					}
				}

				zend_function *ctor = Z_OBJ_HT_P(result)->get_constructor(Z_OBJ_P(result));
				if (ctor) {
					zend_call_known_function(
						ctor, Z_OBJ_P(result), Z_OBJCE_P(result), NULL, 0, NULL, args);
				}

				zend_array_destroy(args);
			} else {
				ALLOCA_FLAG(use_heap)
				zval *args = do_alloca(sizeof(zval) * args_ast->children, use_heap);
				for (uint32_t i = 0; i < args_ast->children; i++) {
					if (zend_ast_evaluate_ex(&args[i], args_ast->child[i], scope, &short_circuited, ctx) == FAILURE) {
						for (uint32_t j = 0; j < i; j++) {
							zval_ptr_dtor(&args[j]);
						}
						free_alloca(args, use_heap);
						zval_ptr_dtor(result);
						return FAILURE;
					}
				}

				zend_function *ctor = Z_OBJ_HT_P(result)->get_constructor(Z_OBJ_P(result));
				if (ctor) {
					zend_call_known_instance_method(
						ctor, Z_OBJ_P(result), NULL, args_ast->children, args);
				}

				for (uint32_t i = 0; i < args_ast->children; i++) {
					zval_ptr_dtor(&args[i]);
				}
				free_alloca(args, use_heap);
			}

			if (EG(exception)) {
				zend_object_store_ctor_failed(Z_OBJ_P(result));
				zval_ptr_dtor(result);
				return FAILURE;
			}
			return SUCCESS;
		}
		case ZEND_AST_CALL:
		case ZEND_AST_STATIC_CALL:
		{
			zend_function *fptr;
			zend_class_entry *called_scope = NULL;

			zend_ast *args_ast = zend_ast_call_get_args(ast);
			ZEND_ASSERT(args_ast->kind == ZEND_AST_CALLABLE_CONVERT);

			zend_ast_fcc *fcc_ast = (zend_ast_fcc*)args_ast;

			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");
			}

			switch (ast->kind) {
				case ZEND_AST_CALL: {
					fptr = ZEND_MAP_PTR_GET(fcc_ast->fptr);

					if (!fptr) {
						zend_string *function_name = zend_ast_get_str(ast->child[0]);
						zend_string *function_name_lc = zend_string_tolower(function_name);
						fptr = zend_fetch_function(function_name_lc);
						if (!fptr && ast->child[0]->attr != ZEND_NAME_FQ) {
							const char *backslash = zend_memrchr(ZSTR_VAL(function_name_lc), '\\', ZSTR_LEN(function_name_lc));
							if (backslash) {
								fptr = zend_fetch_function_str(backslash + 1, ZSTR_LEN(function_name_lc) - (backslash - ZSTR_VAL(function_name_lc) + 1));
							}
						}
						zend_string_release(function_name_lc);
						if (!fptr) {
							zend_throw_error(NULL, "Call to undefined function %s()", ZSTR_VAL(function_name));
							return FAILURE;
						}

						ZEND_MAP_PTR_SET(fcc_ast->fptr, fptr);
					}

					break;
				}
				case ZEND_AST_STATIC_CALL: {
					zend_class_entry *ce = zend_ast_fetch_class(ast->child[0], scope);
					if (!ce) {
						return FAILURE;
					}
					called_scope = ce;

					fptr = ZEND_MAP_PTR_GET(fcc_ast->fptr);

					if (!fptr) {
						zend_string *method_name = zend_ast_get_str(ast->child[1]);
						if (ce->get_static_method) {
							fptr = ce->get_static_method(ce, method_name);
						} else {
							fptr = zend_hash_find_ptr_lc(&ce->function_table, method_name);
							if (fptr) {
								if (!zend_check_method_accessible(fptr, scope)) {
									if (ce->__callstatic) {
										zend_throw_error(NULL, "Creating a callable for the magic __callStatic() method is not supported in constant expressions");
									} else {
										zend_bad_method_call(fptr, method_name, scope);
									}

									return FAILURE;
								}
							} else {
								if (ce->__callstatic) {
									zend_throw_error(NULL, "Creating a callable for the magic __callStatic() method is not supported in constant expressions");
								} else {
									zend_undefined_method(ce, method_name);
								}

								return FAILURE;
							}
						}

						if (!(fptr->common.fn_flags & ZEND_ACC_STATIC)) {
							zend_non_static_method_call(fptr);

							return FAILURE;
						}
						if ((fptr->common.fn_flags & ZEND_ACC_ABSTRACT)) {
							zend_abstract_method_call(fptr);

							return FAILURE;
						} else if (fptr->common.scope->ce_flags & ZEND_ACC_TRAIT) {
							zend_error(E_DEPRECATED,
								"Calling static trait method %s::%s is deprecated, "
								"it should only be called on a class using the trait",
								ZSTR_VAL(fptr->common.scope->name), ZSTR_VAL(fptr->common.function_name));
							if (EG(exception)) {
								return FAILURE;
							}
						}

						ZEND_MAP_PTR_SET(fcc_ast->fptr, fptr);
					}

					break;
				}
				default: ZEND_UNREACHABLE();
			}

			zend_create_fake_closure(result, fptr, fptr->common.scope, called_scope, NULL);

			return SUCCESS;
		}
		case ZEND_AST_OP_ARRAY:
		{
			// Preloading will attempt to resolve constants but objects can't be stored in shm
			// Aborting here to store the const AST instead
			if (CG(in_compilation)) {
				return FAILURE;
			}

			zend_function *func = (zend_function *)zend_ast_get_op_array(ast)->op_array;

			zend_create_closure(result, func, scope, scope, NULL);
			return SUCCESS;
		}
		case ZEND_AST_PROP:
		case ZEND_AST_NULLSAFE_PROP:
		{
			if (UNEXPECTED(zend_ast_evaluate_ex(&op1, ast->child[0], scope, &short_circuited, ctx) != SUCCESS)) {
				return FAILURE;
			}
			if (short_circuited) {
				*short_circuited_ptr = true;
				ZVAL_NULL(result);
				return SUCCESS;
			}
			if (ast->kind == ZEND_AST_NULLSAFE_PROP && Z_TYPE(op1) == IS_NULL) {
				*short_circuited_ptr = true;
				ZVAL_NULL(result);
				return SUCCESS;
			}

			if (UNEXPECTED(zend_ast_evaluate_ex(&op2, ast->child[1], scope, &short_circuited, ctx) != SUCCESS)) {
				zval_ptr_dtor_nogc(&op1);
				return FAILURE;
			}

			if (!try_convert_to_string(&op2)) {
				zval_ptr_dtor_nogc(&op1);
				zval_ptr_dtor_nogc(&op2);
				return FAILURE;
			}

			if (Z_TYPE(op1) != IS_OBJECT) {
				zend_wrong_property_read(&op1, &op2);

				zval_ptr_dtor_nogc(&op1);
				zval_ptr_dtor_nogc(&op2);

				ZVAL_NULL(result);
				return SUCCESS;
			}

			zend_object *zobj = Z_OBJ(op1);
			if (!(zobj->ce->ce_flags & ZEND_ACC_ENUM)) {
				zend_throw_error(NULL, "Fetching properties on non-enums in constant expressions is not allowed");
				zval_ptr_dtor_nogc(&op1);
				zval_ptr_dtor_nogc(&op2);
				return FAILURE;
			}

			zend_string *name = Z_STR(op2);
			zval *property_result = zend_read_property_ex(scope, zobj, name, 0, result);
			if (EG(exception)) {
				zval_ptr_dtor_nogc(&op1);
				zval_ptr_dtor_nogc(&op2);
				return FAILURE;
			}

			if (result != property_result) {
				ZVAL_COPY(result, property_result);
			}
			zval_ptr_dtor_nogc(&op1);
			zval_ptr_dtor_nogc(&op2);
			return SUCCESS;
		}
		default:
			zend_throw_error(NULL, "Unsupported constant expression");
			ret = FAILURE;
	}
	return ret;
}

ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast, zend_class_entry *scope)
{
	zend_ast_evaluate_ctx ctx = {0};
	bool short_circuited;
	return zend_ast_evaluate_ex(result, ast, scope, &short_circuited, &ctx);
}

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) {
		size = sizeof(zend_ast_zval);
	} else if (ast->kind == ZEND_AST_OP_ARRAY) {
		size = sizeof(zend_ast_op_array);
	} else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) {
		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);

		size = zend_ast_list_size(list->children);
		for (i = 0; i < list->children; i++) {
			if (list->child[i]) {
				size += zend_ast_tree_size(list->child[i]);
			}
		}
	} else if (zend_ast_is_decl(ast)) {
		/* Not implemented. */
		ZEND_UNREACHABLE();
	} else {
		uint32_t i, children = zend_ast_get_num_children(ast);

		size = zend_ast_size(children);
		for (i = 0; i < children; i++) {
			if (ast->child[i]) {
				size += zend_ast_tree_size(ast->child[i]);
			}
		}
	}
	return size;
}

static void* ZEND_FASTCALL zend_ast_tree_copy(zend_ast *ast, void *buf)
{
	if (ast->kind == ZEND_AST_ZVAL) {
		zend_ast_zval *new = (zend_ast_zval*)buf;
		new->kind = ZEND_AST_ZVAL;
		new->attr = ast->attr;
		ZVAL_COPY(&new->val, zend_ast_get_zval(ast));
		Z_LINENO(new->val) = zend_ast_get_lineno(ast);
		buf = (void*)((char*)buf + sizeof(zend_ast_zval));
	} else if (ast->kind == ZEND_AST_CONSTANT) {
		zend_ast_zval *new = (zend_ast_zval*)buf;
		new->kind = ZEND_AST_CONSTANT;
		new->attr = ast->attr;
		ZVAL_STR_COPY(&new->val, zend_ast_get_constant_name(ast));
		Z_LINENO(new->val) = zend_ast_get_lineno(ast);
		buf = (void*)((char*)buf + sizeof(zend_ast_zval));
	} else if (zend_ast_is_list(ast)) {
		const zend_ast_list *list = zend_ast_get_list(ast);
		zend_ast_list *new = (zend_ast_list*)buf;
		uint32_t i;
		new->kind = list->kind;
		new->attr = list->attr;
		new->children = list->children;
		new->lineno = list->lineno;
		buf = (void*)((char*)buf + zend_ast_list_size(list->children));
		for (i = 0; i < list->children; i++) {
			if (list->child[i]) {
				new->child[i] = (zend_ast*)buf;
				buf = zend_ast_tree_copy(list->child[i], buf);
			} else {
				new->child[i] = NULL;
			}
		}
	} else if (ast->kind == ZEND_AST_OP_ARRAY) {
		const zend_ast_op_array *old = zend_ast_get_op_array(ast);
		zend_ast_op_array *new = (zend_ast_op_array*)buf;
		new->kind = old->kind;
		new->attr = old->attr;
		new->lineno = old->lineno;
		new->op_array = old->op_array;
		function_add_ref((zend_function *)new->op_array);
		buf = (void*)((char*)buf + sizeof(zend_ast_op_array));
	} else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) {
		const zend_ast_fcc *old = (zend_ast_fcc*)ast;
		zend_ast_fcc *new = (zend_ast_fcc*)buf;
		new->kind = old->kind;
		new->attr = old->attr;
		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();
	} else {
		uint32_t i, children = zend_ast_get_num_children(ast);
		zend_ast *new = (zend_ast*)buf;
		new->kind = ast->kind;
		new->attr = ast->attr;
		new->lineno = ast->lineno;
		buf = (void*)((char*)buf + zend_ast_size(children));
		for (i = 0; i < children; i++) {
			if (ast->child[i]) {
				new->child[i] = (zend_ast*)buf;
				buf = zend_ast_tree_copy(ast->child[i], buf);
			} else {
				new->child[i] = NULL;
			}
		}
	}
	return buf;
}

ZEND_API zend_ast_ref * ZEND_FASTCALL zend_ast_copy(zend_ast *ast)
{
	size_t tree_size;
	zend_ast_ref *ref;

	ZEND_ASSERT(ast != NULL);
	tree_size = zend_ast_tree_size(ast) + sizeof(zend_ast_ref);
	ref = emalloc(tree_size);
	zend_ast_tree_copy(ast, GC_AST(ref));
	GC_SET_REFCOUNT(ref, 1);
	GC_TYPE_INFO(ref) = GC_CONSTANT_AST;
	return ref;
}

ZEND_API void ZEND_FASTCALL zend_ast_destroy(zend_ast *ast)
{
tail_call:
	if (!ast) {
		return;
	}

	if (EXPECTED(ast->kind >= ZEND_AST_VAR)) {
		uint32_t i, children = zend_ast_get_num_children(ast);

		for (i = 1; i < children; i++) {
			zend_ast_destroy(ast->child[i]);
		}
		ast = ast->child[0];
		goto tail_call;
	} else if (EXPECTED(ast->kind == ZEND_AST_ZVAL)) {
		zval_ptr_dtor_nogc(zend_ast_get_zval(ast));
	} else if (EXPECTED(zend_ast_is_list(ast))) {
		const zend_ast_list *list = zend_ast_get_list(ast);
		if (list->children) {
			uint32_t i;

			for (i = 1; i < list->children; i++) {
				zend_ast_destroy(list->child[i]);
			}
			ast = list->child[0];
			goto 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)) {
		destroy_op_array(zend_ast_get_op_array(ast)->op_array);
	} else if (EXPECTED(zend_ast_is_decl(ast))) {
		const zend_ast_decl *decl = (const zend_ast_decl *) ast;

		if (decl->name) {
		    zend_string_release_ex(decl->name, 0);
		}
		if (decl->doc_comment) {
			zend_string_release_ex(decl->doc_comment, 0);
		}
		zend_ast_destroy(decl->child[0]);
		zend_ast_destroy(decl->child[1]);
		zend_ast_destroy(decl->child[2]);
		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;
	}
}

ZEND_API void ZEND_FASTCALL zend_ast_ref_destroy(zend_ast_ref *ast)
{
	zend_ast_destroy(GC_AST(ast));
	efree(ast);
}

ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn, void *context) {
	if (zend_ast_is_list(ast)) {
		zend_ast_list *list = zend_ast_get_list(ast);
		uint32_t i;
		for (i = 0; i < list->children; ++i) {
			fn(&list->child[i], context);
		}
	} else if (zend_ast_is_decl(ast)) {
		/* Not implemented. */
		ZEND_UNREACHABLE();
	} else {
		uint32_t i, children = zend_ast_get_num_children(ast);
		for (i = 0; i < children; ++i) {
			fn(&ast->child[i], context);
		}
	}
}

/*
 * Operator Precedence
 * ====================
 * priority  associativity  operators
 * ----------------------------------
 *   10     left            include, include_once, eval, require, require_once
 *   20     left            ,
 *   30     left            or
 *   40     left            xor
 *   50     left            and
 *   60     right           print
 *   70     right           yield
 *   80     right           =>
 *   85     right           yield from
 *   90     right           = += -= *= /= .= %= &= |= ^= <<= >>= **=
 *  100     left            ? :
 *  110     right           ??
 *  120     left            ||
 *  130     left            &&
 *  140     left            |
 *  150     left            ^
 *  160     left            &
 *  170     non-associative == != === !==
 *  180     non-associative < <= > >= <=>
 *  185     left            .
 *  190     left            << >>
 *  200     left            + -
 *  210     left            * / %
 *  220     right           !
 *  230     non-associative instanceof
 *  240     right           + - ++ -- ~ (type) @
 *  250     right           **
 *  260     left            [
 *  270     non-associative clone new
 */

static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int priority, int indent);

static ZEND_COLD void zend_ast_export_str(smart_str *str, const zend_string *s)
{
	for (size_t i = 0; i < ZSTR_LEN(s); i++) {
		unsigned char c = ZSTR_VAL(s)[i];
		if (c == '\'' || c == '\\') {
			smart_str_appendc(str, '\\');
			smart_str_appendc(str, c);
		} else {
			smart_str_appendc(str, c);
		}
	}
}

static ZEND_COLD void zend_ast_export_qstr(smart_str *str, char quote, const zend_string *s)
{
	for (size_t i = 0; i < ZSTR_LEN(s); i++) {
		unsigned char c = ZSTR_VAL(s)[i];
		if (c < ' ') {
			switch (c) {
				case '\n':
					smart_str_appends(str, "\\n");
					break;
				case '\r':
					smart_str_appends(str, "\\r");
					break;
				case '\t':
					smart_str_appends(str, "\\t");
					break;
				case '\f':
					smart_str_appends(str, "\\f");
					break;
				case '\v':
					smart_str_appends(str, "\\v");
					break;
#ifdef ZEND_WIN32
				case VK_ESCAPE:
#else
				case '\e':
#endif
					smart_str_appends(str, "\\e");
					break;
				default:
					smart_str_appends(str, "\\0");
					smart_str_appendc(str, '0' + (c / 8));
					smart_str_appendc(str, '0' + (c % 8));
					break;
			}
		} else {
			if (c == quote || c == '$' || c == '\\') {
				smart_str_appendc(str, '\\');
			}
			smart_str_appendc(str, c);
		}
	}
}

static ZEND_COLD void zend_ast_export_quoted_str(smart_str *str, const zend_string *s)
{
	for (size_t i = 0; i < ZSTR_LEN(s); i++) {
		unsigned char c = ZSTR_VAL(s)[i];
		if (c < ' ') {
			smart_str_appendc(str, '"');
			zend_ast_export_qstr(str, '"', s);
			smart_str_appendc(str, '"');
			return;
		}
	}
	smart_str_appendc(str, '\'');
	zend_ast_export_str(str, s);
	smart_str_appendc(str, '\'');
}

static ZEND_COLD void zend_ast_export_indent(smart_str *str, int indent)
{
	while (indent > 0) {
		smart_str_appends(str, "    ");
		indent--;
	}
}

static ZEND_COLD void zend_ast_export_name(smart_str *str, zend_ast *ast, int priority, int indent)
{
	if (ast->kind == ZEND_AST_ZVAL) {
		const zval *zv = zend_ast_get_zval(ast);

		if (Z_TYPE_P(zv) == IS_STRING) {
			smart_str_append(str, Z_STR_P(zv));
			return;
		}
	}
	zend_ast_export_ex(str, ast, priority, indent);
}

static ZEND_COLD void zend_ast_export_ns_name(smart_str *str, zend_ast *ast, int priority, int indent)
{
	if (ast->kind == ZEND_AST_ZVAL) {
		const zval *zv = zend_ast_get_zval(ast);

		if (Z_TYPE_P(zv) == IS_STRING) {
		    if (ast->attr == ZEND_NAME_FQ) {
				smart_str_appendc(str, '\\');
		    } else if (ast->attr == ZEND_NAME_RELATIVE) {
				smart_str_appends(str, "namespace\\");
		    }
			smart_str_append(str, Z_STR_P(zv));
			return;
		}
	}
	zend_ast_export_ex(str, ast, priority, indent);
}
static ZEND_COLD void zend_ast_export_ns_name_or_expression(smart_str *str, zend_ast *ast, int priority, int indent)
{
	switch (ast->kind) {
		case ZEND_AST_ZVAL:
		case ZEND_AST_VAR:
			zend_ast_export_ns_name(str, ast, priority, indent);
			break;
		default:
			smart_str_appendc(str, '(');
			zend_ast_export_ex(str, ast, priority, indent);
			smart_str_appendc(str, ')');
	}
}

static ZEND_COLD bool zend_ast_valid_var_name(const char *s, size_t len)
{
	unsigned char c;
	size_t i;

	if (len == 0) {
		return false;
	}
	c = (unsigned char)s[0];
	if (c != '_' && c < 127 &&
	    (c < 'A' || c > 'Z') &&
	    (c < 'a' || c > 'z')) {
		return false;
	}
	for (i = 1; i < len; i++) {
		c = (unsigned char)s[i];
		if (c != '_' && c < 127 &&
		    (c < '0' || c > '9') &&
		    (c < 'A' || c > 'Z') &&
		    (c < 'a' || c > 'z')) {
			return false;
		}
	}
	return true;
}

static ZEND_COLD void zend_ast_export_var(smart_str *str, zend_ast *ast, int indent)
{
	if (ast->kind == ZEND_AST_ZVAL) {
		zval *zv = zend_ast_get_zval(ast);
		if (Z_TYPE_P(zv) == IS_STRING) {
			if (zend_ast_valid_var_name(Z_STRVAL_P(zv), Z_STRLEN_P(zv))) {
				smart_str_append(str, Z_STR_P(zv));
			} else {
				smart_str_appends(str, "{'");
				zend_ast_export_str(str, Z_STR_P(zv));
				smart_str_appends(str, "'}");
			}
			return;
		}
	} else if (ast->kind == ZEND_AST_VAR) {
		zend_ast_export_ex(str, ast, 0, indent);
		return;
	}
	smart_str_appendc(str, '{');
	zend_ast_export_name(str, ast, 0, indent);
	smart_str_appendc(str, '}');
}

/* Use zend_ast_export_list() unless fewer than `list->children` children should
 * be exported. */
static ZEND_COLD void zend_ast_export_list_ex(smart_str *str, const zend_ast_list *list, bool separator, int priority, int indent, uint32_t children)
{
	ZEND_ASSERT(children <= list->children);
	uint32_t i = 0;

	while (i < children) {
		if (i != 0 && separator) {
			smart_str_appends(str, ", ");
		}
		zend_ast_export_ex(str, list->child[i], priority, indent);
		i++;
	}
}

static ZEND_COLD void zend_ast_export_list(smart_str *str, const zend_ast_list *list, bool separator, int priority, int indent)
{
	zend_ast_export_list_ex(str, list, separator, priority, indent, list->children);
}

static ZEND_COLD void zend_ast_export_encaps_list(smart_str *str, char quote, const zend_ast_list *list, int indent)
{
	uint32_t i = 0;
	zend_ast *ast;

	while (i < list->children) {
		ast = list->child[i];
		if (ast->kind == ZEND_AST_ZVAL) {
			const zval *zv = zend_ast_get_zval(ast);

			ZEND_ASSERT(Z_TYPE_P(zv) == IS_STRING);
			zend_ast_export_qstr(str, quote, Z_STR_P(zv));
		} else {
			smart_str_appendc(str, '{');
			zend_ast_export_ex(str, ast, 0, indent);
			smart_str_appendc(str, '}');
		}
		i++;
	}
}

static ZEND_COLD void zend_ast_export_name_list_ex(smart_str *str, const zend_ast_list *list, int indent, const char *separator)
{
	uint32_t i = 0;

	while (i < list->children) {
		if (i != 0) {
			smart_str_appends(str, separator);
		}
		zend_ast_export_name(str, list->child[i], 0, indent);
		i++;
	}
}

#define zend_ast_export_name_list(s, l, i) zend_ast_export_name_list_ex(s, l, i, ", ")
#define zend_ast_export_catch_name_list(s, l, i) zend_ast_export_name_list_ex(s, l, i, "|")

static ZEND_COLD void zend_ast_export_var_list(smart_str *str, const zend_ast_list *list, int indent)
{
	uint32_t i = 0;

	while (i < list->children) {
		if (i != 0) {
			smart_str_appends(str, ", ");
		}
		if (list->child[i]->attr & ZEND_BIND_REF) {
			smart_str_appendc(str, '&');
		}
		smart_str_appendc(str, '$');
		zend_ast_export_name(str, list->child[i], 20, indent);
		i++;
	}
}

static ZEND_COLD void zend_ast_export_stmt(smart_str *str, zend_ast *ast, int indent)
{
	if (!ast) {
		return;
	}

	if (ast->kind == ZEND_AST_STMT_LIST ||
	    ast->kind == ZEND_AST_TRAIT_ADAPTATIONS) {
		const zend_ast_list *list = (const zend_ast_list*)ast;
		uint32_t i = 0;

		while (i < list->children) {
			ast = list->child[i];
			zend_ast_export_stmt(str, ast, indent);
			i++;
		}
	} else {
		zend_ast_export_indent(str, indent);
		zend_ast_export_ex(str, ast, 0, indent);
		switch (ast->kind) {
			case ZEND_AST_LABEL:
			case ZEND_AST_IF:
			case ZEND_AST_SWITCH:
			case ZEND_AST_WHILE:
			case ZEND_AST_TRY:
			case ZEND_AST_FOR:
			case ZEND_AST_FOREACH:
			case ZEND_AST_FUNC_DECL:
			case ZEND_AST_METHOD:
			case ZEND_AST_CLASS:
			case ZEND_AST_USE_TRAIT:
			case ZEND_AST_NAMESPACE:
			case ZEND_AST_DECLARE:
				break;
			case ZEND_AST_PROP_GROUP: {
				const zend_ast *first_prop = zend_ast_get_list(ast->child[1])->child[0];
				const zend_ast *hook_list = first_prop->child[3];
				if (hook_list == NULL) {
					smart_str_appendc(str, ';');
				}
				break;
			}
			default:
				smart_str_appendc(str, ';');
				break;
		}
		smart_str_appendc(str, '\n');
	}
}

static ZEND_COLD void zend_ast_export_if_stmt(smart_str *str, const zend_ast_list *list, int indent)
{
	uint32_t i;
	zend_ast *ast;

tail_call:
	i = 0;
	while (i < list->children) {
		ast = list->child[i];
		ZEND_ASSERT(ast->kind == ZEND_AST_IF_ELEM);
		if (ast->child[0]) {
			if (i == 0) {
				smart_str_appends(str, "if (");
			} else {
				zend_ast_export_indent(str, indent);
				smart_str_appends(str, "} elseif (");
			}
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			smart_str_appends(str, ") {\n");
			zend_ast_export_stmt(str, ast->child[1], indent + 1);
		} else {
			zend_ast_export_indent(str, indent);
			smart_str_appends(str, "} else ");
			if (ast->child[1] && ast->child[1]->kind == ZEND_AST_IF) {
				list = (const zend_ast_list*)ast->child[1];
				goto tail_call;
			} else {
				smart_str_appends(str, "{\n");
				zend_ast_export_stmt(str, ast->child[1], indent + 1);
			}
		}
		i++;
	}
	zend_ast_export_indent(str, indent);
	smart_str_appendc(str, '}');
}

static ZEND_COLD void zend_ast_export_zval(smart_str *str, const zval *zv, int priority, int indent)
{
	ZVAL_DEREF(zv);
	switch (Z_TYPE_P(zv)) {
		case IS_NULL:
			smart_str_appends(str, "null");
			break;
		case IS_FALSE:
			smart_str_appends(str, "false");
			break;
		case IS_TRUE:
			smart_str_appends(str, "true");
			break;
		case IS_LONG:
			smart_str_append_long(str, Z_LVAL_P(zv));
			break;
		case IS_DOUBLE:
			smart_str_append_double(
				str, Z_DVAL_P(zv), (int) EG(precision), /* zero_fraction */ true);
			break;
		case IS_STRING:
			zend_ast_export_quoted_str(str, Z_STR_P(zv));
			break;
		case IS_ARRAY: {
			zend_long idx;
			zend_string *key;
			zval *val;
			bool first = true;
			smart_str_appendc(str, '[');
			ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(zv), idx, key, val) {
				if (first) {
					first = false;
				} else {
					smart_str_appends(str, ", ");
				}
				if (key) {
					zend_ast_export_quoted_str(str, key);
				} else {
					smart_str_append_long(str, idx);
				}
				smart_str_appends(str, " => ");
				zend_ast_export_zval(str, val, 0, indent);
			} ZEND_HASH_FOREACH_END();
			smart_str_appendc(str, ']');
			break;
		}
		case IS_CONSTANT_AST:
			zend_ast_export_ex(str, Z_ASTVAL_P(zv), priority, indent);
			break;
		default: ZEND_UNREACHABLE();
	}
}

static ZEND_COLD void zend_ast_export_class_no_header(smart_str *str, const zend_ast_decl *decl, int indent) {
	if (decl->child[0]) {
		smart_str_appends(str, " extends ");
		zend_ast_export_ns_name(str, decl->child[0], 0, indent);
	}
	if (decl->child[1]) {
		smart_str_appends(str, " implements ");
		zend_ast_export_ex(str, decl->child[1], 0, indent);
	}
	smart_str_appends(str, " {\n");
	zend_ast_export_stmt(str, decl->child[2], indent + 1);
	zend_ast_export_indent(str, indent);
	smart_str_appendc(str, '}');
}

static ZEND_COLD void zend_ast_export_attribute_group(smart_str *str, zend_ast *ast, int indent) {
	const zend_ast_list *list = zend_ast_get_list(ast);
	for (uint32_t i = 0; i < list->children; i++) {
		const zend_ast *attr = list->child[i];

		if (i) {
			smart_str_appends(str, ", ");
		}
		zend_ast_export_ns_name(str, attr->child[0], 0, indent);

		if (attr->child[1]) {
			smart_str_appendc(str, '(');
			zend_ast_export_ex(str, attr->child[1], 0, indent);
			smart_str_appendc(str, ')');
		}
	}
}

static ZEND_COLD void zend_ast_export_attributes(smart_str *str, zend_ast *ast, int indent, bool newlines) {
	const zend_ast_list *list = zend_ast_get_list(ast);
	uint32_t i;

	for (i = 0; i < list->children; i++) {
		smart_str_appends(str, "#[");
		zend_ast_export_attribute_group(str, list->child[i], indent);
		smart_str_appendc(str, ']');

		if (newlines) {
			smart_str_appendc(str, '\n');
			zend_ast_export_indent(str, indent);
		} else {
			smart_str_appendc(str, ' ');
		}
	}
}

static ZEND_COLD void zend_ast_export_visibility(smart_str *str, uint32_t flags, zend_modifier_target target) {
	if (flags & ZEND_ACC_PUBLIC) {
		smart_str_appends(str, "public ");
	} else if (flags & ZEND_ACC_PROTECTED) {
		smart_str_appends(str, "protected ");
	} else if (flags & ZEND_ACC_PRIVATE) {
		smart_str_appends(str, "private ");
	}

	if (target == ZEND_MODIFIER_TARGET_PROPERTY || target == ZEND_MODIFIER_TARGET_CPP) {
		if (flags & ZEND_ACC_PRIVATE_SET) {
			smart_str_appends(str, "private(set) ");
		} else if (flags & ZEND_ACC_PROTECTED_SET) {
			smart_str_appends(str, "protected(set) ");
		} else if (flags & ZEND_ACC_PUBLIC_SET) {
			smart_str_appends(str, "public(set) ");
		}
	}
}

static ZEND_COLD void zend_ast_export_type(smart_str *str, zend_ast *ast, int indent) {
	if (ast->kind == ZEND_AST_TYPE_UNION) {
		const zend_ast_list *list = zend_ast_get_list(ast);
		for (uint32_t i = 0; i < list->children; i++) {
			if (i != 0) {
				smart_str_appendc(str, '|');
			}
			zend_ast_export_type(str, list->child[i], indent);
		}
		return;
	}
	if (ast->kind == ZEND_AST_TYPE_INTERSECTION) {
		const zend_ast_list *list = zend_ast_get_list(ast);
		for (uint32_t i = 0; i < list->children; i++) {
			if (i != 0) {
				smart_str_appendc(str, '&');
			}
			zend_ast_export_type(str, list->child[i], indent);
		}
		return;
	}
	if (ast->attr & ZEND_TYPE_NULLABLE) {
		smart_str_appendc(str, '?');
	}
	zend_ast_export_ns_name(str, ast, 0, indent);
}

static ZEND_COLD void zend_ast_export_hook_list(smart_str *str, const zend_ast_list *hook_list, int indent)
{
	smart_str_appends(str, " {");
	smart_str_appendc(str, '\n');
	indent++;
	zend_ast_export_indent(str, indent);

	for (uint32_t i = 0; i < hook_list->children; i++) {
		const zend_ast_decl *hook = (const zend_ast_decl *)hook_list->child[i];
		zend_ast_export_visibility(str, hook->flags, ZEND_MODIFIER_TARGET_PROPERTY);
		if (hook->flags & ZEND_ACC_FINAL) {
			smart_str_appends(str, "final ");
		}
		smart_str_append(str, hook->name);
		zend_ast *body = hook->child[2];
		if (body == NULL) {
			smart_str_appendc(str, ';');
		} else if (body->kind == ZEND_AST_PROPERTY_HOOK_SHORT_BODY) {
			smart_str_appends(str, " => ");
			zend_ast_export_ex(str, body->child[0], 0, indent);
			smart_str_appendc(str, ';');
		} else {
			smart_str_appends(str, " {\n");
			zend_ast_export_stmt(str, body, indent + 1);
			zend_ast_export_indent(str, indent);
			smart_str_appendc(str, '}');
		}
		if (i < (hook_list->children - 1)) {
			smart_str_appendc(str, '\n');
			zend_ast_export_indent(str, indent);
		}
	}
	smart_str_appendc(str, '\n');
	indent--;
	zend_ast_export_indent(str, indent);
	smart_str_appendc(str, '}');
}

#define BINARY_OP(_op, _p, _pl, _pr) do { \
		op = _op; \
		p = _p; \
		pl = _pl; \
		pr = _pr; \
		goto binary_op; \
	} while (0)

#define PREFIX_OP(_op, _p, _pl) do { \
		op = _op; \
		p = _p; \
		pl = _pl; \
		goto prefix_op; \
	} while (0)

#define FUNC_OP(_op) do { \
		op = _op; \
		goto func_op; \
	} while (0)

#define POSTFIX_OP(_op, _p, _pl) do { \
		op = _op; \
		p = _p; \
		pl = _pl; \
		goto postfix_op; \
	} while (0)

#define APPEND_NODE_1(_op) do { \
		op = _op; \
		goto append_node_1; \
	} while (0)

#define APPEND_STR(_op) do { \
		op = _op; \
		goto append_str; \
	} while (0)

#define APPEND_DEFAULT_VALUE(n) do { \
		p = n; \
		goto append_default_value; \
	} while (0)

static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int priority, int indent)
{
	const zend_ast_decl *decl;
	int p, pl, pr;
	const char *op;

tail_call:
	if (!ast) {
		return;
	}
	switch (ast->kind) {
		/* special nodes */
		case ZEND_AST_ZVAL:
			zend_ast_export_zval(str, zend_ast_get_zval(ast), priority, indent);
			break;
		case ZEND_AST_CONSTANT: {
			zend_string *name = zend_ast_get_constant_name(ast);
			smart_str_append(str, name);
			break;
		}
		case ZEND_AST_OP_ARRAY:
			smart_str_appends(str, "Closure(");
			smart_str_append(str, zend_ast_get_op_array(ast)->op_array->function_name);
			smart_str_appendc(str, ')');
			break;
		case ZEND_AST_CONSTANT_CLASS:
			smart_str_appendl(str, "__CLASS__", sizeof("__CLASS__")-1);
			break;
		case ZEND_AST_ZNODE:
			/* This AST kind is only used for temporary nodes during compilation */
			ZEND_UNREACHABLE();
			break;

		/* declaration nodes */
		case ZEND_AST_FUNC_DECL:
		case ZEND_AST_CLOSURE:
		case ZEND_AST_ARROW_FUNC:
		case ZEND_AST_METHOD:
			decl = (const zend_ast_decl *) ast;
			if (decl->kind == ZEND_AST_ARROW_FUNC && (decl->attr & ZEND_PARENTHESIZED_ARROW_FUNC)) {
				smart_str_appendc(str, '(');
			}
			if (decl->child[4]) {
				bool newlines = !(ast->kind == ZEND_AST_CLOSURE || ast->kind == ZEND_AST_ARROW_FUNC);
				zend_ast_export_attributes(str, decl->child[4], indent, newlines);
			}

			zend_ast_export_visibility(str, decl->flags, ZEND_MODIFIER_TARGET_METHOD);

			if (decl->flags & ZEND_ACC_STATIC) {
				smart_str_appends(str, "static ");
			}
			if (decl->flags & ZEND_ACC_ABSTRACT) {
				smart_str_appends(str, "abstract ");
			}
			if (decl->flags & ZEND_ACC_FINAL) {
				smart_str_appends(str, "final ");
			}
			if (decl->kind == ZEND_AST_ARROW_FUNC) {
				smart_str_appends(str, "fn");
			} else {
				smart_str_appends(str, "function ");
			}
			if (decl->flags & ZEND_ACC_RETURN_REFERENCE) {
				smart_str_appendc(str, '&');
			}
			if (ast->kind != ZEND_AST_CLOSURE && ast->kind != ZEND_AST_ARROW_FUNC) {
				smart_str_append(str, decl->name);
			}
			smart_str_appendc(str, '(');
			zend_ast_export_ex(str, decl->child[0], 0, indent);
			smart_str_appendc(str, ')');
			zend_ast_export_ex(str, decl->child[1], 0, indent);
			if (decl->child[3]) {
				smart_str_appends(str, ": ");
				zend_ast_export_type(str, decl->child[3], indent);
			}
			if (decl->child[2]) {
				if (decl->kind == ZEND_AST_ARROW_FUNC) {
					zend_ast *body = decl->child[2];
					if (body->kind == ZEND_AST_RETURN) {
						body = body->child[0];
					}
					smart_str_appends(str, " => ");
					zend_ast_export_ex(str, body, 0, indent);
					if (decl->attr & ZEND_PARENTHESIZED_ARROW_FUNC) {
						smart_str_appendc(str, ')');
					}
					break;
				}

				smart_str_appends(str, " {\n");
				zend_ast_export_stmt(str, decl->child[2], indent + 1);
				zend_ast_export_indent(str, indent);
				smart_str_appendc(str, '}');
				if (ast->kind != ZEND_AST_CLOSURE) {
					smart_str_appendc(str, '\n');
				}
			} else {
				smart_str_appends(str, ";\n");
			}
			break;
		case ZEND_AST_CLASS:
			decl = (const zend_ast_decl *) ast;
			if (decl->child[3]) {
				zend_ast_export_attributes(str, decl->child[3], indent, true);
			}
			if (decl->flags & ZEND_ACC_INTERFACE) {
				smart_str_appends(str, "interface ");
			} else if (decl->flags & ZEND_ACC_TRAIT) {
				smart_str_appends(str, "trait ");
			} else if (decl->flags & ZEND_ACC_ENUM) {
				smart_str_appends(str, "enum ");
			} else {
				if (decl->flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS) {
					smart_str_appends(str, "abstract ");
				}
				if (decl->flags & ZEND_ACC_FINAL) {
					smart_str_appends(str, "final ");
				}
				if (decl->flags & ZEND_ACC_READONLY_CLASS) {
					smart_str_appends(str, "readonly ");
				}
				smart_str_appends(str, "class ");
			}
			smart_str_append(str, decl->name);
			if (decl->flags & ZEND_ACC_ENUM && decl->child[4]) {
				smart_str_appends(str, ": ");
				zend_ast_export_type(str, decl->child[4], indent);
			}
			zend_ast_export_class_no_header(str, decl, indent);
			smart_str_appendc(str, '\n');
			break;

		/* list nodes */
		case ZEND_AST_ARG_LIST:
		case ZEND_AST_EXPR_LIST:
		case ZEND_AST_PARAM_LIST:
simple_list:
			zend_ast_export_list(str, zend_ast_get_list(ast), true, 20, indent);
			break;
		case ZEND_AST_ARRAY:
			smart_str_appendc(str, '[');
			zend_ast_export_list(str, zend_ast_get_list(ast), true, 20, indent);
			smart_str_appendc(str, ']');
			break;
		case ZEND_AST_ENCAPS_LIST:
			smart_str_appendc(str, '"');
			zend_ast_export_encaps_list(str, '"', zend_ast_get_list(ast), indent);
			smart_str_appendc(str, '"');
			break;
		case ZEND_AST_STMT_LIST:
		case ZEND_AST_TRAIT_ADAPTATIONS:
			zend_ast_export_stmt(str, ast, indent);
			break;
		case ZEND_AST_IF:
			zend_ast_export_if_stmt(str, zend_ast_get_list(ast), indent);
			break;
		case ZEND_AST_SWITCH_LIST:
		case ZEND_AST_CATCH_LIST:
		case ZEND_AST_MATCH_ARM_LIST:
			zend_ast_export_list(str, zend_ast_get_list(ast), false, 0, indent);
			break;
		case ZEND_AST_CLOSURE_USES:
			smart_str_appends(str, " use(");
			zend_ast_export_var_list(str, zend_ast_get_list(ast), indent);
			smart_str_appendc(str, ')');
			break;
		case ZEND_AST_PROP_GROUP: {
			zend_ast *type_ast = ast->child[0];
			zend_ast *prop_ast = ast->child[1];

			if (ast->child[2]) {
				zend_ast_export_attributes(str, ast->child[2], indent, true);
			}

			zend_ast_export_visibility(str, ast->attr, ZEND_MODIFIER_TARGET_PROPERTY);

			if (ast->attr & ZEND_ACC_STATIC) {
				smart_str_appends(str, "static ");
			}
			if (ast->attr & ZEND_ACC_READONLY) {
				smart_str_appends(str, "readonly ");
			}

			if (type_ast) {
				zend_ast_export_type(str, type_ast, indent);
				smart_str_appendc(str, ' ');
			}

			ast = prop_ast;
			goto simple_list;
		}

		case ZEND_AST_CONST_DECL: {
			zend_ast_list *ast_list = zend_ast_get_list(ast);
			/* Attributes are stored at the end of the list if present. */
			if (ast_list->child[ast_list->children - 1]->kind == ZEND_AST_ATTRIBUTE_LIST) {
				zend_ast_export_attributes(
					str,
					ast_list->child[ast_list->children - 1],
					indent,
					true
				);
				/* So that the list printing doesn't try to print the attributes,
				 * use zend_ast_export_list_ex() to override the number of children
				 * to print. */
				smart_str_appends(str, "const ");
				zend_ast_export_list_ex(str, ast_list, true, 20, indent, ast_list->children - 1);
				break;
			}
			smart_str_appends(str, "const ");
			goto simple_list;
		}
		case ZEND_AST_CLASS_CONST_GROUP:
			if (ast->child[1]) {
				zend_ast_export_attributes(str, ast->child[1], indent, true);
			}

			zend_ast_export_visibility(str, ast->attr, ZEND_MODIFIER_TARGET_CONSTANT);
			smart_str_appends(str, "const ");
			if (ast->child[2]) {
				zend_ast_export_type(str, ast->child[2], indent);
				smart_str_appendc(str, ' ');
			}

			ast = ast->child[0];

			goto simple_list;
		case ZEND_AST_NAME_LIST:
			zend_ast_export_name_list(str, zend_ast_get_list(ast), indent);
			break;
		case ZEND_AST_USE:
			smart_str_appends(str, "use ");
			if (ast->attr == T_FUNCTION) {
				smart_str_appends(str, "function ");
			} else if (ast->attr == T_CONST) {
				smart_str_appends(str, "const ");
			}
			goto simple_list;

		/* 0 child nodes */
		case ZEND_AST_MAGIC_CONST:
			switch (ast->attr) {
				case T_LINE:     APPEND_STR("__LINE__");
				case T_FILE:     APPEND_STR("__FILE__");
				case T_DIR:      APPEND_STR("__DIR__");
				case T_TRAIT_C:  APPEND_STR("__TRAIT__");
				case T_METHOD_C: APPEND_STR("__METHOD__");
				case T_FUNC_C:   APPEND_STR("__FUNCTION__");
				case T_PROPERTY_C: APPEND_STR("__PROPERTY__");
				case T_NS_C:     APPEND_STR("__NAMESPACE__");
				case T_CLASS_C:  APPEND_STR("__CLASS__");
				default: ZEND_UNREACHABLE();
			}
			break;
		case ZEND_AST_TYPE:
			switch (ast->attr & ~ZEND_TYPE_NULLABLE) {
				case IS_ARRAY:    APPEND_STR("array");
				case IS_CALLABLE: APPEND_STR("callable");
				case IS_STATIC:   APPEND_STR("static");
				case IS_MIXED:    APPEND_STR("mixed");
				default: ZEND_UNREACHABLE();
			}
			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:
			smart_str_appendc(str, '$');
			zend_ast_export_var(str, ast->child[0], indent);
			break;
		case ZEND_AST_CONST:
			zend_ast_export_ns_name(str, ast->child[0], 0, indent);
			break;
		case ZEND_AST_UNPACK:
			smart_str_appends(str, "...");
			ast = ast->child[0];
			goto tail_call;
		case ZEND_AST_UNARY_PLUS:  PREFIX_OP("+", 240, 241);
		case ZEND_AST_UNARY_MINUS: PREFIX_OP("-", 240, 241);
		case ZEND_AST_CAST:
			switch (ast->attr) {
				case IS_NULL:      PREFIX_OP("(unset)",  240, 241);
				case _IS_BOOL:     PREFIX_OP("(bool)",   240, 241);
				case IS_LONG:      PREFIX_OP("(int)",    240, 241);
				case IS_DOUBLE:    PREFIX_OP("(float)", 240, 241);
				case IS_STRING:    PREFIX_OP("(string)", 240, 241);
				case IS_ARRAY:     PREFIX_OP("(array)",  240, 241);
				case IS_OBJECT:    PREFIX_OP("(object)", 240, 241);
				default: ZEND_UNREACHABLE();
			}
			break;
		case ZEND_AST_CAST_VOID:
			PREFIX_OP("(void)", 240, 241);
			break;
		case ZEND_AST_EMPTY:
			FUNC_OP("empty");
		case ZEND_AST_ISSET:
			FUNC_OP("isset");
		case ZEND_AST_SILENCE:
			PREFIX_OP("@", 240, 241);
		case ZEND_AST_SHELL_EXEC:
			smart_str_appendc(str, '`');
			if (ast->child[0]->kind == ZEND_AST_ENCAPS_LIST) {
				zend_ast_export_encaps_list(str, '`', zend_ast_get_list(ast->child[0]), indent);
			} else {
				zval *zv;
				ZEND_ASSERT(ast->child[0]->kind == ZEND_AST_ZVAL);
				zv = zend_ast_get_zval(ast->child[0]);
				ZEND_ASSERT(Z_TYPE_P(zv) == IS_STRING);
				zend_ast_export_qstr(str, '`', Z_STR_P(zv));
			}
			smart_str_appendc(str, '`');
			break;
		case ZEND_AST_PRINT:
			PREFIX_OP("print ", 60, 61);
		case ZEND_AST_INCLUDE_OR_EVAL:
			switch (ast->attr) {
				case ZEND_INCLUDE_ONCE: FUNC_OP("include_once");
				case ZEND_INCLUDE:      FUNC_OP("include");
				case ZEND_REQUIRE_ONCE: FUNC_OP("require_once");
				case ZEND_REQUIRE:      FUNC_OP("require");
				case ZEND_EVAL:         FUNC_OP("eval");
				default: ZEND_UNREACHABLE();
			}
			break;
		case ZEND_AST_UNARY_OP:
			switch (ast->attr) {
				case ZEND_BW_NOT:   PREFIX_OP("~", 240, 241);
				case ZEND_BOOL_NOT: PREFIX_OP("!", 240, 241);
				default: ZEND_UNREACHABLE();
			}
			break;
		case ZEND_AST_PRE_INC:
			PREFIX_OP("++", 240, 241);
		case ZEND_AST_PRE_DEC:
			PREFIX_OP("--", 240, 241);
		case ZEND_AST_POST_INC:
			POSTFIX_OP("++", 240, 241);
		case ZEND_AST_POST_DEC:
			POSTFIX_OP("--", 240, 241);

		case ZEND_AST_GLOBAL:
			APPEND_NODE_1("global");
		case ZEND_AST_UNSET:
			FUNC_OP("unset");
		case ZEND_AST_RETURN:
			APPEND_NODE_1("return");
		case ZEND_AST_LABEL:
			zend_ast_export_name(str, ast->child[0], 0, indent);
			smart_str_appendc(str, ':');
			break;
		case ZEND_AST_REF:
			smart_str_appendc(str, '&');
			ast = ast->child[0];
			goto tail_call;
		case ZEND_AST_HALT_COMPILER:
			APPEND_STR("__HALT_COMPILER()");
		case ZEND_AST_ECHO:
			APPEND_NODE_1("echo");
		case ZEND_AST_THROW:
			APPEND_NODE_1("throw");
		case ZEND_AST_GOTO:
			smart_str_appends(str, "goto ");
			zend_ast_export_name(str, ast->child[0], 0, indent);
			break;
		case ZEND_AST_BREAK:
			APPEND_NODE_1("break");
		case ZEND_AST_CONTINUE:
			APPEND_NODE_1("continue");

		/* 2 child nodes */
		case ZEND_AST_DIM:
			zend_ast_export_ex(str, ast->child[0], 260, indent);
			smart_str_appendc(str, '[');
			if (ast->child[1]) {
				zend_ast_export_ex(str, ast->child[1], 0, indent);
			}
			smart_str_appendc(str, ']');
			break;
		case ZEND_AST_PROP:
		case ZEND_AST_NULLSAFE_PROP:
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			smart_str_appends(str, ast->kind == ZEND_AST_NULLSAFE_PROP ? "?->" : "->");
			zend_ast_export_var(str, ast->child[1], indent);
			break;
		case ZEND_AST_STATIC_PROP:
			zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
			smart_str_appends(str, "::$");
			zend_ast_export_var(str, ast->child[1], indent);
			break;
		case ZEND_AST_CALL: {
			zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
			smart_str_appendc(str, '(');
			zend_ast_export_ex(str, ast->child[1], 0, indent);
			smart_str_appendc(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_or_expression(str, ast->child[0], 0, indent);
			smart_str_appends(str, "::");
			zend_ast_export_name(str, ast->child[1], 0, indent);
			break;
		case ZEND_AST_CLASS_NAME:
			if (ast->child[0] == NULL) {
				/* The const expr representation stores the fetch type instead. */
				switch (ast->attr) {
					case ZEND_FETCH_CLASS_SELF:
						smart_str_append(str, ZSTR_KNOWN(ZEND_STR_SELF));
						break;
					case ZEND_FETCH_CLASS_PARENT:
						smart_str_append(str, ZSTR_KNOWN(ZEND_STR_PARENT));
						break;
					default: ZEND_UNREACHABLE();
				}
			} else {
				zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
			}
			smart_str_appends(str, "::class");
			break;
		case ZEND_AST_ASSIGN:            BINARY_OP(" = ",   90, 91, 90);
		case ZEND_AST_ASSIGN_REF:        BINARY_OP(" =& ",  90, 91, 90);
		case ZEND_AST_ASSIGN_OP:
			switch (ast->attr) {
				case ZEND_ADD:    BINARY_OP(" += ",  90, 91, 90);
				case ZEND_SUB:    BINARY_OP(" -= ",  90, 91, 90);
				case ZEND_MUL:    BINARY_OP(" *= ",  90, 91, 90);
				case ZEND_DIV:    BINARY_OP(" /= ",  90, 91, 90);
				case ZEND_MOD:    BINARY_OP(" %= ",  90, 91, 90);
				case ZEND_SL:     BINARY_OP(" <<= ", 90, 91, 90);
				case ZEND_SR:     BINARY_OP(" >>= ", 90, 91, 90);
				case ZEND_CONCAT: BINARY_OP(" .= ",  90, 91, 90);
				case ZEND_BW_OR:  BINARY_OP(" |= ",  90, 91, 90);
				case ZEND_BW_AND: BINARY_OP(" &= ",  90, 91, 90);
				case ZEND_BW_XOR: BINARY_OP(" ^= ",  90, 91, 90);
				case ZEND_POW:    BINARY_OP(" **= ", 90, 91, 90);
				default: ZEND_UNREACHABLE();
			}
			break;
		case ZEND_AST_ASSIGN_COALESCE: BINARY_OP(" \?\?= ", 90, 91, 90);
		case ZEND_AST_BINARY_OP:
			switch (ast->attr) {
				case ZEND_ADD:                 BINARY_OP(" + ",   200, 200, 201);
				case ZEND_SUB:                 BINARY_OP(" - ",   200, 200, 201);
				case ZEND_MUL:                 BINARY_OP(" * ",   210, 210, 211);
				case ZEND_DIV:                 BINARY_OP(" / ",   210, 210, 211);
				case ZEND_MOD:                 BINARY_OP(" % ",   210, 210, 211);
				case ZEND_SL:                  BINARY_OP(" << ",  190, 190, 191);
				case ZEND_SR:                  BINARY_OP(" >> ",  190, 190, 191);
				case ZEND_CONCAT:              BINARY_OP(" . ",   185, 185, 186);
				case ZEND_BW_OR:               BINARY_OP(" | ",   140, 140, 141);
				case ZEND_BW_AND:              BINARY_OP(" & ",   160, 160, 161);
				case ZEND_BW_XOR:              BINARY_OP(" ^ ",   150, 150, 151);
				case ZEND_IS_IDENTICAL:        BINARY_OP(" === ", 170, 171, 171);
				case ZEND_IS_NOT_IDENTICAL:    BINARY_OP(" !== ", 170, 171, 171);
				case ZEND_IS_EQUAL:            BINARY_OP(" == ",  170, 171, 171);
				case ZEND_IS_NOT_EQUAL:        BINARY_OP(" != ",  170, 171, 171);
				case ZEND_IS_SMALLER:          BINARY_OP(" < ",   180, 181, 181);
				case ZEND_IS_SMALLER_OR_EQUAL: BINARY_OP(" <= ",  180, 181, 181);
				case ZEND_POW:                 BINARY_OP(" ** ",  250, 251, 250);
				case ZEND_BOOL_XOR:            BINARY_OP(" xor ",  40,  40,  41);
				case ZEND_SPACESHIP:           BINARY_OP(" <=> ", 180, 181, 181);
				default: ZEND_UNREACHABLE();
			}
			break;
		case ZEND_AST_GREATER:                 BINARY_OP(" > ",   180, 181, 181);
		case ZEND_AST_GREATER_EQUAL:           BINARY_OP(" >= ",  180, 181, 181);
		case ZEND_AST_AND:                     BINARY_OP(" && ",  130, 130, 131);
		case ZEND_AST_OR:                      BINARY_OP(" || ",  120, 120, 121);
		case ZEND_AST_PIPE:                    BINARY_OP(" |> ",  183, 183, 184);
		case ZEND_AST_ARRAY_ELEM:
			if (ast->child[1]) {
				zend_ast_export_ex(str, ast->child[1], 80, indent);
				smart_str_appends(str, " => ");
			}
			if (ast->attr)
				smart_str_appendc(str, '&');
			zend_ast_export_ex(str, ast->child[0], 80, indent);
			break;
		case ZEND_AST_NEW:
			smart_str_appends(str, "new ");
			if (ast->child[0]->kind == ZEND_AST_CLASS) {
				const zend_ast_decl *decl = (const zend_ast_decl *) ast->child[0];
				if (decl->child[3]) {
					zend_ast_export_attributes(str, decl->child[3], indent, false);
				}
				smart_str_appends(str, "class");
				if (!zend_ast_is_list(ast->child[1])
						|| zend_ast_get_list(ast->child[1])->children) {
					smart_str_appendc(str, '(');
					zend_ast_export_ex(str, ast->child[1], 0, indent);
					smart_str_appendc(str, ')');
				}
				zend_ast_export_class_no_header(str, decl, indent);
			} else {
				zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
				smart_str_appendc(str, '(');
				zend_ast_export_ex(str, ast->child[1], 0, indent);
				smart_str_appendc(str, ')');
			}
			break;
		case ZEND_AST_INSTANCEOF:
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			smart_str_appends(str, " instanceof ");
			zend_ast_export_ns_name_or_expression(str, ast->child[1], 0, indent);
			break;
		case ZEND_AST_YIELD:
			if (priority > 70) smart_str_appendc(str, '(');
			smart_str_appends(str, "yield ");
			if (ast->child[0]) {
				if (ast->child[1]) {
					zend_ast_export_ex(str, ast->child[1], 70, indent);
					smart_str_appends(str, " => ");
				}
				zend_ast_export_ex(str, ast->child[0], 70, indent);
			}
			if (priority > 70) smart_str_appendc(str, ')');
			break;
		case ZEND_AST_YIELD_FROM:
			PREFIX_OP("yield from ", 85, 86);
		case ZEND_AST_COALESCE: BINARY_OP(" ?? ", 110, 111, 110);
		case ZEND_AST_STATIC:
			smart_str_appends(str, "static $");
			zend_ast_export_name(str, ast->child[0], 0, indent);
			APPEND_DEFAULT_VALUE(1);
		case ZEND_AST_WHILE:
			smart_str_appends(str, "while (");
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			smart_str_appends(str, ") {\n");
			zend_ast_export_stmt(str, ast->child[1], indent + 1);
			zend_ast_export_indent(str, indent);
			smart_str_appendc(str, '}');
			break;
		case ZEND_AST_DO_WHILE:
			smart_str_appends(str, "do {\n");
			zend_ast_export_stmt(str, ast->child[0], indent + 1);
			zend_ast_export_indent(str, indent);
			smart_str_appends(str, "} while (");
			zend_ast_export_ex(str, ast->child[1], 0, indent);
			smart_str_appendc(str, ')');
			break;

		case ZEND_AST_IF_ELEM:
			if (ast->child[0]) {
				smart_str_appends(str, "if (");
				zend_ast_export_ex(str, ast->child[0], 0, indent);
				smart_str_appends(str, ") {\n");
				zend_ast_export_stmt(str, ast->child[1], indent + 1);
			} else {
				smart_str_appends(str, "else {\n");
				zend_ast_export_stmt(str, ast->child[1], indent + 1);
			}
			zend_ast_export_indent(str, indent);
			smart_str_appendc(str, '}');
			break;
		case ZEND_AST_SWITCH:
			smart_str_appends(str, "switch (");
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			smart_str_appends(str, ") {\n");
			zend_ast_export_ex(str, ast->child[1], 0, indent + 1);
			zend_ast_export_indent(str, indent);
			smart_str_appendc(str, '}');
			break;
		case ZEND_AST_SWITCH_CASE:
			zend_ast_export_indent(str, indent);
			if (ast->child[0]) {
				smart_str_appends(str, "case ");
				zend_ast_export_ex(str, ast->child[0], 0, indent);
				smart_str_appends(str, ":\n");
			} else {
				smart_str_appends(str, "default:\n");
			}
			zend_ast_export_stmt(str, ast->child[1], indent + 1);
			break;
		case ZEND_AST_MATCH:
			smart_str_appends(str, "match (");
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			smart_str_appends(str, ") {\n");
			zend_ast_export_ex(str, ast->child[1], 0, indent + 1);
			zend_ast_export_indent(str, indent);
			smart_str_appendc(str, '}');
			break;
		case ZEND_AST_MATCH_ARM:
			zend_ast_export_indent(str, indent);
			if (ast->child[0]) {
				zend_ast_export_list(str, zend_ast_get_list(ast->child[0]), true, 0, indent);
				smart_str_appends(str, " => ");
			} else {
				smart_str_appends(str, "default => ");
			}
			zend_ast_export_ex(str, ast->child[1], 0, 0);
			smart_str_appends(str, ",\n");
			break;
		case ZEND_AST_DECLARE:
			smart_str_appends(str, "declare(");
			ZEND_ASSERT(ast->child[0]->kind == ZEND_AST_CONST_DECL);
			zend_ast_export_list(str, zend_ast_get_list(ast->child[0]), true, 0, indent);
			smart_str_appendc(str, ')');
			if (ast->child[1]) {
				smart_str_appends(str, " {\n");
				zend_ast_export_stmt(str, ast->child[1], indent + 1);
				zend_ast_export_indent(str, indent);
				smart_str_appendc(str, '}');
			} else {
				smart_str_appendc(str, ';');
			}
			break;
		case ZEND_AST_PROP_ELEM:
			smart_str_appendc(str, '$');
			zend_ast_export_name(str, ast->child[0], 0, indent);

			zend_ast *default_value = ast->child[1];
			if (default_value) {
				smart_str_appends(str, " = ");
				zend_ast_export_ex(str, default_value, 0, indent + 1);
			}

			if (ast->child[3]) {
				zend_ast_export_hook_list(str, zend_ast_get_list(ast->child[3]), indent);
			}
			break;
		case ZEND_AST_CONST_ELEM:
			zend_ast_export_name(str, ast->child[0], 0, indent);
			APPEND_DEFAULT_VALUE(1);
		case ZEND_AST_USE_TRAIT:
			smart_str_appends(str, "use ");
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			if (ast->child[1]) {
				smart_str_appends(str, " {\n");
				zend_ast_export_ex(str, ast->child[1], 0, indent + 1);
				zend_ast_export_indent(str, indent);
				smart_str_appendc(str, '}');
			} else {
				smart_str_appendc(str, ';');
			}
			break;
		case ZEND_AST_TRAIT_PRECEDENCE:
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			smart_str_appends(str, " insteadof ");
			zend_ast_export_ex(str, ast->child[1], 0, indent);
			break;
		case ZEND_AST_TRAIT_METHOD_REFERENCE:
			if (ast->child[0]) {
				zend_ast_export_name(str, ast->child[0], 0, indent);
				smart_str_appends(str, "::");
			}
			zend_ast_export_name(str, ast->child[1], 0, indent);
			break;
		case ZEND_AST_NAMESPACE:
			smart_str_appends(str, "namespace");
			if (ast->child[0]) {
				smart_str_appendc(str, ' ');
				zend_ast_export_name(str, ast->child[0], 0, indent);
			}
			if (ast->child[1]) {
				smart_str_appends(str, " {\n");
				zend_ast_export_stmt(str, ast->child[1], indent + 1);
				zend_ast_export_indent(str, indent);
				smart_str_appends(str, "}\n");
			} else {
				smart_str_appendc(str, ';');
			}
			break;
		case ZEND_AST_USE_ELEM:
		case ZEND_AST_TRAIT_ALIAS:
			zend_ast_export_name(str, ast->child[0], 0, indent);
			if (ast->attr & ZEND_ACC_PUBLIC) {
				smart_str_appends(str, " as public");
			} else if (ast->attr & ZEND_ACC_PROTECTED) {
				smart_str_appends(str, " as protected");
			} else if (ast->attr & ZEND_ACC_PRIVATE) {
				smart_str_appends(str, " as private");
			} else if (ast->child[1]) {
				smart_str_appends(str, " as");
			}
			if (ast->child[1]) {
				smart_str_appendc(str, ' ');
				zend_ast_export_name(str, ast->child[1], 0, indent);
			}
			break;
		case ZEND_AST_NAMED_ARG:
			smart_str_append(str, zend_ast_get_str(ast->child[0]));
			smart_str_appends(str, ": ");
			ast = ast->child[1];
			goto tail_call;

		/* 3 child nodes */
		case ZEND_AST_METHOD_CALL:
		case ZEND_AST_NULLSAFE_METHOD_CALL:
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			smart_str_appends(str, ast->kind == ZEND_AST_NULLSAFE_METHOD_CALL ? "?->" : "->");
			zend_ast_export_var(str, ast->child[1], indent);
			smart_str_appendc(str, '(');
			zend_ast_export_ex(str, ast->child[2], 0, indent);
			smart_str_appendc(str, ')');
			break;
		case ZEND_AST_STATIC_CALL:
			if (zend_ast_is_parent_hook_call(ast)) {
				zend_ast_export_ns_name(str, ast->child[0], 0, indent);
			} else {
				zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
			}

			smart_str_appends(str, "::");
			zend_ast_export_var(str, ast->child[1], indent);
			smart_str_appendc(str, '(');
			zend_ast_export_ex(str, ast->child[2], 0, indent);
			smart_str_appendc(str, ')');
			break;
		case ZEND_AST_CONDITIONAL:
			if (priority > 100) smart_str_appendc(str, '(');
			zend_ast_export_ex(str, ast->child[0], 100, indent);
			if (ast->child[1]) {
				smart_str_appends(str, " ? ");
				zend_ast_export_ex(str, ast->child[1], 101, indent);
				smart_str_appends(str, " : ");
			} else {
				smart_str_appends(str, " ?: ");
			}
			zend_ast_export_ex(str, ast->child[2], 101, indent);
			if (priority > 100) smart_str_appendc(str, ')');
			break;

		case ZEND_AST_TRY:
			smart_str_appends(str, "try {\n");
			zend_ast_export_stmt(str, ast->child[0], indent + 1);
			zend_ast_export_indent(str, indent);
			zend_ast_export_ex(str, ast->child[1], 0, indent);
			if (ast->child[2]) {
				smart_str_appends(str, "} finally {\n");
				zend_ast_export_stmt(str, ast->child[2], indent + 1);
				zend_ast_export_indent(str, indent);
			}
			smart_str_appendc(str, '}');
			break;
		case ZEND_AST_CATCH:
			smart_str_appends(str, "} catch (");
			zend_ast_export_catch_name_list(str, zend_ast_get_list(ast->child[0]), indent);
			if (ast->child[1]) {
				smart_str_appends(str, " $");
				zend_ast_export_var(str, ast->child[1], indent);
			}
			smart_str_appends(str, ") {\n");
			zend_ast_export_stmt(str, ast->child[2], indent + 1);
			zend_ast_export_indent(str, indent);
			break;
		case ZEND_AST_PARAM:
			if (ast->child[3]) {
				zend_ast_export_attributes(str, ast->child[3], indent, false);
			}
			zend_ast_export_visibility(str, ast->attr, ZEND_MODIFIER_TARGET_CPP);
			if (ast->attr & ZEND_ACC_FINAL) {
				smart_str_appends(str, "final ");
			}
			if (ast->child[0]) {
				zend_ast_export_type(str, ast->child[0], indent);
				smart_str_appendc(str, ' ');
			}
			if (ast->attr & ZEND_PARAM_REF) {
				smart_str_appendc(str, '&');
			}
			if (ast->attr & ZEND_PARAM_VARIADIC) {
				smart_str_appends(str, "...");
			}
			smart_str_appendc(str, '$');
			zend_ast_export_name(str, ast->child[1], 0, indent);
			if (ast->child[2]) {
				smart_str_appends(str, " = ");
				zend_ast_export_ex(str, ast->child[2], 0, indent);
			}
			if (ast->child[5]) {
				zend_ast_export_hook_list(str, zend_ast_get_list(ast->child[5]), indent);
			}
			break;
		case ZEND_AST_ENUM_CASE:
			if (ast->child[3]) {
				zend_ast_export_attributes(str, ast->child[3], indent, true);
			}
			smart_str_appends(str, "case ");
			zend_ast_export_name(str, ast->child[0], 0, indent);
			if (ast->child[1]) {
				smart_str_appends(str, " = ");
				zend_ast_export_ex(str, ast->child[1], 0, indent);
			}
			break;

		/* 4 child nodes */
		case ZEND_AST_FOR:
			smart_str_appends(str, "for (");
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			smart_str_appendc(str, ';');
			if (ast->child[1]) {
				smart_str_appendc(str, ' ');
				zend_ast_export_ex(str, ast->child[1], 0, indent);
			}
			smart_str_appendc(str, ';');
			if (ast->child[2]) {
				smart_str_appendc(str, ' ');
				zend_ast_export_ex(str, ast->child[2], 0, indent);
			}
			smart_str_appends(str, ") {\n");
			zend_ast_export_stmt(str, ast->child[3], indent + 1);
			zend_ast_export_indent(str, indent);
			smart_str_appendc(str, '}');
			break;
		case ZEND_AST_FOREACH:
			smart_str_appends(str, "foreach (");
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			smart_str_appends(str, " as ");
			if (ast->child[2]) {
				zend_ast_export_ex(str, ast->child[2], 0, indent);
				smart_str_appends(str, " => ");
			}
			zend_ast_export_ex(str, ast->child[1], 0, indent);
			smart_str_appends(str, ") {\n");
			zend_ast_export_stmt(str, ast->child[3], indent + 1);
			zend_ast_export_indent(str, indent);
			smart_str_appendc(str, '}');
			break;
		default: ZEND_UNREACHABLE();
	}
	return;

binary_op:
	if (priority > p) smart_str_appendc(str, '(');
	zend_ast_export_ex(str, ast->child[0], pl, indent);
	smart_str_appends(str, op);
	zend_ast_export_ex(str, ast->child[1], pr, indent);
	if (priority > p) smart_str_appendc(str, ')');
	return;

prefix_op:
	if (priority > p) smart_str_appendc(str, '(');
	smart_str_appends(str, op);
	zend_ast_export_ex(str, ast->child[0], pl, indent);
	if (priority > p) smart_str_appendc(str, ')');
	return;

postfix_op:
	if (priority > p) smart_str_appendc(str, '(');
	zend_ast_export_ex(str, ast->child[0], pl, indent);
	smart_str_appends(str, op);
	if (priority > p) smart_str_appendc(str, ')');
	return;

func_op:
	smart_str_appends(str, op);
	smart_str_appendc(str, '(');
	zend_ast_export_ex(str, ast->child[0], 0, indent);
	smart_str_appendc(str, ')');
	return;

append_node_1:
	smart_str_appends(str, op);
	if (ast->child[0]) {
		smart_str_appendc(str, ' ');
		ast = ast->child[0];
		goto tail_call;
	}
	return;

append_str:
	smart_str_appends(str, op);
	return;

append_default_value:
	if (ast->child[p]) {
		smart_str_appends(str, " = ");
		ast = ast->child[p];
		goto tail_call;
	}
	return;
}

ZEND_API ZEND_COLD zend_string *zend_ast_export(const char *prefix, zend_ast *ast, const char *suffix)
{
	smart_str str = {0};

	smart_str_appends(&str, prefix);
	zend_ast_export_ex(&str, ast, 0, 0);
	smart_str_appends(&str, suffix);
	smart_str_0(&str);
	return str.s;
}

zend_ast * ZEND_FASTCALL zend_ast_with_attributes(zend_ast *ast, zend_ast *attr)
{
	ZEND_ASSERT(attr->kind == ZEND_AST_ATTRIBUTE_LIST);

	switch (ast->kind) {
	case ZEND_AST_FUNC_DECL:
	case ZEND_AST_CLOSURE:
	case ZEND_AST_METHOD:
	case ZEND_AST_ARROW_FUNC:
	case ZEND_AST_PROPERTY_HOOK:
		((zend_ast_decl *) ast)->child[4] = attr;
		break;
	case ZEND_AST_CLASS:
		((zend_ast_decl *) ast)->child[3] = attr;
		break;
	case ZEND_AST_PROP_GROUP:
		ast->child[2] = attr;
		break;
	case ZEND_AST_PARAM:
	case ZEND_AST_ENUM_CASE:
		ast->child[3] = attr;
		break;
	case ZEND_AST_CLASS_CONST_GROUP:
		ast->child[1] = attr;
		break;
	case ZEND_AST_CONST_DECL:
		/* Since constants are already stored in a list, just add the attributes
		 * to that list instead of storing them elsewhere;
		 * zend_compile_const_decl() checks the kind of the list elements. */
		ast = zend_ast_list_add(ast, attr);
		break;
	default: ZEND_UNREACHABLE();
	}

	return ast;
}

zend_ast * ZEND_FASTCALL zend_ast_call_get_args(zend_ast *ast)
{
	if (ast->kind == ZEND_AST_CALL) {
		return ast->child[1];
	} else if (ast->kind == ZEND_AST_STATIC_CALL || ast->kind == ZEND_AST_METHOD_CALL) {
		return ast->child[2];
	}

	ZEND_UNREACHABLE();
	return NULL;
}

bool zend_ast_is_parent_hook_call(const zend_ast *ast)
{
	ZEND_ASSERT(ast->kind == ZEND_AST_STATIC_CALL);

	const zend_ast *class_ast = ast->child[0];
	zend_ast *method_ast = ast->child[1];

	return class_ast->kind == ZEND_AST_STATIC_PROP
		&& !(class_ast->attr & ZEND_PARENTHESIZED_STATIC_PROP)
		&& class_ast->child[0]->kind == ZEND_AST_ZVAL
		&& Z_TYPE_P(zend_ast_get_zval(class_ast->child[0])) == IS_STRING
		&& zend_get_class_fetch_type(zend_ast_get_str(class_ast->child[0])) == ZEND_FETCH_CLASS_PARENT
		&& class_ast->child[1]->kind == ZEND_AST_ZVAL
		&& method_ast->kind == ZEND_AST_ZVAL
		&& Z_TYPE_P(zend_ast_get_zval(method_ast)) == IS_STRING
		&& (zend_string_equals_literal_ci(zend_ast_get_str(method_ast), "get")
			|| zend_string_equals_literal_ci(zend_ast_get_str(method_ast), "set"));
}
