diff --git a/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php b/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php index 1077c912c40..d5a21b7f624 100644 --- a/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php +++ b/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php @@ -32,8 +32,7 @@ public function getClass(): string public function isMethodSupported(MethodReflection $methodReflection): bool { - return $methodReflection->getDeclaringClass()->getName() === $this->className - && $methodReflection->getName() === 'getAttributes'; + return $methodReflection->getName() === 'getAttributes'; } public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type diff --git a/tests/PHPStan/Analyser/nsrt/reflection-attribute-getattributes-union.php b/tests/PHPStan/Analyser/nsrt/reflection-attribute-getattributes-union.php new file mode 100644 index 00000000000..ae4b17ea823 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/reflection-attribute-getattributes-union.php @@ -0,0 +1,54 @@ += 8.0 + +declare(strict_types=1); + +namespace ReflectionAttributeGetAttributesUnion; + +use ReflectionAttribute; +use ReflectionClass; +use ReflectionProperty; +use function PHPStan\Testing\assertType; + +#[\Attribute] +class MyAttr {} + +// Single type — worked before +function testSingle(ReflectionClass $ref): void +{ + /** @var class-string $class */ + $class = MyAttr::class; + $attrs = $ref->getAttributes($class, ReflectionAttribute::IS_INSTANCEOF); + assertType('list>', $attrs); + assertType('ReflectionAttributeGetAttributesUnion\MyAttr', $attrs[0]->newInstance()); +} + +// Union type — dynamic extension should fire for all members +function testUnion(ReflectionClass|ReflectionProperty $ref): void +{ + /** @var class-string $class */ + $class = MyAttr::class; + $attrs = $ref->getAttributes($class, ReflectionAttribute::IS_INSTANCEOF); + assertType('list>', $attrs); + assertType('ReflectionAttributeGetAttributesUnion\MyAttr', $attrs[0]->newInstance()); +} + +// Template T flows through union +/** + * @template T of object + * @param ReflectionClass|ReflectionProperty $reflection + * @param class-string $attributeClassName + * @return T + */ +function getSingleAttribute( + ReflectionClass|ReflectionProperty $reflection, + string $attributeClassName, +): object +{ + $attributes = $reflection->getAttributes($attributeClassName, ReflectionAttribute::IS_INSTANCEOF); + assertType('list>', $attributes); + + $instance = $attributes[0]->newInstance(); + assertType('T of object (function ReflectionAttributeGetAttributesUnion\getSingleAttribute(), argument)', $instance); + + return $instance; +}