Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
201 changes: 134 additions & 67 deletions ext/reflection/php_reflection.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand All @@ -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)
Expand Down
9 changes: 4 additions & 5 deletions ext/reflection/php_reflection.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<ReflectionType>
* @return list<list<ReflectionType>>
* @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
Expand Down
10 changes: 5 additions & 5 deletions ext/reflection/php_reflection_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions ext/reflection/php_reflection_decl.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion ext/reflection/tests/ReflectionClass_toString_001.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ Class [ <internal:Reflection> class ReflectionClass implements Stringable, Refle
- Return [ array ]
}

Method [ <internal:Reflection> public method getGenericArgumentsForParentInterface ] {
Method [ <internal:Reflection> public method getGenericArgumentSetsForParentInterface ] {

- Parameters [1] {
Parameter #0 [ <required> string $name ]
Expand Down
Loading
Loading