Skip to content

Commit 2e13af8

Browse files
phpstan-botclaude
andcommitted
Resolve ReflectionAttribute class from method return type instead of str_contains hack
Instead of filtering out BetterReflection adapter classes with a str_contains check in isMethodSupported(), resolve the correct ReflectionAttribute class name from the method's declared return type in getTypeFromMethodCall(). For native reflection classes, the return type is ReflectionAttribute[] so the resolved class is ReflectionAttribute. For BetterReflection adapters, the return type includes their ReflectionAttribute adapter, so the extension correctly returns GenericObjectType with that class, preserving methods like getArgumentsExpressions(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 119d849 commit 2e13af8

File tree

1 file changed

+19
-4
lines changed

1 file changed

+19
-4
lines changed

src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
use PHPStan\Type\Generic\GenericObjectType;
1212
use PHPStan\Type\IntegerRangeType;
1313
use PHPStan\Type\IntersectionType;
14+
use PHPStan\Type\ObjectType;
1415
use PHPStan\Type\Type;
1516
use ReflectionAttribute;
1617
use function count;
17-
use function str_contains;
1818

1919
final class ReflectionGetAttributesMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension
2020
{
@@ -33,8 +33,7 @@ public function getClass(): string
3333

3434
public function isMethodSupported(MethodReflection $methodReflection): bool
3535
{
36-
return $methodReflection->getName() === 'getAttributes'
37-
&& !str_contains($methodReflection->getDeclaringClass()->getName(), '\\');
36+
return $methodReflection->getName() === 'getAttributes';
3837
}
3938

4039
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
@@ -45,7 +44,23 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
4544
$argType = $scope->getType($methodCall->getArgs()[0]->value);
4645
$classType = $argType->getClassStringObjectType();
4746

48-
return new IntersectionType([new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new GenericObjectType(ReflectionAttribute::class, [$classType])), new AccessoryArrayListType()]);
47+
$reflectionAttributeClassName = $this->resolveReflectionAttributeClassName($methodReflection);
48+
49+
return new IntersectionType([new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new GenericObjectType($reflectionAttributeClassName, [$classType])), new AccessoryArrayListType()]);
50+
}
51+
52+
private function resolveReflectionAttributeClassName(MethodReflection $methodReflection): string
53+
{
54+
$returnType = $methodReflection->getVariants()[0]->getReturnType();
55+
$nativeReflectionAttributeType = new ObjectType(ReflectionAttribute::class);
56+
57+
foreach ($returnType->getIterableValueType()->getObjectClassNames() as $className) {
58+
if ($nativeReflectionAttributeType->isSuperTypeOf(new ObjectType($className))->yes()) {
59+
return $className;
60+
}
61+
}
62+
63+
return ReflectionAttribute::class;
4964
}
5065

5166
}

0 commit comments

Comments
 (0)