Skip to content

Commit 980527c

Browse files
phpstan-botclaudestaabm
authored
Fix ReflectionGetAttributesMethodReturnTypeExtension for UnionTypes (#5463)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Markus Staab <maggus.staab@googlemail.com>
1 parent 77a3244 commit 980527c

File tree

3 files changed

+101
-4
lines changed

3 files changed

+101
-4
lines changed

src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@
55
use PhpParser\Node\Expr\MethodCall;
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Reflection\MethodReflection;
8+
use PHPStan\Reflection\ParametersAcceptorSelector;
89
use PHPStan\Type\Accessory\AccessoryArrayListType;
910
use PHPStan\Type\ArrayType;
1011
use PHPStan\Type\DynamicMethodReturnTypeExtension;
1112
use PHPStan\Type\Generic\GenericObjectType;
1213
use PHPStan\Type\IntegerRangeType;
1314
use PHPStan\Type\IntersectionType;
15+
use PHPStan\Type\ObjectType;
1416
use PHPStan\Type\Type;
17+
use PHPStan\Type\TypeCombinator;
1518
use ReflectionAttribute;
1619
use function count;
1720

@@ -32,8 +35,7 @@ public function getClass(): string
3235

3336
public function isMethodSupported(MethodReflection $methodReflection): bool
3437
{
35-
return $methodReflection->getDeclaringClass()->getName() === $this->className
36-
&& $methodReflection->getName() === 'getAttributes';
38+
return $methodReflection->getName() === 'getAttributes';
3739
}
3840

3941
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
@@ -44,7 +46,34 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
4446
$argType = $scope->getType($methodCall->getArgs()[0]->value);
4547
$classType = $argType->getClassStringObjectType();
4648

47-
return new IntersectionType([new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new GenericObjectType(ReflectionAttribute::class, [$classType])), new AccessoryArrayListType()]);
49+
$variant = ParametersAcceptorSelector::selectFromArgs($scope, $methodCall->getArgs(), $methodReflection->getVariants());
50+
$valueType = $this->resolveReflectionAttributeType($variant->getReturnType(), $classType);
51+
52+
return new IntersectionType([new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), $valueType), new AccessoryArrayListType()]);
53+
}
54+
55+
private function resolveReflectionAttributeType(Type $returnType, Type $classType): Type
56+
{
57+
$nativeReflectionAttributeType = new ObjectType(ReflectionAttribute::class);
58+
59+
$valueTypes = [];
60+
foreach ($returnType->getIterableValueType()->getObjectClassNames() as $className) {
61+
if (!$nativeReflectionAttributeType->isSuperTypeOf(new ObjectType($className))->yes()) {
62+
continue;
63+
}
64+
65+
$valueTypes[] = new GenericObjectType($className, [$classType]);
66+
}
67+
68+
if (count($valueTypes) === 0) {
69+
return new GenericObjectType(ReflectionAttribute::class, [$classType]);
70+
}
71+
72+
if (count($valueTypes) === 1) {
73+
return $valueTypes[0];
74+
}
75+
76+
return TypeCombinator::union(...$valueTypes);
4877
}
4978

5079
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php // lint >= 8.0
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug14466;
6+
7+
use ReflectionAttribute;
8+
use ReflectionClass;
9+
use ReflectionMethod;
10+
use function PHPStan\Testing\assertType;
11+
12+
interface I
13+
{
14+
15+
}
16+
17+
class Bug
18+
{
19+
/**
20+
* @param ReflectionClass<*> $object
21+
*/
22+
protected function c(ReflectionClass $object): void
23+
{
24+
$requirements = $object->getAttributes(I::class, ReflectionAttribute::IS_INSTANCEOF);
25+
26+
assertType('list<ReflectionAttribute<Bug14466\I>>', $requirements);
27+
}
28+
29+
/**
30+
* @param ReflectionMethod $object
31+
*/
32+
protected function m(ReflectionMethod $object): void
33+
{
34+
$requirements = $object->getAttributes(I::class, ReflectionAttribute::IS_INSTANCEOF);
35+
36+
assertType('list<ReflectionAttribute<Bug14466\I>>', $requirements);
37+
}
38+
39+
/**
40+
* @param ReflectionClass<*>|ReflectionMethod $object
41+
*/
42+
protected function classOrMethod(ReflectionClass|ReflectionMethod $object): void
43+
{
44+
$requirements = $object->getAttributes(I::class, ReflectionAttribute::IS_INSTANCEOF);
45+
46+
assertType('list<ReflectionAttribute<Bug14466\I>>', $requirements);
47+
}
48+
49+
/**
50+
* @param ReflectionClass<*>|\ReflectionProperty $object
51+
*/
52+
protected function classOrProperty(ReflectionClass|\ReflectionProperty $object): void
53+
{
54+
$requirements = $object->getAttributes(I::class, ReflectionAttribute::IS_INSTANCEOF);
55+
56+
assertType('list<ReflectionAttribute<Bug14466\I>>', $requirements);
57+
}
58+
59+
/**
60+
* @param ReflectionMethod|\ReflectionProperty $object
61+
*/
62+
protected function methodOrProperty(ReflectionMethod|\ReflectionProperty $object): void
63+
{
64+
$requirements = $object->getAttributes(I::class, ReflectionAttribute::IS_INSTANCEOF);
65+
66+
assertType('list<ReflectionAttribute<Bug14466\I>>', $requirements);
67+
}
68+
}

tests/PHPStan/Analyser/nsrt/bug-14484.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?php
1+
<?php // lint >= 8.0
22

33
namespace Bug14484;
44

0 commit comments

Comments
 (0)