From e6fdff148a633248c3282fe23931c25e251a5a7d Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 5 Apr 2025 17:07:11 +0100 Subject: [PATCH 01/41] Zend: Use pointer to zend_type for variance checks --- Zend/zend_inheritance.c | 58 +++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index d27cca5b76187..f715e7743d363 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -428,16 +428,16 @@ static void track_class_dependency(zend_class_entry *ce, zend_string *class_name /* Check whether any type in the fe_type intersection type is a subtype of the proto class. */ static inheritance_status zend_is_intersection_subtype_of_class( - zend_class_entry *fe_scope, const zend_type fe_type, + zend_class_entry *fe_scope, const zend_type *fe_type_ptr, zend_class_entry *proto_scope, zend_string *proto_class_name, zend_class_entry *proto_ce) { - ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(fe_type)); + ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(*fe_type_ptr)); bool have_unresolved = false; const zend_type *single_type; /* Traverse the list of child types and check that at least one is * a subtype of the parent type being checked */ - ZEND_TYPE_FOREACH(fe_type, single_type) { + ZEND_TYPE_FOREACH(*fe_type_ptr, single_type) { zend_class_entry *fe_ce; zend_string *fe_class_name = NULL; if (ZEND_TYPE_HAS_NAME(*single_type)) { @@ -473,7 +473,9 @@ static inheritance_status zend_is_intersection_subtype_of_class( /* Check whether a single class proto type is a subtype of a potentially complex fe_type. */ static inheritance_status zend_is_class_subtype_of_type( zend_class_entry *fe_scope, zend_string *fe_class_name, - zend_class_entry *proto_scope, const zend_type proto_type) { + zend_class_entry *proto_scope, const zend_type *proto_type_ptr +) { + const zend_type proto_type = *proto_type_ptr; zend_class_entry *fe_ce = NULL; bool have_unresolved = 0; @@ -521,7 +523,7 @@ static inheritance_status zend_is_class_subtype_of_type( ZEND_TYPE_FOREACH(proto_type, single_type) { if (ZEND_TYPE_IS_INTERSECTION(*single_type)) { inheritance_status subtype_status = zend_is_class_subtype_of_type( - fe_scope, fe_class_name, proto_scope, *single_type); + fe_scope, fe_class_name, proto_scope, single_type); switch (subtype_status) { case INHERITANCE_ERROR: @@ -606,9 +608,11 @@ static void register_unresolved_classes(zend_class_entry *scope, const zend_type } static inheritance_status zend_is_intersection_subtype_of_type( - zend_class_entry *fe_scope, const zend_type fe_type, - zend_class_entry *proto_scope, const zend_type proto_type) -{ + zend_class_entry *fe_scope, const zend_type *fe_type_ptr, + zend_class_entry *proto_scope, const zend_type *proto_type_ptr +) { + const zend_type fe_type = *fe_type_ptr; + const zend_type proto_type = *proto_type_ptr; bool have_unresolved = false; const zend_type *single_type; uint32_t proto_type_mask = ZEND_TYPE_PURE_MASK(proto_type); @@ -644,7 +648,7 @@ static inheritance_status zend_is_intersection_subtype_of_type( if (ZEND_TYPE_IS_INTERSECTION(*single_type)) { status = zend_is_intersection_subtype_of_type( - fe_scope, fe_type, proto_scope, *single_type); + fe_scope, fe_type_ptr, proto_scope, single_type); } else { zend_string *proto_class_name = get_class_from_type(proto_scope, *single_type); if (!proto_class_name) { @@ -653,7 +657,7 @@ static inheritance_status zend_is_intersection_subtype_of_type( zend_class_entry *proto_ce = NULL; status = zend_is_intersection_subtype_of_class( - fe_scope, fe_type, proto_scope, proto_class_name, proto_ce); + fe_scope, fe_type_ptr, proto_scope, proto_class_name, proto_ce); } if (status == early_exit_status) { @@ -672,9 +676,11 @@ static inheritance_status zend_is_intersection_subtype_of_type( } ZEND_API inheritance_status zend_perform_covariant_type_check( - zend_class_entry *fe_scope, const zend_type fe_type, - zend_class_entry *proto_scope, const zend_type proto_type) + zend_class_entry *fe_scope, const zend_type *fe_type_ptr, + zend_class_entry *proto_scope, const zend_type *proto_type_ptr) { + const zend_type fe_type = *fe_type_ptr; + const zend_type proto_type = *proto_type_ptr; ZEND_ASSERT(ZEND_TYPE_IS_SET(fe_type) && ZEND_TYPE_IS_SET(proto_type)); /* Apart from void, everything is trivially covariant to the mixed type. @@ -713,7 +719,7 @@ ZEND_API inheritance_status zend_perform_covariant_type_check( early_exit_status = ZEND_TYPE_IS_INTERSECTION(proto_type) ? INHERITANCE_ERROR : INHERITANCE_SUCCESS; inheritance_status status = zend_is_intersection_subtype_of_type( - fe_scope, fe_type, proto_scope, proto_type); + fe_scope, fe_type_ptr, proto_scope, proto_type_ptr); if (status == early_exit_status) { return status; @@ -733,7 +739,7 @@ ZEND_API inheritance_status zend_perform_covariant_type_check( /* Union has an intersection type as it's member */ if (ZEND_TYPE_IS_INTERSECTION(*single_type)) { status = zend_is_intersection_subtype_of_type( - fe_scope, *single_type, proto_scope, proto_type); + fe_scope, single_type, proto_scope, proto_type_ptr); } else { zend_string *fe_class_name = get_class_from_type(fe_scope, *single_type); if (!fe_class_name) { @@ -741,7 +747,7 @@ ZEND_API inheritance_status zend_perform_covariant_type_check( } status = zend_is_class_subtype_of_type( - fe_scope, fe_class_name, proto_scope, proto_type); + fe_scope, fe_class_name, proto_scope, proto_type_ptr); } if (status == early_exit_status) { @@ -763,7 +769,7 @@ ZEND_API inheritance_status zend_perform_covariant_type_check( } static inheritance_status zend_do_perform_arg_type_hint_check( - zend_class_entry *fe_scope, zend_arg_info *fe_arg_info, + zend_class_entry *fe_scope, const zend_arg_info *fe_arg_info, zend_class_entry *proto_scope, zend_arg_info *proto_arg_info) /* {{{ */ { if (!ZEND_TYPE_IS_SET(fe_arg_info->type) || ZEND_TYPE_PURE_MASK(fe_arg_info->type) == MAY_BE_ANY) { @@ -779,7 +785,7 @@ static inheritance_status zend_do_perform_arg_type_hint_check( /* Contravariant type check is performed as a covariant type check with swapped * argument order. */ return zend_perform_covariant_type_check( - proto_scope, proto_arg_info->type, fe_scope, fe_arg_info->type); + proto_scope, &proto_arg_info->type, fe_scope, &fe_arg_info->type); } /* }}} */ @@ -881,7 +887,7 @@ static inheritance_status zend_do_perform_implementation_check( } local_status = zend_perform_covariant_type_check( - fe_scope, fe->common.arg_info[-1].type, proto_scope, proto->common.arg_info[-1].type); + fe_scope, &fe->common.arg_info[-1].type, proto_scope, &proto->common.arg_info[-1].type); if (UNEXPECTED(local_status != INHERITANCE_SUCCESS)) { if (local_status == INHERITANCE_ERROR @@ -1297,10 +1303,10 @@ static inheritance_status full_property_types_compatible( /* Perform a covariant type check in both directions to determined invariance. */ inheritance_status status1 = variance == PROP_CONTRAVARIANT ? INHERITANCE_SUCCESS : zend_perform_covariant_type_check( - child_info->ce, child_info->type, parent_info->ce, parent_info->type); + child_info->ce, &child_info->type, parent_info->ce, &parent_info->type); inheritance_status status2 = variance == PROP_COVARIANT ? INHERITANCE_SUCCESS : zend_perform_covariant_type_check( - parent_info->ce, parent_info->type, child_info->ce, child_info->type); + parent_info->ce, &parent_info->type, child_info->ce, &child_info->type); if (status1 == INHERITANCE_SUCCESS && status2 == INHERITANCE_SUCCESS) { return INHERITANCE_SUCCESS; } @@ -1357,7 +1363,7 @@ static inheritance_status verify_property_type_compatibility( && (!child_info->hooks || !child_info->hooks[ZEND_PROPERTY_HOOK_SET])) { zend_type set_type = parent_info->hooks[ZEND_PROPERTY_HOOK_SET]->common.arg_info[0].type; inheritance_status result = zend_perform_covariant_type_check( - parent_info->ce, set_type, child_info->ce, child_info->type); + parent_info->ce, &set_type, child_info->ce, &child_info->type); if ((result == INHERITANCE_ERROR && throw_on_error) || (result == INHERITANCE_UNRESOLVED && throw_on_unresolved)) { emit_set_hook_type_error(child_info, parent_info); } @@ -1645,7 +1651,7 @@ static inheritance_status class_constant_types_compatible(const zend_class_const return INHERITANCE_ERROR; } - return zend_perform_covariant_type_check(child->ce, child->type, parent->ce, parent->type); + return zend_perform_covariant_type_check(child->ce, &child->type, parent->ce, &parent->type); } static bool do_inherit_constant_check( @@ -1791,7 +1797,7 @@ ZEND_API inheritance_status zend_verify_property_hook_variance(const zend_proper { ZEND_ASSERT(prop_info->hooks && prop_info->hooks[ZEND_PROPERTY_HOOK_SET] == func); - zend_arg_info *value_arg_info = &func->op_array.arg_info[0]; + const zend_arg_info *value_arg_info = &func->op_array.arg_info[0]; if (!ZEND_TYPE_IS_SET(value_arg_info->type)) { return INHERITANCE_SUCCESS; } @@ -1801,7 +1807,7 @@ ZEND_API inheritance_status zend_verify_property_hook_variance(const zend_proper } zend_class_entry *ce = prop_info->ce; - return zend_perform_covariant_type_check(ce, prop_info->type, ce, value_arg_info->type); + return zend_perform_covariant_type_check(ce, &prop_info->type, ce, &value_arg_info->type); } #ifdef ZEND_OPCACHE_SHM_REATTACHMENT @@ -2777,8 +2783,8 @@ static bool do_trait_constant_check( emit_incompatible_trait_constant_error(ce, existing_constant, trait_constant, name, traits, current_trait); return false; } else if (ZEND_TYPE_IS_SET(trait_constant->type)) { - inheritance_status status1 = zend_perform_covariant_type_check(ce, existing_constant->type, traits[current_trait], trait_constant->type); - inheritance_status status2 = zend_perform_covariant_type_check(traits[current_trait], trait_constant->type, ce, existing_constant->type); + inheritance_status status1 = zend_perform_covariant_type_check(ce, &existing_constant->type, traits[current_trait], &trait_constant->type); + inheritance_status status2 = zend_perform_covariant_type_check(traits[current_trait], &trait_constant->type, ce, &existing_constant->type); if (status1 == INHERITANCE_ERROR || status2 == INHERITANCE_ERROR) { emit_incompatible_trait_constant_error(ce, existing_constant, trait_constant, name, traits, current_trait); return false; From a192a3aa7b3eef1e95a4f5a55a8da0fe68f869f5 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 5 Apr 2025 00:54:54 +0100 Subject: [PATCH 02/41] Add T_TYPE token --- .../associated/associated_001.phpt | 17 +++++++++++++++++ Zend/zend_language_parser.y | 1 + Zend/zend_language_scanner.l | 4 ++++ ext/tokenizer/tokenizer_data.c | 1 + ext/tokenizer/tokenizer_data.stub.php | 5 +++++ ext/tokenizer/tokenizer_data_arginfo.h | 3 ++- 6 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/type_declarations/associated/associated_001.phpt diff --git a/Zend/tests/type_declarations/associated/associated_001.phpt b/Zend/tests/type_declarations/associated/associated_001.phpt new file mode 100644 index 0000000000000..47a5a7b236043 --- /dev/null +++ b/Zend/tests/type_declarations/associated/associated_001.phpt @@ -0,0 +1,17 @@ +--TEST-- +Associated types basic +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "type", expecting "function" in %s on line %d diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 08b2ac6b3f39b..a44c58744446f 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -167,6 +167,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_TRAIT "'trait'" %token T_INTERFACE "'interface'" %token T_ENUM "'enum'" +%token T_TYPE "'type'" %token T_EXTENDS "'extends'" %token T_IMPLEMENTS "'implements'" %token T_NAMESPACE "'namespace'" diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 4c883b81c5f7d..abb816f83c5cb 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1545,6 +1545,10 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ RETURN_TOKEN_WITH_IDENT(T_INTERFACE); } +"type" { + RETURN_TOKEN_WITH_IDENT(T_TYPE); +} + "trait" { RETURN_TOKEN_WITH_IDENT(T_TRAIT); } diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index a1e131032bcfb..c53ee96c6226b 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -105,6 +105,7 @@ char *get_token_type_name(int token_type) case T_TRAIT: return "T_TRAIT"; case T_INTERFACE: return "T_INTERFACE"; case T_ENUM: return "T_ENUM"; + case T_TYPE: return "T_TYPE"; case T_EXTENDS: return "T_EXTENDS"; case T_IMPLEMENTS: return "T_IMPLEMENTS"; case T_NAMESPACE: return "T_NAMESPACE"; diff --git a/ext/tokenizer/tokenizer_data.stub.php b/ext/tokenizer/tokenizer_data.stub.php index c1e1fd254dfaa..065453981f223 100644 --- a/ext/tokenizer/tokenizer_data.stub.php +++ b/ext/tokenizer/tokenizer_data.stub.php @@ -402,6 +402,11 @@ * @cvalue T_ENUM */ const T_ENUM = UNKNOWN; +/** + * @var int + * @cvalue T_TYPE + */ +const T_TYPE = UNKNOWN; /** * @var int * @cvalue T_EXTENDS diff --git a/ext/tokenizer/tokenizer_data_arginfo.h b/ext/tokenizer/tokenizer_data_arginfo.h index 9c488d19f1890..1571daf0cda58 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: 19d25d22098f46283b517352cbb302db962b50fd */ + * Stub hash: ba2791ef99a630b81f49a3251f3824d7d4858176 */ static void register_tokenizer_data_symbols(int module_number) { @@ -83,6 +83,7 @@ static void register_tokenizer_data_symbols(int module_number) REGISTER_LONG_CONSTANT("T_TRAIT", T_TRAIT, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_INTERFACE", T_INTERFACE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_ENUM", T_ENUM, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_TYPE", T_TYPE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_EXTENDS", T_EXTENDS, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_IMPLEMENTS", T_IMPLEMENTS, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_NAMESPACE", T_NAMESPACE, CONST_PERSISTENT); From 9304a82dda2ce8228928f3d07289cb82c1b6200e Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 5 Apr 2025 01:36:21 +0100 Subject: [PATCH 03/41] Support minimal compilation of associated type --- .../associated/associated_001.phpt | 2 +- .../associated/associated_type_in_class.phpt | 13 +++++++++++++ .../associated/associated_type_in_trait.phpt | 13 +++++++++++++ .../associated/repeated_associated_type.phpt | 17 +++++++++++++++++ Zend/zend_ast.c | 4 ++++ Zend/zend_ast.h | 1 + Zend/zend_compile.c | 17 +++++++++++++++++ Zend/zend_language_parser.y | 7 +++++++ 8 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/type_declarations/associated/associated_type_in_class.phpt create mode 100644 Zend/tests/type_declarations/associated/associated_type_in_trait.phpt create mode 100644 Zend/tests/type_declarations/associated/repeated_associated_type.phpt diff --git a/Zend/tests/type_declarations/associated/associated_001.phpt b/Zend/tests/type_declarations/associated/associated_001.phpt index 47a5a7b236043..395ca814df844 100644 --- a/Zend/tests/type_declarations/associated/associated_001.phpt +++ b/Zend/tests/type_declarations/associated/associated_001.phpt @@ -14,4 +14,4 @@ class C implements I { ?> --EXPECTF-- -Parse error: syntax error, unexpected token "type", expecting "function" in %s on line %d +Fatal error: Declaration of C::foo(string $param): string must be compatible with I::foo(T $param): T in %s on line %d diff --git a/Zend/tests/type_declarations/associated/associated_type_in_class.phpt b/Zend/tests/type_declarations/associated/associated_type_in_class.phpt new file mode 100644 index 0000000000000..d767621409d6c --- /dev/null +++ b/Zend/tests/type_declarations/associated/associated_type_in_class.phpt @@ -0,0 +1,13 @@ +--TEST-- +Associated types in class is invalid +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use associated types outside of interfaces, used in C in %s on line %d diff --git a/Zend/tests/type_declarations/associated/associated_type_in_trait.phpt b/Zend/tests/type_declarations/associated/associated_type_in_trait.phpt new file mode 100644 index 0000000000000..6e944f47fcdc2 --- /dev/null +++ b/Zend/tests/type_declarations/associated/associated_type_in_trait.phpt @@ -0,0 +1,13 @@ +--TEST-- +Associated types in trait is invalid +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use associated types outside of interfaces, used in C in %s on line %d diff --git a/Zend/tests/type_declarations/associated/repeated_associated_type.phpt b/Zend/tests/type_declarations/associated/repeated_associated_type.phpt new file mode 100644 index 0000000000000..47a5a7b236043 --- /dev/null +++ b/Zend/tests/type_declarations/associated/repeated_associated_type.phpt @@ -0,0 +1,17 @@ +--TEST-- +Associated types basic +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "type", expecting "function" in %s on line %d diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 8bdd29c5512cc..c30d86957715c 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -2403,6 +2403,10 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio APPEND_NODE_1("break"); case ZEND_AST_CONTINUE: APPEND_NODE_1("continue"); + case ZEND_AST_ASSOCIATED_TYPE: + smart_str_appends(str, "type "); + zend_ast_export_name(str, ast->child[0], 0, indent); + break; /* 2 child nodes */ case ZEND_AST_DIM: diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 9348c35f6cc07..cba4782c9becd 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -99,6 +99,7 @@ enum _zend_ast_kind { ZEND_AST_POST_DEC, ZEND_AST_YIELD_FROM, ZEND_AST_CLASS_NAME, + ZEND_AST_ASSOCIATED_TYPE, ZEND_AST_GLOBAL, ZEND_AST_UNSET, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 0669d106f15e9..d98e2838ff7fc 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -9011,6 +9011,20 @@ static void zend_compile_use_trait(zend_ast *ast) /* {{{ */ } /* }}} */ +static void zend_compile_associated_type(zend_ast *ast) { + zend_class_entry *ce = CG(active_class_entry); + + if ((ce->ce_flags & ZEND_ACC_INTERFACE) == 0) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use associated types outside of interfaces, used in %s", ZSTR_VAL(ce->name)); + } + + zend_ast *name_ast = ast->child[0]; + zend_string *name = zend_ast_get_str(name_ast); + ZEND_ASSERT(name != NULL); + // TODO add associated type to CE +} + static void zend_compile_implements(zend_ast *ast) /* {{{ */ { zend_ast_list *list = zend_ast_get_list(ast); @@ -11595,6 +11609,9 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */ case ZEND_AST_USE_TRAIT: zend_compile_use_trait(ast); break; + case ZEND_AST_ASSOCIATED_TYPE: + zend_compile_associated_type(ast); + break; case ZEND_AST_CLASS: zend_compile_class_decl(NULL, ast, 0); break; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index a44c58744446f..ca45e3643686b 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -287,6 +287,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type function_name non_empty_member_modifiers %type property_hook property_hook_list optional_property_hook_list hooked_property property_hook_body %type optional_parameter_list +%type associated_type %type returns_ref function fn is_reference is_variadic property_modifiers property_hook_modifiers %type method_modifiers class_const_modifiers member_modifier optional_cpp_modifiers @@ -666,6 +667,11 @@ enum_case_expr: | '=' expr { $$ = $2; } ; +associated_type: + T_TYPE name ';' + { $$ = zend_ast_create(ZEND_AST_ASSOCIATED_TYPE, $2); } +; + extends_from: %empty { $$ = NULL; } | T_EXTENDS class_name { $$ = $2; } @@ -967,6 +973,7 @@ attributed_class_statement: { $$ = zend_ast_create_decl(ZEND_AST_METHOD, $3 | $1 | $12, $2, $5, zend_ast_get_str($4), $7, NULL, $11, $9, NULL); CG(extra_fn_flags) = $10; } | enum_case { $$ = $1; } + | associated_type { $$ = $1; } ; class_statement: From 066f04fad5baee0ef889cb04a9f89a91225ee50f Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 5 Apr 2025 02:16:15 +0100 Subject: [PATCH 04/41] Associate type to CE and minimal duplicate check --- .../associated/multiple_associated_type.phpt | 20 +++++++++++++++++ .../associated/repeated_associated_type.phpt | 5 +++-- Zend/zend.h | 3 +++ Zend/zend_compile.c | 22 ++++++++++++++++--- Zend/zend_opcode.c | 7 ++++++ 5 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 Zend/tests/type_declarations/associated/multiple_associated_type.phpt diff --git a/Zend/tests/type_declarations/associated/multiple_associated_type.phpt b/Zend/tests/type_declarations/associated/multiple_associated_type.phpt new file mode 100644 index 0000000000000..c3d7e7fa8c1b2 --- /dev/null +++ b/Zend/tests/type_declarations/associated/multiple_associated_type.phpt @@ -0,0 +1,20 @@ +--TEST-- +Multiple associated types +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of C::set(int $key, string $value): void must be compatible with I::set(K $key, V $value): void in %s on line %d diff --git a/Zend/tests/type_declarations/associated/repeated_associated_type.phpt b/Zend/tests/type_declarations/associated/repeated_associated_type.phpt index 47a5a7b236043..be65c60ef7767 100644 --- a/Zend/tests/type_declarations/associated/repeated_associated_type.phpt +++ b/Zend/tests/type_declarations/associated/repeated_associated_type.phpt @@ -1,9 +1,10 @@ --TEST-- -Associated types basic +Repeated associated type --FILE-- --EXPECTF-- -Parse error: syntax error, unexpected token "type", expecting "function" in %s on line %d +Fatal error: Cannot have two associated types with the same name "T" in %s on line %d diff --git a/Zend/zend.h b/Zend/zend.h index 0cf1faeb653fe..cff81284740c1 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -218,6 +218,9 @@ struct _zend_class_entry { zend_trait_precedence **trait_precedences; HashTable *attributes; + /* Only for interfaces */ + HashTable *associated_types; + uint32_t enum_backing_type; HashTable *backed_enum_table; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index d98e2838ff7fc..c9f1d9b4c5759 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2072,6 +2072,7 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, bool nullify_hand ce->default_static_members_count = 0; ce->properties_info_table = NULL; ce->attributes = NULL; + ce->associated_types = NULL; ce->enum_backing_type = IS_UNDEF; ce->backed_enum_table = NULL; @@ -9011,18 +9012,33 @@ static void zend_compile_use_trait(zend_ast *ast) /* {{{ */ } /* }}} */ + + static void zend_compile_associated_type(zend_ast *ast) { zend_class_entry *ce = CG(active_class_entry); + HashTable *associated_types = ce->associated_types; + zend_ast *name_ast = ast->child[0]; + zend_string *name = zend_ast_get_str(name_ast); if ((ce->ce_flags & ZEND_ACC_INTERFACE) == 0) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot use associated types outside of interfaces, used in %s", ZSTR_VAL(ce->name)); } - zend_ast *name_ast = ast->child[0]; - zend_string *name = zend_ast_get_str(name_ast); ZEND_ASSERT(name != NULL); - // TODO add associated type to CE + bool persistent = ce->type == ZEND_INTERNAL_CLASS; + if (associated_types == NULL) { + ce->associated_types = pemalloc(sizeof(HashTable), persistent); + zend_hash_init(ce->associated_types, 8, NULL, NULL, persistent); + associated_types = ce->associated_types; + } + if (zend_hash_exists(associated_types, name)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot have two associated types with the same name \"%s\"", ZSTR_VAL(name)); + } + zval tmp; + ZVAL_UNDEF(&tmp); + zend_hash_add_new(associated_types, name, &tmp); } static void zend_compile_implements(zend_ast *ast) /* {{{ */ diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 6e7d31e15a40f..8aca821931483 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -351,6 +351,10 @@ ZEND_API void destroy_zend_class(zval *zv) zend_hash_release(ce->attributes); } + if (ce->associated_types) { + zend_hash_release(ce->associated_types); + } + if (ce->num_interfaces > 0 && !(ce->ce_flags & ZEND_ACC_RESOLVED_INTERFACES)) { uint32_t i; @@ -527,6 +531,9 @@ ZEND_API void destroy_zend_class(zval *zv) if (ce->attributes) { zend_hash_release(ce->attributes); } + if (ce->associated_types) { + zend_hash_release(ce->associated_types); + } free(ce); break; } From b7cf20bdd6d29e291ed00a9f81e9ba10c75fb8ff Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 5 Apr 2025 03:45:25 +0100 Subject: [PATCH 05/41] Prevent associated type from being in a union or intersection type --- ...iated_type_cannot_be_in_intersection1.phpt | 13 ++++++++++++ ...iated_type_cannot_be_in_intersection2.phpt | 13 ++++++++++++ .../associated_type_cannot_be_in_union1.phpt | 13 ++++++++++++ .../associated_type_cannot_be_in_union2.phpt | 13 ++++++++++++ .../associated_type_cannot_be_in_union3.phpt | 13 ++++++++++++ Zend/zend_compile.c | 20 +++++++++++++++---- Zend/zend_types.h | 12 +++++++---- 7 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 Zend/tests/type_declarations/associated/associated_type_cannot_be_in_intersection1.phpt create mode 100644 Zend/tests/type_declarations/associated/associated_type_cannot_be_in_intersection2.phpt create mode 100644 Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union1.phpt create mode 100644 Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union2.phpt create mode 100644 Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union3.phpt diff --git a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_intersection1.phpt b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_intersection1.phpt new file mode 100644 index 0000000000000..0ea969b68c499 --- /dev/null +++ b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_intersection1.phpt @@ -0,0 +1,13 @@ +--TEST-- +Associated type cannot be in intersection (simple intersection with class type) +--FILE-- + +--EXPECTF-- +Fatal error: Associated type cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_intersection2.phpt b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_intersection2.phpt new file mode 100644 index 0000000000000..f44dab1a1e7f0 --- /dev/null +++ b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_intersection2.phpt @@ -0,0 +1,13 @@ +--TEST-- +Associated type cannot be in intersection (DNF type) +--FILE-- + +--EXPECTF-- +Fatal error: Associated type cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union1.phpt b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union1.phpt new file mode 100644 index 0000000000000..7a4e1809a4d52 --- /dev/null +++ b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union1.phpt @@ -0,0 +1,13 @@ +--TEST-- +Associated type cannot be in union (simple union with built-in type) +--FILE-- + +--EXPECTF-- +Fatal error: Associated type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union2.phpt b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union2.phpt new file mode 100644 index 0000000000000..904f428d09920 --- /dev/null +++ b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union2.phpt @@ -0,0 +1,13 @@ +--TEST-- +Associated type cannot be in union (simple union with class type) +--FILE-- + +--EXPECTF-- +Fatal error: Associated type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union3.phpt b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union3.phpt new file mode 100644 index 0000000000000..a40109bb5ebe3 --- /dev/null +++ b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union3.phpt @@ -0,0 +1,13 @@ +--TEST-- +Associated type cannot be in union (DNF type) +--FILE-- + +--EXPECTF-- +Fatal error: Associated type cannot be part of a union type in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index c9f1d9b4c5759..4195a71a8f565 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6963,9 +6963,11 @@ ZEND_API void zend_set_function_arg_flags(zend_function *func) /* {{{ */ static zend_type zend_compile_single_typename(zend_ast *ast) { + zend_class_entry *ce = CG(active_class_entry); + ZEND_ASSERT(!(ast->attr & ZEND_TYPE_NULLABLE)); if (ast->kind == ZEND_AST_TYPE) { - if (ast->attr == IS_STATIC && !CG(active_class_entry) && zend_is_scope_known()) { + if (ast->attr == IS_STATIC && !ce && zend_is_scope_known()) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot use \"static\" when no class scope is active"); } @@ -6995,10 +6997,14 @@ static zend_type zend_compile_single_typename(zend_ast *ast) const char *correct_name; uint32_t fetch_type = zend_get_class_fetch_type_ast(ast); zend_string *class_name = type_name; + uint32_t flags = 0; if (fetch_type == ZEND_FETCH_CLASS_DEFAULT) { class_name = zend_resolve_class_name_ast(ast); zend_assert_valid_class_name(class_name, "a type name"); + if (ce && ce->associated_types && zend_hash_exists(ce->associated_types, class_name)) { + flags = _ZEND_TYPE_ASSOCIATED_BIT; + } } else { ZEND_ASSERT(fetch_type == ZEND_FETCH_CLASS_SELF || fetch_type == ZEND_FETCH_CLASS_PARENT); @@ -7006,14 +7012,14 @@ static zend_type zend_compile_single_typename(zend_ast *ast) if (fetch_type == ZEND_FETCH_CLASS_SELF) { /* Scope might be unknown for unbound closures and traits */ if (zend_is_scope_known()) { - class_name = CG(active_class_entry)->name; + class_name = ce->name; ZEND_ASSERT(class_name && "must know class name when resolving self type at compile time"); } } else { ZEND_ASSERT(fetch_type == ZEND_FETCH_CLASS_PARENT); /* Scope might be unknown for unbound closures and traits */ if (zend_is_scope_known()) { - class_name = CG(active_class_entry)->parent_name; + class_name = ce->parent_name; ZEND_ASSERT(class_name && "must know class name when resolving parent type at compile time"); } } @@ -7041,7 +7047,7 @@ static zend_type zend_compile_single_typename(zend_ast *ast) class_name = zend_new_interned_string(class_name); zend_alloc_ce_cache(class_name); - return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, /* allow null */ false, 0); + return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, /* allow null */ false, flags); } } } @@ -7190,6 +7196,9 @@ static zend_type zend_compile_typename_ex( single_type = zend_compile_single_typename(type_ast); uint32_t single_type_mask = ZEND_TYPE_PURE_MASK(single_type); + if (ZEND_TYPE_IS_ASSOCIATED(single_type)) { + zend_error_noreturn(E_COMPILE_ERROR, "Associated type cannot be part of a union type"); + } if (single_type_mask == MAY_BE_ANY) { zend_error_noreturn(E_COMPILE_ERROR, "Type mixed can only be used as a standalone type"); } @@ -7272,6 +7281,9 @@ static zend_type zend_compile_typename_ex( zend_ast *type_ast = list->child[i]; zend_type single_type = zend_compile_single_typename(type_ast); + if (ZEND_TYPE_IS_ASSOCIATED(single_type)) { + zend_error_noreturn(E_COMPILE_ERROR, "Associated type cannot be part of an intersection type"); + } /* An intersection of union types cannot exist so invalidate it * Currently only can happen with iterable getting canonicalized to Traversable|array */ if (ZEND_TYPE_IS_ITERABLE_FALLBACK(single_type)) { diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 4a6d00b9d73ea..7764d8cc9e7d5 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -141,14 +141,15 @@ typedef struct { zend_type types[1]; } zend_type_list; -#define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 25 -#define _ZEND_TYPE_MASK ((1u << 25) - 1) +#define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 26 +#define _ZEND_TYPE_MASK ((1u << 26) - 1) /* Only one of these bits may be set. */ +#define _ZEND_TYPE_ASSOCIATED_BIT (1u << 25) #define _ZEND_TYPE_NAME_BIT (1u << 24) // Used to signify that type.ptr is not a `zend_string*` but a `const char*`, #define _ZEND_TYPE_LITERAL_NAME_BIT (1u << 23) #define _ZEND_TYPE_LIST_BIT (1u << 22) -#define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_NAME_BIT|_ZEND_TYPE_LITERAL_NAME_BIT) +#define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_NAME_BIT|_ZEND_TYPE_LITERAL_NAME_BIT|_ZEND_TYPE_ASSOCIATED_BIT) /* For BC behaviour with iterable type */ #define _ZEND_TYPE_ITERABLE_BIT (1u << 21) /* Whether the type list is arena allocated */ @@ -166,7 +167,7 @@ typedef struct { (((t).type_mask & _ZEND_TYPE_MASK) != 0) /* If a type is complex it means it's either a list with a union or intersection, - * or the void pointer is a class name */ + * the void pointer is a class name, or the type is an associated type (which implies it is a name) */ #define ZEND_TYPE_IS_COMPLEX(t) \ ((((t).type_mask) & _ZEND_TYPE_KIND_MASK) != 0) @@ -179,6 +180,9 @@ typedef struct { #define ZEND_TYPE_HAS_LIST(t) \ ((((t).type_mask) & _ZEND_TYPE_LIST_BIT) != 0) +#define ZEND_TYPE_IS_ASSOCIATED(t) \ + ((((t).type_mask) & _ZEND_TYPE_ASSOCIATED_BIT) != 0) + #define ZEND_TYPE_IS_ITERABLE_FALLBACK(t) \ ((((t).type_mask) & _ZEND_TYPE_ITERABLE_BIT) != 0) From 0ec2ea603bee7914ca5d61ccbe164f4c002a303e Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 5 Apr 2025 04:14:03 +0100 Subject: [PATCH 06/41] Support invariant bound types for associated types --- .../associated/associated_001.phpt | 23 +++++-- .../associated/multiple_associated_type.phpt | 62 +++++++++++++++++-- Zend/zend.c | 1 + Zend/zend_compile.c | 2 + Zend/zend_globals.h | 2 + Zend/zend_inheritance.c | 57 +++++++++++++++++ 6 files changed, 139 insertions(+), 8 deletions(-) diff --git a/Zend/tests/type_declarations/associated/associated_001.phpt b/Zend/tests/type_declarations/associated/associated_001.phpt index 395ca814df844..f3b6d6cafaa87 100644 --- a/Zend/tests/type_declarations/associated/associated_001.phpt +++ b/Zend/tests/type_declarations/associated/associated_001.phpt @@ -8,10 +8,25 @@ interface I { public function foo(T $param): T; } -class C implements I { - public function foo(string $param): string {} +class CS implements I { + public function foo(string $param): string { + return $param . '!'; + } } +class CI implements I { + public function foo(int $param): int { + return $param + 42; + } +} + +$cs = new CS(); +var_dump($cs->foo("Hello")); + +$ci = new CI(); +var_dump($ci->foo(5)); + ?> ---EXPECTF-- -Fatal error: Declaration of C::foo(string $param): string must be compatible with I::foo(T $param): T in %s on line %d +--EXPECT-- +string(6) "Hello!" +int(47) diff --git a/Zend/tests/type_declarations/associated/multiple_associated_type.phpt b/Zend/tests/type_declarations/associated/multiple_associated_type.phpt index c3d7e7fa8c1b2..aae8ca4dda877 100644 --- a/Zend/tests/type_declarations/associated/multiple_associated_type.phpt +++ b/Zend/tests/type_declarations/associated/multiple_associated_type.phpt @@ -10,11 +10,65 @@ interface I { public function get(K $key): V; } -class C implements I { - public function set(int $key, string $value): void {} - public function get(int $key): string {} +class C1 implements I { + public array $a = []; + public function set(int $key, string $value): void { + $this->a[$key] = $value . '!'; + } + public function get(int $key): string { + return $this->a[$key]; + } } +class C2 implements I { + public array $a = []; + public function set(string $key, object $value): void { + $this->a[$key] = $value; + } + public function get(string $key): object { + return $this->a[$key]; + } +} + +$c1 = new C1(); +$c1->set(5, "Hello"); +var_dump($c1->a); +var_dump($c1->get(5)); + +$c2 = new C2(); +$c2->set('C1', $c1); +var_dump($c2->a); +var_dump($c2->get('C1')); + +try { + $c1->set('blah', "Hello"); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + + ?> --EXPECTF-- -Fatal error: Declaration of C::set(int $key, string $value): void must be compatible with I::set(K $key, V $value): void in %s on line %d +array(1) { + [5]=> + string(6) "Hello!" +} +string(6) "Hello!" +array(1) { + ["C1"]=> + object(C1)#1 (1) { + ["a"]=> + array(1) { + [5]=> + string(6) "Hello!" + } + } +} +object(C1)#1 (1) { + ["a"]=> + array(1) { + [5]=> + string(6) "Hello!" + } +} +TypeError: C1::set(): Argument #1 ($key) must be of type int, string given, called in %s on line %d diff --git a/Zend/zend.c b/Zend/zend.c index 2d8a0f455f8b4..edec7ba675630 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -732,6 +732,7 @@ static void compiler_globals_ctor(zend_compiler_globals *compiler_globals) /* {{ compiler_globals->script_encoding_list = NULL; compiler_globals->current_linking_class = NULL; + compiler_globals->bound_associated_types = NULL; /* Map region is going to be created and resized at run-time. */ compiler_globals->map_ptr_real_base = NULL; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 4195a71a8f565..2c56701cc0e1d 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -463,6 +463,7 @@ void init_compiler(void) /* {{{ */ CG(delayed_autoloads) = NULL; CG(unlinked_uses) = NULL; CG(current_linking_class) = NULL; + CG(bound_associated_types) = NULL; } /* }}} */ @@ -491,6 +492,7 @@ void shutdown_compiler(void) /* {{{ */ CG(unlinked_uses) = NULL; } CG(current_linking_class) = NULL; + ZEND_ASSERT(CG(bound_associated_types) == NULL); } /* }}} */ diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 079bfb99caccf..dc1a06f52c539 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -151,6 +151,8 @@ struct _zend_compiler_globals { HashTable *delayed_autoloads; HashTable *unlinked_uses; zend_class_entry *current_linking_class; + /* Those are initialized and destroyed by zend_do_inheritance_ex() */ + HashTable *bound_associated_types; uint32_t rtd_key_counter; diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index f715e7743d363..fcc48de18f18b 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -675,6 +675,42 @@ static inheritance_status zend_is_intersection_subtype_of_type( return early_exit_status == INHERITANCE_ERROR ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; } +ZEND_API inheritance_status zend_perform_covariant_type_check( + zend_class_entry *fe_scope, const zend_type *fe_type_ptr, + zend_class_entry *proto_scope, const zend_type *proto_type_ptr); + +static inheritance_status zend_is_type_subtype_of_associated_type( + zend_class_entry *concrete_scope, + const zend_type *concrete_type_ptr, + zend_class_entry *associated_type_scope, + const zend_type *associated_type_ptr +) { + const zend_type associated_type = *associated_type_ptr; + + ZEND_ASSERT(CG(bound_associated_types) && "Have associated type"); + ZEND_ASSERT(ZEND_TYPE_HAS_NAME(associated_type)); + + zend_string *associated_type_name = ZEND_TYPE_NAME(associated_type); + const zend_type *bound_type_ptr = zend_hash_find_ptr(CG(bound_associated_types), associated_type_name); + if (bound_type_ptr == NULL) { + /* Loosing const qualifier here is OK because this hashtable never frees or does anything with the value */ + zend_hash_add_new_ptr(CG(bound_associated_types), associated_type_name, (void*)concrete_type_ptr); + return INHERITANCE_SUCCESS; + } else { + /* Associated type must be invariant */ + const inheritance_status sub_type_status = zend_perform_covariant_type_check( + concrete_scope, concrete_type_ptr, associated_type_scope, bound_type_ptr); + const inheritance_status super_type_status = zend_perform_covariant_type_check( + associated_type_scope, bound_type_ptr, concrete_scope, concrete_type_ptr); + + if (sub_type_status != super_type_status) { + return INHERITANCE_ERROR; + } else { + return sub_type_status; + } + } +} + ZEND_API inheritance_status zend_perform_covariant_type_check( zend_class_entry *fe_scope, const zend_type *fe_type_ptr, zend_class_entry *proto_scope, const zend_type *proto_type_ptr) @@ -690,6 +726,17 @@ ZEND_API inheritance_status zend_perform_covariant_type_check( return INHERITANCE_SUCCESS; } + /* If we check for concrete return type */ + if (ZEND_TYPE_IS_ASSOCIATED(proto_type)) { + return zend_is_type_subtype_of_associated_type( + fe_scope, fe_type_ptr, proto_scope, proto_type_ptr); + } + /* If we check for concrete parameter type */ + if (ZEND_TYPE_IS_ASSOCIATED(fe_type)) { + return zend_is_type_subtype_of_associated_type( + proto_scope, proto_type_ptr, fe_scope, fe_type_ptr); + } + /* Builtin types may be removed, but not added */ uint32_t fe_type_mask = ZEND_TYPE_PURE_MASK(fe_type); uint32_t proto_type_mask = ZEND_TYPE_PURE_MASK(proto_type); @@ -2174,6 +2221,11 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * ZEND_INHERITANCE_RESET_CHILD_OVERRIDE; } + if (iface->associated_types) { + HashTable *ht = emalloc(sizeof(HashTable)); + zend_hash_init(ht, zend_hash_num_elements(iface->associated_types), NULL, NULL, false); + CG(bound_associated_types) = ht; + } ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&iface->constants_table, key, c) { do_inherit_iface_constant(key, c, ce, iface); } ZEND_HASH_FOREACH_END(); @@ -2195,6 +2247,11 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * if (iface->num_interfaces) { zend_do_inherit_interfaces(ce, iface); } + if (CG(bound_associated_types)) { + zend_hash_destroy(CG(bound_associated_types)); + efree(CG(bound_associated_types)); + CG(bound_associated_types) = NULL; + } } /* }}} */ From e215b8b81c9759466cb20f041b9d2b038a418a0a Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sun, 6 Apr 2025 15:52:37 +0100 Subject: [PATCH 07/41] Add true global for mixed type --- Zend/zend_compile.c | 2 ++ Zend/zend_compile.h | 1 + 2 files changed, 3 insertions(+) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 2c56701cc0e1d..e7404f99aa564 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -84,6 +84,8 @@ static inline uint32_t zend_alloc_cache_slot(void) { return zend_alloc_cache_slots(1); } +const zend_type zend_mixed_type = { NULL, MAY_BE_ANY }; + ZEND_API zend_op_array *(*zend_compile_file)(zend_file_handle *file_handle, int type); ZEND_API zend_op_array *(*zend_compile_string)(zend_string *source_string, const char *filename, zend_compile_position position); diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 62d0fbcded2ee..6d8c50e1ea8ba 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -53,6 +53,7 @@ typedef struct _zend_op_array zend_op_array; typedef struct _zend_op zend_op; +extern const zend_type zend_mixed_type; /* On 64-bit systems less optimal, but more compact VM code leads to better * performance. So on 32-bit systems we use absolute addresses for jump From e6a1a7e0c90911b27288ba72b921e55b81c937f0 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sun, 6 Apr 2025 16:41:35 +0100 Subject: [PATCH 08/41] Store zend_type ptr in associated types HT --- Zend/zend_compile.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index e7404f99aa564..d3d37a1c95533 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -9028,7 +9028,10 @@ static void zend_compile_use_trait(zend_ast *ast) /* {{{ */ } /* }}} */ - +static void zend_associated_table_ht_dtor(zval *val) { + /* NO OP as we only use it to be able to refer and save pointers to zend_types */ + // TODO do we actually want to store copies of types? +} static void zend_compile_associated_type(zend_ast *ast) { zend_class_entry *ce = CG(active_class_entry); @@ -9045,16 +9048,15 @@ static void zend_compile_associated_type(zend_ast *ast) { bool persistent = ce->type == ZEND_INTERNAL_CLASS; if (associated_types == NULL) { ce->associated_types = pemalloc(sizeof(HashTable), persistent); - zend_hash_init(ce->associated_types, 8, NULL, NULL, persistent); + zend_hash_init(ce->associated_types, 8, NULL, zend_associated_table_ht_dtor, persistent); associated_types = ce->associated_types; } if (zend_hash_exists(associated_types, name)) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot have two associated types with the same name \"%s\"", ZSTR_VAL(name)); } - zval tmp; - ZVAL_UNDEF(&tmp); - zend_hash_add_new(associated_types, name, &tmp); + + zend_hash_add_new_ptr(associated_types, name, (void*) &zend_mixed_type); } static void zend_compile_implements(zend_ast *ast) /* {{{ */ From d25275969609d3ef29187ac3bda097cb690dfdd9 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 7 Apr 2025 03:16:26 +0100 Subject: [PATCH 09/41] Add parser support for constraint type --- .../associated_type_with_constraint.phpt | 32 +++++++++++++++++++ ...ssociated_type_with_constraint_failed.phpt | 16 ++++++++++ Zend/zend_ast.c | 14 +++++--- Zend/zend_ast.h | 2 +- Zend/zend_language_parser.y | 6 ++-- 5 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 Zend/tests/type_declarations/associated/associated_type_with_constraint.phpt create mode 100644 Zend/tests/type_declarations/associated/associated_type_with_constraint_failed.phpt diff --git a/Zend/tests/type_declarations/associated/associated_type_with_constraint.phpt b/Zend/tests/type_declarations/associated/associated_type_with_constraint.phpt new file mode 100644 index 0000000000000..9e855edd7edb2 --- /dev/null +++ b/Zend/tests/type_declarations/associated/associated_type_with_constraint.phpt @@ -0,0 +1,32 @@ +--TEST-- +Associated type with a constraint +--FILE-- +foo("Hello")); + +$ci = new CI(); +var_dump($ci->foo(5)); + +?> +--EXPECT-- +string(6) "Hello!" +int(47) diff --git a/Zend/tests/type_declarations/associated/associated_type_with_constraint_failed.phpt b/Zend/tests/type_declarations/associated/associated_type_with_constraint_failed.phpt new file mode 100644 index 0000000000000..eae1adbe259e2 --- /dev/null +++ b/Zend/tests/type_declarations/associated/associated_type_with_constraint_failed.phpt @@ -0,0 +1,16 @@ +--TEST-- +Associated type with a constraint that is not satisfied +--FILE-- + +--EXPECT-- diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index c30d86957715c..5dc8a79fae987 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -2403,10 +2403,6 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio APPEND_NODE_1("break"); case ZEND_AST_CONTINUE: APPEND_NODE_1("continue"); - case ZEND_AST_ASSOCIATED_TYPE: - smart_str_appends(str, "type "); - zend_ast_export_name(str, ast->child[0], 0, indent); - break; /* 2 child nodes */ case ZEND_AST_DIM: @@ -2729,6 +2725,16 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio smart_str_appends(str, ": "); ast = ast->child[1]; goto tail_call; + case ZEND_AST_ASSOCIATED_TYPE: + smart_str_appends(str, "type "); + zend_ast_export_name(str, ast->child[0], 0, indent); + if (ast->child[1]) { + smart_str_appends(str, " : "); + smart_str_appends(str, " : "); + zend_ast_export_type(str, ast->child[1], indent); + } + smart_str_appendc(str, ';'); + break; /* 3 child nodes */ case ZEND_AST_METHOD_CALL: diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index cba4782c9becd..81f86f0623f83 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -99,7 +99,6 @@ enum _zend_ast_kind { ZEND_AST_POST_DEC, ZEND_AST_YIELD_FROM, ZEND_AST_CLASS_NAME, - ZEND_AST_ASSOCIATED_TYPE, ZEND_AST_GLOBAL, ZEND_AST_UNSET, @@ -155,6 +154,7 @@ enum _zend_ast_kind { ZEND_AST_MATCH_ARM, ZEND_AST_NAMED_ARG, ZEND_AST_PARENT_PROPERTY_HOOK_CALL, + ZEND_AST_ASSOCIATED_TYPE, /* 3 child nodes */ ZEND_AST_METHOD_CALL = 3 << ZEND_AST_NUM_CHILDREN_SHIFT, diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index ca45e3643686b..f7cffd3c14de6 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -668,8 +668,10 @@ enum_case_expr: ; associated_type: - T_TYPE name ';' - { $$ = zend_ast_create(ZEND_AST_ASSOCIATED_TYPE, $2); } + T_TYPE name ':' type_expr_without_static ';' + { $$ = zend_ast_create(ZEND_AST_ASSOCIATED_TYPE, $2, $4); } + | T_TYPE name ';' + { $$ = zend_ast_create(ZEND_AST_ASSOCIATED_TYPE, $2, NULL); } ; extends_from: From 1691402974c043fc0ff745426a4f08560a760810 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 7 Apr 2025 04:04:20 +0100 Subject: [PATCH 10/41] Add variance checking for constrained associated type --- ...ssociated_type_with_constraint_failed.phpt | 3 +- Zend/zend_compile.c | 42 ++++++++++++++++++- Zend/zend_inheritance.c | 10 +++++ 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/Zend/tests/type_declarations/associated/associated_type_with_constraint_failed.phpt b/Zend/tests/type_declarations/associated/associated_type_with_constraint_failed.phpt index eae1adbe259e2..50f81abcbe830 100644 --- a/Zend/tests/type_declarations/associated/associated_type_with_constraint_failed.phpt +++ b/Zend/tests/type_declarations/associated/associated_type_with_constraint_failed.phpt @@ -13,4 +13,5 @@ class C implements I { } ?> ---EXPECT-- +--EXPECTF-- +Fatal error: Declaration of C::foo(float $param): float must be compatible with I::foo(T $param): T in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index d3d37a1c95533..fe3607ecf4fbb 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -494,7 +494,12 @@ void shutdown_compiler(void) /* {{{ */ CG(unlinked_uses) = NULL; } CG(current_linking_class) = NULL; - ZEND_ASSERT(CG(bound_associated_types) == NULL); + /* This can happen during a fatal error */ + if (CG(bound_associated_types)) { + zend_hash_destroy(CG(bound_associated_types)); + FREE_HASHTABLE(CG(bound_associated_types)); + CG(bound_associated_types) = NULL; + } } /* }}} */ @@ -1436,6 +1441,26 @@ static zend_string *add_intersection_type(zend_string *str, return str; } +static zend_string *add_associated_type(zend_string *associated_type, zend_class_entry *scope) +{ + const zend_type *constraint = zend_hash_find_ptr(scope->associated_types, associated_type); + ZEND_ASSERT(constraint != NULL); + + zend_string *constraint_type_str = zend_type_to_string_resolved(*constraint, scope); + + size_t len = ZSTR_LEN(associated_type) + ZSTR_LEN(constraint_type_str) + strlen("<>"); + zend_string *result = zend_string_alloc(len, 0); + + memcpy(ZSTR_VAL(result), ZSTR_VAL(associated_type), ZSTR_LEN(associated_type)); + ZSTR_VAL(result)[ZSTR_LEN(associated_type)] = '<'; + memcpy(ZSTR_VAL(result) + ZSTR_LEN(associated_type) + 1, ZSTR_VAL(constraint_type_str), ZSTR_LEN(constraint_type_str)); + ZSTR_VAL(result)[len-1] = '>'; + ZSTR_VAL(result)[len] = '\0'; + + zend_string_release(constraint_type_str); + return result; +} + zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry *scope) { zend_string *str = NULL; @@ -1459,6 +1484,8 @@ zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry str = add_type_string(str, resolved, /* is_intersection */ false); zend_string_release(resolved); } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_IS_ASSOCIATED(type)) { + str = add_associated_type(ZEND_TYPE_NAME(type), scope); } else if (ZEND_TYPE_HAS_NAME(type)) { str = resolve_class_name(ZEND_TYPE_NAME(type), scope); } @@ -9031,12 +9058,18 @@ static void zend_compile_use_trait(zend_ast *ast) /* {{{ */ static void zend_associated_table_ht_dtor(zval *val) { /* NO OP as we only use it to be able to refer and save pointers to zend_types */ // TODO do we actually want to store copies of types? + zend_type *associated_type = Z_PTR_P(val); + if (associated_type != &zend_mixed_type) { + zend_type_release(*associated_type, false); + efree(associated_type); + } } static void zend_compile_associated_type(zend_ast *ast) { zend_class_entry *ce = CG(active_class_entry); HashTable *associated_types = ce->associated_types; zend_ast *name_ast = ast->child[0]; + zend_ast *type_ast = ast->child[1]; zend_string *name = zend_ast_get_str(name_ast); if ((ce->ce_flags & ZEND_ACC_INTERFACE) == 0) { @@ -9056,7 +9089,12 @@ static void zend_compile_associated_type(zend_ast *ast) { "Cannot have two associated types with the same name \"%s\"", ZSTR_VAL(name)); } - zend_hash_add_new_ptr(associated_types, name, (void*) &zend_mixed_type); + if (type_ast != NULL) { + zend_type type = zend_compile_typename(type_ast); + zend_hash_add_new_mem(associated_types, name, &type, sizeof(type)); + } else { + zend_hash_add_new_ptr(associated_types, name, (void*) &zend_mixed_type); + } } static void zend_compile_implements(zend_ast *ast) /* {{{ */ diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index fcc48de18f18b..6658ebe15ed18 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -693,6 +693,16 @@ static inheritance_status zend_is_type_subtype_of_associated_type( zend_string *associated_type_name = ZEND_TYPE_NAME(associated_type); const zend_type *bound_type_ptr = zend_hash_find_ptr(CG(bound_associated_types), associated_type_name); if (bound_type_ptr == NULL) { + const zend_type *constraint = zend_hash_find_ptr(associated_type_scope->associated_types, associated_type_name); + ZEND_ASSERT(constraint != NULL); + /* Check that the provided type is a subtype of the constraint */ + const inheritance_status status = zend_perform_covariant_type_check( + concrete_scope, concrete_type_ptr, + associated_type_scope, constraint); + if (status != INHERITANCE_SUCCESS) { + return status; + } + /* Loosing const qualifier here is OK because this hashtable never frees or does anything with the value */ zend_hash_add_new_ptr(CG(bound_associated_types), associated_type_name, (void*)concrete_type_ptr); return INHERITANCE_SUCCESS; From 139bbf6b345a6ec3ab2275f0185a94a4d1742337 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 7 Apr 2025 21:18:08 +0100 Subject: [PATCH 11/41] Add basic support for extending interfaces with associated type --- ..._interface_associated_type_redeclared.phpt | 23 ++++++++++++++++ .../extended_interface_associated_types.phpt | 22 ++++++++++++++++ ...tended_interface_new_associated_types.phpt | 26 +++++++++++++++++++ Zend/zend_inheritance.c | 25 +++++++++++++++++- 4 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/type_declarations/associated/extended_interface_associated_type_redeclared.phpt create mode 100644 Zend/tests/type_declarations/associated/extended_interface_associated_types.phpt create mode 100644 Zend/tests/type_declarations/associated/extended_interface_new_associated_types.phpt diff --git a/Zend/tests/type_declarations/associated/extended_interface_associated_type_redeclared.phpt b/Zend/tests/type_declarations/associated/extended_interface_associated_type_redeclared.phpt new file mode 100644 index 0000000000000..7501eed0e9127 --- /dev/null +++ b/Zend/tests/type_declarations/associated/extended_interface_associated_type_redeclared.phpt @@ -0,0 +1,23 @@ +--TEST-- +Associated type behaviour in extended interface +--FILE-- + +--EXPECTF-- +Fatal error: Cannot redeclare associated type T in interface I2 inherited from interface I in %s on line %d diff --git a/Zend/tests/type_declarations/associated/extended_interface_associated_types.phpt b/Zend/tests/type_declarations/associated/extended_interface_associated_types.phpt new file mode 100644 index 0000000000000..107df2e23ed53 --- /dev/null +++ b/Zend/tests/type_declarations/associated/extended_interface_associated_types.phpt @@ -0,0 +1,22 @@ +--TEST-- +Associated type behaviour in extended interface +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of C::bar(int $o, float $param): float must be compatible with I2::bar(int $o, T $param): T in %s on line %d diff --git a/Zend/tests/type_declarations/associated/extended_interface_new_associated_types.phpt b/Zend/tests/type_declarations/associated/extended_interface_new_associated_types.phpt new file mode 100644 index 0000000000000..65a8f6f515767 --- /dev/null +++ b/Zend/tests/type_declarations/associated/extended_interface_new_associated_types.phpt @@ -0,0 +1,26 @@ +--TEST-- +Associated type behaviour in extended interface +--FILE-- + $o, T<(Traversable&Countable)|int|string> $param): T2 in %s on line %d +//Improve zend_append_type_hint()? +?> +--EXPECTF-- +Fatal error: Declaration of C::bar(float $o, string $param): float must be compatible with I2::bar(T2 $o, T $param): T2 in %s on line %d diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 6658ebe15ed18..12002eedff5be 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2232,8 +2232,31 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * } if (iface->associated_types) { + const uint32_t num_associated_types = zend_hash_num_elements(iface->associated_types); + if (ce->ce_flags & ZEND_ACC_INTERFACE) { + const bool persistent = ce->type == ZEND_INTERNAL_CLASS; + if (ce->associated_types) { + zend_string *associated_type_name; + zend_type *associated_type_ptr; + ZEND_HASH_FOREACH_STR_KEY_PTR(iface->associated_types, associated_type_name, associated_type_ptr) { + if (zend_hash_exists(ce->associated_types, associated_type_name)) { + zend_error_noreturn(E_ERROR, + "Cannot redeclare associated type %s in interface %s inherited from interface %s", + ZSTR_VAL(associated_type_name), ZSTR_VAL(ce->name), ZSTR_VAL(iface->name)); + } + /* Deep copy the type information */ + zend_type_copy_ctor(associated_type_ptr, /* use_arena */ !persistent, /* persistent */ persistent); + zend_hash_add_new_mem(ce->associated_types, associated_type_name, associated_type_ptr, sizeof(*associated_type_ptr)); + } ZEND_HASH_FOREACH_END(); + } else { + ce->associated_types = pemalloc(sizeof(HashTable), persistent); + zend_hash_init(ce->associated_types, num_associated_types, NULL, NULL, false); + zend_hash_copy(ce->associated_types, iface->associated_types, NULL); + } + return; + } HashTable *ht = emalloc(sizeof(HashTable)); - zend_hash_init(ht, zend_hash_num_elements(iface->associated_types), NULL, NULL, false); + zend_hash_init(ht, num_associated_types, NULL, NULL, false); CG(bound_associated_types) = ht; } ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&iface->constants_table, key, c) { From 2b9f0f5cd66d84231f65a48fb435e9800fc7247b Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 28 Apr 2025 13:44:38 +0100 Subject: [PATCH 12/41] Fix namespaces interfering with AT --- .../associated/big_example.phpt | 102 ++++++++++++++++++ Zend/zend_compile.c | 12 +-- 2 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 Zend/tests/type_declarations/associated/big_example.phpt diff --git a/Zend/tests/type_declarations/associated/big_example.phpt b/Zend/tests/type_declarations/associated/big_example.phpt new file mode 100644 index 0000000000000..6a6a4292852a2 --- /dev/null +++ b/Zend/tests/type_declarations/associated/big_example.phpt @@ -0,0 +1,102 @@ +--TEST-- +Concrete example of using AT +--CREDITS-- +Levi Morrison +--FILE-- +strings = $string_table->to_assoc_array(); + } + + function next(): ?StringTablePair + { + $key = \array_key_first($this->strings); + if (!isset($key)) { + return null; + } + $value = \array_shift($this->strings); + return new StringTablePair($key, $value); + } + + /** + * @param callable(Item, Item): Item $f + * @return ?Item + */ + function reduce(callable $f): ?StringTablePair + { + $reduction = $this->next(); + if (!isset($reduction)) { + return null; + } + + while (($next = $this->next()) !== null) { + $reduction = $f($reduction, $next); + } + return $reduction; + } +} + +final class StringTable +{ + private array $strings = ["" => 0]; + + public function __construct() {} + + public function offsetGet(string $offset): int + { + return $this->strings[$offset] ?? throw new \Exception(); + } + + public function offsetExists(string $offset): bool + { + return \isset($this->strings[$offset]); + } + + public function intern(string $str): int + { + return $this->strings[$str] + ?? ($this->strings[$str] = \count($this->strings)); + } + + public function to_sequence(): StringTableSequence + { + return new StringTableSequence($this); + } + + public function to_assoc_array(): array { + return $this->strings; + } +} + +?> +--EXPECTF-- +Fatal error: Declaration of Sequence\StringTableSequence::next(): ?Sequence\StringTablePair must be compatible with Sequence\Sequence::next(): Item|null in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index fe3607ecf4fbb..676147b79e3b7 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7027,15 +7027,15 @@ static zend_type zend_compile_single_typename(zend_ast *ast) } else { const char *correct_name; uint32_t fetch_type = zend_get_class_fetch_type_ast(ast); - zend_string *class_name = type_name; - uint32_t flags = 0; + if (ce && ce->associated_types && zend_hash_exists(ce->associated_types, type_name)) { + return (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(type_name), /* allow null */ false, _ZEND_TYPE_ASSOCIATED_BIT); + } + + zend_string *class_name = type_name; if (fetch_type == ZEND_FETCH_CLASS_DEFAULT) { class_name = zend_resolve_class_name_ast(ast); zend_assert_valid_class_name(class_name, "a type name"); - if (ce && ce->associated_types && zend_hash_exists(ce->associated_types, class_name)) { - flags = _ZEND_TYPE_ASSOCIATED_BIT; - } } else { ZEND_ASSERT(fetch_type == ZEND_FETCH_CLASS_SELF || fetch_type == ZEND_FETCH_CLASS_PARENT); @@ -7078,7 +7078,7 @@ static zend_type zend_compile_single_typename(zend_ast *ast) class_name = zend_new_interned_string(class_name); zend_alloc_ce_cache(class_name); - return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, /* allow null */ false, flags); + return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, /* allow null */ false, 0); } } } From b1a354693fae0bd8d9d1c932f26b653d9db4feb5 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 28 Apr 2025 13:55:33 +0100 Subject: [PATCH 13/41] Improve AT constraint indication when dumping type to string --- .../associated_type_with_constraint_failed.phpt | 2 +- .../type_declarations/associated/big_example.phpt | 2 +- .../extended_interface_new_associated_types.phpt | 4 ++-- Zend/zend_compile.c | 11 +++++++---- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Zend/tests/type_declarations/associated/associated_type_with_constraint_failed.phpt b/Zend/tests/type_declarations/associated/associated_type_with_constraint_failed.phpt index 50f81abcbe830..c9c32c3040ea6 100644 --- a/Zend/tests/type_declarations/associated/associated_type_with_constraint_failed.phpt +++ b/Zend/tests/type_declarations/associated/associated_type_with_constraint_failed.phpt @@ -14,4 +14,4 @@ class C implements I { ?> --EXPECTF-- -Fatal error: Declaration of C::foo(float $param): float must be compatible with I::foo(T $param): T in %s on line %d +Fatal error: Declaration of C::foo(float $param): float must be compatible with I::foo( $param): in %s on line %d diff --git a/Zend/tests/type_declarations/associated/big_example.phpt b/Zend/tests/type_declarations/associated/big_example.phpt index 6a6a4292852a2..a0097f68a5ac3 100644 --- a/Zend/tests/type_declarations/associated/big_example.phpt +++ b/Zend/tests/type_declarations/associated/big_example.phpt @@ -99,4 +99,4 @@ final class StringTable ?> --EXPECTF-- -Fatal error: Declaration of Sequence\StringTableSequence::next(): ?Sequence\StringTablePair must be compatible with Sequence\Sequence::next(): Item|null in %s on line %d +Fatal error: Declaration of Sequence\StringTableSequence::next(): ?Sequence\StringTablePair must be compatible with Sequence\Sequence::next(): |null in %s on line %d diff --git a/Zend/tests/type_declarations/associated/extended_interface_new_associated_types.phpt b/Zend/tests/type_declarations/associated/extended_interface_new_associated_types.phpt index 65a8f6f515767..91925ff1e2a29 100644 --- a/Zend/tests/type_declarations/associated/extended_interface_new_associated_types.phpt +++ b/Zend/tests/type_declarations/associated/extended_interface_new_associated_types.phpt @@ -19,8 +19,8 @@ class C implements I2 { } // TODO: Ideally error message would be: -//Fatal error: Declaration of C::bar(float $o, string $param): float must be compatible with I2::bar(T2 $o, T<(Traversable&Countable)|int|string> $param): T2 in %s on line %d +//Fatal error: Declaration of C::bar(float $o, string $param): float must be compatible with I2::bar( $o, $param): in %s on line %d //Improve zend_append_type_hint()? ?> --EXPECTF-- -Fatal error: Declaration of C::bar(float $o, string $param): float must be compatible with I2::bar(T2 $o, T $param): T2 in %s on line %d +Fatal error: Declaration of C::bar(float $o, string $param): float must be compatible with I2::bar( $o, T $param): in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 676147b79e3b7..910f09a7e5244 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1448,12 +1448,15 @@ static zend_string *add_associated_type(zend_string *associated_type, zend_class zend_string *constraint_type_str = zend_type_to_string_resolved(*constraint, scope); - size_t len = ZSTR_LEN(associated_type) + ZSTR_LEN(constraint_type_str) + strlen("<>"); + size_t len = ZSTR_LEN(associated_type) + ZSTR_LEN(constraint_type_str) + strlen("< : >"); zend_string *result = zend_string_alloc(len, 0); - memcpy(ZSTR_VAL(result), ZSTR_VAL(associated_type), ZSTR_LEN(associated_type)); - ZSTR_VAL(result)[ZSTR_LEN(associated_type)] = '<'; - memcpy(ZSTR_VAL(result) + ZSTR_LEN(associated_type) + 1, ZSTR_VAL(constraint_type_str), ZSTR_LEN(constraint_type_str)); + ZSTR_VAL(result)[0] = '<'; + memcpy(ZSTR_VAL(result) + strlen("<"), ZSTR_VAL(associated_type), ZSTR_LEN(associated_type)); + ZSTR_VAL(result)[ZSTR_LEN(associated_type) + 1] = ' '; + ZSTR_VAL(result)[ZSTR_LEN(associated_type) + 2] = ':'; + ZSTR_VAL(result)[ZSTR_LEN(associated_type) + 3] = ' '; + memcpy(ZSTR_VAL(result) + ZSTR_LEN(associated_type) + strlen("< : "), ZSTR_VAL(constraint_type_str), ZSTR_LEN(constraint_type_str)); ZSTR_VAL(result)[len-1] = '>'; ZSTR_VAL(result)[len] = '\0'; From 363f817441de73a70510fcc07e71a8fed4f25d41 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 28 Apr 2025 14:04:30 +0100 Subject: [PATCH 14/41] Fix various unhandled cases where AT is part of a union type --- .../associated_type_cannot_be_in_union4.phpt | 13 +++++++++++++ .../associated_type_cannot_be_in_union5.phpt | 13 +++++++++++++ .../type_declarations/associated/big_example.phpt | 2 +- Zend/zend_compile.c | 6 ++++++ 4 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union4.phpt create mode 100644 Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union5.phpt diff --git a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union4.phpt b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union4.phpt new file mode 100644 index 0000000000000..237cd54a52898 --- /dev/null +++ b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union4.phpt @@ -0,0 +1,13 @@ +--TEST-- +Associated type cannot be in union (nullable type union with ?) +--FILE-- + +--EXPECTF-- +Fatal error: Associated type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union5.phpt b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union5.phpt new file mode 100644 index 0000000000000..b9e8d3e5090f9 --- /dev/null +++ b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union5.phpt @@ -0,0 +1,13 @@ +--TEST-- +Associated type cannot be in union (forced allowed null) +--FILE-- + +--EXPECTF-- +Fatal error: Associated type cannot be part of a union type (implicitly nullable due to default null value) in %s on line %d diff --git a/Zend/tests/type_declarations/associated/big_example.phpt b/Zend/tests/type_declarations/associated/big_example.phpt index a0097f68a5ac3..32986af27150c 100644 --- a/Zend/tests/type_declarations/associated/big_example.phpt +++ b/Zend/tests/type_declarations/associated/big_example.phpt @@ -99,4 +99,4 @@ final class StringTable ?> --EXPECTF-- -Fatal error: Declaration of Sequence\StringTableSequence::next(): ?Sequence\StringTablePair must be compatible with Sequence\Sequence::next(): |null in %s on line %d +Fatal error: Associated type cannot be part of a union type in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 910f09a7e5244..afb44523a62ae 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7384,6 +7384,12 @@ static zend_type zend_compile_typename_ex( if ((type_mask & MAY_BE_NULL) && is_marked_nullable) { zend_error_noreturn(E_COMPILE_ERROR, "null cannot be marked as nullable"); } + if (ZEND_TYPE_IS_ASSOCIATED(type) && is_marked_nullable) { + zend_error_noreturn(E_COMPILE_ERROR, "Associated type cannot be part of a union type"); + } + if (ZEND_TYPE_IS_ASSOCIATED(type) && force_allow_null) { + zend_error_noreturn(E_COMPILE_ERROR, "Associated type cannot be part of a union type (implicitly nullable due to default null value)"); + } if (force_allow_null && !is_marked_nullable && !(type_mask & MAY_BE_NULL)) { *forced_allow_null = true; From 2198aaf5728484038a889c27c05069a8f4f24ca9 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 6 May 2025 15:47:51 +0100 Subject: [PATCH 15/41] [skip ci] Get explicit notation working Have a HT null access somewhere... --- .../abstract_generic_001.phpt | 31 +++++ ...abstract_generic_type_with_constraint.phpt | 31 +++++ ...t_generic_type_with_constraint_failed.phpt | 16 +++ ...ct_generic_cannot_be_in_intersection1.phpt | 12 ++ ...ct_generic_cannot_be_in_intersection2.phpt | 12 ++ .../abstract_generic_cannot_be_in_union1.phpt | 12 ++ .../abstract_generic_cannot_be_in_union2.phpt | 12 ++ .../abstract_generic_cannot_be_in_union3.phpt | 12 ++ .../abstract_generic_cannot_be_in_union4.phpt | 12 ++ .../abstract_generic_cannot_be_in_union5.phpt | 12 ++ .../errors/abstract_generic_in_class.phpt | 12 ++ .../errors/abstract_generic_in_trait.phpt | 12 ++ .../errors/abstract_generic_redeclared.phpt | 12 ++ .../no_bound_abstract_generic_type.phpt | 16 +++ ...abstract_generic_type_with_constraint.phpt | 16 +++ ...t_generic_type_with_prior_bound_types.phpt | 20 ++++ ...nded_interface_abstract_generic_types.phpt | 21 ++++ ...d_interface_new_abstract_generic_type.phpt | 22 ++++ ..._interface_new_abstract_generic_type2.phpt | 22 ++++ .../multiple_abstract_generic_type.phpt | 72 ++++++++++++ Zend/zend.h | 3 + Zend/zend_ast.h | 4 + Zend/zend_compile.c | 109 +++++++++++++++++- Zend/zend_inheritance.c | 86 ++++++++++++++ Zend/zend_language_parser.y | 81 +++++++++---- Zend/zend_opcode.c | 22 ++++ Zend/zend_types.h | 5 + 27 files changed, 672 insertions(+), 25 deletions(-) create mode 100644 Zend/tests/type_declarations/abstract_generics/abstract_generic_001.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/abstract_generic_type_with_constraint.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/abstract_generic_type_with_constraint_failed.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection1.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection2.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union1.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union2.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union3.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union4.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union5.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_class.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_trait.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_redeclared.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_constraint.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_prior_bound_types.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type2.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/multiple_abstract_generic_type.phpt diff --git a/Zend/tests/type_declarations/abstract_generics/abstract_generic_001.phpt b/Zend/tests/type_declarations/abstract_generics/abstract_generic_001.phpt new file mode 100644 index 0000000000000..251724b6dd840 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/abstract_generic_001.phpt @@ -0,0 +1,31 @@ +--TEST-- +Abstract generic types basic +--FILE-- + { + public function foo(T $param): T; +} + +class CS implements I { + public function foo(string $param): string { + return $param . '!'; + } +} + +class CI implements I { + public function foo(int $param): int { + return $param + 42; + } +} + +$cs = new CS(); +var_dump($cs->foo("Hello")); + +$ci = new CI(); +var_dump($ci->foo(5)); + +?> +--EXPECT-- +string(6) "Hello!" +int(47) diff --git a/Zend/tests/type_declarations/abstract_generics/abstract_generic_type_with_constraint.phpt b/Zend/tests/type_declarations/abstract_generics/abstract_generic_type_with_constraint.phpt new file mode 100644 index 0000000000000..2f1c57492c833 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/abstract_generic_type_with_constraint.phpt @@ -0,0 +1,31 @@ +--TEST-- +Abstract generic type with a constraint +--FILE-- + { + public function foo(T $param): T; +} + +class CS implements I { + public function foo(string $param): string { + return $param . '!'; + } +} + +class CI implements I { + public function foo(int $param): int { + return $param + 42; + } +} + +$cs = new CS(); +var_dump($cs->foo("Hello")); + +$ci = new CI(); +var_dump($ci->foo(5)); + +?> +--EXPECT-- +string(6) "Hello!" +int(47) diff --git a/Zend/tests/type_declarations/abstract_generics/abstract_generic_type_with_constraint_failed.phpt b/Zend/tests/type_declarations/abstract_generics/abstract_generic_type_with_constraint_failed.phpt new file mode 100644 index 0000000000000..440d1868e6162 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/abstract_generic_type_with_constraint_failed.phpt @@ -0,0 +1,16 @@ +--TEST-- +Abstract generic type with a constraint that is not satisfied +--FILE-- + { + public function foo(T $param): T; +} + +class C implements I { + public function foo(float $param): float {} +} + +?> +--EXPECTF-- +Fatal error: Bound type float is not a subtype of the constraint type string|int of generic type T of interface I in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection1.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection1.phpt new file mode 100644 index 0000000000000..1cce2662bbaa6 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection1.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type cannot be in intersection (simple intersection with class type) +--FILE-- + { + public function foo(T&Traversable $param): T&Traversable; +} + +?> +--EXPECTF-- +Fatal error: Associated type cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection2.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection2.phpt new file mode 100644 index 0000000000000..734af57a3e48d --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection2.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type cannot be in intersection (DNF type) +--FILE-- + { + public function foo(stdClass|(T&Traversable) $param): stdClass|(T&Traversable); +} + +?> +--EXPECTF-- +Fatal error: Associated type cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union1.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union1.phpt new file mode 100644 index 0000000000000..becef34df0c8b --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union1.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type cannot be in union (simple union with built-in type) +--FILE-- + { + public function foo(T|int $param): T|int; +} + +?> +--EXPECTF-- +Fatal error: Associated type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union2.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union2.phpt new file mode 100644 index 0000000000000..4fd8b1d6be77e --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union2.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type cannot be in union (simple union with class type) +--FILE-- + { + public function foo(T|stdClass $param): T|stdClass; +} + +?> +--EXPECTF-- +Fatal error: Associated type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union3.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union3.phpt new file mode 100644 index 0000000000000..33e17d1827871 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union3.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type cannot be in union (DNF type) +--FILE-- + { + public function foo(T|(Traversable&Countable) $param): T|(Traversable&Countable); +} + +?> +--EXPECTF-- +Fatal error: Associated type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union4.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union4.phpt new file mode 100644 index 0000000000000..75798214922e5 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union4.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type cannot be in union (nullable type union with ?) +--FILE-- + { + public function foo(?T $param): ?T; +} + +?> +--EXPECTF-- +Fatal error: Associated type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union5.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union5.phpt new file mode 100644 index 0000000000000..1fe8e1aee1705 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union5.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type cannot be in union (forced allowed null) +--FILE-- + { + public function foo(T $param = null): T; +} + +?> +--EXPECTF-- +Fatal error: Associated type cannot be part of a union type (implicitly nullable due to default null value) in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_class.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_class.phpt new file mode 100644 index 0000000000000..a037af45bf87d --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_class.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type in class is invalid +--FILE-- + { + public function foo(T $param): T; +} + +?> +--EXPECTF-- +Parse error: syntax error, unexpected token "<", expecting "{" in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_trait.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_trait.phpt new file mode 100644 index 0000000000000..b00a523069a6b --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_trait.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type in trait is invalid +--FILE-- + { + public function foo(T $param): T; +} + +?> +--EXPECTF-- +Parse error: syntax error, unexpected token "<", expecting "{" in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_redeclared.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_redeclared.phpt new file mode 100644 index 0000000000000..148baa42f3b02 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_redeclared.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type that is redeclared +--FILE-- + { + public function foo(T&Traversable $param): T&Traversable; +} + +?> +--EXPECTF-- +Fatal error: Duplicate generic parameter T in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type.phpt b/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type.phpt new file mode 100644 index 0000000000000..aeca6dfc84271 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type.phpt @@ -0,0 +1,16 @@ +--TEST-- +Implementing class does not bind any abstract generic type +--FILE-- + { + public function foo(T $param): T; +} + +class C implements I { + public function foo(float $param): float {} +} + +?> +--EXPECTF-- +Fatal error: Cannot implement I as it has generic parameters which are not specified in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_constraint.phpt b/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_constraint.phpt new file mode 100644 index 0000000000000..b74f4593d548a --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_constraint.phpt @@ -0,0 +1,16 @@ +--TEST-- +Implementing class does not bind any abstract generic type +--FILE-- + { + public function foo(T $param): T; +} + +class C implements I { + public function foo(float $param): float {} +} + +?> +--EXPECTF-- +Fatal error: Cannot implement I as it has generic parameters which are not specified in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_prior_bound_types.phpt b/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_prior_bound_types.phpt new file mode 100644 index 0000000000000..8e8f6e66e65a8 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_prior_bound_types.phpt @@ -0,0 +1,20 @@ +--TEST-- +Implementing class does not bind any abstract generic type +--FILE-- + { + public function foo(T $param): T; +} +interface I2 { + public function bar(T $param): T; +} + +class C implements I1, I2 { + public function foo(float $param): float {} + public function bar(string $param): string {} +} + +?> +--EXPECTF-- +Fatal error: Cannot implement I2 as it has generic parameters which are not specified in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types.phpt new file mode 100644 index 0000000000000..da395e28caf5d --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types.phpt @@ -0,0 +1,21 @@ +--TEST-- +Abstract generic type behaviour in extended interface +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I { + public function bar(int $o, T $param): T; +} + +class C implements I2 { + public function foo(string $param): string {} + public function bar(int $o, float $param): float {} +} + +?> +--EXPECTF-- +Fatal error: Constraint type mixed of generic type T of interface I2 is not a subtype of the constraint type (Traversable&Countable)|string|int of generic type T of interface I in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type.phpt new file mode 100644 index 0000000000000..8fa85be75ca2c --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type.phpt @@ -0,0 +1,22 @@ +--TEST-- +Abstract generic type behaviour in extended interface +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I { + public function bar(T2 $o, T $param): T2; +} + +class C implements I2 { + public function foo(string $param): string {} + public function bar(float $o, string $param): float {} +} + +?> +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type2.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type2.phpt new file mode 100644 index 0000000000000..784fd9a0aa33b --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type2.phpt @@ -0,0 +1,22 @@ +--TEST-- +Abstract generic type behaviour in extended interface +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I { + public function bar(T $o, T2 $param): T; +} + +class C implements I2 { + public function foo(string $param): string {} + public function bar(float $o, string $param): float {} +} + +?> +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/type_declarations/abstract_generics/multiple_abstract_generic_type.phpt b/Zend/tests/type_declarations/abstract_generics/multiple_abstract_generic_type.phpt new file mode 100644 index 0000000000000..63b3bf6f202f7 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/multiple_abstract_generic_type.phpt @@ -0,0 +1,72 @@ +--TEST-- +Multiple abstract generic type +--FILE-- + { + public function set(K $key, V $value): void; + public function get(K $key): V; +} + +class C1 implements I { + public array $a = []; + public function set(int $key, string $value): void { + $this->a[$key] = $value . '!'; + } + public function get(int $key): string { + return $this->a[$key]; + } +} + +class C2 implements I { + public array $a = []; + public function set(string $key, object $value): void { + $this->a[$key] = $value; + } + public function get(string $key): object { + return $this->a[$key]; + } +} + +$c1 = new C1(); +$c1->set(5, "Hello"); +var_dump($c1->a); +var_dump($c1->get(5)); + +$c2 = new C2(); +$c2->set('C1', $c1); +var_dump($c2->a); +var_dump($c2->get('C1')); + +try { + $c1->set('blah', "Hello"); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + + +?> +--EXPECTF-- +array(1) { + [5]=> + string(6) "Hello!" +} +string(6) "Hello!" +array(1) { + ["C1"]=> + object(C1)#1 (1) { + ["a"]=> + array(1) { + [5]=> + string(6) "Hello!" + } + } +} +object(C1)#1 (1) { + ["a"]=> + array(1) { + [5]=> + string(6) "Hello!" + } +} +TypeError: C1::set(): Argument #1 ($key) must be of type int, string given, called in %s on line %d diff --git a/Zend/zend.h b/Zend/zend.h index cff81284740c1..8520133e8ef89 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -220,6 +220,9 @@ struct _zend_class_entry { /* Only for interfaces */ HashTable *associated_types; + HashTable *bound_types; + zend_generic_parameter *generic_parameters; + uint32_t num_generic_parameters; uint32_t enum_backing_type; HashTable *backed_enum_table; diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 81f86f0623f83..9df7d81373e86 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -70,6 +70,8 @@ enum _zend_ast_kind { ZEND_AST_ATTRIBUTE_GROUP, ZEND_AST_MATCH_ARM_LIST, ZEND_AST_MODIFIER_LIST, + ZEND_AST_GENERIC_PARAM_LIST, + ZEND_AST_GENERIC_ARG_LIST, /* 0 child nodes */ ZEND_AST_MAGIC_CONST = 0 << ZEND_AST_NUM_CHILDREN_SHIFT, @@ -155,6 +157,8 @@ enum _zend_ast_kind { ZEND_AST_NAMED_ARG, ZEND_AST_PARENT_PROPERTY_HOOK_CALL, ZEND_AST_ASSOCIATED_TYPE, + ZEND_AST_GENERIC_PARAM, + ZEND_AST_CLASS_REF, /* 3 child nodes */ ZEND_AST_METHOD_CALL = 3 << ZEND_AST_NUM_CHILDREN_SHIFT, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index afb44523a62ae..e84b18bedc4d7 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1805,6 +1805,17 @@ static zend_string *zend_resolve_const_class_name_reference(zend_ast *ast, const return zend_resolve_class_name(class_name, ast->attr); } +static zend_string *zend_resolve_const_class_name_reference_with_generics(zend_ast *ast, const char *type) +{ + zend_string *class_name = zend_ast_get_str(ast->child[0]); + if (ZEND_FETCH_CLASS_DEFAULT != zend_get_class_fetch_type_ast(ast)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use \"%s\" as %s, as it is reserved", + ZSTR_VAL(class_name), type); + } + return zend_resolve_class_name(class_name, ast->attr); +} + static void zend_ensure_valid_class_fetch_type(uint32_t fetch_type) /* {{{ */ { if (fetch_type != ZEND_FETCH_CLASS_DEFAULT && zend_is_scope_known()) { @@ -2107,6 +2118,10 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, bool nullify_hand ce->properties_info_table = NULL; ce->attributes = NULL; ce->associated_types = NULL; + ce->bound_types = NULL; + // TODO Should these be inside nullify_handlers? + ce->generic_parameters = NULL; + ce->num_generic_parameters = 0; ce->enum_backing_type = IS_UNDEF; ce->backed_enum_table = NULL; @@ -7031,9 +7046,18 @@ static zend_type zend_compile_single_typename(zend_ast *ast) const char *correct_name; uint32_t fetch_type = zend_get_class_fetch_type_ast(ast); + // TODO Old version to remove if (ce && ce->associated_types && zend_hash_exists(ce->associated_types, type_name)) { return (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(type_name), /* allow null */ false, _ZEND_TYPE_ASSOCIATED_BIT); } + if (ce && ce->num_generic_parameters > 0) { + for (uint32_t generic_param_index = 0; generic_param_index < ce->num_generic_parameters; generic_param_index++) { + const zend_generic_parameter *genric_param = &ce->generic_parameters[generic_param_index]; + if (zend_string_equals(type_name, genric_param->name)) { + return (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(type_name), /* allow null */ false, _ZEND_TYPE_ASSOCIATED_BIT); + } + } + } zend_string *class_name = type_name; if (fetch_type == ZEND_FETCH_CLASS_DEFAULT) { @@ -9074,6 +9098,7 @@ static void zend_associated_table_ht_dtor(zval *val) { } } +// TODO Remove static void zend_compile_associated_type(zend_ast *ast) { zend_class_entry *ce = CG(active_class_entry); HashTable *associated_types = ce->associated_types; @@ -9106,20 +9131,53 @@ static void zend_compile_associated_type(zend_ast *ast) { } } +static void zend_bound_types_ht_dtor(zval *ptr) { + HashTable *interface_bound_types = Z_PTR_P(ptr); + zend_hash_destroy(interface_bound_types); + efree(interface_bound_types); +} + +static void zend_types_ht_dtor(zval *ptr) { + zend_type *type = Z_PTR_P(ptr); + // TODO Figure out persistency? + zend_type_release(*type, false); + efree(type); +} + static void zend_compile_implements(zend_ast *ast) /* {{{ */ { zend_ast_list *list = zend_ast_get_list(ast); zend_class_entry *ce = CG(active_class_entry); zend_class_name *interface_names; - uint32_t i; interface_names = emalloc(sizeof(zend_class_name) * list->children); - for (i = 0; i < list->children; ++i) { - zend_ast *class_ast = list->child[i]; + for (uint32_t i = 0; i < list->children; ++i) { + zend_ast *interface_ast = list->child[i]; interface_names[i].name = - zend_resolve_const_class_name_reference(class_ast, "interface name"); + zend_resolve_const_class_name_reference_with_generics(interface_ast, "interface name"); interface_names[i].lc_name = zend_string_tolower(interface_names[i].name); + + // TODO, need the list to a type list + if (interface_ast->child[1]) { + const zend_ast_list *generics_list = zend_ast_get_list(interface_ast->child[1]); + const uint32_t num_generic_args = generics_list->children; + + // TODO Check that we have the same number of generic args? + if (ce->bound_types == NULL) { + ALLOC_HASHTABLE(ce->bound_types); + zend_hash_init(ce->bound_types, list->children-i, NULL, zend_bound_types_ht_dtor, false /* todo depend on internal or not */); + } + + HashTable *bound_interface_types; + ALLOC_HASHTABLE(bound_interface_types); + zend_hash_init(bound_interface_types, num_generic_args, NULL, zend_types_ht_dtor, false /* todo depend on internal or not */); + for (uint32_t generic_param = 0; generic_param < num_generic_args; ++generic_param) { + zend_type bound_type = zend_compile_typename(generics_list->child[generic_param]); + zend_hash_index_add_mem(bound_interface_types, generic_param, &bound_type, sizeof(bound_type)); + } + zend_hash_add_new_ptr(ce->bound_types, interface_names[i].lc_name, bound_interface_types); + } } ce->num_interfaces = list->children; @@ -9167,6 +9225,45 @@ static void zend_compile_enum_backing_type(zend_class_entry *ce, zend_ast *enum_ zend_type_release(type, 0); } + +static void zend_compile_generic_params(zend_ast *params_ast) +{ + const zend_ast_list *list = zend_ast_get_list(params_ast); + zend_generic_parameter *generic_params = emalloc(list->children * sizeof(zend_generic_parameter)); + CG(active_class_entry)->generic_parameters = generic_params; + + for (uint32_t i = 0; i < list->children; i++) { + const zend_ast *param_ast = list->child[i]; + zend_string *name = zend_ast_get_str(param_ast->child[0]); + zend_type constraint_type = zend_mixed_type; + + if (zend_string_equals(name, CG(active_class_entry)->name)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Generic parameter %s has same name as class", ZSTR_VAL(name)); + } + + for (uint32_t j = 0; j < i; j++) { + if (zend_string_equals(name, generic_params[j].name)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Duplicate generic parameter %s", ZSTR_VAL(name)); + } + } + + if (param_ast->child[1]) { + // TODO Need to free this + constraint_type = zend_compile_typename(param_ast->child[1]); + // TODO Validate that void, static, never are not used in the constraint? + } + + generic_params[i].name = zend_string_copy(name); + generic_params[i].constraint = constraint_type; + + /* Update number of parameters on the fly, so that previous parameters can be + * referenced in the type constraint of following parameters. */ + CG(active_class_entry)->num_generic_parameters = i + 1; + } +} + static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */ { zend_ast_decl *decl = (zend_ast_decl *) ast; @@ -9261,6 +9358,10 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) zend_compile_attributes(&ce->attributes, decl->child[3], 0, ZEND_ATTRIBUTE_TARGET_CLASS, 0); } + if (ce->ce_flags & ZEND_ACC_INTERFACE && decl->child[4]) { + zend_compile_generic_params(decl->child[4]); + } + if (implements_ast) { zend_compile_implements(implements_ast); } diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 12002eedff5be..3fa7367fa04fb 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -692,6 +692,7 @@ static inheritance_status zend_is_type_subtype_of_associated_type( zend_string *associated_type_name = ZEND_TYPE_NAME(associated_type); const zend_type *bound_type_ptr = zend_hash_find_ptr(CG(bound_associated_types), associated_type_name); + ZEND_ASSERT(bound_type_ptr != NULL); if (bound_type_ptr == NULL) { const zend_type *constraint = zend_hash_find_ptr(associated_type_scope->associated_types, associated_type_name); ZEND_ASSERT(constraint != NULL); @@ -2231,6 +2232,7 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * ZEND_INHERITANCE_RESET_CHILD_OVERRIDE; } + // TODO Old way to remove if (iface->associated_types) { const uint32_t num_associated_types = zend_hash_num_elements(iface->associated_types); if (ce->ce_flags & ZEND_ACC_INTERFACE) { @@ -2259,6 +2261,90 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * zend_hash_init(ht, num_associated_types, NULL, NULL, false); CG(bound_associated_types) = ht; } + if (iface->num_generic_parameters > 0) { + if (UNEXPECTED(ce->bound_types == NULL)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot implement %s as it has generic parameters which are not specified", + ZSTR_VAL(iface->name) + ); + } + const HashTable *bound_types = zend_hash_find_ptr_lc(ce->bound_types, iface->name); + if (UNEXPECTED(bound_types == NULL)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot implement %s as it has generic parameters which are not specified", + ZSTR_VAL(iface->name) + ); + } + const uint32_t num_bound_types = zend_hash_num_elements(bound_types); + if (UNEXPECTED(num_bound_types != iface->num_generic_parameters)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot implement %s as the number of generic arguments specified (%" PRIu32 ") does not match the number of generic parameters declared on the interface (%" PRIu32 ")", + ZSTR_VAL(iface->name), + num_bound_types, + iface->num_generic_parameters + ); + } + HashTable *ht = emalloc(sizeof(HashTable)); + zend_hash_init(ht, num_bound_types, NULL, NULL, false); + CG(bound_associated_types) = ht; + for (uint32_t i = 0; i < num_bound_types; i++) { + const zend_generic_parameter *generic_parameter = &iface->generic_parameters[i]; + const zend_type* generic_constraint = &generic_parameter->constraint; + zend_type *bound_type_ptr = zend_hash_index_find_ptr(bound_types, i); + ZEND_ASSERT(bound_type_ptr != NULL); + + /* We are currently extending another interface */ + if (ZEND_TYPE_IS_ASSOCIATED(*bound_type_ptr)) { + ZEND_ASSERT(ce->ce_flags & ZEND_ACC_INTERFACE); + ZEND_ASSERT(ce->num_generic_parameters > 0); + ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*bound_type_ptr)); + const zend_string *current_generic_param_name = ZEND_TYPE_NAME(*bound_type_ptr); + for (uint32_t j = 0; j < ce->num_generic_parameters; j++) { + const zend_generic_parameter *current_ce_generic_parameter = &ce->generic_parameters[j]; + if (!zend_string_equals(current_ce_generic_parameter->name, current_generic_param_name)) { + continue; + } + const zend_type *current_ce_generic_type_constraint = ¤t_ce_generic_parameter->constraint; + ZEND_ASSERT(current_ce_generic_type_constraint != NULL); + if (zend_perform_covariant_type_check(ce, current_ce_generic_type_constraint, iface, generic_constraint) != INHERITANCE_SUCCESS) { + zend_string *current_ce_constraint_type_str = zend_type_to_string(*current_ce_generic_type_constraint); + zend_string *constraint_type_str = zend_type_to_string(generic_parameter->constraint); + zend_error_noreturn(E_COMPILE_ERROR, + "Constraint type %s of generic type %s of interface %s is not a subtype of the constraint type %s of generic type %s of interface %s", + ZSTR_VAL(current_ce_constraint_type_str), + ZSTR_VAL(current_ce_generic_parameter->name), + ZSTR_VAL(ce->name), + ZSTR_VAL(constraint_type_str), + ZSTR_VAL(generic_parameter->name), + ZSTR_VAL(iface->name) + ); + zend_string_release(current_ce_constraint_type_str); + zend_string_release(constraint_type_str); + return; + } + /* Loosing const qualifier here is OK because this hashtable never frees or does anything with the value */ + zend_hash_add_new_ptr(ht, generic_parameter->name, (void*)current_ce_generic_type_constraint); + break; + } + } else { + if (zend_perform_covariant_type_check(ce, bound_type_ptr, iface, generic_constraint) != INHERITANCE_SUCCESS) { + zend_string *bound_type_str = zend_type_to_string(*bound_type_ptr); + zend_string *constraint_type_str = zend_type_to_string(generic_parameter->constraint); + zend_error_noreturn(E_COMPILE_ERROR, + "Bound type %s is not a subtype of the constraint type %s of generic type %s of interface %s", + ZSTR_VAL(bound_type_str), + ZSTR_VAL(constraint_type_str), + ZSTR_VAL(generic_parameter->name), + ZSTR_VAL(iface->name) + ); + zend_string_release(bound_type_str); + zend_string_release(constraint_type_str); + return; + } + zend_hash_add_new_ptr(ht, generic_parameter->name, bound_type_ptr); + } + } + } ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&iface->constants_table, key, c) { do_inherit_iface_constant(key, c, ce, iface); } ZEND_HASH_FOREACH_END(); diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index f7cffd3c14de6..f624aa10c7d91 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -262,7 +262,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type static_var class_statement trait_adaptation trait_precedence trait_alias %type absolute_trait_method_reference trait_method_reference property echo_expr %type new_dereferenceable new_non_dereferenceable anonymous_class class_name class_name_reference simple_variable -%type internal_functions_in_yacc +%type internal_functions_in_yacc simple_class_name generic_arg_list %type scalar backticks_expr lexical_var function_call member_name property_name %type variable_class_name dereferenceable_scalar constant class_constant %type fully_dereferenceable array_object_dereferenceable @@ -288,6 +288,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type property_hook property_hook_list optional_property_hook_list hooked_property property_hook_body %type optional_parameter_list %type associated_type +%type optional_generic_params generic_params generic_param class_name_with_generics_list %type returns_ref function fn is_reference is_variadic property_modifiers property_hook_modifiers %type method_modifiers class_const_modifiers member_modifier optional_cpp_modifiers @@ -365,9 +366,9 @@ name: ; attribute_decl: - class_name + simple_class_name { $$ = zend_ast_create(ZEND_AST_ATTRIBUTE, $1, NULL); } - | class_name argument_list + | simple_class_name argument_list { $$ = zend_ast_create(ZEND_AST_ATTRIBUTE, $1, $2); } ; @@ -552,8 +553,8 @@ catch_list: ; catch_name_list: - class_name { $$ = zend_ast_create_list(1, ZEND_AST_NAME_LIST, $1); } - | catch_name_list '|' class_name { $$ = zend_ast_list_add($1, $3); } + simple_class_name { $$ = zend_ast_create_list(1, ZEND_AST_NAME_LIST, $1); } + | catch_name_list '|' simple_class_name { $$ = zend_ast_list_add($1, $3); } ; optional_variable: @@ -642,8 +643,8 @@ trait_declaration_statement: interface_declaration_statement: T_INTERFACE { $$ = CG(zend_lineno); } - T_STRING interface_extends_list backup_doc_comment '{' class_statement_list '}' - { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_INTERFACE, $2, $5, zend_ast_get_str($3), NULL, $4, $7, NULL, NULL); } + T_STRING optional_generic_params interface_extends_list backup_doc_comment '{' class_statement_list '}' + { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_INTERFACE, $2, $6, zend_ast_get_str($3), NULL, $5, $8, NULL, $4); } ; enum_declaration_statement: @@ -667,6 +668,25 @@ enum_case_expr: | '=' expr { $$ = $2; } ; +optional_generic_params: + %empty { $$ = NULL; } + | '<' generic_params '>' { $$ = $2; } +; + +generic_params: + generic_param + { $$ = zend_ast_create_list(1, ZEND_AST_GENERIC_PARAM_LIST, $1); } + | generic_params ',' generic_param + { $$ = zend_ast_list_add($1, $3); } +; + +generic_param: + T_STRING + { $$ = zend_ast_create(ZEND_AST_GENERIC_PARAM, $1, NULL); } + | T_STRING ':' type_expr + { $$ = zend_ast_create(ZEND_AST_GENERIC_PARAM, $1, $3); } +; + associated_type: T_TYPE name ':' type_expr_without_static ';' { $$ = zend_ast_create(ZEND_AST_ASSOCIATED_TYPE, $2, $4); } @@ -676,17 +696,17 @@ associated_type: extends_from: %empty { $$ = NULL; } - | T_EXTENDS class_name { $$ = $2; } + | T_EXTENDS simple_class_name { $$ = $2; } ; interface_extends_list: %empty { $$ = NULL; } - | T_EXTENDS class_name_list { $$ = $2; } + | T_EXTENDS class_name_with_generics_list { $$ = $2; } ; implements_list: %empty { $$ = NULL; } - | T_IMPLEMENTS class_name_list { $$ = $2; } + | T_IMPLEMENTS class_name_with_generics_list { $$ = $2; } ; foreach_variable: @@ -986,8 +1006,13 @@ class_statement: ; class_name_list: + simple_class_name { $$ = zend_ast_create_list(1, ZEND_AST_NAME_LIST, $1); } + | class_name_list ',' simple_class_name { $$ = zend_ast_list_add($1, $3); } +; + +class_name_with_generics_list: class_name { $$ = zend_ast_create_list(1, ZEND_AST_NAME_LIST, $1); } - | class_name_list ',' class_name { $$ = zend_ast_list_add($1, $3); } + | class_name_with_generics_list ',' class_name { $$ = zend_ast_list_add($1, $3); } ; trait_adaptations: @@ -1039,7 +1064,7 @@ trait_method_reference: ; absolute_trait_method_reference: - class_name T_PAAMAYIM_NEKUDOTAYIM identifier + simple_class_name T_PAAMAYIM_NEKUDOTAYIM identifier { $$ = zend_ast_create(ZEND_AST_METHOD_REFERENCE, $1, $3); } ; @@ -1418,7 +1443,7 @@ function_call: if (zend_lex_tstring(&zv, $1) == FAILURE) { YYABORT; } $$ = zend_ast_create(ZEND_AST_CALL, zend_ast_create_zval(&zv), $2); } - | class_name T_PAAMAYIM_NEKUDOTAYIM member_name argument_list + | simple_class_name T_PAAMAYIM_NEKUDOTAYIM member_name argument_list { $$ = zend_ast_create(ZEND_AST_STATIC_CALL, $1, $3, $4); } | variable_class_name T_PAAMAYIM_NEKUDOTAYIM member_name argument_list { $$ = zend_ast_create(ZEND_AST_STATIC_CALL, $1, $3, $4); } @@ -1428,17 +1453,31 @@ function_call: } ; -class_name: +simple_class_name: T_STATIC { zval zv; ZVAL_INTERNED_STR(&zv, ZSTR_KNOWN(ZEND_STR_STATIC)); $$ = zend_ast_create_zval_ex(&zv, ZEND_NAME_NOT_FQ); } | name { $$ = $1; } ; +class_name: + simple_class_name + { $$ = zend_ast_create(ZEND_AST_CLASS_REF, $1, NULL); } + | simple_class_name '<' generic_arg_list '>' + { $$ = zend_ast_create(ZEND_AST_CLASS_REF, $1, $3); } +; + +generic_arg_list: + type_expr + { $$ = zend_ast_create_list(1, ZEND_AST_GENERIC_ARG_LIST, $1); } + | generic_arg_list ',' type_expr + { $$ = zend_ast_list_add($1, $3); } +; + class_name_reference: - class_name { $$ = $1; } - | new_variable { $$ = $1; } - | '(' expr ')' { $$ = $2; } + simple_class_name { $$ = $1; } + | new_variable { $$ = $1; } + | '(' expr ')' { $$ = $2; } ; backticks_expr: @@ -1488,11 +1527,11 @@ constant: ; class_constant: - class_name T_PAAMAYIM_NEKUDOTAYIM identifier + simple_class_name T_PAAMAYIM_NEKUDOTAYIM identifier { $$ = zend_ast_create_class_const_or_name($1, $3); } | variable_class_name T_PAAMAYIM_NEKUDOTAYIM identifier { $$ = zend_ast_create_class_const_or_name($1, $3); } - | class_name T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' + | simple_class_name T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' { $$ = zend_ast_create(ZEND_AST_CLASS_CONST, $1, $4); } | variable_class_name T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' { $$ = zend_ast_create(ZEND_AST_CLASS_CONST, $1, $4); } @@ -1560,7 +1599,7 @@ simple_variable: ; static_member: - class_name T_PAAMAYIM_NEKUDOTAYIM simple_variable + simple_class_name T_PAAMAYIM_NEKUDOTAYIM simple_variable { $$ = zend_ast_create(ZEND_AST_STATIC_PROP, $1, $3); } | variable_class_name T_PAAMAYIM_NEKUDOTAYIM simple_variable { $$ = zend_ast_create(ZEND_AST_STATIC_PROP, $1, $3); } @@ -1575,7 +1614,7 @@ new_variable: { $$ = zend_ast_create(ZEND_AST_PROP, $1, $3); } | new_variable T_NULLSAFE_OBJECT_OPERATOR property_name { $$ = zend_ast_create(ZEND_AST_NULLSAFE_PROP, $1, $3); } - | class_name T_PAAMAYIM_NEKUDOTAYIM simple_variable + | simple_class_name T_PAAMAYIM_NEKUDOTAYIM simple_variable { $$ = zend_ast_create(ZEND_AST_STATIC_PROP, $1, $3); } | new_variable T_PAAMAYIM_NEKUDOTAYIM simple_variable { $$ = zend_ast_create(ZEND_AST_STATIC_PROP, $1, $3); } diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 8aca821931483..ff3b782d7d081 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -354,6 +354,17 @@ ZEND_API void destroy_zend_class(zval *zv) if (ce->associated_types) { zend_hash_release(ce->associated_types); } + if (ce->bound_types) { + zend_hash_release(ce->bound_types); + } + if (ce->num_generic_parameters > 0) { + for (uint32_t generic_param_index = 0; generic_param_index < ce->num_generic_parameters; generic_param_index++) { + const zend_generic_parameter generic_param = ce->generic_parameters[generic_param_index]; + zend_string_release_ex(generic_param.name, false); + zend_type_release(generic_param.constraint, false); + } + efree(ce->generic_parameters); + } if (ce->num_interfaces > 0 && !(ce->ce_flags & ZEND_ACC_RESOLVED_INTERFACES)) { uint32_t i; @@ -534,6 +545,17 @@ ZEND_API void destroy_zend_class(zval *zv) if (ce->associated_types) { zend_hash_release(ce->associated_types); } + if (ce->bound_types) { + zend_hash_release(ce->bound_types); + } + if (ce->num_generic_parameters > 0) { + for (uint32_t generic_param_index = 0; generic_param_index < ce->num_generic_parameters; generic_param_index++) { + const zend_generic_parameter generic_param = ce->generic_parameters[generic_param_index]; + zend_string_release(generic_param.name); + zend_type_release(generic_param.constraint, true); + } + free(ce->generic_parameters); + } free(ce); break; } diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 7764d8cc9e7d5..ed6d4f1921915 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -141,6 +141,11 @@ typedef struct { zend_type types[1]; } zend_type_list; +typedef struct { + zend_string *name; + zend_type constraint; +} zend_generic_parameter; + #define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 26 #define _ZEND_TYPE_MASK ((1u << 26) - 1) /* Only one of these bits may be set. */ From be7efd5b24a568bc80b2cf7ebb169dd791a56382 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Thu, 8 May 2025 12:18:58 +0100 Subject: [PATCH 16/41] Remove most of associated types --- .../big_example.phpt | 10 +-- ...ct_generic_cannot_be_in_intersection1.phpt | 2 +- ...ct_generic_cannot_be_in_intersection2.phpt | 2 +- .../abstract_generic_cannot_be_in_union1.phpt | 2 +- .../abstract_generic_cannot_be_in_union2.phpt | 2 +- .../abstract_generic_cannot_be_in_union3.phpt | 2 +- .../abstract_generic_cannot_be_in_union4.phpt | 2 +- .../abstract_generic_cannot_be_in_union5.phpt | 2 +- ..._interface_new_abstract_generic_type2.phpt | 2 + .../associated/associated_001.phpt | 32 ------- ...iated_type_cannot_be_in_intersection1.phpt | 13 --- ...iated_type_cannot_be_in_intersection2.phpt | 13 --- .../associated_type_cannot_be_in_union1.phpt | 13 --- .../associated_type_cannot_be_in_union2.phpt | 13 --- .../associated_type_cannot_be_in_union3.phpt | 13 --- .../associated_type_cannot_be_in_union4.phpt | 13 --- .../associated_type_cannot_be_in_union5.phpt | 13 --- .../associated/associated_type_in_class.phpt | 13 --- .../associated/associated_type_in_trait.phpt | 13 --- .../associated_type_with_constraint.phpt | 32 ------- ...ssociated_type_with_constraint_failed.phpt | 17 ---- ..._interface_associated_type_redeclared.phpt | 23 ----- .../extended_interface_associated_types.phpt | 22 ----- ...tended_interface_new_associated_types.phpt | 26 ------ .../associated/multiple_associated_type.phpt | 74 ---------------- .../associated/repeated_associated_type.phpt | 18 ---- Zend/zend.h | 1 - Zend/zend_ast.c | 21 ++--- Zend/zend_ast.h | 1 - Zend/zend_compile.c | 87 ++----------------- Zend/zend_inheritance.c | 61 ++----------- Zend/zend_language_parser.y | 10 --- Zend/zend_language_scanner.l | 4 - Zend/zend_opcode.c | 6 -- ext/tokenizer/tokenizer_data.c | 1 - ext/tokenizer/tokenizer_data.stub.php | 5 -- ext/tokenizer/tokenizer_data_arginfo.h | 3 +- 37 files changed, 40 insertions(+), 547 deletions(-) rename Zend/tests/type_declarations/{associated => abstract_generics}/big_example.phpt (87%) delete mode 100644 Zend/tests/type_declarations/associated/associated_001.phpt delete mode 100644 Zend/tests/type_declarations/associated/associated_type_cannot_be_in_intersection1.phpt delete mode 100644 Zend/tests/type_declarations/associated/associated_type_cannot_be_in_intersection2.phpt delete mode 100644 Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union1.phpt delete mode 100644 Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union2.phpt delete mode 100644 Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union3.phpt delete mode 100644 Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union4.phpt delete mode 100644 Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union5.phpt delete mode 100644 Zend/tests/type_declarations/associated/associated_type_in_class.phpt delete mode 100644 Zend/tests/type_declarations/associated/associated_type_in_trait.phpt delete mode 100644 Zend/tests/type_declarations/associated/associated_type_with_constraint.phpt delete mode 100644 Zend/tests/type_declarations/associated/associated_type_with_constraint_failed.phpt delete mode 100644 Zend/tests/type_declarations/associated/extended_interface_associated_type_redeclared.phpt delete mode 100644 Zend/tests/type_declarations/associated/extended_interface_associated_types.phpt delete mode 100644 Zend/tests/type_declarations/associated/extended_interface_new_associated_types.phpt delete mode 100644 Zend/tests/type_declarations/associated/multiple_associated_type.phpt delete mode 100644 Zend/tests/type_declarations/associated/repeated_associated_type.phpt diff --git a/Zend/tests/type_declarations/associated/big_example.phpt b/Zend/tests/type_declarations/abstract_generics/big_example.phpt similarity index 87% rename from Zend/tests/type_declarations/associated/big_example.phpt rename to Zend/tests/type_declarations/abstract_generics/big_example.phpt index 32986af27150c..0beea672751eb 100644 --- a/Zend/tests/type_declarations/associated/big_example.phpt +++ b/Zend/tests/type_declarations/abstract_generics/big_example.phpt @@ -7,11 +7,9 @@ Levi Morrison namespace Sequence; -interface Sequence +// No null. This is probably going to be painful, but let's try it. +interface Sequence { - // No null. This is probably going to be painful, but let's try it. - type Item: object|array|string|float|int|bool; - function next(): ?Item; /** @@ -29,7 +27,7 @@ final class StringTablePair ) {} } -final class StringTableSequence implements Sequence +final class StringTableSequence implements Sequence { private array $strings; @@ -99,4 +97,4 @@ final class StringTable ?> --EXPECTF-- -Fatal error: Associated type cannot be part of a union type in %s on line %d +Fatal error: Generic type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection1.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection1.phpt index 1cce2662bbaa6..a4f2defb5d0d5 100644 --- a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection1.phpt +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection1.phpt @@ -9,4 +9,4 @@ interface I { ?> --EXPECTF-- -Fatal error: Associated type cannot be part of an intersection type in %s on line %d +Fatal error: Generic type cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection2.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection2.phpt index 734af57a3e48d..bbffdc731ae4f 100644 --- a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection2.phpt +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection2.phpt @@ -9,4 +9,4 @@ interface I { ?> --EXPECTF-- -Fatal error: Associated type cannot be part of an intersection type in %s on line %d +Fatal error: Generic type cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union1.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union1.phpt index becef34df0c8b..2880ff7007ff3 100644 --- a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union1.phpt +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union1.phpt @@ -9,4 +9,4 @@ interface I { ?> --EXPECTF-- -Fatal error: Associated type cannot be part of a union type in %s on line %d +Fatal error: Generic type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union2.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union2.phpt index 4fd8b1d6be77e..a1b9d7e2f0fd4 100644 --- a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union2.phpt +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union2.phpt @@ -9,4 +9,4 @@ interface I { ?> --EXPECTF-- -Fatal error: Associated type cannot be part of a union type in %s on line %d +Fatal error: Generic type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union3.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union3.phpt index 33e17d1827871..3f93897a21655 100644 --- a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union3.phpt +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union3.phpt @@ -9,4 +9,4 @@ interface I { ?> --EXPECTF-- -Fatal error: Associated type cannot be part of a union type in %s on line %d +Fatal error: Generic type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union4.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union4.phpt index 75798214922e5..d69a58e155643 100644 --- a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union4.phpt +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union4.phpt @@ -9,4 +9,4 @@ interface I { ?> --EXPECTF-- -Fatal error: Associated type cannot be part of a union type in %s on line %d +Fatal error: Generic type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union5.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union5.phpt index 1fe8e1aee1705..26287188e31cc 100644 --- a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union5.phpt +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union5.phpt @@ -9,4 +9,4 @@ interface I { ?> --EXPECTF-- -Fatal error: Associated type cannot be part of a union type (implicitly nullable due to default null value) in %s on line %d +Fatal error: Generic type cannot be part of a union type (implicitly nullable due to default null value) in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type2.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type2.phpt index 784fd9a0aa33b..2e543aa0ad947 100644 --- a/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type2.phpt +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type2.phpt @@ -1,5 +1,7 @@ --TEST-- Abstract generic type behaviour in extended interface +--XFAIL-- +Generic type is not properly bound (Declaration of C::foo(string $param): string must be compatible with I::foo(T $param): T) --FILE-- foo("Hello")); - -$ci = new CI(); -var_dump($ci->foo(5)); - -?> ---EXPECT-- -string(6) "Hello!" -int(47) diff --git a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_intersection1.phpt b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_intersection1.phpt deleted file mode 100644 index 0ea969b68c499..0000000000000 --- a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_intersection1.phpt +++ /dev/null @@ -1,13 +0,0 @@ ---TEST-- -Associated type cannot be in intersection (simple intersection with class type) ---FILE-- - ---EXPECTF-- -Fatal error: Associated type cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_intersection2.phpt b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_intersection2.phpt deleted file mode 100644 index f44dab1a1e7f0..0000000000000 --- a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_intersection2.phpt +++ /dev/null @@ -1,13 +0,0 @@ ---TEST-- -Associated type cannot be in intersection (DNF type) ---FILE-- - ---EXPECTF-- -Fatal error: Associated type cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union1.phpt b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union1.phpt deleted file mode 100644 index 7a4e1809a4d52..0000000000000 --- a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union1.phpt +++ /dev/null @@ -1,13 +0,0 @@ ---TEST-- -Associated type cannot be in union (simple union with built-in type) ---FILE-- - ---EXPECTF-- -Fatal error: Associated type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union2.phpt b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union2.phpt deleted file mode 100644 index 904f428d09920..0000000000000 --- a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union2.phpt +++ /dev/null @@ -1,13 +0,0 @@ ---TEST-- -Associated type cannot be in union (simple union with class type) ---FILE-- - ---EXPECTF-- -Fatal error: Associated type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union3.phpt b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union3.phpt deleted file mode 100644 index a40109bb5ebe3..0000000000000 --- a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union3.phpt +++ /dev/null @@ -1,13 +0,0 @@ ---TEST-- -Associated type cannot be in union (DNF type) ---FILE-- - ---EXPECTF-- -Fatal error: Associated type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union4.phpt b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union4.phpt deleted file mode 100644 index 237cd54a52898..0000000000000 --- a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union4.phpt +++ /dev/null @@ -1,13 +0,0 @@ ---TEST-- -Associated type cannot be in union (nullable type union with ?) ---FILE-- - ---EXPECTF-- -Fatal error: Associated type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union5.phpt b/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union5.phpt deleted file mode 100644 index b9e8d3e5090f9..0000000000000 --- a/Zend/tests/type_declarations/associated/associated_type_cannot_be_in_union5.phpt +++ /dev/null @@ -1,13 +0,0 @@ ---TEST-- -Associated type cannot be in union (forced allowed null) ---FILE-- - ---EXPECTF-- -Fatal error: Associated type cannot be part of a union type (implicitly nullable due to default null value) in %s on line %d diff --git a/Zend/tests/type_declarations/associated/associated_type_in_class.phpt b/Zend/tests/type_declarations/associated/associated_type_in_class.phpt deleted file mode 100644 index d767621409d6c..0000000000000 --- a/Zend/tests/type_declarations/associated/associated_type_in_class.phpt +++ /dev/null @@ -1,13 +0,0 @@ ---TEST-- -Associated types in class is invalid ---FILE-- - ---EXPECTF-- -Fatal error: Cannot use associated types outside of interfaces, used in C in %s on line %d diff --git a/Zend/tests/type_declarations/associated/associated_type_in_trait.phpt b/Zend/tests/type_declarations/associated/associated_type_in_trait.phpt deleted file mode 100644 index 6e944f47fcdc2..0000000000000 --- a/Zend/tests/type_declarations/associated/associated_type_in_trait.phpt +++ /dev/null @@ -1,13 +0,0 @@ ---TEST-- -Associated types in trait is invalid ---FILE-- - ---EXPECTF-- -Fatal error: Cannot use associated types outside of interfaces, used in C in %s on line %d diff --git a/Zend/tests/type_declarations/associated/associated_type_with_constraint.phpt b/Zend/tests/type_declarations/associated/associated_type_with_constraint.phpt deleted file mode 100644 index 9e855edd7edb2..0000000000000 --- a/Zend/tests/type_declarations/associated/associated_type_with_constraint.phpt +++ /dev/null @@ -1,32 +0,0 @@ ---TEST-- -Associated type with a constraint ---FILE-- -foo("Hello")); - -$ci = new CI(); -var_dump($ci->foo(5)); - -?> ---EXPECT-- -string(6) "Hello!" -int(47) diff --git a/Zend/tests/type_declarations/associated/associated_type_with_constraint_failed.phpt b/Zend/tests/type_declarations/associated/associated_type_with_constraint_failed.phpt deleted file mode 100644 index c9c32c3040ea6..0000000000000 --- a/Zend/tests/type_declarations/associated/associated_type_with_constraint_failed.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -Associated type with a constraint that is not satisfied ---FILE-- - ---EXPECTF-- -Fatal error: Declaration of C::foo(float $param): float must be compatible with I::foo( $param): in %s on line %d diff --git a/Zend/tests/type_declarations/associated/extended_interface_associated_type_redeclared.phpt b/Zend/tests/type_declarations/associated/extended_interface_associated_type_redeclared.phpt deleted file mode 100644 index 7501eed0e9127..0000000000000 --- a/Zend/tests/type_declarations/associated/extended_interface_associated_type_redeclared.phpt +++ /dev/null @@ -1,23 +0,0 @@ ---TEST-- -Associated type behaviour in extended interface ---FILE-- - ---EXPECTF-- -Fatal error: Cannot redeclare associated type T in interface I2 inherited from interface I in %s on line %d diff --git a/Zend/tests/type_declarations/associated/extended_interface_associated_types.phpt b/Zend/tests/type_declarations/associated/extended_interface_associated_types.phpt deleted file mode 100644 index 107df2e23ed53..0000000000000 --- a/Zend/tests/type_declarations/associated/extended_interface_associated_types.phpt +++ /dev/null @@ -1,22 +0,0 @@ ---TEST-- -Associated type behaviour in extended interface ---FILE-- - ---EXPECTF-- -Fatal error: Declaration of C::bar(int $o, float $param): float must be compatible with I2::bar(int $o, T $param): T in %s on line %d diff --git a/Zend/tests/type_declarations/associated/extended_interface_new_associated_types.phpt b/Zend/tests/type_declarations/associated/extended_interface_new_associated_types.phpt deleted file mode 100644 index 91925ff1e2a29..0000000000000 --- a/Zend/tests/type_declarations/associated/extended_interface_new_associated_types.phpt +++ /dev/null @@ -1,26 +0,0 @@ ---TEST-- -Associated type behaviour in extended interface ---FILE-- - $o, $param): in %s on line %d -//Improve zend_append_type_hint()? -?> ---EXPECTF-- -Fatal error: Declaration of C::bar(float $o, string $param): float must be compatible with I2::bar( $o, T $param): in %s on line %d diff --git a/Zend/tests/type_declarations/associated/multiple_associated_type.phpt b/Zend/tests/type_declarations/associated/multiple_associated_type.phpt deleted file mode 100644 index aae8ca4dda877..0000000000000 --- a/Zend/tests/type_declarations/associated/multiple_associated_type.phpt +++ /dev/null @@ -1,74 +0,0 @@ ---TEST-- -Multiple associated types ---FILE-- -a[$key] = $value . '!'; - } - public function get(int $key): string { - return $this->a[$key]; - } -} - -class C2 implements I { - public array $a = []; - public function set(string $key, object $value): void { - $this->a[$key] = $value; - } - public function get(string $key): object { - return $this->a[$key]; - } -} - -$c1 = new C1(); -$c1->set(5, "Hello"); -var_dump($c1->a); -var_dump($c1->get(5)); - -$c2 = new C2(); -$c2->set('C1', $c1); -var_dump($c2->a); -var_dump($c2->get('C1')); - -try { - $c1->set('blah', "Hello"); -} catch (\Throwable $e) { - echo $e::class, ': ', $e->getMessage(), PHP_EOL; -} - - -?> ---EXPECTF-- -array(1) { - [5]=> - string(6) "Hello!" -} -string(6) "Hello!" -array(1) { - ["C1"]=> - object(C1)#1 (1) { - ["a"]=> - array(1) { - [5]=> - string(6) "Hello!" - } - } -} -object(C1)#1 (1) { - ["a"]=> - array(1) { - [5]=> - string(6) "Hello!" - } -} -TypeError: C1::set(): Argument #1 ($key) must be of type int, string given, called in %s on line %d diff --git a/Zend/tests/type_declarations/associated/repeated_associated_type.phpt b/Zend/tests/type_declarations/associated/repeated_associated_type.phpt deleted file mode 100644 index be65c60ef7767..0000000000000 --- a/Zend/tests/type_declarations/associated/repeated_associated_type.phpt +++ /dev/null @@ -1,18 +0,0 @@ ---TEST-- -Repeated associated type ---FILE-- - ---EXPECTF-- -Fatal error: Cannot have two associated types with the same name "T" in %s on line %d diff --git a/Zend/zend.h b/Zend/zend.h index 8520133e8ef89..48cbfe1d950e3 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -219,7 +219,6 @@ struct _zend_class_entry { HashTable *attributes; /* Only for interfaces */ - HashTable *associated_types; HashTable *bound_types; zend_generic_parameter *generic_parameters; uint32_t num_generic_parameters; diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 5dc8a79fae987..83f9613f5243d 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -2725,16 +2725,17 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio smart_str_appends(str, ": "); ast = ast->child[1]; goto tail_call; - case ZEND_AST_ASSOCIATED_TYPE: - smart_str_appends(str, "type "); - zend_ast_export_name(str, ast->child[0], 0, indent); - if (ast->child[1]) { - smart_str_appends(str, " : "); - smart_str_appends(str, " : "); - zend_ast_export_type(str, ast->child[1], indent); - } - smart_str_appendc(str, ';'); - break; + // TODO Export generic types + //case ZEND_AST_ASSOCIATED_TYPE: + // smart_str_appends(str, "type "); + // zend_ast_export_name(str, ast->child[0], 0, indent); + // if (ast->child[1]) { + // smart_str_appends(str, " : "); + // smart_str_appends(str, " : "); + // zend_ast_export_type(str, ast->child[1], indent); + // } + // smart_str_appendc(str, ';'); + //break; /* 3 child nodes */ case ZEND_AST_METHOD_CALL: diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 9df7d81373e86..3b7d56ad54961 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -156,7 +156,6 @@ enum _zend_ast_kind { ZEND_AST_MATCH_ARM, ZEND_AST_NAMED_ARG, ZEND_AST_PARENT_PROPERTY_HOOK_CALL, - ZEND_AST_ASSOCIATED_TYPE, ZEND_AST_GENERIC_PARAM, ZEND_AST_CLASS_REF, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index e84b18bedc4d7..7eca126973bcd 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1441,29 +1441,6 @@ static zend_string *add_intersection_type(zend_string *str, return str; } -static zend_string *add_associated_type(zend_string *associated_type, zend_class_entry *scope) -{ - const zend_type *constraint = zend_hash_find_ptr(scope->associated_types, associated_type); - ZEND_ASSERT(constraint != NULL); - - zend_string *constraint_type_str = zend_type_to_string_resolved(*constraint, scope); - - size_t len = ZSTR_LEN(associated_type) + ZSTR_LEN(constraint_type_str) + strlen("< : >"); - zend_string *result = zend_string_alloc(len, 0); - - ZSTR_VAL(result)[0] = '<'; - memcpy(ZSTR_VAL(result) + strlen("<"), ZSTR_VAL(associated_type), ZSTR_LEN(associated_type)); - ZSTR_VAL(result)[ZSTR_LEN(associated_type) + 1] = ' '; - ZSTR_VAL(result)[ZSTR_LEN(associated_type) + 2] = ':'; - ZSTR_VAL(result)[ZSTR_LEN(associated_type) + 3] = ' '; - memcpy(ZSTR_VAL(result) + ZSTR_LEN(associated_type) + strlen("< : "), ZSTR_VAL(constraint_type_str), ZSTR_LEN(constraint_type_str)); - ZSTR_VAL(result)[len-1] = '>'; - ZSTR_VAL(result)[len] = '\0'; - - zend_string_release(constraint_type_str); - return result; -} - zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry *scope) { zend_string *str = NULL; @@ -1487,8 +1464,9 @@ zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry str = add_type_string(str, resolved, /* is_intersection */ false); zend_string_release(resolved); } ZEND_TYPE_LIST_FOREACH_END(); - } else if (ZEND_TYPE_IS_ASSOCIATED(type)) { - str = add_associated_type(ZEND_TYPE_NAME(type), scope); + // TODO Is this still required? + //} else if (ZEND_TYPE_IS_ASSOCIATED(type)) { + // str = add_associated_type(ZEND_TYPE_NAME(type), scope); } else if (ZEND_TYPE_HAS_NAME(type)) { str = resolve_class_name(ZEND_TYPE_NAME(type), scope); } @@ -2117,7 +2095,6 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, bool nullify_hand ce->default_static_members_count = 0; ce->properties_info_table = NULL; ce->attributes = NULL; - ce->associated_types = NULL; ce->bound_types = NULL; // TODO Should these be inside nullify_handlers? ce->generic_parameters = NULL; @@ -7046,10 +7023,6 @@ static zend_type zend_compile_single_typename(zend_ast *ast) const char *correct_name; uint32_t fetch_type = zend_get_class_fetch_type_ast(ast); - // TODO Old version to remove - if (ce && ce->associated_types && zend_hash_exists(ce->associated_types, type_name)) { - return (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(type_name), /* allow null */ false, _ZEND_TYPE_ASSOCIATED_BIT); - } if (ce && ce->num_generic_parameters > 0) { for (uint32_t generic_param_index = 0; generic_param_index < ce->num_generic_parameters; generic_param_index++) { const zend_generic_parameter *genric_param = &ce->generic_parameters[generic_param_index]; @@ -7255,7 +7228,7 @@ static zend_type zend_compile_typename_ex( uint32_t single_type_mask = ZEND_TYPE_PURE_MASK(single_type); if (ZEND_TYPE_IS_ASSOCIATED(single_type)) { - zend_error_noreturn(E_COMPILE_ERROR, "Associated type cannot be part of a union type"); + zend_error_noreturn(E_COMPILE_ERROR, "Generic type cannot be part of a union type"); } if (single_type_mask == MAY_BE_ANY) { zend_error_noreturn(E_COMPILE_ERROR, "Type mixed can only be used as a standalone type"); @@ -7340,7 +7313,7 @@ static zend_type zend_compile_typename_ex( zend_type single_type = zend_compile_single_typename(type_ast); if (ZEND_TYPE_IS_ASSOCIATED(single_type)) { - zend_error_noreturn(E_COMPILE_ERROR, "Associated type cannot be part of an intersection type"); + zend_error_noreturn(E_COMPILE_ERROR, "Generic type cannot be part of an intersection type"); } /* An intersection of union types cannot exist so invalidate it * Currently only can happen with iterable getting canonicalized to Traversable|array */ @@ -7409,10 +7382,10 @@ static zend_type zend_compile_typename_ex( zend_error_noreturn(E_COMPILE_ERROR, "null cannot be marked as nullable"); } if (ZEND_TYPE_IS_ASSOCIATED(type) && is_marked_nullable) { - zend_error_noreturn(E_COMPILE_ERROR, "Associated type cannot be part of a union type"); + zend_error_noreturn(E_COMPILE_ERROR, "Generic type cannot be part of a union type"); } if (ZEND_TYPE_IS_ASSOCIATED(type) && force_allow_null) { - zend_error_noreturn(E_COMPILE_ERROR, "Associated type cannot be part of a union type (implicitly nullable due to default null value)"); + zend_error_noreturn(E_COMPILE_ERROR, "Generic type cannot be part of a union type (implicitly nullable due to default null value)"); } if (force_allow_null && !is_marked_nullable && !(type_mask & MAY_BE_NULL)) { @@ -9088,49 +9061,6 @@ static void zend_compile_use_trait(zend_ast *ast) /* {{{ */ } /* }}} */ -static void zend_associated_table_ht_dtor(zval *val) { - /* NO OP as we only use it to be able to refer and save pointers to zend_types */ - // TODO do we actually want to store copies of types? - zend_type *associated_type = Z_PTR_P(val); - if (associated_type != &zend_mixed_type) { - zend_type_release(*associated_type, false); - efree(associated_type); - } -} - -// TODO Remove -static void zend_compile_associated_type(zend_ast *ast) { - zend_class_entry *ce = CG(active_class_entry); - HashTable *associated_types = ce->associated_types; - zend_ast *name_ast = ast->child[0]; - zend_ast *type_ast = ast->child[1]; - zend_string *name = zend_ast_get_str(name_ast); - - if ((ce->ce_flags & ZEND_ACC_INTERFACE) == 0) { - zend_error_noreturn(E_COMPILE_ERROR, - "Cannot use associated types outside of interfaces, used in %s", ZSTR_VAL(ce->name)); - } - - ZEND_ASSERT(name != NULL); - bool persistent = ce->type == ZEND_INTERNAL_CLASS; - if (associated_types == NULL) { - ce->associated_types = pemalloc(sizeof(HashTable), persistent); - zend_hash_init(ce->associated_types, 8, NULL, zend_associated_table_ht_dtor, persistent); - associated_types = ce->associated_types; - } - if (zend_hash_exists(associated_types, name)) { - zend_error_noreturn(E_COMPILE_ERROR, - "Cannot have two associated types with the same name \"%s\"", ZSTR_VAL(name)); - } - - if (type_ast != NULL) { - zend_type type = zend_compile_typename(type_ast); - zend_hash_add_new_mem(associated_types, name, &type, sizeof(type)); - } else { - zend_hash_add_new_ptr(associated_types, name, (void*) &zend_mixed_type); - } -} - static void zend_bound_types_ht_dtor(zval *ptr) { HashTable *interface_bound_types = Z_PTR_P(ptr); zend_hash_destroy(interface_bound_types); @@ -11791,9 +11721,6 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */ case ZEND_AST_USE_TRAIT: zend_compile_use_trait(ast); break; - case ZEND_AST_ASSOCIATED_TYPE: - zend_compile_associated_type(ast); - break; case ZEND_AST_CLASS: zend_compile_class_decl(NULL, ast, 0); break; diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 3fa7367fa04fb..fd1d52c292e25 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -693,32 +693,16 @@ static inheritance_status zend_is_type_subtype_of_associated_type( zend_string *associated_type_name = ZEND_TYPE_NAME(associated_type); const zend_type *bound_type_ptr = zend_hash_find_ptr(CG(bound_associated_types), associated_type_name); ZEND_ASSERT(bound_type_ptr != NULL); - if (bound_type_ptr == NULL) { - const zend_type *constraint = zend_hash_find_ptr(associated_type_scope->associated_types, associated_type_name); - ZEND_ASSERT(constraint != NULL); - /* Check that the provided type is a subtype of the constraint */ - const inheritance_status status = zend_perform_covariant_type_check( - concrete_scope, concrete_type_ptr, - associated_type_scope, constraint); - if (status != INHERITANCE_SUCCESS) { - return status; - } + /* Generic type must be invariant */ + const inheritance_status sub_type_status = zend_perform_covariant_type_check( + concrete_scope, concrete_type_ptr, associated_type_scope, bound_type_ptr); + const inheritance_status super_type_status = zend_perform_covariant_type_check( + associated_type_scope, bound_type_ptr, concrete_scope, concrete_type_ptr); - /* Loosing const qualifier here is OK because this hashtable never frees or does anything with the value */ - zend_hash_add_new_ptr(CG(bound_associated_types), associated_type_name, (void*)concrete_type_ptr); - return INHERITANCE_SUCCESS; + if (sub_type_status != super_type_status) { + return INHERITANCE_ERROR; } else { - /* Associated type must be invariant */ - const inheritance_status sub_type_status = zend_perform_covariant_type_check( - concrete_scope, concrete_type_ptr, associated_type_scope, bound_type_ptr); - const inheritance_status super_type_status = zend_perform_covariant_type_check( - associated_type_scope, bound_type_ptr, concrete_scope, concrete_type_ptr); - - if (sub_type_status != super_type_status) { - return INHERITANCE_ERROR; - } else { - return sub_type_status; - } + return sub_type_status; } } @@ -2232,35 +2216,6 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * ZEND_INHERITANCE_RESET_CHILD_OVERRIDE; } - // TODO Old way to remove - if (iface->associated_types) { - const uint32_t num_associated_types = zend_hash_num_elements(iface->associated_types); - if (ce->ce_flags & ZEND_ACC_INTERFACE) { - const bool persistent = ce->type == ZEND_INTERNAL_CLASS; - if (ce->associated_types) { - zend_string *associated_type_name; - zend_type *associated_type_ptr; - ZEND_HASH_FOREACH_STR_KEY_PTR(iface->associated_types, associated_type_name, associated_type_ptr) { - if (zend_hash_exists(ce->associated_types, associated_type_name)) { - zend_error_noreturn(E_ERROR, - "Cannot redeclare associated type %s in interface %s inherited from interface %s", - ZSTR_VAL(associated_type_name), ZSTR_VAL(ce->name), ZSTR_VAL(iface->name)); - } - /* Deep copy the type information */ - zend_type_copy_ctor(associated_type_ptr, /* use_arena */ !persistent, /* persistent */ persistent); - zend_hash_add_new_mem(ce->associated_types, associated_type_name, associated_type_ptr, sizeof(*associated_type_ptr)); - } ZEND_HASH_FOREACH_END(); - } else { - ce->associated_types = pemalloc(sizeof(HashTable), persistent); - zend_hash_init(ce->associated_types, num_associated_types, NULL, NULL, false); - zend_hash_copy(ce->associated_types, iface->associated_types, NULL); - } - return; - } - HashTable *ht = emalloc(sizeof(HashTable)); - zend_hash_init(ht, num_associated_types, NULL, NULL, false); - CG(bound_associated_types) = ht; - } if (iface->num_generic_parameters > 0) { if (UNEXPECTED(ce->bound_types == NULL)) { zend_error_noreturn(E_COMPILE_ERROR, diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index f624aa10c7d91..48d9b6e928b5e 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -167,7 +167,6 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_TRAIT "'trait'" %token T_INTERFACE "'interface'" %token T_ENUM "'enum'" -%token T_TYPE "'type'" %token T_EXTENDS "'extends'" %token T_IMPLEMENTS "'implements'" %token T_NAMESPACE "'namespace'" @@ -287,7 +286,6 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type function_name non_empty_member_modifiers %type property_hook property_hook_list optional_property_hook_list hooked_property property_hook_body %type optional_parameter_list -%type associated_type %type optional_generic_params generic_params generic_param class_name_with_generics_list %type returns_ref function fn is_reference is_variadic property_modifiers property_hook_modifiers @@ -687,13 +685,6 @@ generic_param: { $$ = zend_ast_create(ZEND_AST_GENERIC_PARAM, $1, $3); } ; -associated_type: - T_TYPE name ':' type_expr_without_static ';' - { $$ = zend_ast_create(ZEND_AST_ASSOCIATED_TYPE, $2, $4); } - | T_TYPE name ';' - { $$ = zend_ast_create(ZEND_AST_ASSOCIATED_TYPE, $2, NULL); } -; - extends_from: %empty { $$ = NULL; } | T_EXTENDS simple_class_name { $$ = $2; } @@ -995,7 +986,6 @@ attributed_class_statement: { $$ = zend_ast_create_decl(ZEND_AST_METHOD, $3 | $1 | $12, $2, $5, zend_ast_get_str($4), $7, NULL, $11, $9, NULL); CG(extra_fn_flags) = $10; } | enum_case { $$ = $1; } - | associated_type { $$ = $1; } ; class_statement: diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index abb816f83c5cb..4c883b81c5f7d 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1545,10 +1545,6 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ RETURN_TOKEN_WITH_IDENT(T_INTERFACE); } -"type" { - RETURN_TOKEN_WITH_IDENT(T_TYPE); -} - "trait" { RETURN_TOKEN_WITH_IDENT(T_TRAIT); } diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index ff3b782d7d081..0f59eb2d1973d 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -351,9 +351,6 @@ ZEND_API void destroy_zend_class(zval *zv) zend_hash_release(ce->attributes); } - if (ce->associated_types) { - zend_hash_release(ce->associated_types); - } if (ce->bound_types) { zend_hash_release(ce->bound_types); } @@ -542,9 +539,6 @@ ZEND_API void destroy_zend_class(zval *zv) if (ce->attributes) { zend_hash_release(ce->attributes); } - if (ce->associated_types) { - zend_hash_release(ce->associated_types); - } if (ce->bound_types) { zend_hash_release(ce->bound_types); } diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index c53ee96c6226b..a1e131032bcfb 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -105,7 +105,6 @@ char *get_token_type_name(int token_type) case T_TRAIT: return "T_TRAIT"; case T_INTERFACE: return "T_INTERFACE"; case T_ENUM: return "T_ENUM"; - case T_TYPE: return "T_TYPE"; case T_EXTENDS: return "T_EXTENDS"; case T_IMPLEMENTS: return "T_IMPLEMENTS"; case T_NAMESPACE: return "T_NAMESPACE"; diff --git a/ext/tokenizer/tokenizer_data.stub.php b/ext/tokenizer/tokenizer_data.stub.php index 065453981f223..c1e1fd254dfaa 100644 --- a/ext/tokenizer/tokenizer_data.stub.php +++ b/ext/tokenizer/tokenizer_data.stub.php @@ -402,11 +402,6 @@ * @cvalue T_ENUM */ const T_ENUM = UNKNOWN; -/** - * @var int - * @cvalue T_TYPE - */ -const T_TYPE = UNKNOWN; /** * @var int * @cvalue T_EXTENDS diff --git a/ext/tokenizer/tokenizer_data_arginfo.h b/ext/tokenizer/tokenizer_data_arginfo.h index 1571daf0cda58..9c488d19f1890 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: ba2791ef99a630b81f49a3251f3824d7d4858176 */ + * Stub hash: 19d25d22098f46283b517352cbb302db962b50fd */ static void register_tokenizer_data_symbols(int module_number) { @@ -83,7 +83,6 @@ static void register_tokenizer_data_symbols(int module_number) REGISTER_LONG_CONSTANT("T_TRAIT", T_TRAIT, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_INTERFACE", T_INTERFACE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_ENUM", T_ENUM, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("T_TYPE", T_TYPE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_EXTENDS", T_EXTENDS, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_IMPLEMENTS", T_IMPLEMENTS, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_NAMESPACE", T_NAMESPACE, CONST_PERSISTENT); From 87f954498963667f9d41eb4280abfc7356704447 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Thu, 8 May 2025 12:52:18 +0100 Subject: [PATCH 17/41] rename --- Zend/zend_globals.h | 2 +- Zend/zend_inheritance.c | 33 ++++++++++++++++----------------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index dc1a06f52c539..1269bb6969a74 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -152,7 +152,7 @@ struct _zend_compiler_globals { HashTable *unlinked_uses; zend_class_entry *current_linking_class; /* Those are initialized and destroyed by zend_do_inheritance_ex() */ - HashTable *bound_associated_types; + HashTable *bound_generic_types; uint32_t rtd_key_counter; diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index fd1d52c292e25..fcd9dddf56aa2 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -679,25 +679,24 @@ ZEND_API inheritance_status zend_perform_covariant_type_check( zend_class_entry *fe_scope, const zend_type *fe_type_ptr, zend_class_entry *proto_scope, const zend_type *proto_type_ptr); -static inheritance_status zend_is_type_subtype_of_associated_type( +static inheritance_status zend_is_type_subtype_of_generic_type( zend_class_entry *concrete_scope, const zend_type *concrete_type_ptr, - zend_class_entry *associated_type_scope, - const zend_type *associated_type_ptr + zend_class_entry *generic_type_scope, + const zend_type *generic_type_ptr ) { - const zend_type associated_type = *associated_type_ptr; + const zend_type generic_type = *generic_type_ptr; - ZEND_ASSERT(CG(bound_associated_types) && "Have associated type"); - ZEND_ASSERT(ZEND_TYPE_HAS_NAME(associated_type)); + ZEND_ASSERT(CG(bound_generic_types) && "Have generic type"); + ZEND_ASSERT(ZEND_TYPE_HAS_NAME(generic_type)); - zend_string *associated_type_name = ZEND_TYPE_NAME(associated_type); - const zend_type *bound_type_ptr = zend_hash_find_ptr(CG(bound_associated_types), associated_type_name); + const zend_type *bound_type_ptr = zend_hash_find_ptr(CG(bound_generic_types), ZEND_TYPE_NAME(generic_type)); ZEND_ASSERT(bound_type_ptr != NULL); /* Generic type must be invariant */ const inheritance_status sub_type_status = zend_perform_covariant_type_check( - concrete_scope, concrete_type_ptr, associated_type_scope, bound_type_ptr); + concrete_scope, concrete_type_ptr, generic_type_scope, bound_type_ptr); const inheritance_status super_type_status = zend_perform_covariant_type_check( - associated_type_scope, bound_type_ptr, concrete_scope, concrete_type_ptr); + generic_type_scope, bound_type_ptr, concrete_scope, concrete_type_ptr); if (sub_type_status != super_type_status) { return INHERITANCE_ERROR; @@ -723,12 +722,12 @@ ZEND_API inheritance_status zend_perform_covariant_type_check( /* If we check for concrete return type */ if (ZEND_TYPE_IS_ASSOCIATED(proto_type)) { - return zend_is_type_subtype_of_associated_type( + return zend_is_type_subtype_of_generic_type( fe_scope, fe_type_ptr, proto_scope, proto_type_ptr); } /* If we check for concrete parameter type */ if (ZEND_TYPE_IS_ASSOCIATED(fe_type)) { - return zend_is_type_subtype_of_associated_type( + return zend_is_type_subtype_of_generic_type( proto_scope, proto_type_ptr, fe_scope, fe_type_ptr); } @@ -2241,7 +2240,7 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * } HashTable *ht = emalloc(sizeof(HashTable)); zend_hash_init(ht, num_bound_types, NULL, NULL, false); - CG(bound_associated_types) = ht; + CG(bound_generic_types) = ht; for (uint32_t i = 0; i < num_bound_types; i++) { const zend_generic_parameter *generic_parameter = &iface->generic_parameters[i]; const zend_type* generic_constraint = &generic_parameter->constraint; @@ -2321,10 +2320,10 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * if (iface->num_interfaces) { zend_do_inherit_interfaces(ce, iface); } - if (CG(bound_associated_types)) { - zend_hash_destroy(CG(bound_associated_types)); - efree(CG(bound_associated_types)); - CG(bound_associated_types) = NULL; + if (CG(bound_generic_types)) { + zend_hash_destroy(CG(bound_generic_types)); + efree(CG(bound_generic_types)); + CG(bound_generic_types) = NULL; } } /* }}} */ From 56f2f1cefb8d65ee9ec6aebda99869a917c92f98 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 10 May 2025 18:19:43 +0100 Subject: [PATCH 18/41] Did I finally fix generic type binding? --- ...abstract_generic_type_with_constraint.phpt | 0 ...t_generic_type_with_constraint_failed.phpt | 0 .../using_generic_type_as_constraint.phpt | 12 +++ .../errors/using_never_as_constraint.phpt | 12 +++ .../errors/using_static_as_constraint.phpt | 12 +++ .../errors/using_void_as_constraint.phpt | 12 +++ ...terface_abstract_generic_types_1_to_2.phpt | 22 +++++ ...nterface_abstract_generic_types_basic.phpt | 22 +++++ ..._interface_new_abstract_generic_type2.phpt | 2 - Zend/zend_compile.c | 23 +++-- Zend/zend_inheritance.c | 88 +++++++++++++++---- 11 files changed, 181 insertions(+), 24 deletions(-) rename Zend/tests/type_declarations/abstract_generics/{ => constraints}/abstract_generic_type_with_constraint.phpt (100%) rename Zend/tests/type_declarations/abstract_generics/{ => constraints}/abstract_generic_type_with_constraint_failed.phpt (100%) create mode 100644 Zend/tests/type_declarations/abstract_generics/constraints/errors/using_generic_type_as_constraint.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/constraints/errors/using_never_as_constraint.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/constraints/errors/using_static_as_constraint.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/constraints/errors/using_void_as_constraint.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types_1_to_2.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types_basic.phpt diff --git a/Zend/tests/type_declarations/abstract_generics/abstract_generic_type_with_constraint.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/abstract_generic_type_with_constraint.phpt similarity index 100% rename from Zend/tests/type_declarations/abstract_generics/abstract_generic_type_with_constraint.phpt rename to Zend/tests/type_declarations/abstract_generics/constraints/abstract_generic_type_with_constraint.phpt diff --git a/Zend/tests/type_declarations/abstract_generics/abstract_generic_type_with_constraint_failed.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/abstract_generic_type_with_constraint_failed.phpt similarity index 100% rename from Zend/tests/type_declarations/abstract_generics/abstract_generic_type_with_constraint_failed.phpt rename to Zend/tests/type_declarations/abstract_generics/constraints/abstract_generic_type_with_constraint_failed.phpt diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_generic_type_as_constraint.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_generic_type_as_constraint.phpt new file mode 100644 index 0000000000000..7a964dd0f60a5 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_generic_type_as_constraint.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cannot use generic type as a constraint +--FILE-- + { + public function foo(T $param): T; +} + +?> +--EXPECTF-- +Fatal error: Cannot use generic parameter T1 to constrain generic parameter T2 in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_never_as_constraint.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_never_as_constraint.phpt new file mode 100644 index 0000000000000..485841f50ee59 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_never_as_constraint.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cannot use never as a constraint +--FILE-- + { + public function foo(T $param): T; +} + +?> +--EXPECTF-- +Fatal error: Cannot use static, void, or never to constrain generic parameter T in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_static_as_constraint.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_static_as_constraint.phpt new file mode 100644 index 0000000000000..86bcc1d2b7613 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_static_as_constraint.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cannot use void as a constraint +--FILE-- + { + public function foo(T $param): T; +} + +?> +--EXPECTF-- +Fatal error: Cannot use static, void, or never to constrain generic parameter T in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_void_as_constraint.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_void_as_constraint.phpt new file mode 100644 index 0000000000000..1f727f33665ae --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_void_as_constraint.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cannot use void as a constraint +--FILE-- + { + public function foo(T $param): T; +} + +?> +--EXPECTF-- +Fatal error: Cannot use static, void, or never to constrain generic parameter T in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types_1_to_2.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types_1_to_2.phpt new file mode 100644 index 0000000000000..3b5ebbde839ef --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types_1_to_2.phpt @@ -0,0 +1,22 @@ +--TEST-- +Abstract generic type behaviour in extended interface 2 generic type to 1 +--FILE-- + { + public function foo(T1 $param): T2; +} + +interface I2 extends I { + public function bar(int $o, S $param): S; +} + +class C implements I2 { + public function foo(string $param): string {} + public function bar(int $o, string $param): string {} +} + +?> +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types_basic.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types_basic.phpt new file mode 100644 index 0000000000000..d58ee8960239c --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types_basic.phpt @@ -0,0 +1,22 @@ +--TEST-- +Abstract generic type behaviour in extended interface +--FILE-- + { + public function foo(T1 $param): T1; +} + +interface I2 extends IWG { + public function bar(int $o, T2 $param): T2; +} + +class C implements I2 { + public function foo(string $param): string {} + public function bar(int $o, string $param): string {} +} + +?> +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type2.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type2.phpt index 2e543aa0ad947..784fd9a0aa33b 100644 --- a/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type2.phpt +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type2.phpt @@ -1,7 +1,5 @@ --TEST-- Abstract generic type behaviour in extended interface ---XFAIL-- -Generic type is not properly bound (Declaration of C::foo(string $param): string must be compatible with I::foo(T $param): T) --FILE-- child[1]) { - // TODO Need to free this + // TODO Need to free this? constraint_type = zend_compile_typename(param_ast->child[1]); - // TODO Validate that void, static, never are not used in the constraint? + if (ZEND_TYPE_IS_ASSOCIATED(constraint_type)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use generic parameter %s to constrain generic parameter %s", + ZSTR_VAL(ZEND_TYPE_NAME(constraint_type)), ZSTR_VAL(name)); + } + if (ZEND_TYPE_FULL_MASK(constraint_type) & (MAY_BE_STATIC|MAY_BE_VOID|MAY_BE_NEVER)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use static, void, or never to constrain generic parameter %s", + ZSTR_VAL(name)); + } } generic_params[i].name = zend_string_copy(name); diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index fcd9dddf56aa2..5972664ada959 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -685,12 +685,14 @@ static inheritance_status zend_is_type_subtype_of_generic_type( zend_class_entry *generic_type_scope, const zend_type *generic_type_ptr ) { + ZEND_ASSERT(concrete_scope->bound_types); const zend_type generic_type = *generic_type_ptr; + const HashTable *bound_generic_types = zend_hash_find_ptr_lc(concrete_scope->bound_types, generic_type_scope->name); - ZEND_ASSERT(CG(bound_generic_types) && "Have generic type"); + ZEND_ASSERT(bound_generic_types && "Have generic type"); ZEND_ASSERT(ZEND_TYPE_HAS_NAME(generic_type)); - const zend_type *bound_type_ptr = zend_hash_find_ptr(CG(bound_generic_types), ZEND_TYPE_NAME(generic_type)); + const zend_type *bound_type_ptr = zend_hash_find_ptr(bound_generic_types, ZEND_TYPE_NAME(generic_type)); ZEND_ASSERT(bound_type_ptr != NULL); /* Generic type must be invariant */ const inheritance_status sub_type_status = zend_perform_covariant_type_check( @@ -1638,6 +1640,57 @@ static inline void do_implement_interface(zend_class_entry *ce, zend_class_entry } /* }}} */ +// TODO Merge with the one in zend_compile +static void zend_types_ht_dtor(zval *ptr) { + zend_type *type = Z_PTR_P(ptr); + // TODO Figure out persistency? + zend_type_release(*type, false); + efree(type); +} + +static void interface_bind_generic_types_for_interfaces(zend_class_entry *ce, const zend_class_entry *iface) { + zend_string *iface_lc_name = zend_string_tolower(iface->name); + const HashTable *ce_bound_types = ce->bound_types ? zend_hash_find_ptr(ce->bound_types, iface_lc_name) : NULL; + for (uint32_t i = 0; i < iface->num_interfaces; i++) { + zend_class_entry *entry = iface->interfaces[i]; + /* Bind generic types */ + /* We need to propagate the bound generic parameters to the inherited interfaces */ + if (entry->num_generic_parameters == 0) { + continue; + } + zend_string *inherited_iface_lc_name = zend_string_tolower(entry->name); + const HashTable *interface_bound_types = zend_hash_find_ptr(iface->bound_types, inherited_iface_lc_name); + HashTable *ce_bound_types_to_inherited_iface = zend_hash_find_ptr(ce->bound_types, inherited_iface_lc_name); + ZEND_ASSERT(interface_bound_types != NULL && "This must exist at this point"); + if (ce_bound_types_to_inherited_iface == NULL) { + ALLOC_HASHTABLE(ce_bound_types_to_inherited_iface); + zend_hash_init(ce_bound_types_to_inherited_iface, entry->num_generic_parameters, NULL, zend_types_ht_dtor, false /* todo depend on internal or not */); + zend_hash_add_new_ptr(ce->bound_types, inherited_iface_lc_name, ce_bound_types_to_inherited_iface); + } + for ( + uint32_t inherited_iface_generic_param_index = 0; + inherited_iface_generic_param_index < entry->num_generic_parameters; + inherited_iface_generic_param_index++ + ) { + const zend_generic_parameter *inherited_generic_parameter = &entry->generic_parameters[inherited_iface_generic_param_index]; + const zend_type *iface_bound_type_ptr = zend_hash_index_find_ptr(interface_bound_types, inherited_iface_generic_param_index); + ZEND_ASSERT(iface_bound_type_ptr != NULL); + zend_type bound_type; + if (ZEND_TYPE_IS_ASSOCIATED(*iface_bound_type_ptr)) { + memcpy(&bound_type, zend_hash_find_ptr(ce_bound_types, ZEND_TYPE_NAME(*iface_bound_type_ptr)), sizeof(zend_type)); + } else { + bound_type = *iface_bound_type_ptr; + } + /* Deep type copy */ + zend_type_copy_ctor(&bound_type, true, false); + zend_hash_add_mem(ce_bound_types_to_inherited_iface, inherited_generic_parameter->name, + &bound_type, sizeof(bound_type)); + } + zend_string_release_ex(inherited_iface_lc_name, false); + } + zend_string_release_ex(iface_lc_name, false); +} + static void zend_do_inherit_interfaces(zend_class_entry *ce, const zend_class_entry *iface) /* {{{ */ { /* expects interface to be contained in ce's interface list already */ @@ -1656,6 +1709,7 @@ static void zend_do_inherit_interfaces(zend_class_entry *ce, const zend_class_en zend_class_entry *entry = iface->interfaces[if_num]; for (i = 0; i < ce_num; i++) { if (ce->interfaces[i] == entry) { + // TODO Check bound generic types match? Or is this done before? break; } } @@ -2222,7 +2276,7 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * ZSTR_VAL(iface->name) ); } - const HashTable *bound_types = zend_hash_find_ptr_lc(ce->bound_types, iface->name); + HashTable *bound_types = zend_hash_find_ptr_lc(ce->bound_types, iface->name); if (UNEXPECTED(bound_types == NULL)) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot implement %s as it has generic parameters which are not specified", @@ -2238,9 +2292,6 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * iface->num_generic_parameters ); } - HashTable *ht = emalloc(sizeof(HashTable)); - zend_hash_init(ht, num_bound_types, NULL, NULL, false); - CG(bound_generic_types) = ht; for (uint32_t i = 0; i < num_bound_types; i++) { const zend_generic_parameter *generic_parameter = &iface->generic_parameters[i]; const zend_type* generic_constraint = &generic_parameter->constraint; @@ -2260,7 +2311,14 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * } const zend_type *current_ce_generic_type_constraint = ¤t_ce_generic_parameter->constraint; ZEND_ASSERT(current_ce_generic_type_constraint != NULL); - if (zend_perform_covariant_type_check(ce, current_ce_generic_type_constraint, iface, generic_constraint) != INHERITANCE_SUCCESS) { + if ( + zend_perform_covariant_type_check( + ce, + current_ce_generic_type_constraint, + iface, + generic_constraint + ) != INHERITANCE_SUCCESS + ) { zend_string *current_ce_constraint_type_str = zend_type_to_string(*current_ce_generic_type_constraint); zend_string *constraint_type_str = zend_type_to_string(generic_parameter->constraint); zend_error_noreturn(E_COMPILE_ERROR, @@ -2276,8 +2334,6 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * zend_string_release(constraint_type_str); return; } - /* Loosing const qualifier here is OK because this hashtable never frees or does anything with the value */ - zend_hash_add_new_ptr(ht, generic_parameter->name, (void*)current_ce_generic_type_constraint); break; } } else { @@ -2295,10 +2351,17 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * zend_string_release(constraint_type_str); return; } - zend_hash_add_new_ptr(ht, generic_parameter->name, bound_type_ptr); + /* Change key from index to generic parameter name */ + /* Deep type copy */ + zend_type bound_type = *bound_type_ptr; + zend_type_copy_ctor(&bound_type, true, false); + zend_hash_add_mem(bound_types, generic_parameter->name, + &bound_type, sizeof(bound_type)); + //zend_hash_index_del(bound_types, i); } } } + interface_bind_generic_types_for_interfaces(ce, iface); ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&iface->constants_table, key, c) { do_inherit_iface_constant(key, c, ce, iface); } ZEND_HASH_FOREACH_END(); @@ -2320,11 +2383,6 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * if (iface->num_interfaces) { zend_do_inherit_interfaces(ce, iface); } - if (CG(bound_generic_types)) { - zend_hash_destroy(CG(bound_generic_types)); - efree(CG(bound_generic_types)); - CG(bound_generic_types) = NULL; - } } /* }}} */ From 2d83b54d1222ee16b002e8146b4b36def85dd1e2 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 12 May 2025 04:06:24 +0100 Subject: [PATCH 19/41] fix stuff --- ...nded_interface_abstract_generic_types.phpt | 0 ...nterface_abstract_generic_types_basic.phpt | 4 +- Zend/zend.c | 1 - Zend/zend.h | 3 +- Zend/zend_compile.c | 7 --- Zend/zend_globals.h | 2 - Zend/zend_opcode.c | 47 +++++++------------ 7 files changed, 21 insertions(+), 43 deletions(-) rename Zend/tests/type_declarations/abstract_generics/{ => constraints}/extended_interface_abstract_generic_types.phpt (100%) diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/extended_interface_abstract_generic_types.phpt similarity index 100% rename from Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types.phpt rename to Zend/tests/type_declarations/abstract_generics/constraints/extended_interface_abstract_generic_types.phpt diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types_basic.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types_basic.phpt index d58ee8960239c..038e66ef63e3c 100644 --- a/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types_basic.phpt +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types_basic.phpt @@ -3,11 +3,11 @@ Abstract generic type behaviour in extended interface --FILE-- { +interface I { public function foo(T1 $param): T1; } -interface I2 extends IWG { +interface I2 extends I { public function bar(int $o, T2 $param): T2; } diff --git a/Zend/zend.c b/Zend/zend.c index edec7ba675630..2d8a0f455f8b4 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -732,7 +732,6 @@ static void compiler_globals_ctor(zend_compiler_globals *compiler_globals) /* {{ compiler_globals->script_encoding_list = NULL; compiler_globals->current_linking_class = NULL; - compiler_globals->bound_associated_types = NULL; /* Map region is going to be created and resized at run-time. */ compiler_globals->map_ptr_real_base = NULL; diff --git a/Zend/zend.h b/Zend/zend.h index 48cbfe1d950e3..d2f8d15c763b6 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -218,7 +218,8 @@ struct _zend_class_entry { zend_trait_precedence **trait_precedences; HashTable *attributes; - /* Only for interfaces */ + /* The bound_types HashTable is a map: "lower_case_interface_names" => map + * Where an integer index refers to the position, and the string to the name of the generic parameter */ HashTable *bound_types; zend_generic_parameter *generic_parameters; uint32_t num_generic_parameters; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 7e4478888f279..2e76165e2e355 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -465,7 +465,6 @@ void init_compiler(void) /* {{{ */ CG(delayed_autoloads) = NULL; CG(unlinked_uses) = NULL; CG(current_linking_class) = NULL; - CG(bound_generic_types) = NULL; } /* }}} */ @@ -494,12 +493,6 @@ void shutdown_compiler(void) /* {{{ */ CG(unlinked_uses) = NULL; } CG(current_linking_class) = NULL; - /* This can happen during a fatal error */ - if (CG(bound_generic_types)) { - zend_hash_destroy(CG(bound_generic_types)); - FREE_HASHTABLE(CG(bound_generic_types)); - CG(bound_generic_types) = NULL; - } } /* }}} */ diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 1269bb6969a74..079bfb99caccf 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -151,8 +151,6 @@ struct _zend_compiler_globals { HashTable *delayed_autoloads; HashTable *unlinked_uses; zend_class_entry *current_linking_class; - /* Those are initialized and destroyed by zend_do_inheritance_ex() */ - HashTable *bound_generic_types; uint32_t rtd_key_counter; diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 0f59eb2d1973d..9fcf2f06ca08b 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -333,6 +333,23 @@ ZEND_API void destroy_zend_class(zval *zv) return; } + bool persistent = ce->type == ZEND_INTERNAL_CLASS; + /* Common to internal and user classes */ + if (ce->attributes) { + zend_hash_release(ce->attributes); + } + if (ce->bound_types) { + zend_hash_release(ce->bound_types); + } + if (ce->num_generic_parameters > 0) { + for (uint32_t generic_param_index = 0; generic_param_index < ce->num_generic_parameters; generic_param_index++) { + const zend_generic_parameter generic_param = ce->generic_parameters[generic_param_index]; + zend_string_release(generic_param.name); + zend_type_release(generic_param.constraint, persistent); + } + pefree(ce->generic_parameters, persistent); + } + switch (ce->type) { case ZEND_USER_CLASS: if (!(ce->ce_flags & ZEND_ACC_CACHED)) { @@ -347,22 +364,6 @@ ZEND_API void destroy_zend_class(zval *zv) zend_string_release_ex(ce->doc_comment, 0); } - if (ce->attributes) { - zend_hash_release(ce->attributes); - } - - if (ce->bound_types) { - zend_hash_release(ce->bound_types); - } - if (ce->num_generic_parameters > 0) { - for (uint32_t generic_param_index = 0; generic_param_index < ce->num_generic_parameters; generic_param_index++) { - const zend_generic_parameter generic_param = ce->generic_parameters[generic_param_index]; - zend_string_release_ex(generic_param.name, false); - zend_type_release(generic_param.constraint, false); - } - efree(ce->generic_parameters); - } - if (ce->num_interfaces > 0 && !(ce->ce_flags & ZEND_ACC_RESOLVED_INTERFACES)) { uint32_t i; @@ -536,20 +537,6 @@ ZEND_API void destroy_zend_class(zval *zv) if (ce->properties_info_table) { free(ce->properties_info_table); } - if (ce->attributes) { - zend_hash_release(ce->attributes); - } - if (ce->bound_types) { - zend_hash_release(ce->bound_types); - } - if (ce->num_generic_parameters > 0) { - for (uint32_t generic_param_index = 0; generic_param_index < ce->num_generic_parameters; generic_param_index++) { - const zend_generic_parameter generic_param = ce->generic_parameters[generic_param_index]; - zend_string_release(generic_param.name); - zend_type_release(generic_param.constraint, true); - } - free(ce->generic_parameters); - } free(ce); break; } From 51b689e3280ce266eeafa6d48f0a553ec8043d01 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 12 May 2025 15:16:11 +0100 Subject: [PATCH 20/41] move stuff --- ..._abstract_generic_constraint_failure.phpt} | 2 +- ...licit_interface_different_bound_types.phpt | 21 ++++ .../implicit_interface_no_bound_types.phpt | 21 ++++ ...nterface_abstract_generic_types_basic.phpt | 35 ++++++ ...twice_interface_generic_into_concrete.phpt | 27 +++++ Zend/zend_inheritance.c | 107 +++++++++--------- 6 files changed, 160 insertions(+), 53 deletions(-) rename Zend/tests/type_declarations/abstract_generics/constraints/{extended_interface_abstract_generic_types.phpt => extended_interface_abstract_generic_constraint_failure.phpt} (87%) create mode 100644 Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/extended_twice_interface_abstract_generic_types_basic.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/extended_twice_interface_generic_into_concrete.phpt diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/extended_interface_abstract_generic_types.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/extended_interface_abstract_generic_constraint_failure.phpt similarity index 87% rename from Zend/tests/type_declarations/abstract_generics/constraints/extended_interface_abstract_generic_types.phpt rename to Zend/tests/type_declarations/abstract_generics/constraints/extended_interface_abstract_generic_constraint_failure.phpt index da395e28caf5d..4203cc15b3ec9 100644 --- a/Zend/tests/type_declarations/abstract_generics/constraints/extended_interface_abstract_generic_types.phpt +++ b/Zend/tests/type_declarations/abstract_generics/constraints/extended_interface_abstract_generic_constraint_failure.phpt @@ -1,5 +1,5 @@ --TEST-- -Abstract generic type behaviour in extended interface +Abstract generic type behaviour in extended interface violates type constraint --FILE-- { + public function foo(T $param): T; +} + +interface I2 extends I1 { + public function bar(int $o, T $param): T; +} + +class C implements I2, I1 { + public function foo(float $param): float {} + public function bar(int $o, float $param): float {} +} + +?> +--EXPECTF-- +Fatal error: Bound type float is not a subtype of the constraint type string|int of generic type T of interface I in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types.phpt new file mode 100644 index 0000000000000..6b68b070df81c --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types.phpt @@ -0,0 +1,21 @@ +--TEST-- +Implicit interface inheritance missing bound types +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I1 { + public function bar(int $o, T $param): T; +} + +class C implements I2, I1 { + public function foo(float $param): float {} + public function bar(int $o, float $param): float {} +} + +?> +--EXPECTF-- +Fatal error: Bound type float is not a subtype of the constraint type string|int of generic type T of interface I in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/extended_twice_interface_abstract_generic_types_basic.phpt b/Zend/tests/type_declarations/abstract_generics/extended_twice_interface_abstract_generic_types_basic.phpt new file mode 100644 index 0000000000000..0fe5f3a4ea70e --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_twice_interface_abstract_generic_types_basic.phpt @@ -0,0 +1,35 @@ +--TEST-- +Abstract generic type behaviour in extended interface +--FILE-- + { + public function foo(T1 $param): T1; +} + +interface I2 extends I1 { + public function bar(int $o, T2 $param): T2; +} + +interface I3 extends I2 { + public function foobar(T3 $a, float $b): float; +} + +class C implements I3 { + public function foo(string $param): string {} + public function bar(int $o, string $param): string {} + public function foobar(string $a, float $b): float {} +} + +?> +DONE +--EXPECT-- +DONE + + + +Fatal error: CE: I3, Iface: I2, binding to Iface: I1, Generic type name: T2 +Binding generic types for CE: I2 for interface: I1 +Binding generic types for CE: I3 for interface: I2 +Binding generic types for CE: I3 for inherited interface: I1 +Binding generic types to generic type: T2 for inherited interface: I1 diff --git a/Zend/tests/type_declarations/abstract_generics/extended_twice_interface_generic_into_concrete.phpt b/Zend/tests/type_declarations/abstract_generics/extended_twice_interface_generic_into_concrete.phpt new file mode 100644 index 0000000000000..f42fb17c691b0 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_twice_interface_generic_into_concrete.phpt @@ -0,0 +1,27 @@ +--TEST-- +Abstract generic type behaviour in extended interface +--FILE-- + { + public function foo(T1 $param): T1; +} + +interface I2 extends I1 { + public function bar(int $o, T2 $param): T2; +} + +interface I3 extends I2 { + public function foobar(string $a, float $b): float; +} + +class C implements I3 { + public function foo(string $param): string {} + public function bar(int $o, string $param): string {} + public function foobar(string $a, float $b): float {} +} + +?> +DONE +--EXPECT-- +DONE diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 5972664ada959..f5525b21bce4d 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1640,57 +1640,6 @@ static inline void do_implement_interface(zend_class_entry *ce, zend_class_entry } /* }}} */ -// TODO Merge with the one in zend_compile -static void zend_types_ht_dtor(zval *ptr) { - zend_type *type = Z_PTR_P(ptr); - // TODO Figure out persistency? - zend_type_release(*type, false); - efree(type); -} - -static void interface_bind_generic_types_for_interfaces(zend_class_entry *ce, const zend_class_entry *iface) { - zend_string *iface_lc_name = zend_string_tolower(iface->name); - const HashTable *ce_bound_types = ce->bound_types ? zend_hash_find_ptr(ce->bound_types, iface_lc_name) : NULL; - for (uint32_t i = 0; i < iface->num_interfaces; i++) { - zend_class_entry *entry = iface->interfaces[i]; - /* Bind generic types */ - /* We need to propagate the bound generic parameters to the inherited interfaces */ - if (entry->num_generic_parameters == 0) { - continue; - } - zend_string *inherited_iface_lc_name = zend_string_tolower(entry->name); - const HashTable *interface_bound_types = zend_hash_find_ptr(iface->bound_types, inherited_iface_lc_name); - HashTable *ce_bound_types_to_inherited_iface = zend_hash_find_ptr(ce->bound_types, inherited_iface_lc_name); - ZEND_ASSERT(interface_bound_types != NULL && "This must exist at this point"); - if (ce_bound_types_to_inherited_iface == NULL) { - ALLOC_HASHTABLE(ce_bound_types_to_inherited_iface); - zend_hash_init(ce_bound_types_to_inherited_iface, entry->num_generic_parameters, NULL, zend_types_ht_dtor, false /* todo depend on internal or not */); - zend_hash_add_new_ptr(ce->bound_types, inherited_iface_lc_name, ce_bound_types_to_inherited_iface); - } - for ( - uint32_t inherited_iface_generic_param_index = 0; - inherited_iface_generic_param_index < entry->num_generic_parameters; - inherited_iface_generic_param_index++ - ) { - const zend_generic_parameter *inherited_generic_parameter = &entry->generic_parameters[inherited_iface_generic_param_index]; - const zend_type *iface_bound_type_ptr = zend_hash_index_find_ptr(interface_bound_types, inherited_iface_generic_param_index); - ZEND_ASSERT(iface_bound_type_ptr != NULL); - zend_type bound_type; - if (ZEND_TYPE_IS_ASSOCIATED(*iface_bound_type_ptr)) { - memcpy(&bound_type, zend_hash_find_ptr(ce_bound_types, ZEND_TYPE_NAME(*iface_bound_type_ptr)), sizeof(zend_type)); - } else { - bound_type = *iface_bound_type_ptr; - } - /* Deep type copy */ - zend_type_copy_ctor(&bound_type, true, false); - zend_hash_add_mem(ce_bound_types_to_inherited_iface, inherited_generic_parameter->name, - &bound_type, sizeof(bound_type)); - } - zend_string_release_ex(inherited_iface_lc_name, false); - } - zend_string_release_ex(iface_lc_name, false); -} - static void zend_do_inherit_interfaces(zend_class_entry *ce, const zend_class_entry *iface) /* {{{ */ { /* expects interface to be contained in ce's interface list already */ @@ -2249,6 +2198,57 @@ static void do_inherit_iface_constant(zend_string *name, zend_class_constant *c, } /* }}} */ +// TODO Merge with the one in zend_compile +static void zend_types_ht_dtor(zval *ptr) { + zend_type *type = Z_PTR_P(ptr); + // TODO Figure out persistency? + zend_type_release(*type, false); + efree(type); +} + +ZEND_ATTRIBUTE_NONNULL static void bind_generic_types_for_inherited_interfaces(zend_class_entry *ce, const zend_class_entry *iface) { + zend_string *iface_lc_name = zend_string_tolower(iface->name); + const HashTable *ce_bound_types = ce->bound_types ? zend_hash_find_ptr(ce->bound_types, iface_lc_name) : NULL; + for (uint32_t i = 0; i < iface->num_interfaces; i++) { + zend_class_entry *entry = iface->interfaces[i]; + /* Bind generic types */ + /* We need to propagate the bound generic parameters to the inherited interfaces */ + if (entry->num_generic_parameters == 0) { + continue; + } + zend_string *inherited_iface_lc_name = zend_string_tolower(entry->name); + const HashTable *interface_bound_types = zend_hash_find_ptr(iface->bound_types, inherited_iface_lc_name); + HashTable *ce_bound_types_to_inherited_iface = zend_hash_find_ptr(ce->bound_types, inherited_iface_lc_name); + ZEND_ASSERT(interface_bound_types != NULL && "This must exist at this point"); + if (ce_bound_types_to_inherited_iface == NULL) { + ALLOC_HASHTABLE(ce_bound_types_to_inherited_iface); + zend_hash_init(ce_bound_types_to_inherited_iface, entry->num_generic_parameters, NULL, zend_types_ht_dtor, false /* todo depend on internal or not */); + zend_hash_add_new_ptr(ce->bound_types, inherited_iface_lc_name, ce_bound_types_to_inherited_iface); + } + for ( + uint32_t inherited_iface_generic_param_index = 0; + inherited_iface_generic_param_index < entry->num_generic_parameters; + inherited_iface_generic_param_index++ + ) { + const zend_generic_parameter *inherited_generic_parameter = &entry->generic_parameters[inherited_iface_generic_param_index]; + const zend_type *iface_bound_type_ptr = zend_hash_index_find_ptr(interface_bound_types, inherited_iface_generic_param_index); + ZEND_ASSERT(iface_bound_type_ptr != NULL); + zend_type bound_type; + if (ZEND_TYPE_IS_ASSOCIATED(*iface_bound_type_ptr)) { + memcpy(&bound_type, zend_hash_find_ptr(ce_bound_types, ZEND_TYPE_NAME(*iface_bound_type_ptr)), sizeof(zend_type)); + } else { + bound_type = *iface_bound_type_ptr; + } + /* Deep type copy */ + zend_type_copy_ctor(&bound_type, true, false); + zend_hash_add_mem(ce_bound_types_to_inherited_iface, inherited_generic_parameter->name, + &bound_type, sizeof(bound_type)); + } + zend_string_release_ex(inherited_iface_lc_name, false); + } + zend_string_release_ex(iface_lc_name, false); +} + static void do_interface_implementation(zend_class_entry *ce, zend_class_entry *iface) /* {{{ */ { zend_function *func; @@ -2334,6 +2334,9 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * zend_string_release(constraint_type_str); return; } + ///* Bind our generic type (the key) to the generic iface type */ + //zend_type iface_generic_type = (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(generic_parameter->name), /* allow null */ false, _ZEND_TYPE_ASSOCIATED_BIT); + //zend_hash_add_mem(bound_types, current_generic_param_name, &iface_generic_type, sizeof(iface_generic_type)); break; } } else { @@ -2361,7 +2364,7 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * } } } - interface_bind_generic_types_for_interfaces(ce, iface); + bind_generic_types_for_inherited_interfaces(ce, iface); ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&iface->constants_table, key, c) { do_inherit_iface_constant(key, c, ce, iface); } ZEND_HASH_FOREACH_END(); From cad9308b659c885c63773b1a45534ba9bf55b419 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 12 May 2025 15:19:23 +0100 Subject: [PATCH 21/41] name of variable --- Zend/zend_inheritance.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index f5525b21bce4d..737e7d8f4c815 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2210,27 +2210,27 @@ ZEND_ATTRIBUTE_NONNULL static void bind_generic_types_for_inherited_interfaces(z zend_string *iface_lc_name = zend_string_tolower(iface->name); const HashTable *ce_bound_types = ce->bound_types ? zend_hash_find_ptr(ce->bound_types, iface_lc_name) : NULL; for (uint32_t i = 0; i < iface->num_interfaces; i++) { - zend_class_entry *entry = iface->interfaces[i]; + const zend_class_entry *inherited_iface = iface->interfaces[i]; /* Bind generic types */ /* We need to propagate the bound generic parameters to the inherited interfaces */ - if (entry->num_generic_parameters == 0) { + if (inherited_iface->num_generic_parameters == 0) { continue; } - zend_string *inherited_iface_lc_name = zend_string_tolower(entry->name); + zend_string *inherited_iface_lc_name = zend_string_tolower(inherited_iface->name); const HashTable *interface_bound_types = zend_hash_find_ptr(iface->bound_types, inherited_iface_lc_name); HashTable *ce_bound_types_to_inherited_iface = zend_hash_find_ptr(ce->bound_types, inherited_iface_lc_name); ZEND_ASSERT(interface_bound_types != NULL && "This must exist at this point"); if (ce_bound_types_to_inherited_iface == NULL) { ALLOC_HASHTABLE(ce_bound_types_to_inherited_iface); - zend_hash_init(ce_bound_types_to_inherited_iface, entry->num_generic_parameters, NULL, zend_types_ht_dtor, false /* todo depend on internal or not */); + zend_hash_init(ce_bound_types_to_inherited_iface, inherited_iface->num_generic_parameters, NULL, zend_types_ht_dtor, false /* todo depend on internal or not */); zend_hash_add_new_ptr(ce->bound_types, inherited_iface_lc_name, ce_bound_types_to_inherited_iface); } for ( uint32_t inherited_iface_generic_param_index = 0; - inherited_iface_generic_param_index < entry->num_generic_parameters; + inherited_iface_generic_param_index < inherited_iface->num_generic_parameters; inherited_iface_generic_param_index++ ) { - const zend_generic_parameter *inherited_generic_parameter = &entry->generic_parameters[inherited_iface_generic_param_index]; + const zend_generic_parameter *inherited_generic_parameter = &inherited_iface->generic_parameters[inherited_iface_generic_param_index]; const zend_type *iface_bound_type_ptr = zend_hash_index_find_ptr(interface_bound_types, inherited_iface_generic_param_index); ZEND_ASSERT(iface_bound_type_ptr != NULL); zend_type bound_type; From 5e076d02cdadc4fcfb5ebbbf19fee477f1063187 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 12 May 2025 16:34:00 +0100 Subject: [PATCH 22/41] Rewrite generic type binding for inherited interfaces --- ...nterface_abstract_generic_types_basic.phpt | 8 -- Zend/zend_inheritance.c | 118 +++++++++++------- 2 files changed, 70 insertions(+), 56 deletions(-) diff --git a/Zend/tests/type_declarations/abstract_generics/extended_twice_interface_abstract_generic_types_basic.phpt b/Zend/tests/type_declarations/abstract_generics/extended_twice_interface_abstract_generic_types_basic.phpt index 0fe5f3a4ea70e..14ce05d3e248b 100644 --- a/Zend/tests/type_declarations/abstract_generics/extended_twice_interface_abstract_generic_types_basic.phpt +++ b/Zend/tests/type_declarations/abstract_generics/extended_twice_interface_abstract_generic_types_basic.phpt @@ -25,11 +25,3 @@ class C implements I3 { DONE --EXPECT-- DONE - - - -Fatal error: CE: I3, Iface: I2, binding to Iface: I1, Generic type name: T2 -Binding generic types for CE: I2 for interface: I1 -Binding generic types for CE: I3 for interface: I2 -Binding generic types for CE: I3 for inherited interface: I1 -Binding generic types to generic type: T2 for inherited interface: I1 diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 737e7d8f4c815..af10cf981561a 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2198,7 +2198,12 @@ static void do_inherit_iface_constant(zend_string *name, zend_class_constant *c, } /* }}} */ -// TODO Merge with the one in zend_compile +// TODO Merge with the ones in zend_compile +static void zend_bound_types_ht_dtor(zval *ptr) { + HashTable *interface_bound_types = Z_PTR_P(ptr); + zend_hash_destroy(interface_bound_types); + efree(interface_bound_types); +} static void zend_types_ht_dtor(zval *ptr) { zend_type *type = Z_PTR_P(ptr); // TODO Figure out persistency? @@ -2207,46 +2212,66 @@ static void zend_types_ht_dtor(zval *ptr) { } ZEND_ATTRIBUTE_NONNULL static void bind_generic_types_for_inherited_interfaces(zend_class_entry *ce, const zend_class_entry *iface) { - zend_string *iface_lc_name = zend_string_tolower(iface->name); - const HashTable *ce_bound_types = ce->bound_types ? zend_hash_find_ptr(ce->bound_types, iface_lc_name) : NULL; - for (uint32_t i = 0; i < iface->num_interfaces; i++) { - const zend_class_entry *inherited_iface = iface->interfaces[i]; - /* Bind generic types */ - /* We need to propagate the bound generic parameters to the inherited interfaces */ - if (inherited_iface->num_generic_parameters == 0) { - continue; + const HashTable *iface_bound_types = iface->bound_types; + if (iface_bound_types == NULL) { +#ifdef ZEND_DEBUG + for (uint32_t i = 0; i < iface->num_interfaces; i++) { + const zend_class_entry *inherited_iface = iface->interfaces[i]; + ZEND_ASSERT(inherited_iface->num_generic_parameters == 0); } - zend_string *inherited_iface_lc_name = zend_string_tolower(inherited_iface->name); - const HashTable *interface_bound_types = zend_hash_find_ptr(iface->bound_types, inherited_iface_lc_name); - HashTable *ce_bound_types_to_inherited_iface = zend_hash_find_ptr(ce->bound_types, inherited_iface_lc_name); - ZEND_ASSERT(interface_bound_types != NULL && "This must exist at this point"); - if (ce_bound_types_to_inherited_iface == NULL) { - ALLOC_HASHTABLE(ce_bound_types_to_inherited_iface); - zend_hash_init(ce_bound_types_to_inherited_iface, inherited_iface->num_generic_parameters, NULL, zend_types_ht_dtor, false /* todo depend on internal or not */); - zend_hash_add_new_ptr(ce->bound_types, inherited_iface_lc_name, ce_bound_types_to_inherited_iface); - } - for ( - uint32_t inherited_iface_generic_param_index = 0; - inherited_iface_generic_param_index < inherited_iface->num_generic_parameters; - inherited_iface_generic_param_index++ - ) { - const zend_generic_parameter *inherited_generic_parameter = &inherited_iface->generic_parameters[inherited_iface_generic_param_index]; - const zend_type *iface_bound_type_ptr = zend_hash_index_find_ptr(interface_bound_types, inherited_iface_generic_param_index); - ZEND_ASSERT(iface_bound_type_ptr != NULL); - zend_type bound_type; - if (ZEND_TYPE_IS_ASSOCIATED(*iface_bound_type_ptr)) { - memcpy(&bound_type, zend_hash_find_ptr(ce_bound_types, ZEND_TYPE_NAME(*iface_bound_type_ptr)), sizeof(zend_type)); +#endif + return; + } + + if (ce->bound_types == NULL) { + ALLOC_HASHTABLE(ce->bound_types); + zend_hash_init(ce->bound_types, zend_hash_num_elements(iface_bound_types), NULL, zend_bound_types_ht_dtor, false /* todo depend on internal or not */); + } + const HashTable *ce_bound_types_for_direct_iface = zend_hash_find_ptr_lc(ce->bound_types, iface->name); + + zend_string *lc_inherited_iface_name = NULL; + const HashTable *interface_bound_types_for_inherited_iface = NULL; + ZEND_HASH_FOREACH_STR_KEY_PTR(iface_bound_types, lc_inherited_iface_name, interface_bound_types_for_inherited_iface) { + ZEND_ASSERT(lc_inherited_iface_name != NULL); + + zend_string *generic_param_name = NULL; + zend_ulong generic_param_index = 0; + zend_type *bound_type_ptr = NULL; + HashTable *ce_bound_types_for_inherited_iface = NULL; + ALLOC_HASHTABLE(ce_bound_types_for_inherited_iface); + zend_hash_init( + ce_bound_types_for_inherited_iface, + zend_hash_num_elements(interface_bound_types_for_inherited_iface), + NULL, + zend_types_ht_dtor, + false /* TODO depends on internals */ + ); + ZEND_HASH_FOREACH_KEY_PTR(interface_bound_types_for_inherited_iface, generic_param_index, generic_param_name, bound_type_ptr) { + zend_type bound_type = *bound_type_ptr; + if (ZEND_TYPE_IS_ASSOCIATED(bound_type)) { + ZEND_ASSERT(ce_bound_types_for_direct_iface != NULL && + "If a bound type is generic then we must have bound types for the current interface"); + // TODO Resolve + const zend_type *ce_bound_type_ptr = zend_hash_find_ptr(ce_bound_types_for_direct_iface, ZEND_TYPE_NAME(bound_type)); + ZEND_ASSERT(ce_bound_type_ptr != NULL); + bound_type = *ce_bound_type_ptr; + } + + zend_type_copy_ctor(&bound_type, true, false /* TODO Depends on internal or not? */); + if (generic_param_name) { + zend_hash_add_mem(ce_bound_types_for_inherited_iface, generic_param_name, + &bound_type, sizeof(bound_type)); } else { - bound_type = *iface_bound_type_ptr; + zend_hash_index_add_mem(ce_bound_types_for_inherited_iface, generic_param_index, + &bound_type, sizeof(bound_type)); } - /* Deep type copy */ - zend_type_copy_ctor(&bound_type, true, false); - zend_hash_add_mem(ce_bound_types_to_inherited_iface, inherited_generic_parameter->name, - &bound_type, sizeof(bound_type)); - } - zend_string_release_ex(inherited_iface_lc_name, false); - } - zend_string_release_ex(iface_lc_name, false); + } ZEND_HASH_FOREACH_END(); + + // TODO Check we don't already have the bound types for the inherited CE + //HashTable * + + zend_hash_add_new_ptr(ce->bound_types, lc_inherited_iface_name, ce_bound_types_for_inherited_iface); + } ZEND_HASH_FOREACH_END(); } static void do_interface_implementation(zend_class_entry *ce, zend_class_entry *iface) /* {{{ */ @@ -2334,9 +2359,6 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * zend_string_release(constraint_type_str); return; } - ///* Bind our generic type (the key) to the generic iface type */ - //zend_type iface_generic_type = (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(generic_parameter->name), /* allow null */ false, _ZEND_TYPE_ASSOCIATED_BIT); - //zend_hash_add_mem(bound_types, current_generic_param_name, &iface_generic_type, sizeof(iface_generic_type)); break; } } else { @@ -2354,14 +2376,14 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * zend_string_release(constraint_type_str); return; } - /* Change key from index to generic parameter name */ - /* Deep type copy */ - zend_type bound_type = *bound_type_ptr; - zend_type_copy_ctor(&bound_type, true, false); - zend_hash_add_mem(bound_types, generic_parameter->name, - &bound_type, sizeof(bound_type)); - //zend_hash_index_del(bound_types, i); } + /* Deep type copy */ + zend_type bound_type = *bound_type_ptr; + zend_type_copy_ctor(&bound_type, true, false); + zend_hash_add_mem(bound_types, generic_parameter->name, + &bound_type, sizeof(bound_type)); + ///* Should we change the key from index to generic parameter name? */ + //zend_hash_index_del(bound_types, i); } } bind_generic_types_for_inherited_interfaces(ce, iface); From beabf2bf17abfcebe95f4b32e41c0b5b3ff9561c Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 12 May 2025 17:28:48 +0100 Subject: [PATCH 23/41] Do some fixes about implicit and explicit interface implementations --- ...licit_interface_different_bound_types.phpt | 2 +- ...icit_interface_different_bound_types2.phpt | 21 +++++++++++++ .../implicit_interface_no_bound_types.phpt | 4 ++- .../implicit_interface_no_bound_types2.phpt | 21 +++++++++++++ .../implicit_interface_same_bound_types.phpt | 24 ++++++++++++++ .../implicit_interface_same_bound_types2.phpt | 22 +++++++++++++ Zend/zend_inheritance.c | 31 ++++++++++++++++--- 7 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types2.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types2.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_same_bound_types.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_same_bound_types2.phpt diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types.phpt index f16a88f463387..9389f2918ce04 100644 --- a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types.phpt +++ b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types.phpt @@ -18,4 +18,4 @@ class C implements I2, I1 { ?> --EXPECTF-- -Fatal error: Bound type float is not a subtype of the constraint type string|int of generic type T of interface I in %s on line %d +Fatal error: Bound types for implicitly and explicitly implemented interfaces must match in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types2.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types2.phpt new file mode 100644 index 0000000000000..afaf58f9e37f9 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types2.phpt @@ -0,0 +1,21 @@ +--TEST-- +Implicit interface inheritance with different bound types 2 +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I1 { + public function bar(int $o, T $param): T; +} + +class C implements I1, I2 { + public function foo(string $param): string {} + public function bar(int $o, float $param): float {} +} + +?> +--EXPECTF-- +Fatal error: Bound types for implicitly and explicitly implemented interfaces must match in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types.phpt index 6b68b070df81c..bfdbc01eb7d15 100644 --- a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types.phpt +++ b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types.phpt @@ -1,5 +1,7 @@ --TEST-- Implicit interface inheritance missing bound types +--XFAIL-- +Wrong number of missing params --FILE-- , I1 { ?> --EXPECTF-- -Fatal error: Bound type float is not a subtype of the constraint type string|int of generic type T of interface I in %s on line %d +Fatal error: Cannot implement I1 as the number of generic arguments specified (0) does not match the number of generic parameters declared on the interface (1) in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types2.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types2.phpt new file mode 100644 index 0000000000000..9fcf066125b95 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types2.phpt @@ -0,0 +1,21 @@ +--TEST-- +Implicit interface inheritance missing bound types 2 +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I1 { + public function bar(int $o, T $param): T; +} + +class C implements I1, I2 { + public function foo(float $param): float {} + public function bar(int $o, float $param): float {} +} + +?> +--EXPECTF-- +Fatal error: Cannot implement I1 as it has generic parameters which are not specified in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_same_bound_types.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_same_bound_types.phpt new file mode 100644 index 0000000000000..61236d7261a57 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_same_bound_types.phpt @@ -0,0 +1,24 @@ +--TEST-- +Implicit interface inheritance with same bound types +--XFAIL-- +This emits an error when it shouldn't +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I1 { + public function bar(int $o, T $param): T; +} + +class C implements I2, I1 { + public function foo(string $param): string {} + public function bar(int $o, string $param): string {} +} + +?> +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_same_bound_types2.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_same_bound_types2.phpt new file mode 100644 index 0000000000000..d6ca51381c9df --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_same_bound_types2.phpt @@ -0,0 +1,22 @@ +--TEST-- +Implicit interface inheritance with same bound types 2 +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I1 { + public function bar(int $o, T $param): T; +} + +class C implements I1, I2 { + public function foo(string $param): string {} + public function bar(int $o, string $param): string {} +} + +?> +DONE +--EXPECT-- +DONE diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index af10cf981561a..1c7317f99cfa9 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2251,7 +2251,6 @@ ZEND_ATTRIBUTE_NONNULL static void bind_generic_types_for_inherited_interfaces(z if (ZEND_TYPE_IS_ASSOCIATED(bound_type)) { ZEND_ASSERT(ce_bound_types_for_direct_iface != NULL && "If a bound type is generic then we must have bound types for the current interface"); - // TODO Resolve const zend_type *ce_bound_type_ptr = zend_hash_find_ptr(ce_bound_types_for_direct_iface, ZEND_TYPE_NAME(bound_type)); ZEND_ASSERT(ce_bound_type_ptr != NULL); bound_type = *ce_bound_type_ptr; @@ -2267,9 +2266,32 @@ ZEND_ATTRIBUTE_NONNULL static void bind_generic_types_for_inherited_interfaces(z } } ZEND_HASH_FOREACH_END(); - // TODO Check we don't already have the bound types for the inherited CE - //HashTable * - + const HashTable *existing_bound_types_for_inherited_iface = zend_hash_find_ptr(ce->bound_types, lc_inherited_iface_name); + if (EXPECTED(existing_bound_types_for_inherited_iface == NULL)) { + } else { + zend_ulong idx; + zend_string *bound_name; + const zend_type *ptr; + ZEND_HASH_FOREACH_KEY_PTR(existing_bound_types_for_inherited_iface, idx, bound_name, ptr) { + if (bound_name != NULL) { + continue; + } + const zend_type t1 = *ptr; + const zend_type *ptr2 = zend_hash_index_find_ptr(ce_bound_types_for_inherited_iface, idx); + ZEND_ASSERT(ptr2 != NULL); + const zend_type t2 = *ptr2; + if ( + ZEND_TYPE_FULL_MASK(t1) != ZEND_TYPE_FULL_MASK(t2) + || (ZEND_TYPE_HAS_NAME(t1) && !zend_string_equals(ZEND_TYPE_NAME(t1), ZEND_TYPE_NAME(t2))) + // || ZEND_TYPE_HAS_LIST(t1) && TODO Check list types are equal + ) { + // TODO Improve this error message + zend_error_noreturn(E_COMPILE_ERROR, "Bound types for implicitly and explicitly implemented interfaces must match"); + } + } ZEND_HASH_FOREACH_END(); + /* Remove current ones as they may be incomplete without the type name binding */ + zend_hash_del(ce->bound_types, lc_inherited_iface_name); + } zend_hash_add_new_ptr(ce->bound_types, lc_inherited_iface_name, ce_bound_types_for_inherited_iface); } ZEND_HASH_FOREACH_END(); } @@ -2310,6 +2332,7 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * } const uint32_t num_bound_types = zend_hash_num_elements(bound_types); if (UNEXPECTED(num_bound_types != iface->num_generic_parameters)) { + // TODO Need to handle implicit inherited interfaces zend_error_noreturn(E_COMPILE_ERROR, "Cannot implement %s as the number of generic arguments specified (%" PRIu32 ") does not match the number of generic parameters declared on the interface (%" PRIu32 ")", ZSTR_VAL(iface->name), From 36a4736f9ccdd9c8a3aa774c643ec0125bcde5d7 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 13 May 2025 00:08:13 +0100 Subject: [PATCH 24/41] Fix namespace resolution for interfaces --- Zend/zend_compile.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 2e76165e2e355..c18a43ac3fbb1 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1778,13 +1778,14 @@ static zend_string *zend_resolve_const_class_name_reference(zend_ast *ast, const static zend_string *zend_resolve_const_class_name_reference_with_generics(zend_ast *ast, const char *type) { - zend_string *class_name = zend_ast_get_str(ast->child[0]); - if (ZEND_FETCH_CLASS_DEFAULT != zend_get_class_fetch_type_ast(ast)) { + zend_ast *name_ast = ast->child[0]; + zend_string *class_name = zend_ast_get_str(name_ast); + if (ZEND_FETCH_CLASS_DEFAULT != zend_get_class_fetch_type_ast(name_ast)) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot use \"%s\" as %s, as it is reserved", ZSTR_VAL(class_name), type); } - return zend_resolve_class_name(class_name, ast->attr); + return zend_resolve_class_name(class_name, name_ast->attr); } static void zend_ensure_valid_class_fetch_type(uint32_t fetch_type) /* {{{ */ From 7fbdc0aab8f77a9dfe84367055ae7546f8212aaf Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 13 May 2025 06:45:54 +0100 Subject: [PATCH 25/41] Use safe_pemalloc for generic params alloc --- Zend/zend_compile.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index c18a43ac3fbb1..3805d5a080dff 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -9153,7 +9153,7 @@ static void zend_compile_enum_backing_type(zend_class_entry *ce, zend_ast *enum_ static void zend_compile_generic_params(zend_ast *params_ast) { const zend_ast_list *list = zend_ast_get_list(params_ast); - zend_generic_parameter *generic_params = emalloc(list->children * sizeof(zend_generic_parameter)); + zend_generic_parameter *generic_params = safe_pemalloc(list->children, sizeof(zend_generic_parameter), 0, CG(active_class_entry)->type & ZEND_INTERNAL_CLASS); CG(active_class_entry)->generic_parameters = generic_params; for (uint32_t i = 0; i < list->children; i++) { @@ -9174,7 +9174,6 @@ static void zend_compile_generic_params(zend_ast *params_ast) } if (param_ast->child[1]) { - // TODO Need to free this? constraint_type = zend_compile_typename(param_ast->child[1]); if (ZEND_TYPE_IS_ASSOCIATED(constraint_type)) { zend_error_noreturn(E_COMPILE_ERROR, From 6cdc4f5f4c2e0ff3463665f820a32f990fc27ec0 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 13 May 2025 06:58:40 +0100 Subject: [PATCH 26/41] Fix AST and name resolution --- Zend/zend_ast.c | 70 ++++++++++++++----- Zend/zend_compile.c | 2 +- .../interface_with_generic_types.phpt | 58 +++++++++++++++ 3 files changed, 112 insertions(+), 18 deletions(-) create mode 100644 ext/zend_test/tests/compile_to_ast/interface_with_generic_types.phpt diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 83f9613f5243d..5738109020d52 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1570,6 +1570,16 @@ static ZEND_COLD void zend_ast_export_ns_name(smart_str *str, zend_ast *ast, int zend_ast_export_ex(str, ast, priority, indent); } +static ZEND_COLD void zend_ast_export_class_name(smart_str *str, zend_ast *ast, int priority, int indent) +{ + if (ast->kind == ZEND_AST_CLASS_REF) { + ZEND_ASSERT(ast->child[1] == NULL && "Generic params not supported yet"); + zend_ast_export_ns_name(str, ast->child[0], priority, indent); + return; + } + zend_ast_export_ex(str, ast, priority, indent); +} + static ZEND_COLD bool zend_ast_valid_var_char(char ch) { unsigned char c = (unsigned char)ch; @@ -1690,7 +1700,7 @@ static ZEND_COLD void zend_ast_export_name_list_ex(smart_str *str, zend_ast_list if (i != 0) { smart_str_appends(str, separator); } - zend_ast_export_name(str, list->child[i], 0, indent); + zend_ast_export_ns_name(str, list->child[i], 0, indent); i++; } } @@ -1957,6 +1967,21 @@ static ZEND_COLD void zend_ast_export_type(smart_str *str, zend_ast *ast, int in zend_ast_export_ns_name(str, ast, 0, indent); } +static ZEND_COLD void zend_ast_export_generic_arg_list(smart_str *str, const zend_ast_list *list, int indent) { + // TODO Why cannot I just use + // zend_ast_export_list(str, list, true, 0, indent); + // ? + + uint32_t i = 0; + while (i < list->children) { + if (i != 0) { + smart_str_appends(str, ", "); + } + zend_ast_export_type(str, list->child[i], indent); + i++; + } +} + static ZEND_COLD void zend_ast_export_hook_list(smart_str *str, zend_ast_list *hook_list, int indent) { smart_str_appends(str, " {"); @@ -2156,10 +2181,17 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio } smart_str_appends(str, "class "); } - smart_str_appendl(str, ZSTR_VAL(decl->name), ZSTR_LEN(decl->name)); - if (decl->flags & ZEND_ACC_ENUM && decl->child[4]) { - smart_str_appends(str, ": "); - zend_ast_export_type(str, decl->child[4], indent); + smart_str_append(str, decl->name); + if (decl->child[4]) { + if (decl->flags & ZEND_ACC_ENUM) { + smart_str_appends(str, ": "); + zend_ast_export_type(str, decl->child[4], indent); + } else { + ZEND_ASSERT(decl->flags & ZEND_ACC_INTERFACE); + smart_str_appendc(str, '<'); + zend_ast_export_list(str, zend_ast_get_list(decl->child[4]), true, 0, indent); + smart_str_appendc(str, '>'); + } } zend_ast_export_class_no_header(str, decl, indent); smart_str_appendc(str, '\n'); @@ -2444,6 +2476,21 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio smart_str_appends(str, "::"); zend_ast_export_name(str, ast->child[1], 0, indent); break; + case ZEND_AST_GENERIC_PARAM: + zend_ast_export_name(str, ast->child[0], 0, indent); + if (ast->child[1]) { + smart_str_appendl(str, ZEND_STRL(" : ")); + zend_ast_export_type(str, ast->child[1], indent); + } + break; + case ZEND_AST_CLASS_REF: + zend_ast_export_ns_name(str, ast->child[0], 0, indent); + if (ast->child[1]) { + smart_str_appendc(str, '<'); + zend_ast_export_generic_arg_list(str, zend_ast_get_list(ast->child[1]), indent); + smart_str_appendc(str, '>'); + } + break; case ZEND_AST_CLASS_NAME: if (ast->child[0] == NULL) { /* The const expr representation stores the fetch type instead. */ @@ -2457,7 +2504,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio EMPTY_SWITCH_DEFAULT_CASE() } } else { - zend_ast_export_ns_name(str, ast->child[0], 0, indent); + zend_ast_export_class_name(str, ast->child[0], 0, indent); } smart_str_appends(str, "::class"); break; @@ -2725,17 +2772,6 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio smart_str_appends(str, ": "); ast = ast->child[1]; goto tail_call; - // TODO Export generic types - //case ZEND_AST_ASSOCIATED_TYPE: - // smart_str_appends(str, "type "); - // zend_ast_export_name(str, ast->child[0], 0, indent); - // if (ast->child[1]) { - // smart_str_appends(str, " : "); - // smart_str_appends(str, " : "); - // zend_ast_export_type(str, ast->child[1], indent); - // } - // smart_str_appendc(str, ';'); - //break; /* 3 child nodes */ case ZEND_AST_METHOD_CALL: diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 3805d5a080dff..4f0687dce0041 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -9120,7 +9120,7 @@ static zend_string *zend_generate_anon_class_name(zend_ast_decl *decl) prefix = zend_resolve_const_class_name_reference(decl->child[0], "class name"); } else if (decl->child[1]) { zend_ast_list *list = zend_ast_get_list(decl->child[1]); - prefix = zend_resolve_const_class_name_reference(list->child[0], "interface name"); + prefix = zend_resolve_const_class_name_reference_with_generics(list->child[0], "interface name"); } zend_string *result = zend_strpprintf(0, "%s@anonymous%c%s:%" PRIu32 "$%" PRIx32, diff --git a/ext/zend_test/tests/compile_to_ast/interface_with_generic_types.phpt b/ext/zend_test/tests/compile_to_ast/interface_with_generic_types.phpt new file mode 100644 index 0000000000000..d57bce88a1f70 --- /dev/null +++ b/ext/zend_test/tests/compile_to_ast/interface_with_generic_types.phpt @@ -0,0 +1,58 @@ +--TEST-- +AST can be recreated (interface with generic types) +--EXTENSIONS-- +zend_test +--FILE-- + { + public function bar(T1 $v): T2; + } +} + +namespace Foo { + interface MyInterface2 extends \MyInterface1 { + public function foobar(S $v): int; + } + + class MyClass implements MyInterface2 { + public function bar(string $v): string {} + public function foobar(string $v): int {} + } +} + +namespace { + echo zend_test_compile_to_ast( file_get_contents( __FILE__ ) ); +} + +?> +--EXPECT-- +namespace { + interface MyInterface1 { + public function bar(T1 $v): T2; + + } + +} + +namespace Foo { + interface MyInterface2 implements \MyInterface1 { + public function foobar(S $v): int; + + } + + class MyClass implements MyInterface2 { + public function bar(string $v): string { + } + + public function foobar(string $v): int { + } + + } + +} + +namespace { + echo zend_test_compile_to_ast(file_get_contents(__FILE__)); +} From 7b38d21f6a855e99060b537fe518b4a39ffbd644 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 14 May 2025 09:14:10 +0100 Subject: [PATCH 27/41] Extra fixes --- Zend/zend_compile.c | 12 +++++------- Zend/zend_opcode.c | 10 +++++++--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 4f0687dce0041..03ac1040ffa0a 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1457,9 +1457,8 @@ zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry str = add_type_string(str, resolved, /* is_intersection */ false); zend_string_release(resolved); } ZEND_TYPE_LIST_FOREACH_END(); - // TODO Is this still required? - //} else if (ZEND_TYPE_IS_ASSOCIATED(type)) { - // str = add_associated_type(ZEND_TYPE_NAME(type), scope); + } else if (ZEND_TYPE_IS_ASSOCIATED(type)) { + ZEND_ASSERT(false && "Generic type declarations do not exist yet"); } else if (ZEND_TYPE_HAS_NAME(type)) { str = resolve_class_name(ZEND_TYPE_NAME(type), scope); } @@ -9082,20 +9081,19 @@ static void zend_compile_implements(zend_ast *ast) /* {{{ */ zend_resolve_const_class_name_reference_with_generics(interface_ast, "interface name"); interface_names[i].lc_name = zend_string_tolower(interface_names[i].name); - // TODO, need the list to a type list if (interface_ast->child[1]) { const zend_ast_list *generics_list = zend_ast_get_list(interface_ast->child[1]); const uint32_t num_generic_args = generics_list->children; - // TODO Check that we have the same number of generic args? + // TODO Can we already check that we have correct number of generic args? if (ce->bound_types == NULL) { ALLOC_HASHTABLE(ce->bound_types); - zend_hash_init(ce->bound_types, list->children-i, NULL, zend_bound_types_ht_dtor, false /* todo depend on internal or not */); + zend_hash_init(ce->bound_types, list->children-i, NULL, zend_bound_types_ht_dtor, false /* todo depend on internal or not? */); } HashTable *bound_interface_types; ALLOC_HASHTABLE(bound_interface_types); - zend_hash_init(bound_interface_types, num_generic_args, NULL, zend_types_ht_dtor, false /* todo depend on internal or not */); + zend_hash_init(bound_interface_types, num_generic_args, NULL, zend_types_ht_dtor, false /* todo depend on internal or not? */); for (uint32_t generic_param = 0; generic_param < num_generic_args; ++generic_param) { zend_type bound_type = zend_compile_typename(generics_list->child[generic_param]); zend_hash_index_add_mem(bound_interface_types, generic_param, &bound_type, sizeof(bound_type)); diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 9fcf2f06ca08b..ed319e2ee5ec8 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -335,9 +335,6 @@ ZEND_API void destroy_zend_class(zval *zv) bool persistent = ce->type == ZEND_INTERNAL_CLASS; /* Common to internal and user classes */ - if (ce->attributes) { - zend_hash_release(ce->attributes); - } if (ce->bound_types) { zend_hash_release(ce->bound_types); } @@ -364,6 +361,10 @@ ZEND_API void destroy_zend_class(zval *zv) zend_string_release_ex(ce->doc_comment, 0); } + if (ce->attributes) { + zend_hash_release(ce->attributes); + } + if (ce->num_interfaces > 0 && !(ce->ce_flags & ZEND_ACC_RESOLVED_INTERFACES)) { uint32_t i; @@ -537,6 +538,9 @@ ZEND_API void destroy_zend_class(zval *zv) if (ce->properties_info_table) { free(ce->properties_info_table); } + if (ce->attributes) { + zend_hash_release(ce->attributes); + } free(ce); break; } From 20e21aba80be54a86942d8723b213722a8082d6d Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 14 May 2025 09:20:12 +0100 Subject: [PATCH 28/41] Rename last associated macro stuff to generic --- Zend/zend_compile.c | 15 +++++++-------- Zend/zend_inheritance.c | 8 ++++---- Zend/zend_types.h | 10 +++++----- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 03ac1040ffa0a..0eed77505545e 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1437,6 +1437,7 @@ static zend_string *add_intersection_type(zend_string *str, zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry *scope) { zend_string *str = NULL; + ZEND_ASSERT(!ZEND_TYPE_IS_GENERIC_PARAM_NAME(type) && "Generic type declarations do not exist yet"); /* Pure intersection type */ if (ZEND_TYPE_IS_INTERSECTION(type)) { ZEND_ASSERT(!ZEND_TYPE_IS_UNION(type)); @@ -1457,8 +1458,6 @@ zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry str = add_type_string(str, resolved, /* is_intersection */ false); zend_string_release(resolved); } ZEND_TYPE_LIST_FOREACH_END(); - } else if (ZEND_TYPE_IS_ASSOCIATED(type)) { - ZEND_ASSERT(false && "Generic type declarations do not exist yet"); } else if (ZEND_TYPE_HAS_NAME(type)) { str = resolve_class_name(ZEND_TYPE_NAME(type), scope); } @@ -7020,7 +7019,7 @@ static zend_type zend_compile_single_typename(zend_ast *ast) for (uint32_t generic_param_index = 0; generic_param_index < ce->num_generic_parameters; generic_param_index++) { const zend_generic_parameter *genric_param = &ce->generic_parameters[generic_param_index]; if (zend_string_equals(type_name, genric_param->name)) { - return (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(type_name), /* allow null */ false, _ZEND_TYPE_ASSOCIATED_BIT); + return (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(type_name), /* allow null */ false, _ZEND_TYPE_GENERIC_PARAM_NAME_BIT); } } } @@ -7220,7 +7219,7 @@ static zend_type zend_compile_typename_ex( single_type = zend_compile_single_typename(type_ast); uint32_t single_type_mask = ZEND_TYPE_PURE_MASK(single_type); - if (ZEND_TYPE_IS_ASSOCIATED(single_type)) { + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(single_type)) { zend_error_noreturn(E_COMPILE_ERROR, "Generic type cannot be part of a union type"); } if (single_type_mask == MAY_BE_ANY) { @@ -7305,7 +7304,7 @@ static zend_type zend_compile_typename_ex( zend_ast *type_ast = list->child[i]; zend_type single_type = zend_compile_single_typename(type_ast); - if (ZEND_TYPE_IS_ASSOCIATED(single_type)) { + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(single_type)) { zend_error_noreturn(E_COMPILE_ERROR, "Generic type cannot be part of an intersection type"); } /* An intersection of union types cannot exist so invalidate it @@ -7374,10 +7373,10 @@ static zend_type zend_compile_typename_ex( if ((type_mask & MAY_BE_NULL) && is_marked_nullable) { zend_error_noreturn(E_COMPILE_ERROR, "null cannot be marked as nullable"); } - if (ZEND_TYPE_IS_ASSOCIATED(type) && is_marked_nullable) { + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(type) && is_marked_nullable) { zend_error_noreturn(E_COMPILE_ERROR, "Generic type cannot be part of a union type"); } - if (ZEND_TYPE_IS_ASSOCIATED(type) && force_allow_null) { + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(type) && force_allow_null) { zend_error_noreturn(E_COMPILE_ERROR, "Generic type cannot be part of a union type (implicitly nullable due to default null value)"); } @@ -9173,7 +9172,7 @@ static void zend_compile_generic_params(zend_ast *params_ast) if (param_ast->child[1]) { constraint_type = zend_compile_typename(param_ast->child[1]); - if (ZEND_TYPE_IS_ASSOCIATED(constraint_type)) { + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(constraint_type)) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot use generic parameter %s to constrain generic parameter %s", ZSTR_VAL(ZEND_TYPE_NAME(constraint_type)), ZSTR_VAL(name)); diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 1c7317f99cfa9..b69cd2aa543b6 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -723,12 +723,12 @@ ZEND_API inheritance_status zend_perform_covariant_type_check( } /* If we check for concrete return type */ - if (ZEND_TYPE_IS_ASSOCIATED(proto_type)) { + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(proto_type)) { return zend_is_type_subtype_of_generic_type( fe_scope, fe_type_ptr, proto_scope, proto_type_ptr); } /* If we check for concrete parameter type */ - if (ZEND_TYPE_IS_ASSOCIATED(fe_type)) { + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(fe_type)) { return zend_is_type_subtype_of_generic_type( proto_scope, proto_type_ptr, fe_scope, fe_type_ptr); } @@ -2248,7 +2248,7 @@ ZEND_ATTRIBUTE_NONNULL static void bind_generic_types_for_inherited_interfaces(z ); ZEND_HASH_FOREACH_KEY_PTR(interface_bound_types_for_inherited_iface, generic_param_index, generic_param_name, bound_type_ptr) { zend_type bound_type = *bound_type_ptr; - if (ZEND_TYPE_IS_ASSOCIATED(bound_type)) { + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(bound_type)) { ZEND_ASSERT(ce_bound_types_for_direct_iface != NULL && "If a bound type is generic then we must have bound types for the current interface"); const zend_type *ce_bound_type_ptr = zend_hash_find_ptr(ce_bound_types_for_direct_iface, ZEND_TYPE_NAME(bound_type)); @@ -2347,7 +2347,7 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * ZEND_ASSERT(bound_type_ptr != NULL); /* We are currently extending another interface */ - if (ZEND_TYPE_IS_ASSOCIATED(*bound_type_ptr)) { + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(*bound_type_ptr)) { ZEND_ASSERT(ce->ce_flags & ZEND_ACC_INTERFACE); ZEND_ASSERT(ce->num_generic_parameters > 0); ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*bound_type_ptr)); diff --git a/Zend/zend_types.h b/Zend/zend_types.h index ed6d4f1921915..5a5dd050a8a48 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -149,12 +149,12 @@ typedef struct { #define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 26 #define _ZEND_TYPE_MASK ((1u << 26) - 1) /* Only one of these bits may be set. */ -#define _ZEND_TYPE_ASSOCIATED_BIT (1u << 25) +#define _ZEND_TYPE_GENERIC_PARAM_NAME_BIT (1u << 25) #define _ZEND_TYPE_NAME_BIT (1u << 24) // Used to signify that type.ptr is not a `zend_string*` but a `const char*`, #define _ZEND_TYPE_LITERAL_NAME_BIT (1u << 23) #define _ZEND_TYPE_LIST_BIT (1u << 22) -#define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_NAME_BIT|_ZEND_TYPE_LITERAL_NAME_BIT|_ZEND_TYPE_ASSOCIATED_BIT) +#define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_NAME_BIT|_ZEND_TYPE_LITERAL_NAME_BIT|_ZEND_TYPE_GENERIC_PARAM_NAME_BIT) /* For BC behaviour with iterable type */ #define _ZEND_TYPE_ITERABLE_BIT (1u << 21) /* Whether the type list is arena allocated */ @@ -172,7 +172,7 @@ typedef struct { (((t).type_mask & _ZEND_TYPE_MASK) != 0) /* If a type is complex it means it's either a list with a union or intersection, - * the void pointer is a class name, or the type is an associated type (which implies it is a name) */ + * the void pointer is a class name, or the type is a generic parameter name */ #define ZEND_TYPE_IS_COMPLEX(t) \ ((((t).type_mask) & _ZEND_TYPE_KIND_MASK) != 0) @@ -185,8 +185,8 @@ typedef struct { #define ZEND_TYPE_HAS_LIST(t) \ ((((t).type_mask) & _ZEND_TYPE_LIST_BIT) != 0) -#define ZEND_TYPE_IS_ASSOCIATED(t) \ - ((((t).type_mask) & _ZEND_TYPE_ASSOCIATED_BIT) != 0) +#define ZEND_TYPE_IS_GENERIC_PARAM_NAME(t) \ + ((((t).type_mask) & _ZEND_TYPE_GENERIC_PARAM_NAME_BIT) != 0) #define ZEND_TYPE_IS_ITERABLE_FALLBACK(t) \ ((((t).type_mask) & _ZEND_TYPE_ITERABLE_BIT) != 0) From 92a45c7cf2e8ec2a9570f4f623cc167b309ac2fc Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Thu, 15 May 2025 16:42:32 +0200 Subject: [PATCH 29/41] Add bound resolved type to string for error messages --- .../multiple_abstract_generic_type-error.phpt | 23 +++++++++++++++ Zend/zend_compile.c | 29 +++++++++++++++++-- Zend/zend_compile.h | 2 +- Zend/zend_execute.c | 2 +- Zend/zend_inheritance.c | 24 ++++++++------- 5 files changed, 65 insertions(+), 15 deletions(-) create mode 100644 Zend/tests/type_declarations/abstract_generics/multiple_abstract_generic_type-error.phpt diff --git a/Zend/tests/type_declarations/abstract_generics/multiple_abstract_generic_type-error.phpt b/Zend/tests/type_declarations/abstract_generics/multiple_abstract_generic_type-error.phpt new file mode 100644 index 0000000000000..f1626b9e9d0a8 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/multiple_abstract_generic_type-error.phpt @@ -0,0 +1,23 @@ +--TEST-- +Multiple abstract generic type incorrect bound types in implementation +--FILE-- + { + public function set(K $key, V $value): void; + public function get(K $key): V; +} + +class C implements I { + public array $a = []; + public function set(int $key, string $value): void { + $this->a[$key] = $value . '!'; + } + public function get(int $key): string { + return $this->a[$key]; + } +} + +?> +--EXPECTF-- +Fatal error: Declaration of C::set(int $key, string $value): void must be compatible with I::set( $key, $value): void in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 0eed77505545e..acf73d6c9a976 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1434,10 +1434,31 @@ static zend_string *add_intersection_type(zend_string *str, return str; } -zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry *scope) { +static zend_string* resolve_bound_generic_type(zend_string *type_name, zend_class_entry *scope, const HashTable *bound_types) { + const zend_type *constraint = zend_hash_find_ptr(bound_types, type_name); + ZEND_ASSERT(constraint != NULL); + + zend_string *constraint_type_str = zend_type_to_string_resolved(*constraint, scope, /* need the bound types? */NULL); + + size_t len = ZSTR_LEN(type_name) + ZSTR_LEN(constraint_type_str) + strlen("< : >"); + zend_string *result = zend_string_alloc(len, 0); + + ZSTR_VAL(result)[0] = '<'; + memcpy(ZSTR_VAL(result) + strlen("<"), ZSTR_VAL(type_name), ZSTR_LEN(type_name)); + ZSTR_VAL(result)[ZSTR_LEN(type_name) + 1] = ' '; + ZSTR_VAL(result)[ZSTR_LEN(type_name) + 2] = ':'; + ZSTR_VAL(result)[ZSTR_LEN(type_name) + 3] = ' '; + memcpy(ZSTR_VAL(result) + ZSTR_LEN(type_name) + strlen("< : "), ZSTR_VAL(constraint_type_str), ZSTR_LEN(constraint_type_str)); + ZSTR_VAL(result)[len-1] = '>'; + ZSTR_VAL(result)[len] = '\0'; + + zend_string_release(constraint_type_str); + return result; +} + +zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry *scope, const HashTable *bound_types_to_scope) { zend_string *str = NULL; - ZEND_ASSERT(!ZEND_TYPE_IS_GENERIC_PARAM_NAME(type) && "Generic type declarations do not exist yet"); /* Pure intersection type */ if (ZEND_TYPE_IS_INTERSECTION(type)) { ZEND_ASSERT(!ZEND_TYPE_IS_UNION(type)); @@ -1458,6 +1479,8 @@ zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry str = add_type_string(str, resolved, /* is_intersection */ false); zend_string_release(resolved); } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(type)) { + str = resolve_bound_generic_type(ZEND_TYPE_NAME(type), scope, bound_types_to_scope); } else if (ZEND_TYPE_HAS_NAME(type)) { str = resolve_class_name(ZEND_TYPE_NAME(type), scope); } @@ -1527,7 +1550,7 @@ zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry } ZEND_API zend_string *zend_type_to_string(zend_type type) { - return zend_type_to_string_resolved(type, NULL); + return zend_type_to_string_resolved(type, NULL, NULL); } static bool is_generator_compatible_class_type(const zend_string *name) { diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 6d8c50e1ea8ba..9cfea5e2097b8 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -1014,7 +1014,7 @@ int ZEND_FASTCALL zendlex(zend_parser_stack_elem *elem); void zend_assert_valid_class_name(const zend_string *const_name, const char *type); -zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scope); +zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scope, const HashTable *bound_types_to_scope); ZEND_API zend_string *zend_type_to_string(zend_type type); /* BEGIN: OPCODES */ diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 0fbfdfa07ef04..3c0679233556d 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -686,7 +686,7 @@ static ZEND_COLD void zend_verify_type_error_common( *fclass = ""; } - *need_msg = zend_type_to_string_resolved(arg_info->type, zf->common.scope); + *need_msg = zend_type_to_string_resolved(arg_info->type, zf->common.scope, /* TODO? */ NULL); if (value) { *given_kind = zend_zval_value_name(value); diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index b69cd2aa543b6..29903aae21487 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -946,10 +946,10 @@ static inheritance_status zend_do_perform_implementation_check( /* }}} */ static ZEND_COLD void zend_append_type_hint( - smart_str *str, zend_class_entry *scope, const zend_arg_info *arg_info, bool return_hint) /* {{{ */ + smart_str *str, zend_class_entry *scope, const HashTable *bound_types_to_scope, const zend_arg_info *arg_info, bool return_hint) /* {{{ */ { if (ZEND_TYPE_IS_SET(arg_info->type)) { - zend_string *type_str = zend_type_to_string_resolved(arg_info->type, scope); + zend_string *type_str = zend_type_to_string_resolved(arg_info->type, scope, bound_types_to_scope); smart_str_append(str, type_str); zend_string_release(type_str); if (!return_hint) { @@ -960,7 +960,7 @@ static ZEND_COLD void zend_append_type_hint( /* }}} */ static ZEND_COLD zend_string *zend_get_function_declaration( - const zend_function *fptr, zend_class_entry *scope) /* {{{ */ + const zend_function *fptr, zend_class_entry *scope, const HashTable *bound_types_to_scope) /* {{{ */ { smart_str str = {0}; @@ -991,7 +991,7 @@ static ZEND_COLD zend_string *zend_get_function_declaration( num_args++; } for (uint32_t i = 0; i < num_args;) { - zend_append_type_hint(&str, scope, arg_info, 0); + zend_append_type_hint(&str, scope, bound_types_to_scope, arg_info, 0); if (ZEND_ARG_SEND_MODE(arg_info)) { smart_str_appendc(&str, '&'); @@ -1088,7 +1088,7 @@ static ZEND_COLD zend_string *zend_get_function_declaration( if (fptr->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { smart_str_appends(&str, ": "); - zend_append_type_hint(&str, scope, fptr->common.arg_info - 1, 1); + zend_append_type_hint(&str, scope, bound_types_to_scope, fptr->common.arg_info - 1, 1); } smart_str_0(&str); @@ -1108,8 +1108,12 @@ static void ZEND_COLD emit_incompatible_method_error( const zend_function *child, zend_class_entry *child_scope, const zend_function *parent, zend_class_entry *parent_scope, inheritance_status status) { - zend_string *parent_prototype = zend_get_function_declaration(parent, parent_scope); - zend_string *child_prototype = zend_get_function_declaration(child, child_scope); + const HashTable *bound_types_to_parent = NULL; + if (child_scope->bound_types) { + bound_types_to_parent = zend_hash_find_ptr_lc(child_scope->bound_types, parent_scope->name); + } + zend_string *parent_prototype = zend_get_function_declaration(parent, parent_scope, bound_types_to_parent); + zend_string *child_prototype = zend_get_function_declaration(child, child_scope, NULL); if (status == INHERITANCE_UNRESOLVED) { // TODO Improve error message if first unresolved class is present in child and parent? /* Fetch the first unresolved class from registered autoloads */ @@ -1362,7 +1366,7 @@ static inheritance_status full_property_types_compatible( static ZEND_COLD void emit_incompatible_property_error( const zend_property_info *child, const zend_property_info *parent, prop_variance variance) { - zend_string *type_str = zend_type_to_string_resolved(parent->type, parent->ce); + zend_string *type_str = zend_type_to_string_resolved(parent->type, parent->ce, /* TODO? */ NULL); zend_error_noreturn(E_COMPILE_ERROR, "Type of %s::$%s must be %s%s (as in class %s)", ZSTR_VAL(child->ce->name), @@ -1376,7 +1380,7 @@ static ZEND_COLD void emit_incompatible_property_error( static ZEND_COLD void emit_set_hook_type_error(const zend_property_info *child, const zend_property_info *parent) { zend_type set_type = parent->hooks[ZEND_PROPERTY_HOOK_SET]->common.arg_info[0].type; - zend_string *type_str = zend_type_to_string_resolved(set_type, parent->ce); + zend_string *type_str = zend_type_to_string_resolved(set_type, parent->ce, /* TODO? */ NULL); zend_error_noreturn(E_COMPILE_ERROR, "Set type of %s::$%s must be supertype of %s (as in %s %s)", ZSTR_VAL(child->ce->name), @@ -1677,7 +1681,7 @@ static void zend_do_inherit_interfaces(zend_class_entry *ce, const zend_class_en static void emit_incompatible_class_constant_error( const zend_class_constant *child, const zend_class_constant *parent, const zend_string *const_name) { - zend_string *type_str = zend_type_to_string_resolved(parent->type, parent->ce); + zend_string *type_str = zend_type_to_string_resolved(parent->type, parent->ce, NULL); zend_error_noreturn(E_COMPILE_ERROR, "Type of %s::%s must be compatible with %s::%s of type %s", ZSTR_VAL(child->ce->name), From d1bae3825061939742623c589dea760e9ffaca3f Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 19 May 2025 12:56:14 +0100 Subject: [PATCH 30/41] Fix typo in variable name --- Zend/zend_compile.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index acf73d6c9a976..73debfbaaf56f 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7040,8 +7040,8 @@ static zend_type zend_compile_single_typename(zend_ast *ast) if (ce && ce->num_generic_parameters > 0) { for (uint32_t generic_param_index = 0; generic_param_index < ce->num_generic_parameters; generic_param_index++) { - const zend_generic_parameter *genric_param = &ce->generic_parameters[generic_param_index]; - if (zend_string_equals(type_name, genric_param->name)) { + const zend_generic_parameter *generic_param = &ce->generic_parameters[generic_param_index]; + if (zend_string_equals(type_name, generic_param->name)) { return (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(type_name), /* allow null */ false, _ZEND_TYPE_GENERIC_PARAM_NAME_BIT); } } From 518d7ea459baa038cb0d46f3baab00cd559df166 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 19 May 2025 13:03:43 +0100 Subject: [PATCH 31/41] Update wording for error message --- .../implicit_interface_no_bound_types.phpt | 2 +- .../implicit_interface_no_bound_types2.phpt | 2 +- .../errors/no_bound_abstract_generic_type.phpt | 2 +- ...nd_abstract_generic_type_with_constraint.phpt | 2 +- ...ract_generic_type_with_prior_bound_types.phpt | 2 +- Zend/zend_inheritance.c | 16 +++++++++------- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types.phpt index bfdbc01eb7d15..e4d5769f5a48b 100644 --- a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types.phpt +++ b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types.phpt @@ -20,4 +20,4 @@ class C implements I2, I1 { ?> --EXPECTF-- -Fatal error: Cannot implement I1 as the number of generic arguments specified (0) does not match the number of generic parameters declared on the interface (1) in %s on line %d +Fatal error: Interface I1 expects 1 generic parameters, 0 given in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types2.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types2.phpt index 9fcf066125b95..4f505ebb0a9e4 100644 --- a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types2.phpt +++ b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types2.phpt @@ -18,4 +18,4 @@ class C implements I1, I2 { ?> --EXPECTF-- -Fatal error: Cannot implement I1 as it has generic parameters which are not specified in %s on line %d +Fatal error: Interface I1 expects 1 generic parameters, 0 given in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type.phpt b/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type.phpt index aeca6dfc84271..932ff702388e2 100644 --- a/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type.phpt +++ b/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type.phpt @@ -13,4 +13,4 @@ class C implements I { ?> --EXPECTF-- -Fatal error: Cannot implement I as it has generic parameters which are not specified in %s on line %d +Fatal error: Interface I expects 1 generic parameters, 0 given in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_constraint.phpt b/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_constraint.phpt index b74f4593d548a..bce328f805b1d 100644 --- a/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_constraint.phpt +++ b/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_constraint.phpt @@ -13,4 +13,4 @@ class C implements I { ?> --EXPECTF-- -Fatal error: Cannot implement I as it has generic parameters which are not specified in %s on line %d +Fatal error: Interface I expects 1 generic parameters, 0 given in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_prior_bound_types.phpt b/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_prior_bound_types.phpt index 8e8f6e66e65a8..417c90a8d2aae 100644 --- a/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_prior_bound_types.phpt +++ b/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_prior_bound_types.phpt @@ -17,4 +17,4 @@ class C implements I1, I2 { ?> --EXPECTF-- -Fatal error: Cannot implement I2 as it has generic parameters which are not specified in %s on line %d +Fatal error: Interface I2 expects 1 generic parameters, 0 given in %s on line %d diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 29903aae21487..e1fda71005cfc 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2323,25 +2323,27 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * if (iface->num_generic_parameters > 0) { if (UNEXPECTED(ce->bound_types == NULL)) { zend_error_noreturn(E_COMPILE_ERROR, - "Cannot implement %s as it has generic parameters which are not specified", - ZSTR_VAL(iface->name) + "Interface %s expects %" PRIu32 " generic parameters, 0 given", + ZSTR_VAL(iface->name), + iface->num_generic_parameters ); } HashTable *bound_types = zend_hash_find_ptr_lc(ce->bound_types, iface->name); if (UNEXPECTED(bound_types == NULL)) { zend_error_noreturn(E_COMPILE_ERROR, - "Cannot implement %s as it has generic parameters which are not specified", - ZSTR_VAL(iface->name) + "Interface %s expects %" PRIu32 " generic parameters, 0 given", + ZSTR_VAL(iface->name), + iface->num_generic_parameters ); } const uint32_t num_bound_types = zend_hash_num_elements(bound_types); if (UNEXPECTED(num_bound_types != iface->num_generic_parameters)) { // TODO Need to handle implicit inherited interfaces zend_error_noreturn(E_COMPILE_ERROR, - "Cannot implement %s as the number of generic arguments specified (%" PRIu32 ") does not match the number of generic parameters declared on the interface (%" PRIu32 ")", + "Interface %s expects %" PRIu32 " generic parameters, %" PRIu32 " given", ZSTR_VAL(iface->name), - num_bound_types, - iface->num_generic_parameters + iface->num_generic_parameters, + num_bound_types ); } for (uint32_t i = 0; i < num_bound_types; i++) { From 9d17caeff14f2d9b707fae9fb5c79c5f6f7bd4f2 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 19 May 2025 18:23:05 +0100 Subject: [PATCH 32/41] Add failing test --- .../extended_interface_redeclares_method.phpt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Zend/tests/type_declarations/abstract_generics/extended_interface_redeclares_method.phpt diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_redeclares_method.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_redeclares_method.phpt new file mode 100644 index 0000000000000..c9dd55a59dde3 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_redeclares_method.phpt @@ -0,0 +1,18 @@ +--TEST-- +Abstract generic type behaviour in extended interface which redeclares method +--FILE-- + { + public function foo(T $param): int; +} + +interface I2 extends I1 { + public function foo(S $param): int; +} + +?> +DONE +--EXPECT-- +DONE + From 00372bd9f4661b9c3daf352296108680210915a3 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 19 May 2025 18:27:18 +0100 Subject: [PATCH 33/41] Add some const qualifiers --- Zend/zend_inheritance.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index e1fda71005cfc..3c2e8759a0a4d 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -813,7 +813,7 @@ ZEND_API inheritance_status zend_perform_covariant_type_check( static inheritance_status zend_do_perform_arg_type_hint_check( zend_class_entry *fe_scope, const zend_arg_info *fe_arg_info, - zend_class_entry *proto_scope, zend_arg_info *proto_arg_info) /* {{{ */ + zend_class_entry *proto_scope, const zend_arg_info *proto_arg_info) /* {{{ */ { if (!ZEND_TYPE_IS_SET(fe_arg_info->type) || ZEND_TYPE_PURE_MASK(fe_arg_info->type) == MAY_BE_ANY) { /* Child with no type or mixed type is always compatible */ @@ -881,10 +881,10 @@ static inheritance_status zend_do_perform_implementation_check( status = INHERITANCE_SUCCESS; for (uint32_t i = 0; i < num_args; i++) { - zend_arg_info *proto_arg_info = + const zend_arg_info *proto_arg_info = i < proto_num_args ? &proto->common.arg_info[i] : proto_is_variadic ? &proto->common.arg_info[proto_num_args - 1] : NULL; - zend_arg_info *fe_arg_info = + const zend_arg_info *fe_arg_info = i < fe_num_args ? &fe->common.arg_info[i] : fe_is_variadic ? &fe->common.arg_info[fe_num_args - 1] : NULL; if (!proto_arg_info) { From 02695e09bf96561653b30327a3eea45dcb879e97 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 19 May 2025 18:27:51 +0100 Subject: [PATCH 34/41] Change to static storage --- Zend/zend_inheritance.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 3c2e8759a0a4d..2b088d7b0eca9 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -675,7 +675,7 @@ static inheritance_status zend_is_intersection_subtype_of_type( return early_exit_status == INHERITANCE_ERROR ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; } -ZEND_API inheritance_status zend_perform_covariant_type_check( +static inheritance_status zend_perform_covariant_type_check( zend_class_entry *fe_scope, const zend_type *fe_type_ptr, zend_class_entry *proto_scope, const zend_type *proto_type_ptr); @@ -707,7 +707,7 @@ static inheritance_status zend_is_type_subtype_of_generic_type( } } -ZEND_API inheritance_status zend_perform_covariant_type_check( +static inheritance_status zend_perform_covariant_type_check( zend_class_entry *fe_scope, const zend_type *fe_type_ptr, zend_class_entry *proto_scope, const zend_type *proto_type_ptr) { From 6fd886029ad685971376480fb22d0173a87b0826 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 21 May 2025 00:12:55 +0100 Subject: [PATCH 35/41] rename interfaces --- .../extended_interface_redeclares_method.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_redeclares_method.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_redeclares_method.phpt index c9dd55a59dde3..cb7c82d589f75 100644 --- a/Zend/tests/type_declarations/abstract_generics/extended_interface_redeclares_method.phpt +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_redeclares_method.phpt @@ -3,11 +3,11 @@ Abstract generic type behaviour in extended interface which redeclares method --FILE-- { +interface I { public function foo(T $param): int; } -interface I2 extends I1 { +interface J extends I { public function foo(S $param): int; } From ad1ae053bbb0a5c113c9b82c9dbf90b651f116f9 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 21 May 2025 18:34:13 +0100 Subject: [PATCH 36/41] Fix redeclaration of method, and other issues --- .../extended_interface_redeclares_method.phpt | 4 +- ...erics_redeclares_method_contravariant.phpt | 17 ++ ..._generics_redeclares_method_covariant.phpt | 18 ++ ...ce_concrete_to_generic_contravariance.phpt | 17 ++ ...erface_concrete_to_generic_covariance.phpt | 17 ++ ...eclares_method_invalid_contravariance.phpt | 17 ++ ..._redeclares_method_invalid_covariance.phpt | 17 ++ .../multiple_abstract_generic_type-error.phpt | 2 +- Zend/zend_compile.c | 23 ++- Zend/zend_compile.h | 2 +- Zend/zend_inheritance.c | 177 ++++++++++++++---- Zend/zend_opcode.c | 2 +- 12 files changed, 263 insertions(+), 50 deletions(-) create mode 100644 Zend/tests/type_declarations/abstract_generics/extended_interface_with_generics_redeclares_method_contravariant.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/extended_interface_with_generics_redeclares_method_covariant.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/variance/extended_interface_concrete_to_generic_contravariance.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/variance/extended_interface_concrete_to_generic_covariance.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/variance/extended_interface_with_generics_redeclares_method_invalid_contravariance.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/variance/extended_interface_with_generics_redeclares_method_invalid_covariance.phpt rename Zend/tests/type_declarations/abstract_generics/{ => variance}/multiple_abstract_generic_type-error.phpt (82%) diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_redeclares_method.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_redeclares_method.phpt index cb7c82d589f75..359d94d594374 100644 --- a/Zend/tests/type_declarations/abstract_generics/extended_interface_redeclares_method.phpt +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_redeclares_method.phpt @@ -7,8 +7,8 @@ interface I { public function foo(T $param): int; } -interface J extends I { - public function foo(S $param): int; +interface J extends I { + public function foo(float $param): int; } ?> diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_with_generics_redeclares_method_contravariant.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_with_generics_redeclares_method_contravariant.phpt new file mode 100644 index 0000000000000..44ca267727701 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_with_generics_redeclares_method_contravariant.phpt @@ -0,0 +1,17 @@ +--TEST-- +Abstract generic type behaviour in extended interface which redeclares method generic type contravariant +--FILE-- + { + public function foo(T $param): int; +} + +interface J extends I { + public function foo(S $param): int; +} + +?> +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_with_generics_redeclares_method_covariant.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_with_generics_redeclares_method_covariant.phpt new file mode 100644 index 0000000000000..b73f71c68a14b --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_with_generics_redeclares_method_covariant.phpt @@ -0,0 +1,18 @@ +--TEST-- +Abstract generic type behaviour in extended interface which redeclares method generic type covariant +--FILE-- + { + public function foo(int $param): T; +} + +interface J extends I { + public function foo(int $param): S; +} + +?> +DONE +--EXPECT-- +DONE + diff --git a/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_concrete_to_generic_contravariance.phpt b/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_concrete_to_generic_contravariance.phpt new file mode 100644 index 0000000000000..602b917ea5fc0 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_concrete_to_generic_contravariance.phpt @@ -0,0 +1,17 @@ +--TEST-- +Redeclaring a method that has a concrete type into a generic type (contravariance) +--FILE-- + extends I { + public function foo(S $param): int; +} + +?> +DONE +--EXPECTF-- +Fatal error: Declaration of J::foo( $param): int must be compatible with I::foo(int $param): int %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_concrete_to_generic_covariance.phpt b/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_concrete_to_generic_covariance.phpt new file mode 100644 index 0000000000000..005a055e3f0fd --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_concrete_to_generic_covariance.phpt @@ -0,0 +1,17 @@ +--TEST-- +Redeclaring a method that has a concrete type into a generic type (covariance) +--FILE-- + extends I { + public function foo(int $param): S; +} + +?> +DONE +--EXPECTF-- +Fatal error: Declaration of J::foo(int $param): must be compatible with I::foo(int $param): int %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_with_generics_redeclares_method_invalid_contravariance.phpt b/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_with_generics_redeclares_method_invalid_contravariance.phpt new file mode 100644 index 0000000000000..f5360557bb6a3 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_with_generics_redeclares_method_invalid_contravariance.phpt @@ -0,0 +1,17 @@ +--TEST-- +Abstract generic type behaviour in extended interface which redeclares method but does not use bound generic type (contravariance) +--FILE-- + { + public function foo(T $param): int; +} + +interface J extends I { + public function foo(int $param): int; +} + +?> +DONE +--EXPECTF-- +Fatal error: Declaration of J::foo(int $param): int must be compatible with I>::foo(> $param): int %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_with_generics_redeclares_method_invalid_covariance.phpt b/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_with_generics_redeclares_method_invalid_covariance.phpt new file mode 100644 index 0000000000000..beb571606cf0f --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_with_generics_redeclares_method_invalid_covariance.phpt @@ -0,0 +1,17 @@ +--TEST-- +Abstract generic type behaviour in extended interface which redeclares method but does not use bound generic type (covariance) +--FILE-- + { + public function foo(int $param): T; +} + +interface J extends I { + public function foo(int $param): int; +} + +?> +DONE +--EXPECTF-- +Fatal error: Declaration of J::foo(int $param): int must be compatible with I>::foo(int $param): > in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/multiple_abstract_generic_type-error.phpt b/Zend/tests/type_declarations/abstract_generics/variance/multiple_abstract_generic_type-error.phpt similarity index 82% rename from Zend/tests/type_declarations/abstract_generics/multiple_abstract_generic_type-error.phpt rename to Zend/tests/type_declarations/abstract_generics/variance/multiple_abstract_generic_type-error.phpt index f1626b9e9d0a8..9112b8fd11aaa 100644 --- a/Zend/tests/type_declarations/abstract_generics/multiple_abstract_generic_type-error.phpt +++ b/Zend/tests/type_declarations/abstract_generics/variance/multiple_abstract_generic_type-error.phpt @@ -20,4 +20,4 @@ class C implements I { ?> --EXPECTF-- -Fatal error: Declaration of C::set(int $key, string $value): void must be compatible with I::set( $key, $value): void in %s on line %d +Fatal error: Declaration of C::set(int $key, string $value): void must be compatible with I::set( $key, $value): void in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 73debfbaaf56f..08fd412abfbae 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1389,7 +1389,7 @@ static zend_string *add_type_string(zend_string *type, zend_string *new_type, bo return result; } -static zend_string *resolve_class_name(zend_string *name, zend_class_entry *scope) { +static zend_string *resolve_class_name(zend_string *name, const zend_class_entry *scope) { if (scope) { if (zend_string_equals_ci(name, ZSTR_KNOWN(ZEND_STR_SELF))) { name = scope->name; @@ -1409,7 +1409,7 @@ static zend_string *resolve_class_name(zend_string *name, zend_class_entry *scop } static zend_string *add_intersection_type(zend_string *str, - const zend_type_list *intersection_type_list, zend_class_entry *scope, + const zend_type_list *intersection_type_list, const zend_class_entry *scope, bool is_bracketed) { const zend_type *single_type; @@ -1434,11 +1434,21 @@ static zend_string *add_intersection_type(zend_string *str, return str; } -static zend_string* resolve_bound_generic_type(zend_string *type_name, zend_class_entry *scope, const HashTable *bound_types) { +static zend_string* resolve_bound_generic_type(zend_string *type_name, const zend_class_entry *scope, const HashTable *bound_types) { + if (bound_types == NULL) { + const size_t len = ZSTR_LEN(type_name) + strlen("<>"); + zend_string *result = zend_string_alloc(len, 0); + ZSTR_VAL(result)[0] = '<'; + memcpy(ZSTR_VAL(result) + strlen("<"), ZSTR_VAL(type_name), ZSTR_LEN(type_name)); + ZSTR_VAL(result)[len-1] = '>'; + ZSTR_VAL(result)[len] = '\0'; + return result; + } + const zend_type *constraint = zend_hash_find_ptr(bound_types, type_name); ZEND_ASSERT(constraint != NULL); - zend_string *constraint_type_str = zend_type_to_string_resolved(*constraint, scope, /* need the bound types? */NULL); + zend_string *constraint_type_str = zend_type_to_string_resolved(*constraint, scope, NULL); size_t len = ZSTR_LEN(type_name) + ZSTR_LEN(constraint_type_str) + strlen("< : >"); zend_string *result = zend_string_alloc(len, 0); @@ -1456,7 +1466,7 @@ static zend_string* resolve_bound_generic_type(zend_string *type_name, zend_clas return result; } -zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry *scope, const HashTable *bound_types_to_scope) { +zend_string *zend_type_to_string_resolved(const zend_type type, const zend_class_entry *scope, const HashTable *bound_types_to_scope) { zend_string *str = NULL; /* Pure intersection type */ @@ -7042,7 +7052,8 @@ static zend_type zend_compile_single_typename(zend_ast *ast) for (uint32_t generic_param_index = 0; generic_param_index < ce->num_generic_parameters; generic_param_index++) { const zend_generic_parameter *generic_param = &ce->generic_parameters[generic_param_index]; if (zend_string_equals(type_name, generic_param->name)) { - return (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(type_name), /* allow null */ false, _ZEND_TYPE_GENERIC_PARAM_NAME_BIT); + // TODO Add ZEND_TYPE_INIT_GENERIC() macro that takes an index + return (zend_type) ZEND_TYPE_INIT_PTR(zend_string_copy(type_name), _ZEND_TYPE_GENERIC_PARAM_NAME_BIT, /* allow null */ false, 0); } } } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 9cfea5e2097b8..8e333127ce49a 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -1014,7 +1014,7 @@ int ZEND_FASTCALL zendlex(zend_parser_stack_elem *elem); void zend_assert_valid_class_name(const zend_string *const_name, const char *type); -zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scope, const HashTable *bound_types_to_scope); +zend_string *zend_type_to_string_resolved(zend_type type, const zend_class_entry *scope, const HashTable *bound_types_to_scope); ZEND_API zend_string *zend_type_to_string(zend_type type); /* BEGIN: OPCODES */ diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 2b088d7b0eca9..632a5721443cb 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -60,8 +60,8 @@ static void add_property_hook_obligation( zend_class_entry *ce, const zend_property_info *hooked_prop, const zend_function *hook_func); static void ZEND_COLD emit_incompatible_method_error( - const zend_function *child, zend_class_entry *child_scope, - const zend_function *parent, zend_class_entry *parent_scope, + const zend_function *child, const zend_class_entry *child_scope, + const zend_function *parent, const zend_class_entry *parent_scope, inheritance_status status); static void zend_type_copy_ctor(zend_type *const type, bool use_arena, bool persistent); @@ -91,7 +91,7 @@ static void zend_type_list_copy_ctor( static void zend_type_copy_ctor(zend_type *const type, bool use_arena, bool persistent) { if (ZEND_TYPE_HAS_LIST(*type)) { zend_type_list_copy_ctor(type, use_arena, persistent); - } else if (ZEND_TYPE_HAS_NAME(*type)) { + } else if (ZEND_TYPE_HAS_NAME(*type) || ZEND_TYPE_IS_GENERIC_PARAM_NAME(*type)) { zend_string_addref(ZEND_TYPE_NAME(*type)); } } @@ -679,6 +679,10 @@ static inheritance_status zend_perform_covariant_type_check( zend_class_entry *fe_scope, const zend_type *fe_type_ptr, zend_class_entry *proto_scope, const zend_type *proto_type_ptr); +static inheritance_status zend_perform_contravariant_type_check( + zend_class_entry *fe_scope, const zend_type *fe_type_ptr, + zend_class_entry *proto_scope, const zend_type *proto_type_ptr); + static inheritance_status zend_is_type_subtype_of_generic_type( zend_class_entry *concrete_scope, const zend_type *concrete_type_ptr, @@ -690,24 +694,37 @@ static inheritance_status zend_is_type_subtype_of_generic_type( const HashTable *bound_generic_types = zend_hash_find_ptr_lc(concrete_scope->bound_types, generic_type_scope->name); ZEND_ASSERT(bound_generic_types && "Have generic type"); - ZEND_ASSERT(ZEND_TYPE_HAS_NAME(generic_type)); + ZEND_ASSERT(ZEND_TYPE_IS_GENERIC_PARAM_NAME(generic_type)); const zend_type *bound_type_ptr = zend_hash_find_ptr(bound_generic_types, ZEND_TYPE_NAME(generic_type)); ZEND_ASSERT(bound_type_ptr != NULL); - /* Generic type must be invariant */ - const inheritance_status sub_type_status = zend_perform_covariant_type_check( - concrete_scope, concrete_type_ptr, generic_type_scope, bound_type_ptr); - const inheritance_status super_type_status = zend_perform_covariant_type_check( - generic_type_scope, bound_type_ptr, concrete_scope, concrete_type_ptr); - - if (sub_type_status != super_type_status) { - return INHERITANCE_ERROR; + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(*bound_type_ptr)) { + const zend_type concrete_type = *concrete_type_ptr; + if ( + ZEND_TYPE_IS_GENERIC_PARAM_NAME(concrete_type) + && zend_string_equals(ZEND_TYPE_NAME(concrete_type), ZEND_TYPE_NAME(*bound_type_ptr)) + ) { + return INHERITANCE_SUCCESS; + } else { + return INHERITANCE_ERROR; + } } else { - return sub_type_status; + /* Generic type must be invariant */ + // TODO Use zend_perform_contravariant_type_check()? + const inheritance_status sub_type_status = zend_perform_covariant_type_check( + concrete_scope, concrete_type_ptr, generic_type_scope, bound_type_ptr); + const inheritance_status super_type_status = zend_perform_contravariant_type_check( + concrete_scope, concrete_type_ptr, generic_type_scope, bound_type_ptr); + + if (sub_type_status != super_type_status) { + return INHERITANCE_ERROR; + } else { + return sub_type_status; + } } } -static inheritance_status zend_perform_covariant_type_check( +static inheritance_status zend_perform_covariant_type_check_no_generics( zend_class_entry *fe_scope, const zend_type *fe_type_ptr, zend_class_entry *proto_scope, const zend_type *proto_type_ptr) { @@ -722,17 +739,6 @@ static inheritance_status zend_perform_covariant_type_check( return INHERITANCE_SUCCESS; } - /* If we check for concrete return type */ - if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(proto_type)) { - return zend_is_type_subtype_of_generic_type( - fe_scope, fe_type_ptr, proto_scope, proto_type_ptr); - } - /* If we check for concrete parameter type */ - if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(fe_type)) { - return zend_is_type_subtype_of_generic_type( - proto_scope, proto_type_ptr, fe_scope, fe_type_ptr); - } - /* Builtin types may be removed, but not added */ uint32_t fe_type_mask = ZEND_TYPE_PURE_MASK(fe_type); uint32_t proto_type_mask = ZEND_TYPE_PURE_MASK(proto_type); @@ -811,6 +817,77 @@ static inheritance_status zend_perform_covariant_type_check( return INHERITANCE_UNRESOLVED; } +static inheritance_status zend_is_generic_type_subtype_of_generic_type( + const zend_class_entry *fe_scope, const zend_type *fe_type_ptr, + const zend_class_entry *proto_scope, const zend_type *proto_type_ptr +) { + const zend_type fe_type = *fe_type_ptr; + const zend_type proto_type = *proto_type_ptr; + + if (UNEXPECTED(!ZEND_TYPE_IS_GENERIC_PARAM_NAME(proto_type))) { + /* A generic type cannot be a subtype of a concrete one */ + return INHERITANCE_ERROR; + } + ZEND_ASSERT(ZEND_TYPE_IS_GENERIC_PARAM_NAME(fe_type)); + ZEND_ASSERT(fe_scope->bound_types); + + const HashTable *bound_generic_types = zend_hash_find_ptr_lc(fe_scope->bound_types, proto_scope->name); + ZEND_ASSERT(bound_generic_types && "Must have bound generic type"); + + zend_string *proto_type_name = ZEND_TYPE_NAME(proto_type); + ZEND_ASSERT(proto_type_name); + + const zend_type *bound_type_ptr = zend_hash_find_ptr(bound_generic_types, proto_type_name); + ZEND_ASSERT(bound_type_ptr != NULL); + + const zend_type bound_type = *bound_type_ptr; + ZEND_ASSERT(ZEND_TYPE_IS_GENERIC_PARAM_NAME(bound_type)); + + return zend_string_equals(ZEND_TYPE_NAME(bound_type), ZEND_TYPE_NAME(fe_type)) ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; +} + +static inheritance_status zend_perform_covariant_type_check( + zend_class_entry *fe_scope, const zend_type *fe_type_ptr, + zend_class_entry *proto_scope, const zend_type *proto_type_ptr +) { + const zend_type fe_type = *fe_type_ptr; + const zend_type proto_type = *proto_type_ptr; + + /* If we check for concrete return type */ + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(proto_type)) { + return zend_is_type_subtype_of_generic_type( + fe_scope, fe_type_ptr, proto_scope, proto_type_ptr); + } else if (UNEXPECTED(ZEND_TYPE_IS_GENERIC_PARAM_NAME(fe_type))) { + /* A generic type cannot be a subtype of a concrete one */ + return INHERITANCE_ERROR; + } + + return zend_perform_covariant_type_check_no_generics( + fe_scope, fe_type_ptr, proto_scope, proto_type_ptr); +} + +static inheritance_status zend_perform_contravariant_type_check( + zend_class_entry *fe_scope, const zend_type *fe_type_ptr, + zend_class_entry *proto_scope, const zend_type *proto_type_ptr +) { + const zend_type fe_type = *fe_type_ptr; + const zend_type proto_type = *proto_type_ptr; + + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(fe_type)) { + return zend_is_generic_type_subtype_of_generic_type( + fe_scope, fe_type_ptr, proto_scope, proto_type_ptr); + } + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(proto_type)) { + return zend_is_type_subtype_of_generic_type( + fe_scope, fe_type_ptr, proto_scope, proto_type_ptr); + } + + /* Contravariant type check is performed as a covariant type check with swapped + * argument order. */ + return zend_perform_covariant_type_check_no_generics( + proto_scope, proto_type_ptr, fe_scope, fe_type_ptr); +} + static inheritance_status zend_do_perform_arg_type_hint_check( zend_class_entry *fe_scope, const zend_arg_info *fe_arg_info, zend_class_entry *proto_scope, const zend_arg_info *proto_arg_info) /* {{{ */ @@ -825,10 +902,8 @@ static inheritance_status zend_do_perform_arg_type_hint_check( return INHERITANCE_ERROR; } - /* Contravariant type check is performed as a covariant type check with swapped - * argument order. */ - return zend_perform_covariant_type_check( - proto_scope, &proto_arg_info->type, fe_scope, &fe_arg_info->type); + return zend_perform_contravariant_type_check( + fe_scope, &fe_arg_info->type, proto_scope, &proto_arg_info->type); } /* }}} */ @@ -946,7 +1021,7 @@ static inheritance_status zend_do_perform_implementation_check( /* }}} */ static ZEND_COLD void zend_append_type_hint( - smart_str *str, zend_class_entry *scope, const HashTable *bound_types_to_scope, const zend_arg_info *arg_info, bool return_hint) /* {{{ */ + smart_str *str, const zend_class_entry *scope, const HashTable *bound_types_to_scope, const zend_arg_info *arg_info, bool return_hint) /* {{{ */ { if (ZEND_TYPE_IS_SET(arg_info->type)) { zend_string *type_str = zend_type_to_string_resolved(arg_info->type, scope, bound_types_to_scope); @@ -960,7 +1035,7 @@ static ZEND_COLD void zend_append_type_hint( /* }}} */ static ZEND_COLD zend_string *zend_get_function_declaration( - const zend_function *fptr, zend_class_entry *scope, const HashTable *bound_types_to_scope) /* {{{ */ + const zend_function *fptr, const zend_class_entry *scope, const HashTable *bound_types_to_scope) /* {{{ */ { smart_str str = {0}; @@ -975,6 +1050,31 @@ static ZEND_COLD zend_string *zend_get_function_declaration( } else { smart_str_appendl(&str, ZSTR_VAL(fptr->common.scope->name), ZSTR_LEN(fptr->common.scope->name)); } + if (scope->num_generic_parameters) { + bool is_first = true; + smart_str_appendc(&str, '<'); + for (uint32_t i = 0; i < scope->num_generic_parameters; i++) { + const zend_generic_parameter param = scope->generic_parameters[i]; + zend_string *constraint_type_str; + if (bound_types_to_scope) { + const zend_type *constraint = zend_hash_find_ptr(bound_types_to_scope, param.name); + ZEND_ASSERT(constraint != NULL); + constraint_type_str = zend_type_to_string_resolved(*constraint, scope, NULL); + } else { + constraint_type_str = zend_type_to_string_resolved(param.constraint, scope, NULL); + } + + if (!is_first) { + smart_str_appends(&str, ", "); + } + smart_str_append(&str, param.name); + smart_str_appends(&str, " : "); + smart_str_append(&str, constraint_type_str); + zend_string_release(constraint_type_str); + is_first = false; + } + smart_str_appendc(&str, '>'); + } smart_str_appends(&str, "::"); } @@ -1105,8 +1205,8 @@ static zend_always_inline uint32_t func_lineno(const zend_function *fn) { } static void ZEND_COLD emit_incompatible_method_error( - const zend_function *child, zend_class_entry *child_scope, - const zend_function *parent, zend_class_entry *parent_scope, + const zend_function *child, const zend_class_entry *child_scope, + const zend_function *parent, const zend_class_entry *parent_scope, inheritance_status status) { const HashTable *bound_types_to_parent = NULL; if (child_scope->bound_types) { @@ -1352,8 +1452,8 @@ static inheritance_status full_property_types_compatible( zend_perform_covariant_type_check( child_info->ce, &child_info->type, parent_info->ce, &parent_info->type); inheritance_status status2 = variance == PROP_COVARIANT ? INHERITANCE_SUCCESS : - zend_perform_covariant_type_check( - parent_info->ce, &parent_info->type, child_info->ce, &child_info->type); + zend_perform_contravariant_type_check( + child_info->ce, &child_info->type, parent_info->ce, &parent_info->type); if (status1 == INHERITANCE_SUCCESS && status2 == INHERITANCE_SUCCESS) { return INHERITANCE_SUCCESS; } @@ -1409,8 +1509,8 @@ static inheritance_status verify_property_type_compatibility( if (parent_info->hooks[ZEND_PROPERTY_HOOK_SET] && (!child_info->hooks || !child_info->hooks[ZEND_PROPERTY_HOOK_SET])) { zend_type set_type = parent_info->hooks[ZEND_PROPERTY_HOOK_SET]->common.arg_info[0].type; - inheritance_status result = zend_perform_covariant_type_check( - parent_info->ce, &set_type, child_info->ce, &child_info->type); + inheritance_status result = zend_perform_contravariant_type_check( + child_info->ce, &child_info->type, parent_info->ce, &set_type); if ((result == INHERITANCE_ERROR && throw_on_error) || (result == INHERITANCE_UNRESOLVED && throw_on_unresolved)) { emit_set_hook_type_error(child_info, parent_info); } @@ -2356,7 +2456,6 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(*bound_type_ptr)) { ZEND_ASSERT(ce->ce_flags & ZEND_ACC_INTERFACE); ZEND_ASSERT(ce->num_generic_parameters > 0); - ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*bound_type_ptr)); const zend_string *current_generic_param_name = ZEND_TYPE_NAME(*bound_type_ptr); for (uint32_t j = 0; j < ce->num_generic_parameters; j++) { const zend_generic_parameter *current_ce_generic_parameter = &ce->generic_parameters[j]; @@ -3026,7 +3125,7 @@ static bool do_trait_constant_check( return false; } else if (ZEND_TYPE_IS_SET(trait_constant->type)) { inheritance_status status1 = zend_perform_covariant_type_check(ce, &existing_constant->type, traits[current_trait], &trait_constant->type); - inheritance_status status2 = zend_perform_covariant_type_check(traits[current_trait], &trait_constant->type, ce, &existing_constant->type); + inheritance_status status2 = zend_perform_contravariant_type_check(ce, &existing_constant->type, traits[current_trait], &trait_constant->type); if (status1 == INHERITANCE_ERROR || status2 == INHERITANCE_ERROR) { emit_incompatible_trait_constant_error(ce, existing_constant, trait_constant, name, traits, current_trait); return false; diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index ed319e2ee5ec8..990cf6c36dfbd 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -118,7 +118,7 @@ ZEND_API void zend_type_release(zend_type type, bool persistent) { if (!ZEND_TYPE_USES_ARENA(type)) { pefree(ZEND_TYPE_LIST(type), persistent); } - } else if (ZEND_TYPE_HAS_NAME(type)) { + } else if (ZEND_TYPE_HAS_NAME(type) || ZEND_TYPE_IS_GENERIC_PARAM_NAME(type)) { zend_string_release(ZEND_TYPE_NAME(type)); } } From fba8f76f3f50eab24a1dea9435c5f9298ef6ee7e Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Thu, 22 May 2025 01:13:08 +0100 Subject: [PATCH 37/41] Add an index field to zend_type --- Zend/zend_compile.c | 5 ++--- Zend/zend_types.h | 18 +++++++++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 08fd412abfbae..a056d86d93ae1 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -84,7 +84,7 @@ static inline uint32_t zend_alloc_cache_slot(void) { return zend_alloc_cache_slots(1); } -const zend_type zend_mixed_type = { NULL, MAY_BE_ANY }; +const zend_type zend_mixed_type = { NULL, MAY_BE_ANY, 0 }; ZEND_API zend_op_array *(*zend_compile_file)(zend_file_handle *file_handle, int type); ZEND_API zend_op_array *(*zend_compile_string)(zend_string *source_string, const char *filename, zend_compile_position position); @@ -7052,8 +7052,7 @@ static zend_type zend_compile_single_typename(zend_ast *ast) for (uint32_t generic_param_index = 0; generic_param_index < ce->num_generic_parameters; generic_param_index++) { const zend_generic_parameter *generic_param = &ce->generic_parameters[generic_param_index]; if (zend_string_equals(type_name, generic_param->name)) { - // TODO Add ZEND_TYPE_INIT_GENERIC() macro that takes an index - return (zend_type) ZEND_TYPE_INIT_PTR(zend_string_copy(type_name), _ZEND_TYPE_GENERIC_PARAM_NAME_BIT, /* allow null */ false, 0); + return (zend_type) ZEND_TYPE_INIT_GENERIC_PARAM(zend_string_copy(type_name), generic_param_index); } } } diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 5a5dd050a8a48..f88cb28e4dac2 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -133,7 +133,7 @@ typedef struct { * are only supported since C++20). */ void *ptr; uint32_t type_mask; - /* TODO: We could use the extra 32-bit of padding on 64-bit systems. */ + uint32_t generic_param_index; } zend_type; typedef struct { @@ -307,10 +307,10 @@ typedef struct { #endif #define ZEND_TYPE_INIT_NONE(extra_flags) \ - _ZEND_TYPE_PREFIX { NULL, (extra_flags) } + _ZEND_TYPE_PREFIX { NULL, (extra_flags), 0 } #define ZEND_TYPE_INIT_MASK(_type_mask) \ - _ZEND_TYPE_PREFIX { NULL, (_type_mask) } + _ZEND_TYPE_PREFIX { NULL, (_type_mask), 0 } #define ZEND_TYPE_INIT_CODE(code, allow_null, extra_flags) \ ZEND_TYPE_INIT_MASK(((code) == _IS_BOOL ? MAY_BE_BOOL : ( (code) == IS_ITERABLE ? _ZEND_TYPE_ITERABLE_BIT : ((code) == IS_MIXED ? MAY_BE_ANY : (1 << (code))))) \ @@ -318,16 +318,17 @@ typedef struct { #define ZEND_TYPE_INIT_PTR(ptr, type_kind, allow_null, extra_flags) \ _ZEND_TYPE_PREFIX { (void *) (ptr), \ - (type_kind) | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags) } + (type_kind) | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags), \ + 0 } #define ZEND_TYPE_INIT_PTR_MASK(ptr, type_mask) \ - _ZEND_TYPE_PREFIX { (void *) (ptr), (type_mask) } + _ZEND_TYPE_PREFIX { (void *) (ptr), (type_mask), 0 } #define ZEND_TYPE_INIT_UNION(ptr, extra_flags) \ - _ZEND_TYPE_PREFIX { (void *) (ptr), (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_UNION_BIT) | (extra_flags) } + _ZEND_TYPE_PREFIX { (void *) (ptr), (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_UNION_BIT) | (extra_flags), 0 } #define ZEND_TYPE_INIT_INTERSECTION(ptr, extra_flags) \ - _ZEND_TYPE_PREFIX { (void *) (ptr), (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_INTERSECTION_BIT) | (extra_flags) } + _ZEND_TYPE_PREFIX { (void *) (ptr), (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_INTERSECTION_BIT) | (extra_flags), 0 } #define ZEND_TYPE_INIT_CLASS(class_name, allow_null, extra_flags) \ ZEND_TYPE_INIT_PTR(class_name, _ZEND_TYPE_NAME_BIT, allow_null, extra_flags) @@ -341,6 +342,9 @@ typedef struct { #define ZEND_TYPE_INIT_CLASS_CONST_MASK(class_name, type_mask) \ ZEND_TYPE_INIT_PTR_MASK(class_name, (_ZEND_TYPE_LITERAL_NAME_BIT | (type_mask))) +#define ZEND_TYPE_INIT_GENERIC_PARAM(generic_name, index) \ + _ZEND_TYPE_PREFIX { (void *) (generic_name), _ZEND_TYPE_GENERIC_PARAM_NAME_BIT, index } + typedef union _zend_value { zend_long lval; /* long value */ double dval; /* double value */ From 106c3d2848d0450bcc30631b9b95b92e4b93146d Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Thu, 22 May 2025 01:34:40 +0100 Subject: [PATCH 38/41] Stop relying on the generic type name to find bound type Use the index that is now part of the zend_type This should reduce memory consumption as we are not doing a second deep copy of the types Fixes one of the implicit interface tests --- .../implicit_interface_same_bound_types.phpt | 2 - Zend/zend_compile.c | 7 +-- Zend/zend_inheritance.c | 47 +++++-------------- 3 files changed, 17 insertions(+), 39 deletions(-) diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_same_bound_types.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_same_bound_types.phpt index 61236d7261a57..4e3e09e9a872b 100644 --- a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_same_bound_types.phpt +++ b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_same_bound_types.phpt @@ -1,7 +1,5 @@ --TEST-- Implicit interface inheritance with same bound types ---XFAIL-- -This emits an error when it shouldn't --FILE-- "); zend_string *result = zend_string_alloc(len, 0); @@ -1445,7 +1446,7 @@ static zend_string* resolve_bound_generic_type(zend_string *type_name, const zen return result; } - const zend_type *constraint = zend_hash_find_ptr(bound_types, type_name); + const zend_type *constraint = zend_hash_index_find_ptr(bound_types, type->generic_param_index); ZEND_ASSERT(constraint != NULL); zend_string *constraint_type_str = zend_type_to_string_resolved(*constraint, scope, NULL); @@ -1490,7 +1491,7 @@ zend_string *zend_type_to_string_resolved(const zend_type type, const zend_class zend_string_release(resolved); } ZEND_TYPE_LIST_FOREACH_END(); } else if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(type)) { - str = resolve_bound_generic_type(ZEND_TYPE_NAME(type), scope, bound_types_to_scope); + str = resolve_bound_generic_type(&type, scope, bound_types_to_scope); } else if (ZEND_TYPE_HAS_NAME(type)) { str = resolve_class_name(ZEND_TYPE_NAME(type), scope); } diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 632a5721443cb..d133127b94c1f 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -696,13 +696,13 @@ static inheritance_status zend_is_type_subtype_of_generic_type( ZEND_ASSERT(bound_generic_types && "Have generic type"); ZEND_ASSERT(ZEND_TYPE_IS_GENERIC_PARAM_NAME(generic_type)); - const zend_type *bound_type_ptr = zend_hash_find_ptr(bound_generic_types, ZEND_TYPE_NAME(generic_type)); + const zend_type *bound_type_ptr = zend_hash_index_find_ptr(bound_generic_types, generic_type.generic_param_index); ZEND_ASSERT(bound_type_ptr != NULL); if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(*bound_type_ptr)) { const zend_type concrete_type = *concrete_type_ptr; if ( ZEND_TYPE_IS_GENERIC_PARAM_NAME(concrete_type) - && zend_string_equals(ZEND_TYPE_NAME(concrete_type), ZEND_TYPE_NAME(*bound_type_ptr)) + && concrete_type.generic_param_index == bound_type_ptr->generic_param_index ) { return INHERITANCE_SUCCESS; } else { @@ -710,7 +710,6 @@ static inheritance_status zend_is_type_subtype_of_generic_type( } } else { /* Generic type must be invariant */ - // TODO Use zend_perform_contravariant_type_check()? const inheritance_status sub_type_status = zend_perform_covariant_type_check( concrete_scope, concrete_type_ptr, generic_type_scope, bound_type_ptr); const inheritance_status super_type_status = zend_perform_contravariant_type_check( @@ -821,43 +820,34 @@ static inheritance_status zend_is_generic_type_subtype_of_generic_type( const zend_class_entry *fe_scope, const zend_type *fe_type_ptr, const zend_class_entry *proto_scope, const zend_type *proto_type_ptr ) { - const zend_type fe_type = *fe_type_ptr; - const zend_type proto_type = *proto_type_ptr; - - if (UNEXPECTED(!ZEND_TYPE_IS_GENERIC_PARAM_NAME(proto_type))) { + if (UNEXPECTED(!ZEND_TYPE_IS_GENERIC_PARAM_NAME(*proto_type_ptr))) { /* A generic type cannot be a subtype of a concrete one */ return INHERITANCE_ERROR; } - ZEND_ASSERT(ZEND_TYPE_IS_GENERIC_PARAM_NAME(fe_type)); + ZEND_ASSERT(ZEND_TYPE_IS_GENERIC_PARAM_NAME(*fe_type_ptr)); ZEND_ASSERT(fe_scope->bound_types); const HashTable *bound_generic_types = zend_hash_find_ptr_lc(fe_scope->bound_types, proto_scope->name); ZEND_ASSERT(bound_generic_types && "Must have bound generic type"); - zend_string *proto_type_name = ZEND_TYPE_NAME(proto_type); - ZEND_ASSERT(proto_type_name); - - const zend_type *bound_type_ptr = zend_hash_find_ptr(bound_generic_types, proto_type_name); + const zend_type *bound_type_ptr = zend_hash_index_find_ptr(bound_generic_types, proto_type_ptr->generic_param_index); ZEND_ASSERT(bound_type_ptr != NULL); const zend_type bound_type = *bound_type_ptr; ZEND_ASSERT(ZEND_TYPE_IS_GENERIC_PARAM_NAME(bound_type)); - return zend_string_equals(ZEND_TYPE_NAME(bound_type), ZEND_TYPE_NAME(fe_type)) ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; + return bound_type_ptr->generic_param_index == fe_type_ptr->generic_param_index ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; } static inheritance_status zend_perform_covariant_type_check( zend_class_entry *fe_scope, const zend_type *fe_type_ptr, zend_class_entry *proto_scope, const zend_type *proto_type_ptr ) { - const zend_type fe_type = *fe_type_ptr; - const zend_type proto_type = *proto_type_ptr; - /* If we check for concrete return type */ - if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(proto_type)) { + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(*proto_type_ptr)) { return zend_is_type_subtype_of_generic_type( fe_scope, fe_type_ptr, proto_scope, proto_type_ptr); - } else if (UNEXPECTED(ZEND_TYPE_IS_GENERIC_PARAM_NAME(fe_type))) { + } else if (UNEXPECTED(ZEND_TYPE_IS_GENERIC_PARAM_NAME(*fe_type_ptr))) { /* A generic type cannot be a subtype of a concrete one */ return INHERITANCE_ERROR; } @@ -1057,7 +1047,7 @@ static ZEND_COLD zend_string *zend_get_function_declaration( const zend_generic_parameter param = scope->generic_parameters[i]; zend_string *constraint_type_str; if (bound_types_to_scope) { - const zend_type *constraint = zend_hash_find_ptr(bound_types_to_scope, param.name); + const zend_type *constraint = zend_hash_index_find_ptr(bound_types_to_scope, i); ZEND_ASSERT(constraint != NULL); constraint_type_str = zend_type_to_string_resolved(*constraint, scope, NULL); } else { @@ -2351,23 +2341,19 @@ ZEND_ATTRIBUTE_NONNULL static void bind_generic_types_for_inherited_interfaces(z false /* TODO depends on internals */ ); ZEND_HASH_FOREACH_KEY_PTR(interface_bound_types_for_inherited_iface, generic_param_index, generic_param_name, bound_type_ptr) { + ZEND_ASSERT(generic_param_name == NULL); // TODO Change foreach macro; zend_type bound_type = *bound_type_ptr; if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(bound_type)) { ZEND_ASSERT(ce_bound_types_for_direct_iface != NULL && "If a bound type is generic then we must have bound types for the current interface"); - const zend_type *ce_bound_type_ptr = zend_hash_find_ptr(ce_bound_types_for_direct_iface, ZEND_TYPE_NAME(bound_type)); + const zend_type *ce_bound_type_ptr = zend_hash_index_find_ptr(ce_bound_types_for_direct_iface, bound_type_ptr->generic_param_index); ZEND_ASSERT(ce_bound_type_ptr != NULL); bound_type = *ce_bound_type_ptr; } zend_type_copy_ctor(&bound_type, true, false /* TODO Depends on internal or not? */); - if (generic_param_name) { - zend_hash_add_mem(ce_bound_types_for_inherited_iface, generic_param_name, - &bound_type, sizeof(bound_type)); - } else { - zend_hash_index_add_mem(ce_bound_types_for_inherited_iface, generic_param_index, - &bound_type, sizeof(bound_type)); - } + zend_hash_index_add_mem(ce_bound_types_for_inherited_iface, generic_param_index, + &bound_type, sizeof(bound_type)); } ZEND_HASH_FOREACH_END(); const HashTable *existing_bound_types_for_inherited_iface = zend_hash_find_ptr(ce->bound_types, lc_inherited_iface_name); @@ -2505,13 +2491,6 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * return; } } - /* Deep type copy */ - zend_type bound_type = *bound_type_ptr; - zend_type_copy_ctor(&bound_type, true, false); - zend_hash_add_mem(bound_types, generic_parameter->name, - &bound_type, sizeof(bound_type)); - ///* Should we change the key from index to generic parameter name? */ - //zend_hash_index_del(bound_types, i); } } bind_generic_types_for_inherited_interfaces(ce, iface); From 439458c688cd2037f47ad3e1e6945a348f91f986 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Thu, 22 May 2025 02:38:44 +0100 Subject: [PATCH 39/41] Refactor bind_generic_types_for_inherited_interfaces --- ...licit_interface_different_bound_types.phpt | 2 +- ...icit_interface_different_bound_types2.phpt | 2 +- Zend/zend_inheritance.c | 101 ++++++++++-------- 3 files changed, 59 insertions(+), 46 deletions(-) diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types.phpt index 9389f2918ce04..c8dd8d7eda0f0 100644 --- a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types.phpt +++ b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types.phpt @@ -18,4 +18,4 @@ class C implements I2, I1 { ?> --EXPECTF-- -Fatal error: Bound types for implicitly and explicitly implemented interfaces must match in %s on line %d +Fatal error: Bound type T for interface I1 implemented explicitly in C with type string must match the implicitly bound type float from interface I2 in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types2.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types2.phpt index afaf58f9e37f9..eda45f32ef6d0 100644 --- a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types2.phpt +++ b/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types2.phpt @@ -18,4 +18,4 @@ class C implements I1, I2 { ?> --EXPECTF-- -Fatal error: Bound types for implicitly and explicitly implemented interfaces must match in %s on line %d +Fatal error: Bound type T for interface I1 implemented explicitly in C with type string must match the implicitly bound type float from interface I2 in %s on line %d diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index d133127b94c1f..613bd3fea1587 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2328,61 +2328,74 @@ ZEND_ATTRIBUTE_NONNULL static void bind_generic_types_for_inherited_interfaces(z ZEND_HASH_FOREACH_STR_KEY_PTR(iface_bound_types, lc_inherited_iface_name, interface_bound_types_for_inherited_iface) { ZEND_ASSERT(lc_inherited_iface_name != NULL); - zend_string *generic_param_name = NULL; - zend_ulong generic_param_index = 0; - zend_type *bound_type_ptr = NULL; - HashTable *ce_bound_types_for_inherited_iface = NULL; - ALLOC_HASHTABLE(ce_bound_types_for_inherited_iface); - zend_hash_init( - ce_bound_types_for_inherited_iface, - zend_hash_num_elements(interface_bound_types_for_inherited_iface), - NULL, - zend_types_ht_dtor, - false /* TODO depends on internals */ - ); - ZEND_HASH_FOREACH_KEY_PTR(interface_bound_types_for_inherited_iface, generic_param_index, generic_param_name, bound_type_ptr) { - ZEND_ASSERT(generic_param_name == NULL); // TODO Change foreach macro; - zend_type bound_type = *bound_type_ptr; - if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(bound_type)) { - ZEND_ASSERT(ce_bound_types_for_direct_iface != NULL && - "If a bound type is generic then we must have bound types for the current interface"); - const zend_type *ce_bound_type_ptr = zend_hash_index_find_ptr(ce_bound_types_for_direct_iface, bound_type_ptr->generic_param_index); - ZEND_ASSERT(ce_bound_type_ptr != NULL); - bound_type = *ce_bound_type_ptr; - } - - zend_type_copy_ctor(&bound_type, true, false /* TODO Depends on internal or not? */); - zend_hash_index_add_mem(ce_bound_types_for_inherited_iface, generic_param_index, - &bound_type, sizeof(bound_type)); - } ZEND_HASH_FOREACH_END(); - const HashTable *existing_bound_types_for_inherited_iface = zend_hash_find_ptr(ce->bound_types, lc_inherited_iface_name); if (EXPECTED(existing_bound_types_for_inherited_iface == NULL)) { + HashTable *ce_bound_types_for_inherited_iface = NULL; + ALLOC_HASHTABLE(ce_bound_types_for_inherited_iface); + zend_hash_init( + ce_bound_types_for_inherited_iface, + zend_hash_num_elements(interface_bound_types_for_inherited_iface), + NULL, + zend_types_ht_dtor, + false /* TODO depends on internals */ + ); + + zend_ulong generic_param_index = 0; + const zend_type *bound_type_ptr = NULL; + ZEND_HASH_FOREACH_NUM_KEY_PTR(interface_bound_types_for_inherited_iface, generic_param_index, bound_type_ptr) { + zend_type bound_type = *bound_type_ptr; + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(bound_type)) { + ZEND_ASSERT(ce_bound_types_for_direct_iface != NULL && + "If a bound type is generic then we must have bound types for the current interface"); + const zend_type *ce_bound_type_ptr = zend_hash_index_find_ptr(ce_bound_types_for_direct_iface, bound_type_ptr->generic_param_index); + ZEND_ASSERT(ce_bound_type_ptr != NULL); + bound_type = *ce_bound_type_ptr; + } + + zend_type_copy_ctor(&bound_type, true, false /* TODO Depends on internal or not? */); + zend_hash_index_add_mem(ce_bound_types_for_inherited_iface, generic_param_index, + &bound_type, sizeof(bound_type)); + } ZEND_HASH_FOREACH_END(); + zend_hash_add_new_ptr(ce->bound_types, lc_inherited_iface_name, ce_bound_types_for_inherited_iface); } else { - zend_ulong idx; - zend_string *bound_name; - const zend_type *ptr; - ZEND_HASH_FOREACH_KEY_PTR(existing_bound_types_for_inherited_iface, idx, bound_name, ptr) { - if (bound_name != NULL) { - continue; + const uint32_t num_generic_types = zend_hash_num_elements(interface_bound_types_for_inherited_iface); + ZEND_ASSERT(zend_hash_num_elements(existing_bound_types_for_inherited_iface) == num_generic_types && "Existing bound types should have errored before"); + + for (zend_ulong bound_type_index = 0; bound_type_index < num_generic_types; bound_type_index++) { + const zend_type *iface_bound_type_ptr = zend_hash_index_find_ptr(interface_bound_types_for_inherited_iface, bound_type_index); + const zend_type *ce_bound_type_ptr = zend_hash_index_find_ptr(existing_bound_types_for_inherited_iface, bound_type_index); + ZEND_ASSERT(iface_bound_type_ptr != NULL && ce_bound_type_ptr != NULL); + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(*iface_bound_type_ptr)) { + iface_bound_type_ptr = zend_hash_index_find_ptr(ce_bound_types_for_direct_iface, iface_bound_type_ptr->generic_param_index); + ZEND_ASSERT(iface_bound_type_ptr != NULL); } - const zend_type t1 = *ptr; - const zend_type *ptr2 = zend_hash_index_find_ptr(ce_bound_types_for_inherited_iface, idx); - ZEND_ASSERT(ptr2 != NULL); - const zend_type t2 = *ptr2; + const zend_type t1 = *iface_bound_type_ptr; + const zend_type t2 = *ce_bound_type_ptr; if ( ZEND_TYPE_FULL_MASK(t1) != ZEND_TYPE_FULL_MASK(t2) || (ZEND_TYPE_HAS_NAME(t1) && !zend_string_equals(ZEND_TYPE_NAME(t1), ZEND_TYPE_NAME(t2))) // || ZEND_TYPE_HAS_LIST(t1) && TODO Check list types are equal ) { - // TODO Improve this error message - zend_error_noreturn(E_COMPILE_ERROR, "Bound types for implicitly and explicitly implemented interfaces must match"); + const zend_class_entry *inherited_iface = zend_hash_find_ptr(CG(class_table), lc_inherited_iface_name); + ZEND_ASSERT(inherited_iface != NULL); + const zend_generic_parameter param = inherited_iface->generic_parameters[bound_type_index]; + + zend_string *ce_bound_type_str = zend_type_to_string_resolved(t2, ce, NULL); + zend_string *iface_bound_type_str = zend_type_to_string_resolved(t1, iface, NULL); + zend_error_noreturn(E_COMPILE_ERROR, + "Bound type %s for interface %s implemented explicitly in %s with type %s must match the implicitly bound type %s from interface %s", + ZSTR_VAL(param.name), + ZSTR_VAL(inherited_iface->name), + ZSTR_VAL(ce->name), + ZSTR_VAL(ce_bound_type_str), + ZSTR_VAL(iface_bound_type_str), + ZSTR_VAL(iface->name) + ); + zend_string_release_ex(ce_bound_type_str, false); + zend_string_release_ex(iface_bound_type_str, false); } - } ZEND_HASH_FOREACH_END(); - /* Remove current ones as they may be incomplete without the type name binding */ - zend_hash_del(ce->bound_types, lc_inherited_iface_name); + } } - zend_hash_add_new_ptr(ce->bound_types, lc_inherited_iface_name, ce_bound_types_for_inherited_iface); } ZEND_HASH_FOREACH_END(); } From 229fcc92aee28a9ea6d5cd8c29004e078865e52b Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Thu, 22 May 2025 14:13:11 +0100 Subject: [PATCH 40/41] Zend: Inherit interfaces early --- Zend/zend_inheritance.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 613bd3fea1587..42b370d429925 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1724,17 +1724,21 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke } /* }}} */ -static inline void do_implement_interface(zend_class_entry *ce, zend_class_entry *iface) /* {{{ */ +static inline void do_implement_interface_ex(zend_class_entry *ce, zend_class_entry *inherited_face, zend_class_entry *base_iface) { - if (!(ce->ce_flags & ZEND_ACC_INTERFACE) && iface->interface_gets_implemented && iface->interface_gets_implemented(iface, ce) == FAILURE) { - zend_error_noreturn(E_CORE_ERROR, "%s %s could not implement interface %s", zend_get_object_type_uc(ce), ZSTR_VAL(ce->name), ZSTR_VAL(iface->name)); + if (!(ce->ce_flags & ZEND_ACC_INTERFACE) && inherited_face->interface_gets_implemented && inherited_face->interface_gets_implemented(base_iface, ce) == FAILURE) { + zend_error_noreturn(E_CORE_ERROR, "%s %s could not implement interface %s", zend_get_object_type_uc(ce), ZSTR_VAL(ce->name), ZSTR_VAL(base_iface->name)); } /* This should be prevented by the class lookup logic. */ - ZEND_ASSERT(ce != iface); + ZEND_ASSERT(ce != base_iface); +} + +static inline void do_implement_interface(zend_class_entry *ce, zend_class_entry *iface) +{ + do_implement_interface_ex(ce, iface, iface); } -/* }}} */ -static void zend_do_inherit_interfaces(zend_class_entry *ce, const zend_class_entry *iface) /* {{{ */ +static void zend_do_inherit_interfaces(zend_class_entry *ce, zend_class_entry *iface) /* {{{ */ { /* expects interface to be contained in ce's interface list already */ uint32_t i, ce_num, if_num = iface->num_interfaces; @@ -1764,7 +1768,7 @@ static void zend_do_inherit_interfaces(zend_class_entry *ce, const zend_class_en /* and now call the implementing handlers */ while (ce_num < ce->num_interfaces) { - do_implement_interface(ce, ce->interfaces[ce_num++]); + do_implement_interface_ex(ce, ce->interfaces[ce_num++], iface); } } /* }}} */ @@ -2406,6 +2410,10 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * zend_class_constant *c; uint32_t flags = ZEND_INHERITANCE_CHECK_PROTO | ZEND_INHERITANCE_CHECK_VISIBILITY; + if (iface->num_interfaces) { + zend_do_inherit_interfaces(ce, iface); + } + if (!(ce->ce_flags & ZEND_ACC_INTERFACE)) { /* We are not setting the prototype of overridden interface methods because of abstract * constructors. See Zend/tests/interface_constructor_prototype_001.phpt. */ @@ -2525,9 +2533,6 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * } ZEND_HASH_FOREACH_END(); do_implement_interface(ce, iface); - if (iface->num_interfaces) { - zend_do_inherit_interfaces(ce, iface); - } } /* }}} */ From 891f24dedd4480a881ca1f272bf0e196142c70b4 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Thu, 22 May 2025 14:47:25 +0100 Subject: [PATCH 41/41] Fix some implicit bound type issues --- ...licit_interface_different_bound_types.phpt | 0 ...icit_interface_different_bound_types2.phpt | 0 ...icit_interface_different_bound_types3.phpt | 21 +++++++++ ...icit_interface_different_bound_types4.phpt | 21 +++++++++ .../implicit_interface_no_bound_types.phpt | 2 - .../implicit_interface_no_bound_types2.phpt | 0 .../implicit_interface_no_bound_types3.phpt | 21 +++++++++ .../implicit_interface_no_bound_types4.phpt | 21 +++++++++ .../implicit_interface_same_bound_types.phpt | 0 .../implicit_interface_same_bound_types2.phpt | 0 Zend/zend_inheritance.c | 43 +++++++++++-------- 11 files changed, 109 insertions(+), 20 deletions(-) rename Zend/tests/type_declarations/abstract_generics/{constraints => bound_types}/implicit_interface_different_bound_types.phpt (100%) rename Zend/tests/type_declarations/abstract_generics/{constraints => bound_types}/implicit_interface_different_bound_types2.phpt (100%) create mode 100644 Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types3.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types4.phpt rename Zend/tests/type_declarations/abstract_generics/{constraints => bound_types}/implicit_interface_no_bound_types.phpt (91%) rename Zend/tests/type_declarations/abstract_generics/{constraints => bound_types}/implicit_interface_no_bound_types2.phpt (100%) create mode 100644 Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types3.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types4.phpt rename Zend/tests/type_declarations/abstract_generics/{constraints => bound_types}/implicit_interface_same_bound_types.phpt (100%) rename Zend/tests/type_declarations/abstract_generics/{constraints => bound_types}/implicit_interface_same_bound_types2.phpt (100%) diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types.phpt b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types.phpt similarity index 100% rename from Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types.phpt rename to Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types.phpt diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types2.phpt b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types2.phpt similarity index 100% rename from Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_different_bound_types2.phpt rename to Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types2.phpt diff --git a/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types3.phpt b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types3.phpt new file mode 100644 index 0000000000000..6a37e34bbc184 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types3.phpt @@ -0,0 +1,21 @@ +--TEST-- +Implicit interface inheritance with different bound types 3 +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I1 { + public function bar(int $o, float $param): float; +} + +class C implements I2, I1 { + public function foo(float $param): float {} + public function bar(int $o, float $param): float {} +} + +?> +--EXPECTF-- +Fatal error: Bound type T for interface I1 implemented explicitly in C with type string must match the implicitly bound type float from interface I2 in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types4.phpt b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types4.phpt new file mode 100644 index 0000000000000..49029e3904d00 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types4.phpt @@ -0,0 +1,21 @@ +--TEST-- +Implicit interface inheritance with different bound types 4 +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I1 { + public function bar(int $o, float $param): float; +} + +class C implements I1, I2 { + public function foo(string $param): string {} + public function bar(int $o, float $param): float {} +} + +?> +--EXPECTF-- +Fatal error: Bound type T for interface I1 implemented explicitly in C with type string must match the implicitly bound type float from interface I2 in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types.phpt b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types.phpt similarity index 91% rename from Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types.phpt rename to Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types.phpt index e4d5769f5a48b..3595c2b650298 100644 --- a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_no_bound_types.phpt +++ b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types.phpt @@ -1,7 +1,5 @@ --TEST-- Implicit interface inheritance missing bound types ---XFAIL-- -Wrong number of missing params --FILE-- { + public function foo(T $param): T; +} + +interface I2 extends I1 { + public function bar(int $o, string $param): string; +} + +class C implements I1, I2 { + public function foo(string $param): string {} + public function bar(int $o, string $param): string {} +} + +?> +--EXPECTF-- +Fatal error: Interface I1 expects 1 generic parameters, 0 given in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types4.phpt b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types4.phpt new file mode 100644 index 0000000000000..22ae5a730a95b --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types4.phpt @@ -0,0 +1,21 @@ +--TEST-- +Implicit interface inheritance missing bound types 4 +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I1 { + public function bar(int $o, string $param): string; +} + +class C implements I2, I1 { + public function foo(string $param): string {} + public function bar(int $o, string $param): string {} +} + +?> +--EXPECTF-- +Fatal error: Interface I1 expects 1 generic parameters, 0 given in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_same_bound_types.phpt b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_same_bound_types.phpt similarity index 100% rename from Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_same_bound_types.phpt rename to Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_same_bound_types.phpt diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_same_bound_types2.phpt b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_same_bound_types2.phpt similarity index 100% rename from Zend/tests/type_declarations/abstract_generics/constraints/implicit_interface_same_bound_types2.phpt rename to Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_same_bound_types2.phpt diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 42b370d429925..cf4c7528eb84b 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1738,6 +1738,15 @@ static inline void do_implement_interface(zend_class_entry *ce, zend_class_entry do_implement_interface_ex(ce, iface, iface); } +static ZEND_COLD void emit_incompatible_generic_arg_count_error(const zend_class_entry *iface, uint32_t given_args) { + zend_error_noreturn(E_COMPILE_ERROR, + "Interface %s expects %" PRIu32 " generic parameters, %" PRIu32 " given", + ZSTR_VAL(iface->name), + iface->num_generic_parameters, + given_args + ); +} + static void zend_do_inherit_interfaces(zend_class_entry *ce, zend_class_entry *iface) /* {{{ */ { /* expects interface to be contained in ce's interface list already */ @@ -1756,7 +1765,19 @@ static void zend_do_inherit_interfaces(zend_class_entry *ce, zend_class_entry *i zend_class_entry *entry = iface->interfaces[if_num]; for (i = 0; i < ce_num; i++) { if (ce->interfaces[i] == entry) { - // TODO Check bound generic types match? Or is this done before? + if (entry->num_generic_parameters) { + if (UNEXPECTED(ce->bound_types == NULL)) { + emit_incompatible_generic_arg_count_error(entry, 0); + } + const HashTable *bound_types = zend_hash_find_ptr_lc(ce->bound_types, entry->name); + if (UNEXPECTED(bound_types == NULL)) { + emit_incompatible_generic_arg_count_error(entry, 0); + } + const uint32_t num_bound_types = zend_hash_num_elements(bound_types); + if (UNEXPECTED(num_bound_types != entry->num_generic_parameters)) { + emit_incompatible_generic_arg_count_error(entry, num_bound_types); + } + } break; } } @@ -2429,29 +2450,15 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * if (iface->num_generic_parameters > 0) { if (UNEXPECTED(ce->bound_types == NULL)) { - zend_error_noreturn(E_COMPILE_ERROR, - "Interface %s expects %" PRIu32 " generic parameters, 0 given", - ZSTR_VAL(iface->name), - iface->num_generic_parameters - ); + emit_incompatible_generic_arg_count_error(iface, 0); } HashTable *bound_types = zend_hash_find_ptr_lc(ce->bound_types, iface->name); if (UNEXPECTED(bound_types == NULL)) { - zend_error_noreturn(E_COMPILE_ERROR, - "Interface %s expects %" PRIu32 " generic parameters, 0 given", - ZSTR_VAL(iface->name), - iface->num_generic_parameters - ); + emit_incompatible_generic_arg_count_error(iface, 0); } const uint32_t num_bound_types = zend_hash_num_elements(bound_types); if (UNEXPECTED(num_bound_types != iface->num_generic_parameters)) { - // TODO Need to handle implicit inherited interfaces - zend_error_noreturn(E_COMPILE_ERROR, - "Interface %s expects %" PRIu32 " generic parameters, %" PRIu32 " given", - ZSTR_VAL(iface->name), - iface->num_generic_parameters, - num_bound_types - ); + emit_incompatible_generic_arg_count_error(iface, num_bound_types); } for (uint32_t i = 0; i < num_bound_types; i++) { const zend_generic_parameter *generic_parameter = &iface->generic_parameters[i];