From 8363a5c2bd533e676f67dfebc86b00e4021b9db8 Mon Sep 17 00:00:00 2001 From: Pratik Bhujel Date: Thu, 14 May 2026 12:21:06 +0545 Subject: [PATCH 1/3] Expose duplicate generic interface arg sets --- ext/reflection/php_reflection.c | 180 ++++++++++++++++++ ext/reflection/php_reflection.stub.php | 11 ++ ext/reflection/php_reflection_arginfo.h | 6 +- ext/reflection/php_reflection_decl.h | 8 +- ...ncestor_args_duplicate_interface_sets.phpt | 62 ++++++ 5 files changed, 262 insertions(+), 5 deletions(-) create mode 100644 ext/reflection/tests/generics/ancestor_args_duplicate_interface_sets.phpt diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index f2b8aa536fd0..2dfd9df4c8b7 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -8858,6 +8858,14 @@ static void reflection_build_args_list(zval *return_value, const zend_type *args reflection_build_args_list_ex(return_value, args, count, declaring_class, false); } +static void reflection_append_args_list(zval *return_value, const zend_type *args, + uint32_t count, zend_class_entry *declaring_class, bool copy_types) +{ + zval entry; + reflection_build_args_list_ex(&entry, args, count, declaring_class, copy_types); + zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &entry); +} + static void reflection_build_named_args_list(zval *return_value, const zend_type *boxed, zend_class_entry *declaring_class) { @@ -8888,6 +8896,32 @@ static zend_class_entry *reflection_find_interface_by_name( return NULL; } +static void reflection_append_mapped_args_list( + zval *return_value, const zend_type *args, uint32_t arity, + const zend_type *map_args, uint32_t map_arity, + zend_class_entry *declaring_class) +{ + if (!map_args) { + reflection_append_args_list(return_value, args, arity, declaring_class, false); + return; + } + + if (arity == 0) { + reflection_append_args_list(return_value, args, arity, declaring_class, false); + return; + } + + ALLOCA_FLAG(use_heap) + zend_type *mapped_args = (zend_type *) do_alloca(sizeof(zend_type) * arity, use_heap); + for (uint32_t i = 0; i < arity; i++) { + mapped_args[i] = reflection_type_substitute_class_params(args[i], map_args, map_arity); + } + + reflection_append_args_list(return_value, mapped_args, arity, declaring_class, true); + reflection_type_array_release(mapped_args, arity); + free_alloca(mapped_args, use_heap); +} + static bool reflection_get_direct_inheritance_binding( zend_class_entry *ce, zend_class_entry *target, const zend_type **out_args, uint32_t *out_arity) @@ -8928,6 +8962,107 @@ static bool reflection_get_direct_inheritance_binding( return false; } +static bool reflection_collect_interface_arg_sets( + zval *return_value, zend_class_entry *ce, zend_class_entry *ancestor, + const zend_type *ce_args, uint32_t ce_arity, + zend_class_entry *declaring_class) +{ + bool found = false; + + if ((ce->ce_flags & ZEND_ACC_RESOLVED_PARENT) && ce->parent && ce->parent != ancestor) { + const zend_type *parent_args; + uint32_t parent_arity; + if (reflection_get_direct_inheritance_binding(ce, ce->parent, &parent_args, &parent_arity)) { + if (ce_args && parent_arity > 0) { + ALLOCA_FLAG(use_heap) + zend_type *mapped_args = (zend_type *) do_alloca(sizeof(zend_type) * parent_arity, use_heap); + for (uint32_t i = 0; i < parent_arity; i++) { + mapped_args[i] = reflection_type_substitute_class_params( + parent_args[i], ce_args, ce_arity); + } + + if (reflection_collect_interface_arg_sets(return_value, ce->parent, ancestor, + mapped_args, parent_arity, declaring_class)) { + found = true; + } + reflection_type_array_release(mapped_args, parent_arity); + free_alloca(mapped_args, use_heap); + } else if (reflection_collect_interface_arg_sets(return_value, ce->parent, ancestor, + parent_args, parent_arity, declaring_class)) { + found = true; + } + } else if (reflection_collect_interface_arg_sets(return_value, ce->parent, ancestor, + NULL, 0, declaring_class)) { + found = true; + } + } + + HashTable *generic_implements = ce->generic_types ? ce->generic_types->implements : NULL; + if (generic_implements) { + zval *zv; + ZEND_HASH_FOREACH_VAL(generic_implements, zv) { + zend_type *boxed = (zend_type *) Z_PTR_P(zv); + if (!ZEND_TYPE_HAS_NAMED_WITH_ARGS(*boxed)) { + continue; + } + + zend_type_named_with_args *named = ZEND_TYPE_NAMED_WITH_ARGS(*boxed); + if (named->name && zend_string_equals_ci(named->name, ancestor->name)) { + reflection_append_mapped_args_list(return_value, named->args, named->count, + ce_args, ce_arity, declaring_class); + found = true; + continue; + } + + zend_class_entry *intermediate = named->name + ? reflection_find_interface_by_name(ce, named->name) : NULL; + if (!intermediate || intermediate == ancestor) { + continue; + } + + if (ce_args && named->count > 0) { + ALLOCA_FLAG(use_heap) + zend_type *mapped_args = (zend_type *) do_alloca(sizeof(zend_type) * named->count, use_heap); + for (uint32_t i = 0; i < named->count; i++) { + mapped_args[i] = reflection_type_substitute_class_params( + named->args[i], ce_args, ce_arity); + } + + if (reflection_collect_interface_arg_sets(return_value, intermediate, ancestor, + mapped_args, named->count, declaring_class)) { + found = true; + } + reflection_type_array_release(mapped_args, named->count); + free_alloca(mapped_args, use_heap); + } else if (reflection_collect_interface_arg_sets(return_value, intermediate, ancestor, + named->args, named->count, declaring_class)) { + found = true; + } + } ZEND_HASH_FOREACH_END(); + } + + if (ce->ce_flags & ZEND_ACC_RESOLVED_INTERFACES) { + uint32_t parent_interface_count = ce->parent ? ce->parent->num_interfaces : 0; + + for (uint32_t i = parent_interface_count; i < ce->num_interfaces; i++) { + zend_class_entry *intermediate = ce->interfaces[i]; + if (!intermediate || intermediate == ancestor) { + continue; + } + if (generic_implements + && zend_hash_index_exists(generic_implements, i - parent_interface_count)) { + continue; + } + if (reflection_collect_interface_arg_sets(return_value, intermediate, ancestor, + NULL, 0, declaring_class)) { + found = true; + } + } + } + + return found; +} + static bool reflection_get_transitive_interface_args( zend_class_entry *ce, zend_class_entry *ancestor, zend_type *args, uint32_t cap, uint32_t *arity) @@ -9115,6 +9250,51 @@ ZEND_METHOD(ReflectionClass, getGenericArgumentsForParentInterface) RETURN_EMPTY_ARRAY(); } +ZEND_METHOD(ReflectionClass, getGenericArgumentSetsForParentInterface) +{ + reflection_object *intern; + zend_class_entry *ce; + zend_string *name; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(name) + ZEND_PARSE_PARAMETERS_END(); + GET_REFLECTION_OBJECT_PTR(ce); + + bool is_ancestor = false; + zend_class_entry *ancestor = NULL; + if (ce->ce_flags & ZEND_ACC_LINKED) { + for (uint32_t i = 0; i < ce->num_interfaces; i++) { + if (zend_string_equals_ci(ce->interfaces[i]->name, name)) { + is_ancestor = true; + ancestor = ce->interfaces[i]; + break; + } + } + } else { + for (uint32_t i = 0; i < ce->num_interfaces; i++) { + if (zend_string_equals_ci(ce->interface_names[i].name, name)) { + is_ancestor = true; + break; + } + } + } + if (!is_ancestor) { + zend_throw_exception_ex(reflection_exception_ptr, 0, + "%s is not an ancestor interface of %s", ZSTR_VAL(name), ZSTR_VAL(ce->name)); + RETURN_THROWS(); + } + + array_init(return_value); + if (ancestor && reflection_collect_interface_arg_sets(return_value, ce, ancestor, NULL, 0, ce)) { + return; + } + + zval entry; + array_init(&entry); + zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &entry); +} + ZEND_METHOD(ReflectionClass, getGenericArgumentsForUsedTrait) { reflection_object *intern; diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 22ced0706cd2..7a18b9cbfe18 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -467,6 +467,17 @@ public function getGenericArgumentsForParentClass(): array {} */ public function getGenericArgumentsForParentInterface(string $name): array {} + /** + * Returns every generic argument set this class supplies for the named + * ancestor interface, in inheritance traversal order. This differs from + * getGenericArgumentsForParentInterface() for duplicate generic interface + * bindings such as Foo, Foo. + * + * @return list> + * @throws ReflectionException if $name is not an ancestor interface + */ + public function getGenericArgumentSetsForParentInterface(string $name): array {} + /** * Returns the type arguments this class supplies at the use site for trait * $name, in source order. Returns an empty array if no type arguments were diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 87da407f889b..bcd7c49d2cfb 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit php_reflection.stub.php instead. - * Stub hash: 12092cbfe98615b205c146ece58ed58f3b92b100 + * Stub hash: 21d668155a1472f2a1913df06089c9c92402bf1a * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) @@ -381,6 +381,8 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionClass_getGeneric ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) ZEND_END_ARG_INFO() +#define arginfo_class_ReflectionClass_getGenericArgumentSetsForParentInterface arginfo_class_ReflectionClass_getGenericArgumentsForParentInterface + #define arginfo_class_ReflectionClass_getGenericArgumentsForUsedTrait arginfo_class_ReflectionClass_getGenericArgumentsForParentInterface ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionObject___construct, 0, 0, 1) @@ -925,6 +927,7 @@ ZEND_METHOD(ReflectionClass, isGeneric); ZEND_METHOD(ReflectionClass, getGenericParameters); ZEND_METHOD(ReflectionClass, getGenericArgumentsForParentClass); ZEND_METHOD(ReflectionClass, getGenericArgumentsForParentInterface); +ZEND_METHOD(ReflectionClass, getGenericArgumentSetsForParentInterface); ZEND_METHOD(ReflectionClass, getGenericArgumentsForUsedTrait); ZEND_METHOD(ReflectionObject, __construct); ZEND_METHOD(ReflectionProperty, __construct); @@ -1247,6 +1250,7 @@ static const zend_function_entry class_ReflectionClass_methods[] = { ZEND_ME(ReflectionClass, getGenericParameters, arginfo_class_ReflectionClass_getGenericParameters, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getGenericArgumentsForParentClass, arginfo_class_ReflectionClass_getGenericArgumentsForParentClass, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getGenericArgumentsForParentInterface, arginfo_class_ReflectionClass_getGenericArgumentsForParentInterface, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, getGenericArgumentSetsForParentInterface, arginfo_class_ReflectionClass_getGenericArgumentSetsForParentInterface, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getGenericArgumentsForUsedTrait, arginfo_class_ReflectionClass_getGenericArgumentsForUsedTrait, ZEND_ACC_PUBLIC) ZEND_FE_END }; diff --git a/ext/reflection/php_reflection_decl.h b/ext/reflection/php_reflection_decl.h index ce22d1d93bab..75f5277fd114 100644 --- a/ext/reflection/php_reflection_decl.h +++ b/ext/reflection/php_reflection_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit php_reflection.stub.php instead. - * Stub hash: 12092cbfe98615b205c146ece58ed58f3b92b100 */ + * Stub hash: 21d668155a1472f2a1913df06089c9c92402bf1a */ -#ifndef ZEND_PHP_REFLECTION_DECL_12092cbfe98615b205c146ece58ed58f3b92b100_H -#define ZEND_PHP_REFLECTION_DECL_12092cbfe98615b205c146ece58ed58f3b92b100_H +#ifndef ZEND_PHP_REFLECTION_DECL_21d668155a1472f2a1913df06089c9c92402bf1a_H +#define ZEND_PHP_REFLECTION_DECL_21d668155a1472f2a1913df06089c9c92402bf1a_H typedef enum zend_enum_PropertyHookType { ZEND_ENUM_PropertyHookType_Get = 1, @@ -15,4 +15,4 @@ typedef enum zend_enum_ReflectionGenericVariance { ZEND_ENUM_ReflectionGenericVariance_Contravariant = 3, } zend_enum_ReflectionGenericVariance; -#endif /* ZEND_PHP_REFLECTION_DECL_12092cbfe98615b205c146ece58ed58f3b92b100_H */ +#endif /* ZEND_PHP_REFLECTION_DECL_21d668155a1472f2a1913df06089c9c92402bf1a_H */ diff --git a/ext/reflection/tests/generics/ancestor_args_duplicate_interface_sets.phpt b/ext/reflection/tests/generics/ancestor_args_duplicate_interface_sets.phpt new file mode 100644 index 000000000000..9b489ef19aac --- /dev/null +++ b/ext/reflection/tests/generics/ancestor_args_duplicate_interface_sets.phpt @@ -0,0 +1,62 @@ +--TEST-- +Reflection: duplicate generic interface bindings expose every argument set +--FILE-- + {} +interface Root {} +interface Mid extends Root {} +interface DA<+T> {} +interface I1 extends DA {} +interface I2 extends DA {} + +class Direct implements Foo, Foo {} +class ThroughMid implements Mid, Mid {} +class WithoutArgs implements Foo {} +class ParentDirect implements Foo, Foo {} +class ChildDirect extends ParentDirect {} +class ParentGeneric implements Root {} +class ChildGeneric extends ParentGeneric {} +class Diamond implements I1, I2 {} + +function render_sets(string $class, string $interface): void { + $sets = (new ReflectionClass($class))->getGenericArgumentSetsForParentInterface($interface); + echo "$class/$interface\n"; + foreach ($sets as $set) { + echo " [", implode(", ", array_map( + static fn(ReflectionType $type): string => $type->getName(), + $set, + )), "]\n"; + } +} + +render_sets(Direct::class, Foo::class); +render_sets(ThroughMid::class, Root::class); +render_sets(WithoutArgs::class, Foo::class); +render_sets(ChildDirect::class, Foo::class); +render_sets(ChildGeneric::class, Root::class); +render_sets(Diamond::class, DA::class); + +try { + (new ReflectionClass(Direct::class))->getGenericArgumentSetsForParentInterface(Root::class); +} catch (ReflectionException $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +Direct/Foo + [string] + [int] +ThroughMid/Root + [string] + [int] +WithoutArgs/Foo + [] +ChildDirect/Foo + [bool] + [float] +ChildGeneric/Root + [U] +Diamond/DA + [int] + [string] +Root is not an ancestor interface of Direct From 676a49f9c362e5238a076a41eea2d3db57d5252a Mon Sep 17 00:00:00 2001 From: Pratik Bhujel Date: Thu, 14 May 2026 13:27:51 +0545 Subject: [PATCH 2/3] Update ReflectionClass string expectation for generics --- ext/reflection/tests/ReflectionClass_toString_001.phpt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ext/reflection/tests/ReflectionClass_toString_001.phpt b/ext/reflection/tests/ReflectionClass_toString_001.phpt index 01415d08aacb..b9c6ce0b92d8 100644 --- a/ext/reflection/tests/ReflectionClass_toString_001.phpt +++ b/ext/reflection/tests/ReflectionClass_toString_001.phpt @@ -30,7 +30,7 @@ Class [ class ReflectionClass implements Stringable, Refle Property [ public string $name ] } - - Methods [69] { + - Methods [70] { Method [ private method __clone ] { - Parameters [0] { @@ -544,6 +544,14 @@ Class [ class ReflectionClass implements Stringable, Refle - Return [ array ] } + Method [ public method getGenericArgumentSetsForParentInterface ] { + + - Parameters [1] { + Parameter #0 [ string $name ] + } + - Return [ array ] + } + Method [ public method getGenericArgumentsForUsedTrait ] { - Parameters [1] { From a42547f2f47a72c6699b15c7eb4b557b29e7de03 Mon Sep 17 00:00:00 2001 From: Pratik Bhujel Date: Fri, 15 May 2026 02:19:31 +0545 Subject: [PATCH 3/3] Remove singular parent interface generic reflection API --- ...pace_relative_extends_implements_uses.phpt | 6 +- ext/reflection/php_reflection.c | 181 ++++-------------- ext/reflection/php_reflection.stub.php | 14 +- ext/reflection/php_reflection_arginfo.h | 10 +- ext/reflection/php_reflection_decl.h | 8 +- .../tests/ReflectionClass_toString_001.phpt | 10 +- .../tests/generics/ancestor_args_nested.phpt | 2 +- .../ancestor_args_nested_transitive.phpt | 4 +- .../ancestor_args_no_generics_class.phpt | 4 +- .../ancestor_args_parent_interface.phpt | 4 +- .../ancestor_args_transitive_paths.phpt | 16 +- .../ancestor_args_type_param_refs.phpt | 2 +- 12 files changed, 65 insertions(+), 196 deletions(-) diff --git a/Zend/tests/generics/reflection/namespace_relative_extends_implements_uses.phpt b/Zend/tests/generics/reflection/namespace_relative_extends_implements_uses.phpt index 54548b82b18c..cd0db5526218 100644 --- a/Zend/tests/generics/reflection/namespace_relative_extends_implements_uses.phpt +++ b/Zend/tests/generics/reflection/namespace_relative_extends_implements_uses.phpt @@ -25,9 +25,9 @@ $pairs = [ 'extends FQN' => (new \ReflectionClass(FqnExt::class))->getGenericArgumentsForParentClass(), 'extends ns' => (new \ReflectionClass(NsExt::class))->getGenericArgumentsForParentClass(), 'extends rel' => (new \ReflectionClass(RelExt::class))->getGenericArgumentsForParentClass(), - 'impl FQN' => (new \ReflectionClass(FqnImpl::class))->getGenericArgumentsForParentInterface(Iface::class), - 'impl ns' => (new \ReflectionClass(NsImpl::class))->getGenericArgumentsForParentInterface(Iface::class), - 'impl rel' => (new \ReflectionClass(RelImpl::class))->getGenericArgumentsForParentInterface(Iface::class), + 'impl FQN' => (new \ReflectionClass(FqnImpl::class))->getGenericArgumentSetsForParentInterface(Iface::class)[0], + 'impl ns' => (new \ReflectionClass(NsImpl::class))->getGenericArgumentSetsForParentInterface(Iface::class)[0], + 'impl rel' => (new \ReflectionClass(RelImpl::class))->getGenericArgumentSetsForParentInterface(Iface::class)[0], 'use FQN' => (new \ReflectionClass(FqnUse::class))->getGenericArgumentsForUsedTrait(Mixin::class), 'use ns' => (new \ReflectionClass(NsUse::class))->getGenericArgumentsForUsedTrait(Mixin::class), 'use rel' => (new \ReflectionClass(RelUse::class))->getGenericArgumentsForUsedTrait(Mixin::class), diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 2dfd9df4c8b7..bf13081de53d 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -8962,6 +8962,39 @@ static bool reflection_get_direct_inheritance_binding( return false; } +static bool reflection_interface_is_reached_through_generic_binding( + zend_class_entry *ce, zend_class_entry *iface, HashTable *generic_implements) +{ + if (!generic_implements) { + return false; + } + + zval *zv; + ZEND_HASH_FOREACH_VAL(generic_implements, zv) { + zend_type *boxed = (zend_type *) Z_PTR_P(zv); + if (!ZEND_TYPE_HAS_NAMED_WITH_ARGS(*boxed)) { + continue; + } + + zend_type_named_with_args *named = ZEND_TYPE_NAMED_WITH_ARGS(*boxed); + zend_class_entry *bound = named->name + ? reflection_find_interface_by_name(ce, named->name) : NULL; + if (!bound) { + continue; + } + if (bound == iface) { + return true; + } + for (uint32_t i = 0; i < bound->num_interfaces; i++) { + if (bound->interfaces[i] == iface) { + return true; + } + } + } ZEND_HASH_FOREACH_END(); + + return false; +} + static bool reflection_collect_interface_arg_sets( zval *return_value, zend_class_entry *ce, zend_class_entry *ancestor, const zend_type *ce_args, uint32_t ce_arity, @@ -9049,8 +9082,7 @@ static bool reflection_collect_interface_arg_sets( if (!intermediate || intermediate == ancestor) { continue; } - if (generic_implements - && zend_hash_index_exists(generic_implements, i - parent_interface_count)) { + if (reflection_interface_is_reached_through_generic_binding(ce, intermediate, generic_implements)) { continue; } if (reflection_collect_interface_arg_sets(return_value, intermediate, ancestor, @@ -9063,96 +9095,6 @@ static bool reflection_collect_interface_arg_sets( return found; } -static bool reflection_get_transitive_interface_args( - zend_class_entry *ce, zend_class_entry *ancestor, - zend_type *args, uint32_t cap, uint32_t *arity) -{ - const zend_type *direct_args; - if (reflection_get_direct_inheritance_binding(ce, ancestor, &direct_args, arity)) { - if (*arity > cap) { - return false; - } - for (uint32_t i = 0; i < *arity; i++) { - args[i] = reflection_type_copy(direct_args[i]); - } - return true; - } - - if ((ce->ce_flags & ZEND_ACC_RESOLVED_PARENT) && ce->parent && ce->parent != ancestor) { - ALLOCA_FLAG(use_heap) - zend_type *parent_args = (zend_type *) do_alloca(sizeof(zend_type) * cap, use_heap); - uint32_t parent_arity; - if (reflection_get_transitive_interface_args(ce->parent, ancestor, parent_args, cap, &parent_arity)) { - const zend_type *ce_to_parent; - uint32_t ce_to_parent_arity; - if (reflection_get_direct_inheritance_binding(ce, ce->parent, &ce_to_parent, &ce_to_parent_arity)) { - for (uint32_t i = 0; i < parent_arity; i++) { - args[i] = reflection_type_substitute_class_params( - parent_args[i], ce_to_parent, ce_to_parent_arity); - } - reflection_type_array_release(parent_args, parent_arity); - } else { - for (uint32_t i = 0; i < parent_arity; i++) { - args[i] = parent_args[i]; - } - } - *arity = parent_arity; - free_alloca(parent_args, use_heap); - return true; - } - free_alloca(parent_args, use_heap); - } - - if (ce->generic_types && ce->generic_types->implements) { - zval *zv; - ZEND_HASH_FOREACH_VAL(ce->generic_types->implements, zv) { - zend_type *boxed = (zend_type *) Z_PTR_P(zv); - if (!ZEND_TYPE_HAS_NAMED_WITH_ARGS(*boxed)) { - continue; - } - - zend_type_named_with_args *named = ZEND_TYPE_NAMED_WITH_ARGS(*boxed); - zend_class_entry *intermediate = named->name - ? reflection_find_interface_by_name(ce, named->name) : NULL; - if (!intermediate || intermediate == ancestor) { - continue; - } - - ALLOCA_FLAG(use_heap) - zend_type *intermediate_args = (zend_type *) do_alloca(sizeof(zend_type) * cap, use_heap); - uint32_t intermediate_arity; - if (!reflection_get_transitive_interface_args( - intermediate, ancestor, intermediate_args, cap, &intermediate_arity)) { - free_alloca(intermediate_args, use_heap); - continue; - } - - for (uint32_t i = 0; i < intermediate_arity; i++) { - args[i] = reflection_type_substitute_class_params( - intermediate_args[i], named->args, named->count); - } - reflection_type_array_release(intermediate_args, intermediate_arity); - *arity = intermediate_arity; - free_alloca(intermediate_args, use_heap); - return true; - } ZEND_HASH_FOREACH_END(); - } - - if (ce->ce_flags & ZEND_ACC_RESOLVED_INTERFACES) { - for (uint32_t i = 0; i < ce->num_interfaces; i++) { - zend_class_entry *intermediate = ce->interfaces[i]; - if (!intermediate || intermediate == ancestor) { - continue; - } - if (reflection_get_transitive_interface_args(intermediate, ancestor, args, cap, arity)) { - return true; - } - } - } - - return false; -} - ZEND_METHOD(ReflectionClass, getGenericArgumentsForParentClass) { reflection_object *intern; @@ -9195,61 +9137,6 @@ static bool reflection_try_build_named_args_from_table(HashTable *ht, zend_class return false; } -ZEND_METHOD(ReflectionClass, getGenericArgumentsForParentInterface) -{ - reflection_object *intern; - zend_class_entry *ce; - zend_string *name; - - ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_STR(name) - ZEND_PARSE_PARAMETERS_END(); - GET_REFLECTION_OBJECT_PTR(ce); - - HashTable *ht = ce->generic_types ? ce->generic_types->implements : NULL; - if (reflection_try_build_named_args_from_table(ht, ce, name, return_value)) { - return; - } - - bool is_ancestor = false; - zend_class_entry *ancestor = NULL; - if (ce->ce_flags & ZEND_ACC_LINKED) { - for (uint32_t i = 0; i < ce->num_interfaces; i++) { - if (zend_string_equals_ci(ce->interfaces[i]->name, name)) { - is_ancestor = true; - ancestor = ce->interfaces[i]; - break; - } - } - } else { - for (uint32_t i = 0; i < ce->num_interfaces; i++) { - if (zend_string_equals_ci(ce->interface_names[i].name, name)) { - is_ancestor = true; - break; - } - } - } - if (!is_ancestor) { - zend_throw_exception_ex(reflection_exception_ptr, 0, - "%s is not an ancestor interface of %s", ZSTR_VAL(name), ZSTR_VAL(ce->name)); - RETURN_THROWS(); - } - if (ancestor && ancestor->generic_parameters) { - uint32_t cap = ancestor->generic_parameters->count; - ALLOCA_FLAG(use_heap) - zend_type *args = (zend_type *) do_alloca(sizeof(zend_type) * cap, use_heap); - uint32_t arity; - if (reflection_get_transitive_interface_args(ce, ancestor, args, cap, &arity)) { - reflection_build_args_list_ex(return_value, args, arity, ce, true); - reflection_type_array_release(args, arity); - free_alloca(args, use_heap); - return; - } - free_alloca(args, use_heap); - } - RETURN_EMPTY_ARRAY(); -} - ZEND_METHOD(ReflectionClass, getGenericArgumentSetsForParentInterface) { reflection_object *intern; diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 7a18b9cbfe18..783c3206d24c 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -457,21 +457,9 @@ public function getGenericParameters(): array {} */ public function getGenericArgumentsForParentClass(): array {} - /** - * Returns the type arguments this class supplies for the named ancestor - * interface, in source order. Returns an empty array if no type arguments - * were specified at the use site. - * - * @return list - * @throws ReflectionException if $name is not an ancestor interface - */ - public function getGenericArgumentsForParentInterface(string $name): array {} - /** * Returns every generic argument set this class supplies for the named - * ancestor interface, in inheritance traversal order. This differs from - * getGenericArgumentsForParentInterface() for duplicate generic interface - * bindings such as Foo, Foo. + * ancestor interface, in inheritance traversal order. * * @return list> * @throws ReflectionException if $name is not an ancestor interface diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index bcd7c49d2cfb..680d78ed5d4b 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit php_reflection.stub.php instead. - * Stub hash: 21d668155a1472f2a1913df06089c9c92402bf1a + * Stub hash: 893c62c650203278595afbb056569dabfb918c20 * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) @@ -377,13 +377,11 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClass_getGenericArgumentsForParentClass arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionClass_getGenericArgumentsForParentInterface, 0, 1, IS_ARRAY, 0) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionClass_getGenericArgumentSetsForParentInterface, 0, 1, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) ZEND_END_ARG_INFO() -#define arginfo_class_ReflectionClass_getGenericArgumentSetsForParentInterface arginfo_class_ReflectionClass_getGenericArgumentsForParentInterface - -#define arginfo_class_ReflectionClass_getGenericArgumentsForUsedTrait arginfo_class_ReflectionClass_getGenericArgumentsForParentInterface +#define arginfo_class_ReflectionClass_getGenericArgumentsForUsedTrait arginfo_class_ReflectionClass_getGenericArgumentSetsForParentInterface ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionObject___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0) @@ -926,7 +924,6 @@ ZEND_METHOD(ReflectionClass, getAttributes); ZEND_METHOD(ReflectionClass, isGeneric); ZEND_METHOD(ReflectionClass, getGenericParameters); ZEND_METHOD(ReflectionClass, getGenericArgumentsForParentClass); -ZEND_METHOD(ReflectionClass, getGenericArgumentsForParentInterface); ZEND_METHOD(ReflectionClass, getGenericArgumentSetsForParentInterface); ZEND_METHOD(ReflectionClass, getGenericArgumentsForUsedTrait); ZEND_METHOD(ReflectionObject, __construct); @@ -1249,7 +1246,6 @@ static const zend_function_entry class_ReflectionClass_methods[] = { ZEND_ME(ReflectionClass, isGeneric, arginfo_class_ReflectionClass_isGeneric, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getGenericParameters, arginfo_class_ReflectionClass_getGenericParameters, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getGenericArgumentsForParentClass, arginfo_class_ReflectionClass_getGenericArgumentsForParentClass, ZEND_ACC_PUBLIC) - ZEND_ME(ReflectionClass, getGenericArgumentsForParentInterface, arginfo_class_ReflectionClass_getGenericArgumentsForParentInterface, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getGenericArgumentSetsForParentInterface, arginfo_class_ReflectionClass_getGenericArgumentSetsForParentInterface, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getGenericArgumentsForUsedTrait, arginfo_class_ReflectionClass_getGenericArgumentsForUsedTrait, ZEND_ACC_PUBLIC) ZEND_FE_END diff --git a/ext/reflection/php_reflection_decl.h b/ext/reflection/php_reflection_decl.h index 75f5277fd114..f2ae5dc44870 100644 --- a/ext/reflection/php_reflection_decl.h +++ b/ext/reflection/php_reflection_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit php_reflection.stub.php instead. - * Stub hash: 21d668155a1472f2a1913df06089c9c92402bf1a */ + * Stub hash: 893c62c650203278595afbb056569dabfb918c20 */ -#ifndef ZEND_PHP_REFLECTION_DECL_21d668155a1472f2a1913df06089c9c92402bf1a_H -#define ZEND_PHP_REFLECTION_DECL_21d668155a1472f2a1913df06089c9c92402bf1a_H +#ifndef ZEND_PHP_REFLECTION_DECL_893c62c650203278595afbb056569dabfb918c20_H +#define ZEND_PHP_REFLECTION_DECL_893c62c650203278595afbb056569dabfb918c20_H typedef enum zend_enum_PropertyHookType { ZEND_ENUM_PropertyHookType_Get = 1, @@ -15,4 +15,4 @@ typedef enum zend_enum_ReflectionGenericVariance { ZEND_ENUM_ReflectionGenericVariance_Contravariant = 3, } zend_enum_ReflectionGenericVariance; -#endif /* ZEND_PHP_REFLECTION_DECL_21d668155a1472f2a1913df06089c9c92402bf1a_H */ +#endif /* ZEND_PHP_REFLECTION_DECL_893c62c650203278595afbb056569dabfb918c20_H */ diff --git a/ext/reflection/tests/ReflectionClass_toString_001.phpt b/ext/reflection/tests/ReflectionClass_toString_001.phpt index b9c6ce0b92d8..8172d967e3b3 100644 --- a/ext/reflection/tests/ReflectionClass_toString_001.phpt +++ b/ext/reflection/tests/ReflectionClass_toString_001.phpt @@ -30,7 +30,7 @@ Class [ class ReflectionClass implements Stringable, Refle Property [ public string $name ] } - - Methods [70] { + - Methods [69] { Method [ private method __clone ] { - Parameters [0] { @@ -536,14 +536,6 @@ Class [ class ReflectionClass implements Stringable, Refle - Return [ array ] } - Method [ public method getGenericArgumentsForParentInterface ] { - - - Parameters [1] { - Parameter #0 [ string $name ] - } - - Return [ array ] - } - Method [ public method getGenericArgumentSetsForParentInterface ] { - Parameters [1] { diff --git a/ext/reflection/tests/generics/ancestor_args_nested.phpt b/ext/reflection/tests/generics/ancestor_args_nested.phpt index 3a85be230d0c..4c49db04b9fb 100644 --- a/ext/reflection/tests/generics/ancestor_args_nested.phpt +++ b/ext/reflection/tests/generics/ancestor_args_nested.phpt @@ -23,7 +23,7 @@ echo "<", $args[0]->getGenericArguments()[0]->getName(), ">\n"; echo "parent[1]: ", $args[1]->getName(), "\n"; // Nested in implements -$args = $rc->getGenericArgumentsForParentInterface('I'); +$args = $rc->getGenericArgumentSetsForParentInterface('I')[0]; echo "I[0]: ", $args[0]->getName(); echo "<", $args[0]->getGenericArguments()[0]->getName(), ">\n"; diff --git a/ext/reflection/tests/generics/ancestor_args_nested_transitive.phpt b/ext/reflection/tests/generics/ancestor_args_nested_transitive.phpt index 4fecaab13de6..00cad767ca3a 100644 --- a/ext/reflection/tests/generics/ancestor_args_nested_transitive.phpt +++ b/ext/reflection/tests/generics/ancestor_args_nested_transitive.phpt @@ -13,7 +13,7 @@ class Forwarded implements Mid {} class Reordered implements Flip {} function show(string $class, string $interface): void { - $args = (new ReflectionClass($class))->getGenericArgumentsForParentInterface($interface); + $args = (new ReflectionClass($class))->getGenericArgumentSetsForParentInterface($interface)[0]; echo "$class/$interface\n"; foreach ($args as $arg) { echo " ", $arg->getName(); @@ -31,7 +31,7 @@ show(Concrete::class, Root::class); show(Forwarded::class, Root::class); show(Reordered::class, PairRoot::class); -$forwardedArgs = (new ReflectionClass(Forwarded::class))->getGenericArgumentsForParentInterface(Root::class); +$forwardedArgs = (new ReflectionClass(Forwarded::class))->getGenericArgumentSetsForParentInterface(Root::class)[0]; $forwardedInner = $forwardedArgs[0]->getGenericArguments()[0]; echo "Forwarded nested parameter owner: ", $forwardedInner->getTypeParameter()->getDeclaringEntity()->getName(), "\n"; diff --git a/ext/reflection/tests/generics/ancestor_args_no_generics_class.phpt b/ext/reflection/tests/generics/ancestor_args_no_generics_class.phpt index b446b8b01789..f04f26f7dc3c 100644 --- a/ext/reflection/tests/generics/ancestor_args_no_generics_class.phpt +++ b/ext/reflection/tests/generics/ancestor_args_no_generics_class.phpt @@ -20,7 +20,7 @@ function show(string $cls): void { } try { - $i = $rc->getGenericArgumentsForParentInterface('IPlain'); + $i = $rc->getGenericArgumentSetsForParentInterface('IPlain'); echo " iface=", json_encode($i); } catch (ReflectionException $e) { echo " iface=throw(", $e->getMessage(), ")"; @@ -43,5 +43,5 @@ foreach (['Plain', 'WithParent', 'WithIface', 'WithTrait'] as $cls) { --EXPECT-- Plain: parent=throw(Class Plain has no parent class) iface=throw(IPlain is not an ancestor interface of Plain) trait=throw(TPlain is not a trait used by Plain) WithParent: parent=[] iface=throw(IPlain is not an ancestor interface of WithParent) trait=throw(TPlain is not a trait used by WithParent) -WithIface: parent=throw(Class WithIface has no parent class) iface=[] trait=throw(TPlain is not a trait used by WithIface) +WithIface: parent=throw(Class WithIface has no parent class) iface=[[]] trait=throw(TPlain is not a trait used by WithIface) WithTrait: parent=throw(Class WithTrait has no parent class) iface=throw(IPlain is not an ancestor interface of WithTrait) trait=[] diff --git a/ext/reflection/tests/generics/ancestor_args_parent_interface.phpt b/ext/reflection/tests/generics/ancestor_args_parent_interface.phpt index 93e97ad15554..305d607e657c 100644 --- a/ext/reflection/tests/generics/ancestor_args_parent_interface.phpt +++ b/ext/reflection/tests/generics/ancestor_args_parent_interface.phpt @@ -1,5 +1,5 @@ --TEST-- -Reflection: getGenericArgumentsForParentInterface returns args from implements / interface-extends; throws when not ancestor +Reflection: getGenericArgumentSetsForParentInterface returns args from implements / interface-extends; throws when not ancestor --FILE-- {} @@ -17,7 +17,7 @@ class ChildWithArgs extends WithArgs {} function show(string $cls, string $iface): void { try { - $args = (new ReflectionClass($cls))->getGenericArgumentsForParentInterface($iface); + $args = (new ReflectionClass($cls))->getGenericArgumentSetsForParentInterface($iface)[0]; } catch (ReflectionException $e) { echo "$cls/$iface: throw ({$e->getMessage()})\n"; return; diff --git a/ext/reflection/tests/generics/ancestor_args_transitive_paths.phpt b/ext/reflection/tests/generics/ancestor_args_transitive_paths.phpt index 388409f3ca9f..99fac72a48f9 100644 --- a/ext/reflection/tests/generics/ancestor_args_transitive_paths.phpt +++ b/ext/reflection/tests/generics/ancestor_args_transitive_paths.phpt @@ -1,5 +1,5 @@ --TEST-- -Reflection: getGenericArgumentsForParentInterface composes substitutions through transitive paths and returns first-match for diamonds +Reflection: getGenericArgumentSetsForParentInterface composes substitutions through transitive paths --FILE-- {} class Diamond implements I1, I2 {} function show(string $cls, string $ancestor): void { - $args = (new ReflectionClass($cls))->getGenericArgumentsForParentInterface($ancestor); - $rendered = array_map(static fn(ReflectionType $t): string => (string) $t, $args); - printf("%-30s -> %-10s = [%s]\n", $cls, $ancestor, implode(', ', $rendered)); + $sets = (new ReflectionClass($cls))->getGenericArgumentSetsForParentInterface($ancestor); + $rendered = array_map( + static fn(array $args): string => '[' . implode(', ', array_map( + static fn(ReflectionType $t): string => (string) $t, + $args, + )) . ']', + $sets, + ); + printf("%-30s -> %-10s = %s\n", $cls, $ancestor, implode('; ', $rendered)); } show('FwdGeneric', 'Root'); @@ -65,4 +71,4 @@ ForwardingChild -> Root = [V] ChildViaMid -> Root = [string] ChildViaMid -> Mid = [string] Leaf -> Root = [bool] -Diamond -> DA = [int] +Diamond -> DA = [int]; [string] diff --git a/ext/reflection/tests/generics/ancestor_args_type_param_refs.phpt b/ext/reflection/tests/generics/ancestor_args_type_param_refs.phpt index e647933447ac..70d21d02c2b3 100644 --- a/ext/reflection/tests/generics/ancestor_args_type_param_refs.phpt +++ b/ext/reflection/tests/generics/ancestor_args_type_param_refs.phpt @@ -6,7 +6,7 @@ interface Container {} class Holder implements Container {} $rc = new ReflectionClass('Holder'); -$args = $rc->getGenericArgumentsForParentInterface('Container'); +$args = $rc->getGenericArgumentSetsForParentInterface('Container')[0]; echo "count: ", count($args), "\n"; echo "class: ", get_class($args[0]), "\n";