From 576ce94903923e830d1bb0c064af6667ce68d43c Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Tue, 2 Dec 2025 23:02:09 +0100 Subject: [PATCH 1/2] type aliases Signed-off-by: Robert Landers --- Zend/tests/type_aliases/basic_type_alias.phpt | 55 ++++++ Zend/tests/type_aliases/include_types.phpt | 42 +++++ .../include_types_invalid_file.phpt | 8 + Zend/zend_ast.h | 2 + Zend/zend_compile.c | 174 ++++++++++++++++++ Zend/zend_compile.h | 1 + Zend/zend_language_parser.y | 8 +- Zend/zend_language_scanner.l | 8 + ext/tokenizer/tokenizer_data.c | 2 + ext/tokenizer/tokenizer_data.stub.php | 10 + ext/tokenizer/tokenizer_data_arginfo.h | 4 +- 11 files changed, 312 insertions(+), 2 deletions(-) create mode 100644 Zend/tests/type_aliases/basic_type_alias.phpt create mode 100644 Zend/tests/type_aliases/include_types.phpt create mode 100644 Zend/tests/type_aliases/include_types_invalid_file.phpt diff --git a/Zend/tests/type_aliases/basic_type_alias.phpt b/Zend/tests/type_aliases/basic_type_alias.phpt new file mode 100644 index 0000000000000..b34391787617b --- /dev/null +++ b/Zend/tests/type_aliases/basic_type_alias.phpt @@ -0,0 +1,55 @@ +--TEST-- +Basic type alias functionality +--FILE-- + +--EXPECT-- +int(3) +float(4) +float(3.5) +string(12) "Hello, World" +int(42) +NULL +int(42) +float(3.14) +string(5) "hello" +Done diff --git a/Zend/tests/type_aliases/include_types.phpt b/Zend/tests/type_aliases/include_types.phpt new file mode 100644 index 0000000000000..a8d6e3cced951 --- /dev/null +++ b/Zend/tests/type_aliases/include_types.phpt @@ -0,0 +1,42 @@ +--TEST-- +include types functionality +--FILE-- + +--EXPECT-- +int(3) +float(4) +int(42) +float(3.14) +string(5) "hello" +string(12) "Hello, World" +NULL +Done diff --git a/Zend/tests/type_aliases/include_types_invalid_file.phpt b/Zend/tests/type_aliases/include_types_invalid_file.phpt new file mode 100644 index 0000000000000..b3a9dc11ad5f0 --- /dev/null +++ b/Zend/tests/type_aliases/include_types_invalid_file.phpt @@ -0,0 +1,8 @@ +--TEST-- +include types with invalid file (contains non-type-alias code) +--FILE-- + +--EXPECTF-- +Fatal error: include types: Types file 'invalid_types.php' must only contain type aliases (use type ... as ...;) in %s on line %d diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index fb48b187252b3..2b68de1cf5ee4 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -105,6 +105,7 @@ enum _zend_ast_kind { ZEND_AST_LABEL, ZEND_AST_REF, ZEND_AST_HALT_COMPILER, + ZEND_AST_INCLUDE_TYPES, ZEND_AST_ECHO, ZEND_AST_THROW, ZEND_AST_GOTO, @@ -146,6 +147,7 @@ enum _zend_ast_kind { ZEND_AST_METHOD_REFERENCE, ZEND_AST_NAMESPACE, ZEND_AST_USE_ELEM, + ZEND_AST_TYPE_ALIAS, ZEND_AST_TRAIT_ALIAS, ZEND_AST_GROUP_USE, ZEND_AST_ATTRIBUTE, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 50ba8029873ad..53201c37b756c 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -384,6 +384,12 @@ static void zend_reset_import_tables(void) /* {{{ */ FC(imports_const) = NULL; } + if (FC(imports_type)) { + zend_hash_destroy(FC(imports_type)); + efree(FC(imports_type)); + FC(imports_type) = NULL; + } + zend_hash_clean(&FC(seen_symbols)); } /* }}} */ @@ -404,6 +410,7 @@ void zend_file_context_begin(zend_file_context *prev_context) /* {{{ */ FC(imports) = NULL; FC(imports_function) = NULL; FC(imports_const) = NULL; + FC(imports_type) = NULL; FC(current_namespace) = NULL; FC(in_namespace) = 0; FC(has_bracketed_namespaces) = 0; @@ -1225,6 +1232,14 @@ static void str_dtor(zval *zv) /* {{{ */ { } /* }}} */ +static void ast_ref_dtor(zval *zv) /* {{{ */ { + /* The AST was created via zend_ast_copy, so we need to destroy the ref */ + zend_ast *ast = Z_PTR_P(zv); + zend_ast_ref *ref = (zend_ast_ref *)((char *)ast - sizeof(zend_ast_ref)); + zend_ast_ref_destroy(ref); +} +/* }}} */ + static uint32_t zend_add_try_element(uint32_t try_op) /* {{{ */ { zend_op_array *op_array = CG(active_op_array); @@ -7170,6 +7185,9 @@ ZEND_API void zend_set_function_arg_flags(zend_function *func) /* {{{ */ } /* }}} */ +/* Forward declaration for type alias support */ +static zend_type zend_compile_typename(zend_ast *ast); + static zend_type zend_compile_single_typename(zend_ast *ast) { ZEND_ASSERT(!(ast->attr & ZEND_TYPE_NULLABLE)); @@ -7201,6 +7219,17 @@ static zend_type zend_compile_single_typename(zend_ast *ast) return (zend_type) ZEND_TYPE_INIT_CODE(type_code, 0, 0); } else { + /* Check for type alias (only for unqualified names) */ + if (ast->attr == ZEND_NAME_NOT_FQ && FC(imports_type)) { + zend_string *lookup_name = zend_string_tolower(type_name); + zend_ast *alias_ast = zend_hash_find_ptr(FC(imports_type), lookup_name); + zend_string_release_ex(lookup_name, 0); + if (alias_ast) { + /* Recursively compile the aliased type */ + return zend_compile_typename(alias_ast); + } + } + const char *correct_name; uint32_t fetch_type = zend_get_class_fetch_type_ast(ast); zend_string *class_name = type_name; @@ -9745,6 +9774,145 @@ static void zend_compile_use(zend_ast *ast) /* {{{ */ } /* }}} */ +static void zend_compile_use_type_alias(zend_ast *ast) /* {{{ */ +{ + zend_ast *type_ast = ast->child[0]; + zend_ast *alias_ast = ast->child[1]; + zend_string *alias_name = zend_ast_get_str(alias_ast); + zend_string *lookup_name = zend_string_tolower(alias_name); + + /* Initialize imports_type hash table if needed */ + if (!FC(imports_type)) { + FC(imports_type) = emalloc(sizeof(HashTable)); + zend_hash_init(FC(imports_type), 8, NULL, ast_ref_dtor, 0); + } + + /* Check if alias is already in use */ + if (zend_hash_exists(FC(imports_type), lookup_name)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use type alias %s because the name is already in use", + ZSTR_VAL(alias_name)); + } + + /* Copy the type AST to heap so it can be properly freed */ + zend_ast_ref *ast_ref = zend_ast_copy(type_ast); + zend_ast *copied_type_ast = GC_AST(ast_ref); + + /* Store the copied type AST in the hash table */ + zend_hash_add_ptr(FC(imports_type), lookup_name, copied_type_ast); + + zend_string_release_ex(lookup_name, 0); +} +/* }}} */ + +static void zend_compile_include_types(zend_ast *ast) /* {{{ */ +{ + zend_ast *filename_ast = ast->child[0]; + zval *filename_zv = zend_ast_get_zval(filename_ast); + zend_string *filename = Z_STR_P(filename_zv); + + /* Resolve the path relative to current file */ + zend_string *resolved_path = zend_resolve_path(filename); + if (!resolved_path) { + zend_error_noreturn(E_COMPILE_ERROR, + "include types: Failed to resolve path '%s'", ZSTR_VAL(filename)); + } + + /* Open and read the file */ + zend_file_handle file_handle; + zend_stream_init_filename_ex(&file_handle, resolved_path); + if (zend_stream_open(&file_handle) == FAILURE) { + zend_string_release_ex(resolved_path, 0); + zend_error_noreturn(E_COMPILE_ERROR, + "include types: Failed to open '%s'", ZSTR_VAL(filename)); + } + + /* Get file contents */ + char *buf; + size_t len; + if (zend_stream_fixup(&file_handle, &buf, &len) == FAILURE) { + zend_destroy_file_handle(&file_handle); + zend_string_release_ex(resolved_path, 0); + zend_error_noreturn(E_COMPILE_ERROR, + "include types: Failed to read '%s'", ZSTR_VAL(filename)); + } + + /* Compile to AST */ + zend_string *code = zend_string_init(buf, len, 0); + zend_arena *ast_arena = NULL; + zend_ast *types_ast = zend_compile_string_to_ast(code, &ast_arena, resolved_path); + zend_string_release_ex(code, 0); + zend_destroy_file_handle(&file_handle); + + if (!types_ast) { + zend_string_release_ex(resolved_path, 0); + zend_error_noreturn(E_COMPILE_ERROR, + "include types: Failed to parse '%s'", ZSTR_VAL(filename)); + } + + /* Validate and process the AST - must only contain type aliases */ + if (types_ast->kind != ZEND_AST_STMT_LIST) { + zend_ast_destroy(types_ast); + zend_arena_destroy(ast_arena); + zend_string_release_ex(resolved_path, 0); + zend_error_noreturn(E_COMPILE_ERROR, + "include types: Invalid types file '%s'", ZSTR_VAL(filename)); + } + + zend_ast_list *list = zend_ast_get_list(types_ast); + for (uint32_t i = 0; i < list->children; i++) { + zend_ast *stmt = list->child[i]; + if (stmt == NULL) { + continue; + } + if (stmt->kind != ZEND_AST_TYPE_ALIAS) { + zend_ast_destroy(types_ast); + zend_arena_destroy(ast_arena); + zend_string_release_ex(resolved_path, 0); + zend_error_noreturn(E_COMPILE_ERROR, + "include types: Types file '%s' must only contain type aliases (use type ... as ...;)", + ZSTR_VAL(filename)); + } + + /* Process the type alias - copy AST to current arena */ + zend_ast *type_ast = stmt->child[0]; + zend_ast *alias_ast = stmt->child[1]; + zend_string *alias_name = zend_ast_get_str(alias_ast); + zend_string *lookup_name = zend_string_tolower(alias_name); + + /* Initialize imports_type hash table if needed */ + if (!FC(imports_type)) { + FC(imports_type) = emalloc(sizeof(HashTable)); + zend_hash_init(FC(imports_type), 8, NULL, ast_ref_dtor, 0); + } + + /* Check if alias is already in use */ + if (zend_hash_exists(FC(imports_type), lookup_name)) { + zend_string_release_ex(lookup_name, 0); + zend_ast_destroy(types_ast); + zend_arena_destroy(ast_arena); + zend_string_release_ex(resolved_path, 0); + zend_error_noreturn(E_COMPILE_ERROR, + "include types: Cannot use type alias %s because the name is already in use", + ZSTR_VAL(alias_name)); + } + + /* Copy the type AST to heap so it survives arena destruction */ + zend_ast_ref *ast_ref = zend_ast_copy(type_ast); + zend_ast *copied_type_ast = GC_AST(ast_ref); + + /* Store the copied type AST (the ref keeps it alive) */ + zend_hash_add_ptr(FC(imports_type), lookup_name, copied_type_ast); + zend_string_release_ex(lookup_name, 0); + } + + /* Clean up the types file AST - but the copied ASTs remain in current arena */ + zend_ast_destroy(types_ast); + zend_arena_destroy(ast_arena); + zend_string_release_ex(resolved_path, 0); +} +/* }}} */ + static void zend_compile_group_use(const zend_ast *ast) /* {{{ */ { uint32_t i; @@ -11873,6 +12041,12 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */ case ZEND_AST_USE: zend_compile_use(ast); break; + case ZEND_AST_TYPE_ALIAS: + zend_compile_use_type_alias(ast); + break; + case ZEND_AST_INCLUDE_TYPES: + zend_compile_include_types(ast); + break; case ZEND_AST_CONST_DECL: zend_compile_const_decl(ast); break; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 86fab4b57ded6..70e030062c729 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -118,6 +118,7 @@ typedef struct _zend_file_context { HashTable *imports; HashTable *imports_function; HashTable *imports_const; + HashTable *imports_type; HashTable seen_symbols; } zend_file_context; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index e4d61006fe12f..8e7f0a0544fab 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -141,6 +141,8 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_FUNCTION "'function'" %token T_FN "'fn'" %token T_CONST "'const'" +%token T_TYPE "'type'" +%token T_TYPES "'types'" %token T_RETURN "'return'" %token T_TRY "'try'" %token T_CATCH "'catch'" @@ -309,7 +311,7 @@ reserved_non_modifiers: | T_INSTANCEOF | T_NEW | T_CLONE | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE | T_ENDWHILE | T_FOR | T_ENDFOR | T_FOREACH | T_ENDFOREACH | T_DECLARE | T_ENDDECLARE | T_AS | T_TRY | T_CATCH | T_FINALLY | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO - | T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT | T_BREAK + | T_FUNCTION | T_CONST | T_TYPE | T_TYPES | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT | T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS | T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_FN | T_MATCH | T_ENUM | T_PROPERTY_C @@ -421,6 +423,10 @@ top_statement: | T_USE use_type group_use_declaration ';' { $$ = $3; $$->attr = $2; } | T_USE use_declarations ';' { $$ = $2; $$->attr = ZEND_SYMBOL_CLASS; } | T_USE use_type use_declarations ';' { $$ = $3; $$->attr = $2; } + | T_USE T_TYPE type_expr T_AS T_STRING ';' + { $$ = zend_ast_create(ZEND_AST_TYPE_ALIAS, $3, $5); } + | T_INCLUDE T_TYPES T_CONSTANT_ENCAPSED_STRING ';' + { $$ = zend_ast_create(ZEND_AST_INCLUDE_TYPES, $3); } ; use_type: diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 1e26ddbd99199..5f28c6c023697 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1416,6 +1416,14 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ RETURN_TOKEN_WITH_IDENT(T_CONST); } +"type" { + RETURN_TOKEN_WITH_IDENT(T_TYPE); +} + +"types" { + RETURN_TOKEN_WITH_IDENT(T_TYPES); +} + "return" { RETURN_TOKEN_WITH_IDENT(T_RETURN); } diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index 0900c51d3d95a..d89b83b540133 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -78,6 +78,8 @@ char *get_token_type_name(int token_type) case T_FUNCTION: return "T_FUNCTION"; case T_FN: return "T_FN"; case T_CONST: return "T_CONST"; + case T_TYPE: return "T_TYPE"; + case T_TYPES: return "T_TYPES"; case T_RETURN: return "T_RETURN"; case T_TRY: return "T_TRY"; case T_CATCH: return "T_CATCH"; diff --git a/ext/tokenizer/tokenizer_data.stub.php b/ext/tokenizer/tokenizer_data.stub.php index 57c8edad8acb6..a110e89f09215 100644 --- a/ext/tokenizer/tokenizer_data.stub.php +++ b/ext/tokenizer/tokenizer_data.stub.php @@ -267,6 +267,16 @@ * @cvalue T_CONST */ const T_CONST = UNKNOWN; +/** + * @var int + * @cvalue T_TYPE + */ +const T_TYPE = UNKNOWN; +/** + * @var int + * @cvalue T_TYPES + */ +const T_TYPES = UNKNOWN; /** * @var int * @cvalue T_RETURN diff --git a/ext/tokenizer/tokenizer_data_arginfo.h b/ext/tokenizer/tokenizer_data_arginfo.h index 3a3cdaa468133..be3c06060c82a 100644 --- a/ext/tokenizer/tokenizer_data_arginfo.h +++ b/ext/tokenizer/tokenizer_data_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: c5235344b7c651d27c2c33c90696a418a9c96837 */ + * Stub hash: 888a330dea1ff27a581303d959d24be0bb1177b8 */ static void register_tokenizer_data_symbols(int module_number) { @@ -56,6 +56,8 @@ static void register_tokenizer_data_symbols(int module_number) REGISTER_LONG_CONSTANT("T_FUNCTION", T_FUNCTION, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_FN", T_FN, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_CONST", T_CONST, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_TYPE", T_TYPE, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_TYPES", T_TYPES, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_RETURN", T_RETURN, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_TRY", T_TRY, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_CATCH", T_CATCH, CONST_PERSISTENT); From 0aacee703914ef0b2b7e455c6256a3ce998eb942 Mon Sep 17 00:00:00 2001 From: Robert Landers Date: Wed, 3 Dec 2025 23:12:26 +0100 Subject: [PATCH 2/2] handle some edge cases Signed-off-by: Robert Landers --- .../type_aliases/error_duplicate_alias.phpt | 9 +++ .../error_reserved_type_name_int.phpt | 8 +++ .../error_reserved_type_name_mixed.phpt | 8 +++ .../error_reserved_type_name_string.phpt | 8 +++ .../error_reserved_type_name_void.phpt | 8 +++ .../type_aliases/error_self_referential.phpt | 8 +++ .../error_self_referential_dnf.phpt | 8 +++ .../error_self_referential_union.phpt | 8 +++ Zend/zend_compile.c | 62 ++++++++++++++++++- 9 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/type_aliases/error_duplicate_alias.phpt create mode 100644 Zend/tests/type_aliases/error_reserved_type_name_int.phpt create mode 100644 Zend/tests/type_aliases/error_reserved_type_name_mixed.phpt create mode 100644 Zend/tests/type_aliases/error_reserved_type_name_string.phpt create mode 100644 Zend/tests/type_aliases/error_reserved_type_name_void.phpt create mode 100644 Zend/tests/type_aliases/error_self_referential.phpt create mode 100644 Zend/tests/type_aliases/error_self_referential_dnf.phpt create mode 100644 Zend/tests/type_aliases/error_self_referential_union.phpt diff --git a/Zend/tests/type_aliases/error_duplicate_alias.phpt b/Zend/tests/type_aliases/error_duplicate_alias.phpt new file mode 100644 index 0000000000000..65310650d424e --- /dev/null +++ b/Zend/tests/type_aliases/error_duplicate_alias.phpt @@ -0,0 +1,9 @@ +--TEST-- +Type alias name cannot be used twice +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use type alias 'Number' because the name is already in use in %s on line %d diff --git a/Zend/tests/type_aliases/error_reserved_type_name_int.phpt b/Zend/tests/type_aliases/error_reserved_type_name_int.phpt new file mode 100644 index 0000000000000..34c8e732bae7a --- /dev/null +++ b/Zend/tests/type_aliases/error_reserved_type_name_int.phpt @@ -0,0 +1,8 @@ +--TEST-- +Type alias cannot use reserved type name (int) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use 'int' as type alias name as it is reserved in %s on line %d diff --git a/Zend/tests/type_aliases/error_reserved_type_name_mixed.phpt b/Zend/tests/type_aliases/error_reserved_type_name_mixed.phpt new file mode 100644 index 0000000000000..38d0f23ccee2f --- /dev/null +++ b/Zend/tests/type_aliases/error_reserved_type_name_mixed.phpt @@ -0,0 +1,8 @@ +--TEST-- +Type alias cannot use reserved type name (mixed) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use 'mixed' as type alias name as it is reserved in %s on line %d diff --git a/Zend/tests/type_aliases/error_reserved_type_name_string.phpt b/Zend/tests/type_aliases/error_reserved_type_name_string.phpt new file mode 100644 index 0000000000000..dd64abb575160 --- /dev/null +++ b/Zend/tests/type_aliases/error_reserved_type_name_string.phpt @@ -0,0 +1,8 @@ +--TEST-- +Type alias cannot use reserved type name (string) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use 'string' as type alias name as it is reserved in %s on line %d diff --git a/Zend/tests/type_aliases/error_reserved_type_name_void.phpt b/Zend/tests/type_aliases/error_reserved_type_name_void.phpt new file mode 100644 index 0000000000000..cd81440447631 --- /dev/null +++ b/Zend/tests/type_aliases/error_reserved_type_name_void.phpt @@ -0,0 +1,8 @@ +--TEST-- +Type alias cannot use reserved type name (void) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use 'void' as type alias name as it is reserved in %s on line %d diff --git a/Zend/tests/type_aliases/error_self_referential.phpt b/Zend/tests/type_aliases/error_self_referential.phpt new file mode 100644 index 0000000000000..12433e3ea6cb0 --- /dev/null +++ b/Zend/tests/type_aliases/error_self_referential.phpt @@ -0,0 +1,8 @@ +--TEST-- +Type alias cannot reference itself +--FILE-- + +--EXPECTF-- +Fatal error: Type alias 'Foo' cannot reference itself in %s on line %d diff --git a/Zend/tests/type_aliases/error_self_referential_dnf.phpt b/Zend/tests/type_aliases/error_self_referential_dnf.phpt new file mode 100644 index 0000000000000..e109b82c05c1f --- /dev/null +++ b/Zend/tests/type_aliases/error_self_referential_dnf.phpt @@ -0,0 +1,8 @@ +--TEST-- +Type alias cannot reference itself in DNF type +--FILE-- + +--EXPECTF-- +Fatal error: Type alias 'Foo' cannot reference itself in %s on line %d diff --git a/Zend/tests/type_aliases/error_self_referential_union.phpt b/Zend/tests/type_aliases/error_self_referential_union.phpt new file mode 100644 index 0000000000000..f989ff32ff6b8 --- /dev/null +++ b/Zend/tests/type_aliases/error_self_referential_union.phpt @@ -0,0 +1,8 @@ +--TEST-- +Type alias cannot reference itself in union type +--FILE-- + +--EXPECTF-- +Fatal error: Type alias 'MyType' cannot reference itself in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 53201c37b756c..4744b6bd1e23b 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -9774,6 +9774,49 @@ static void zend_compile_use(zend_ast *ast) /* {{{ */ } /* }}} */ +/* Helper function to check if an alias name appears in a type AST (self-referential check) */ +static bool zend_type_ast_contains_name(zend_ast *ast, zend_string *name) /* {{{ */ +{ + if (ast == NULL) { + return false; + } + + switch (ast->kind) { + case ZEND_AST_TYPE: + /* Built-in types like int, string - never match alias names */ + return false; + + case ZEND_AST_CLASS_NAME: + case ZEND_AST_ZVAL: { + /* Check if this name matches the alias name */ + zend_string *type_name = zend_ast_get_str(ast); + if (zend_string_equals_ci(type_name, name)) { + return true; + } + return false; + } + + case ZEND_AST_TYPE_UNION: + case ZEND_AST_TYPE_INTERSECTION: { + /* Recursively check children */ + zend_ast_list *list = zend_ast_get_list(ast); + for (uint32_t i = 0; i < list->children; i++) { + if (zend_type_ast_contains_name(list->child[i], name)) { + return true; + } + } + return false; + } + + default: + if (ast->kind < (1 << ZEND_AST_NUM_CHILDREN_SHIFT) && ast->child[0]) { + return zend_type_ast_contains_name(ast->child[0], name); + } + return false; + } +} +/* }}} */ + static void zend_compile_use_type_alias(zend_ast *ast) /* {{{ */ { zend_ast *type_ast = ast->child[0]; @@ -9781,6 +9824,22 @@ static void zend_compile_use_type_alias(zend_ast *ast) /* {{{ */ zend_string *alias_name = zend_ast_get_str(alias_ast); zend_string *lookup_name = zend_string_tolower(alias_name); + /* Check if alias name is a reserved type name */ + if (zend_is_reserved_class_name(alias_name)) { + zend_string_release_ex(lookup_name, 0); + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use '%s' as type alias name as it is reserved", + ZSTR_VAL(alias_name)); + } + + /* Check for self-referential alias */ + if (zend_type_ast_contains_name(type_ast, alias_name)) { + zend_string_release_ex(lookup_name, 0); + zend_error_noreturn(E_COMPILE_ERROR, + "Type alias '%s' cannot reference itself", + ZSTR_VAL(alias_name)); + } + /* Initialize imports_type hash table if needed */ if (!FC(imports_type)) { FC(imports_type) = emalloc(sizeof(HashTable)); @@ -9789,8 +9848,9 @@ static void zend_compile_use_type_alias(zend_ast *ast) /* {{{ */ /* Check if alias is already in use */ if (zend_hash_exists(FC(imports_type), lookup_name)) { + zend_string_release_ex(lookup_name, 0); zend_error_noreturn(E_COMPILE_ERROR, - "Cannot use type alias %s because the name is already in use", + "Cannot use type alias '%s' because the name is already in use", ZSTR_VAL(alias_name)); }