Skip to content

Commit dd4f771

Browse files
committed
Classes for dynamic return type extensions are found only on direct types
1 parent eeb8ae4 commit dd4f771

File tree

4 files changed

+129
-2
lines changed

4 files changed

+129
-2
lines changed

src/Analyser/Scope.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,7 +1112,7 @@ private function resolveType(Expr $node): Type
11121112

11131113
if ($node instanceof MethodCall && $node->name instanceof Node\Identifier) {
11141114
$methodCalledOnType = $this->getType($node->var);
1115-
$referencedClasses = $methodCalledOnType->getReferencedClasses();
1115+
$referencedClasses = TypeUtils::getDirectClassNames($methodCalledOnType);
11161116
$resolvedTypes = [];
11171117
foreach ($referencedClasses as $referencedClass) {
11181118
if (!$this->broker->hasClass($referencedClass)) {
@@ -1175,7 +1175,7 @@ private function resolveType(Expr $node): Type
11751175
return new ErrorType();
11761176
}
11771177
$staticMethodReflection = $calleeType->getMethod((string) $node->name, $this);
1178-
$referencedClasses = $calleeType->getReferencedClasses();
1178+
$referencedClasses = TypeUtils::getDirectClassNames($calleeType);
11791179

11801180
$resolvedTypes = [];
11811181
foreach ($referencedClasses as $referencedClass) {

src/Type/TypeUtils.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,32 @@ public static function generalizeType(Type $type): Type
4848
return $type;
4949
}
5050

51+
/**
52+
* @param Type $type
53+
* @return string[]
54+
*/
55+
public static function getDirectClassNames(Type $type): array
56+
{
57+
if ($type instanceof TypeWithClassName) {
58+
return [$type->getClassName()];
59+
}
60+
61+
if ($type instanceof UnionType || $type instanceof IntersectionType) {
62+
$classNames = [];
63+
foreach ($type->getTypes() as $innerType) {
64+
if (!$innerType instanceof TypeWithClassName) {
65+
continue;
66+
}
67+
68+
$classNames[] = $innerType->getClassName();
69+
}
70+
71+
return $classNames;
72+
}
73+
74+
return [];
75+
}
76+
5177
/**
5278
* @param Type $type
5379
* @return \PHPStan\Type\ConstantScalarType[]

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3471,6 +3471,75 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection,
34713471
);
34723472
}
34733473

3474+
public function dataDynamicReturnTypeExtensionsOnCompoundTypes(): array
3475+
{
3476+
return [
3477+
[
3478+
'DynamicMethodReturnCompoundTypes\Collection',
3479+
'$collection->getSelf()',
3480+
],
3481+
[
3482+
'DynamicMethodReturnCompoundTypes\Collection|DynamicMethodReturnCompoundTypes\Foo',
3483+
'$collectionOrFoo->getSelf()',
3484+
],
3485+
];
3486+
}
3487+
3488+
/**
3489+
* @dataProvider dataDynamicReturnTypeExtensionsOnCompoundTypes
3490+
* @param string $description
3491+
* @param string $expression
3492+
*/
3493+
public function testDynamicReturnTypeExtensionsOnCompoundTypes(
3494+
string $description,
3495+
string $expression
3496+
): void
3497+
{
3498+
$this->assertTypes(
3499+
__DIR__ . '/data/dynamic-method-return-compound-types.php',
3500+
$description,
3501+
$expression,
3502+
[
3503+
new class () implements DynamicMethodReturnTypeExtension {
3504+
3505+
public function getClass(): string
3506+
{
3507+
return \DynamicMethodReturnCompoundTypes\Collection::class;
3508+
}
3509+
3510+
public function isMethodSupported(MethodReflection $methodReflection): bool
3511+
{
3512+
return $methodReflection->getName() === 'getSelf';
3513+
}
3514+
3515+
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
3516+
{
3517+
return new ObjectType(\DynamicMethodReturnCompoundTypes\Collection::class);
3518+
}
3519+
3520+
},
3521+
new class () implements DynamicMethodReturnTypeExtension {
3522+
3523+
public function getClass(): string
3524+
{
3525+
return \DynamicMethodReturnCompoundTypes\Foo::class;
3526+
}
3527+
3528+
public function isMethodSupported(MethodReflection $methodReflection): bool
3529+
{
3530+
return $methodReflection->getName() === 'getSelf';
3531+
}
3532+
3533+
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
3534+
{
3535+
return new ObjectType(\DynamicMethodReturnCompoundTypes\Foo::class);
3536+
}
3537+
3538+
},
3539+
]
3540+
);
3541+
}
3542+
34743543
public function dataOverwritingVariable(): array
34753544
{
34763545
return [
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace DynamicMethodReturnCompoundTypes;
4+
5+
class Collection
6+
{
7+
8+
public function getSelf()
9+
{
10+
11+
}
12+
13+
}
14+
15+
class Foo
16+
{
17+
18+
public function getSelf()
19+
{
20+
21+
}
22+
23+
/**
24+
* @param Collection|Foo[] $collection
25+
* @param Collection|Foo $collectionOrFoo
26+
*/
27+
public function doFoo($collection, $collectionOrFoo)
28+
{
29+
die;
30+
}
31+
32+
}

0 commit comments

Comments
 (0)