diff --git a/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php b/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php index 1077c912c4..bd0ef04491 100644 --- a/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php +++ b/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php @@ -5,13 +5,16 @@ use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\ArrayType; use PHPStan\Type\DynamicMethodReturnTypeExtension; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\ObjectType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use ReflectionAttribute; use function count; @@ -32,8 +35,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 @@ -44,7 +46,34 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method $argType = $scope->getType($methodCall->getArgs()[0]->value); $classType = $argType->getClassStringObjectType(); - return new IntersectionType([new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new GenericObjectType(ReflectionAttribute::class, [$classType])), new AccessoryArrayListType()]); + $variant = ParametersAcceptorSelector::selectFromArgs($scope, $methodCall->getArgs(), $methodReflection->getVariants()); + $valueType = $this->resolveReflectionAttributeType($variant->getReturnType(), $classType); + + return new IntersectionType([new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), $valueType), new AccessoryArrayListType()]); + } + + private function resolveReflectionAttributeType(Type $returnType, Type $classType): Type + { + $nativeReflectionAttributeType = new ObjectType(ReflectionAttribute::class); + + $valueTypes = []; + foreach ($returnType->getIterableValueType()->getObjectClassNames() as $className) { + if (!$nativeReflectionAttributeType->isSuperTypeOf(new ObjectType($className))->yes()) { + continue; + } + + $valueTypes[] = new GenericObjectType($className, [$classType]); + } + + if (count($valueTypes) === 0) { + return new GenericObjectType(ReflectionAttribute::class, [$classType]); + } + + if (count($valueTypes) === 1) { + return $valueTypes[0]; + } + + return TypeCombinator::union(...$valueTypes); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-14466.php b/tests/PHPStan/Analyser/nsrt/bug-14466.php new file mode 100644 index 0000000000..33c83ac23b --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-14466.php @@ -0,0 +1,68 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug14466; + +use ReflectionAttribute; +use ReflectionClass; +use ReflectionMethod; +use function PHPStan\Testing\assertType; + +interface I +{ + +} + +class Bug +{ + /** + * @param ReflectionClass<*> $object + */ + protected function c(ReflectionClass $object): void + { + $requirements = $object->getAttributes(I::class, ReflectionAttribute::IS_INSTANCEOF); + + assertType('list>', $requirements); + } + + /** + * @param ReflectionMethod $object + */ + protected function m(ReflectionMethod $object): void + { + $requirements = $object->getAttributes(I::class, ReflectionAttribute::IS_INSTANCEOF); + + assertType('list>', $requirements); + } + + /** + * @param ReflectionClass<*>|ReflectionMethod $object + */ + protected function classOrMethod(ReflectionClass|ReflectionMethod $object): void + { + $requirements = $object->getAttributes(I::class, ReflectionAttribute::IS_INSTANCEOF); + + assertType('list>', $requirements); + } + + /** + * @param ReflectionClass<*>|\ReflectionProperty $object + */ + protected function classOrProperty(ReflectionClass|\ReflectionProperty $object): void + { + $requirements = $object->getAttributes(I::class, ReflectionAttribute::IS_INSTANCEOF); + + assertType('list>', $requirements); + } + + /** + * @param ReflectionMethod|\ReflectionProperty $object + */ + protected function methodOrProperty(ReflectionMethod|\ReflectionProperty $object): void + { + $requirements = $object->getAttributes(I::class, ReflectionAttribute::IS_INSTANCEOF); + + assertType('list>', $requirements); + } +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-14484.php b/tests/PHPStan/Analyser/nsrt/bug-14484.php index 14f70b759c..37ca614fa4 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-14484.php +++ b/tests/PHPStan/Analyser/nsrt/bug-14484.php @@ -1,4 +1,4 @@ -= 8.0 namespace Bug14484;