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 f2b8aa536fd0..bf13081de53d 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,94 +8962,137 @@ static bool reflection_get_direct_inheritance_binding( return false; } -static bool reflection_get_transitive_interface_args( - zend_class_entry *ce, zend_class_entry *ancestor, - zend_type *args, uint32_t cap, uint32_t *arity) +static bool reflection_interface_is_reached_through_generic_binding( + zend_class_entry *ce, zend_class_entry *iface, HashTable *generic_implements) { - const zend_type *direct_args; - if (reflection_get_direct_inheritance_binding(ce, ancestor, &direct_args, arity)) { - if (*arity > cap) { - return false; + 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; } - for (uint32_t i = 0; i < *arity; i++) { - args[i] = reflection_type_copy(direct_args[i]); + if (bound == iface) { + return true; } - 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, + zend_class_entry *declaring_class) +{ + bool found = false; 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); + const zend_type *parent_args; 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)) { + 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++) { - args[i] = reflection_type_substitute_class_params( - parent_args[i], ce_to_parent, ce_to_parent_arity); + mapped_args[i] = reflection_type_substitute_class_params( + parent_args[i], ce_args, ce_arity); } - reflection_type_array_release(parent_args, parent_arity); - } else { - for (uint32_t i = 0; i < parent_arity; i++) { - args[i] = parent_args[i]; + + 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; } - *arity = parent_arity; - free_alloca(parent_args, use_heap); - return true; + } else if (reflection_collect_interface_arg_sets(return_value, ce->parent, ancestor, + NULL, 0, declaring_class)) { + found = true; } - free_alloca(parent_args, use_heap); } - if (ce->generic_types && ce->generic_types->implements) { + HashTable *generic_implements = ce->generic_types ? ce->generic_types->implements : NULL; + if (generic_implements) { zval *zv; - ZEND_HASH_FOREACH_VAL(ce->generic_types->implements, 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; } - 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; - } + 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); + } - for (uint32_t i = 0; i < intermediate_arity; i++) { - args[i] = reflection_type_substitute_class_params( - intermediate_args[i], named->args, named->count); + 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; } - 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++) { + 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 (reflection_get_transitive_interface_args(intermediate, ancestor, args, cap, arity)) { - return true; + if (reflection_interface_is_reached_through_generic_binding(ce, intermediate, generic_implements)) { + continue; + } + if (reflection_collect_interface_arg_sets(return_value, intermediate, ancestor, + NULL, 0, declaring_class)) { + found = true; } } } - return false; + return found; } ZEND_METHOD(ReflectionClass, getGenericArgumentsForParentClass) @@ -9060,7 +9137,7 @@ static bool reflection_try_build_named_args_from_table(HashTable *ht, zend_class return false; } -ZEND_METHOD(ReflectionClass, getGenericArgumentsForParentInterface) +ZEND_METHOD(ReflectionClass, getGenericArgumentSetsForParentInterface) { reflection_object *intern; zend_class_entry *ce; @@ -9071,11 +9148,6 @@ ZEND_METHOD(ReflectionClass, getGenericArgumentsForParentInterface) 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) { @@ -9099,20 +9171,15 @@ ZEND_METHOD(ReflectionClass, getGenericArgumentsForParentInterface) "%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); + + array_init(return_value); + if (ancestor && reflection_collect_interface_arg_sets(return_value, ce, ancestor, NULL, 0, ce)) { + return; } - RETURN_EMPTY_ARRAY(); + + zval entry; + array_init(&entry); + zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &entry); } ZEND_METHOD(ReflectionClass, getGenericArgumentsForUsedTrait) diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 22ced0706cd2..783c3206d24c 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -458,14 +458,13 @@ 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. + * Returns every generic argument set this class supplies for the named + * ancestor interface, in inheritance traversal order. * - * @return list + * @return list> * @throws ReflectionException if $name is not an ancestor interface */ - public function getGenericArgumentsForParentInterface(string $name): array {} + public function getGenericArgumentSetsForParentInterface(string $name): array {} /** * Returns the type arguments this class supplies at the use site for trait diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 87da407f889b..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: 12092cbfe98615b205c146ece58ed58f3b92b100 + * 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,11 +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_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) @@ -924,7 +924,7 @@ 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); ZEND_METHOD(ReflectionProperty, __construct); @@ -1246,7 +1246,7 @@ 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 ce22d1d93bab..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: 12092cbfe98615b205c146ece58ed58f3b92b100 */ + * Stub hash: 893c62c650203278595afbb056569dabfb918c20 */ -#ifndef ZEND_PHP_REFLECTION_DECL_12092cbfe98615b205c146ece58ed58f3b92b100_H -#define ZEND_PHP_REFLECTION_DECL_12092cbfe98615b205c146ece58ed58f3b92b100_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_12092cbfe98615b205c146ece58ed58f3b92b100_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 01415d08aacb..8172d967e3b3 100644 --- a/ext/reflection/tests/ReflectionClass_toString_001.phpt +++ b/ext/reflection/tests/ReflectionClass_toString_001.phpt @@ -536,7 +536,7 @@ Class [ class ReflectionClass implements Stringable, Refle - Return [ array ] } - Method [ public method getGenericArgumentsForParentInterface ] { + Method [ public method getGenericArgumentSetsForParentInterface ] { - Parameters [1] { Parameter #0 [ string $name ] 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 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";