Skip to content

Commit a8d4a09

Browse files
committed
Use per-class method reflection when resolving dynamic return type extensions on union types
- In `MethodCallReturnTypeHelper::methodCallReturnType()`, the per-class-name loop passed the union's combined `MethodReflection` to `isMethodSupported()` and `getTypeFromMethodCall()`/`getTypeFromStaticMethodCall()`. Extensions that check `$methodReflection->getDeclaringClass()->getName()` (e.g. `ReflectionGetAttributesMethodReturnTypeExtension`) only matched the declaring class of `UnionTypeMethodReflection::$methods[0]`, causing extensions for other union members to be skipped. - Now create a per-class `ObjectType` and get the method reflection from it for each iteration, so each extension sees the correct declaring class. - Also applies to the static method call branch in the same loop.
1 parent 04a99c1 commit a8d4a09

File tree

2 files changed

+77
-4
lines changed

2 files changed

+77
-4
lines changed

src/Analyser/ExprHandler/Helper/MethodCallReturnTypeHelper.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,18 @@ public function methodCallReturnType(
5656
$allClassNames = $typeWithMethod->getObjectClassNames();
5757
$handledClassNames = [];
5858
foreach ($allClassNames as $className) {
59+
$classType = new ObjectType($className);
60+
if (!$classType->hasMethod($methodName)->yes()) {
61+
continue;
62+
}
63+
$classMethodReflection = $classType->getMethod($methodName, $scope);
5964
if ($normalizedMethodCall instanceof MethodCall) {
6065
foreach ($this->dynamicReturnTypeExtensionRegistryProvider->getRegistry()->getDynamicMethodReturnTypeExtensionsForClass($className) as $dynamicMethodReturnTypeExtension) {
61-
if (!$dynamicMethodReturnTypeExtension->isMethodSupported($methodReflection)) {
66+
if (!$dynamicMethodReturnTypeExtension->isMethodSupported($classMethodReflection)) {
6267
continue;
6368
}
6469

65-
$resolvedType = $dynamicMethodReturnTypeExtension->getTypeFromMethodCall($methodReflection, $normalizedMethodCall, $scope);
70+
$resolvedType = $dynamicMethodReturnTypeExtension->getTypeFromMethodCall($classMethodReflection, $normalizedMethodCall, $scope);
6671
if ($resolvedType === null) {
6772
continue;
6873
}
@@ -72,12 +77,12 @@ public function methodCallReturnType(
7277
}
7378
} else {
7479
foreach ($this->dynamicReturnTypeExtensionRegistryProvider->getRegistry()->getDynamicStaticMethodReturnTypeExtensionsForClass($className) as $dynamicStaticMethodReturnTypeExtension) {
75-
if (!$dynamicStaticMethodReturnTypeExtension->isStaticMethodSupported($methodReflection)) {
80+
if (!$dynamicStaticMethodReturnTypeExtension->isStaticMethodSupported($classMethodReflection)) {
7681
continue;
7782
}
7883

7984
$resolvedType = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall(
80-
$methodReflection,
85+
$classMethodReflection,
8186
$normalizedMethodCall,
8287
$scope,
8388
);
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+
}

0 commit comments

Comments
 (0)