Skip to content

Commit e3acc77

Browse files
committed
Extract MethodCallReturnTypeHelper from MutatingScope
1 parent eb6a95a commit e3acc77

File tree

6 files changed

+194
-96
lines changed

6 files changed

+194
-96
lines changed

conf/config.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2156,6 +2156,9 @@ services:
21562156
-
21572157
class: PHPStan\Reflection\BetterReflection\SourceStubber\ReflectionSourceStubberFactory
21582158

2159+
-
2160+
class: PHPStan\Analyser\MethodCallReturnTypeHelper
2161+
21592162
php8Lexer:
21602163
class: PhpParser\Lexer\Emulative
21612164
factory: @PHPStan\Parser\LexerFactory::createEmulative()

src/Analyser/DirectInternalScopeFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public function __construct(
3535
private Parser $parser,
3636
private NodeScopeResolver $nodeScopeResolver,
3737
private PhpVersion $phpVersion,
38+
private MethodCallReturnTypeHelper $methodCallReturnTypeHelper,
3839
private bool $explicitMixedInUnknownGenericNew,
3940
private bool $explicitMixedForGlobalVariables,
4041
private ConstantResolver $constantResolver,
@@ -88,6 +89,7 @@ public function create(
8889
$this->constantResolver,
8990
$context,
9091
$this->phpVersion,
92+
$this->methodCallReturnTypeHelper,
9193
$declareStrictTypes,
9294
$function,
9395
$namespace,

src/Analyser/LazyInternalScopeFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public function create(
8282
$this->container->getByType(ConstantResolver::class),
8383
$context,
8484
$this->container->getByType(PhpVersion::class),
85+
$this->container->getByType(MethodCallReturnTypeHelper::class),
8586
$declareStrictTypes,
8687
$function,
8788
$namespace,
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser;
4+
5+
use PhpParser\Node\Expr;
6+
use PhpParser\Node\Expr\MethodCall;
7+
use PhpParser\Node\Expr\StaticCall;
8+
use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider;
9+
use PHPStan\Reflection\ExtendedMethodReflection;
10+
use PHPStan\Reflection\ParametersAcceptor;
11+
use PHPStan\Type\DynamicReturnTypeExtensionRegistry;
12+
use PHPStan\Type\Type;
13+
use PHPStan\Type\TypeCombinator;
14+
use PHPStan\Type\UnionType;
15+
use function count;
16+
17+
final class MethodCallReturnTypeHelper
18+
{
19+
20+
private DynamicReturnTypeExtensionRegistry $dynamicReturnTypeExtensionRegistry;
21+
22+
public function __construct(
23+
DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider,
24+
)
25+
{
26+
$this->dynamicReturnTypeExtensionRegistry = $dynamicReturnTypeExtensionRegistryProvider->getRegistry();
27+
}
28+
29+
public function constructorReturnType(
30+
ParametersAcceptor $parametersAcceptor,
31+
Scope $scope,
32+
StaticCall $methodCall,
33+
ExtendedMethodReflection $constructorMethod,
34+
string $className,
35+
): ?Type
36+
{
37+
$normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $methodCall);
38+
39+
$resolvedTypes = [];
40+
if ($normalizedMethodCall !== null) {
41+
foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicStaticMethodReturnTypeExtensionsForClass($className) as $dynamicStaticMethodReturnTypeExtension) {
42+
if (!$dynamicStaticMethodReturnTypeExtension->isStaticMethodSupported($constructorMethod)) {
43+
continue;
44+
}
45+
46+
$resolvedType = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall(
47+
$constructorMethod,
48+
$normalizedMethodCall,
49+
$scope,
50+
);
51+
if ($resolvedType === null) {
52+
continue;
53+
}
54+
55+
$resolvedTypes[] = $resolvedType;
56+
}
57+
}
58+
59+
if (count($resolvedTypes) > 0) {
60+
return TypeCombinator::union(...$resolvedTypes);
61+
}
62+
63+
return null;
64+
}
65+
66+
/**
67+
* @param MethodCall|StaticCall $methodCall
68+
*/
69+
public function methodCallReturnType(
70+
ParametersAcceptor $parametersAcceptor,
71+
Scope $scope,
72+
Type $typeWithMethod,
73+
string $methodName,
74+
Expr $methodCall,
75+
): ?Type
76+
{
77+
$typeWithMethod = $this->filterTypeWithMethod($typeWithMethod, $methodName);
78+
if ($typeWithMethod === null) {
79+
return null;
80+
}
81+
82+
$methodReflection = $typeWithMethod->getMethod($methodName, $scope);
83+
84+
if ($methodCall instanceof MethodCall) {
85+
$normalizedMethodCall = ArgumentsNormalizer::reorderMethodArguments($parametersAcceptor, $methodCall);
86+
} else {
87+
$normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $methodCall);
88+
}
89+
if ($normalizedMethodCall === null) {
90+
return $parametersAcceptor->getReturnType();
91+
}
92+
93+
$resolvedTypes = [];
94+
foreach ($typeWithMethod->getObjectClassNames() as $className) {
95+
if ($normalizedMethodCall instanceof MethodCall) {
96+
foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicMethodReturnTypeExtensionsForClass($className) as $dynamicMethodReturnTypeExtension) {
97+
if (!$dynamicMethodReturnTypeExtension->isMethodSupported($methodReflection)) {
98+
continue;
99+
}
100+
101+
$resolvedType = $dynamicMethodReturnTypeExtension->getTypeFromMethodCall(
102+
$methodReflection,
103+
$normalizedMethodCall,
104+
$scope,
105+
);
106+
if ($resolvedType === null) {
107+
continue;
108+
}
109+
110+
$resolvedTypes[] = $resolvedType;
111+
}
112+
} else {
113+
foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicStaticMethodReturnTypeExtensionsForClass($className) as $dynamicStaticMethodReturnTypeExtension) {
114+
if (!$dynamicStaticMethodReturnTypeExtension->isStaticMethodSupported($methodReflection)) {
115+
continue;
116+
}
117+
118+
$resolvedType = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall(
119+
$methodReflection,
120+
$normalizedMethodCall,
121+
$scope,
122+
);
123+
if ($resolvedType === null) {
124+
continue;
125+
}
126+
127+
$resolvedTypes[] = $resolvedType;
128+
}
129+
}
130+
}
131+
132+
if (count($resolvedTypes) > 0) {
133+
return TypeCombinator::union(...$resolvedTypes);
134+
}
135+
136+
return $parametersAcceptor->getReturnType();
137+
}
138+
139+
public function filterTypeWithMethod(Type $typeWithMethod, string $methodName): ?Type
140+
{
141+
if ($typeWithMethod instanceof UnionType) {
142+
$newTypes = [];
143+
foreach ($typeWithMethod->getTypes() as $innerType) {
144+
if (!$innerType->hasMethod($methodName)->yes()) {
145+
continue;
146+
}
147+
148+
$newTypes[] = $innerType;
149+
}
150+
if (count($newTypes) === 0) {
151+
return null;
152+
}
153+
$typeWithMethod = TypeCombinator::union(...$newTypes);
154+
}
155+
156+
if (!$typeWithMethod->hasMethod($methodName)->yes()) {
157+
return null;
158+
}
159+
160+
return $typeWithMethod;
161+
}
162+
163+
}

src/Analyser/MutatingScope.php

Lines changed: 23 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ public function __construct(
207207
private ConstantResolver $constantResolver,
208208
private ScopeContext $context,
209209
private PhpVersion $phpVersion,
210+
private MethodCallReturnTypeHelper $methodCallReturnTypeHelper,
210211
private bool $declareStrictTypes = false,
211212
private PhpFunctionFromParserNodeReflection|null $function = null,
212213
?string $namespace = null,
@@ -5485,7 +5486,6 @@ private function exactInstantiation(New_ $node, string $className): ?Type
54855486
$constructorMethod = new DummyConstructorReflection($classReflection);
54865487
}
54875488

5488-
$resolvedTypes = [];
54895489
$methodCall = new Expr\StaticCall(
54905490
new Name($resolvedClassName),
54915491
new Node\Identifier($constructorMethod->getName()),
@@ -5498,29 +5498,15 @@ private function exactInstantiation(New_ $node, string $className): ?Type
54985498
$constructorMethod->getVariants(),
54995499
$constructorMethod->getNamedArgumentsVariants(),
55005500
);
5501-
$normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $methodCall);
5502-
5503-
if ($normalizedMethodCall !== null) {
5504-
foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicStaticMethodReturnTypeExtensionsForClass($classReflection->getName()) as $dynamicStaticMethodReturnTypeExtension) {
5505-
if (!$dynamicStaticMethodReturnTypeExtension->isStaticMethodSupported($constructorMethod)) {
5506-
continue;
5507-
}
5508-
5509-
$resolvedType = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall(
5510-
$constructorMethod,
5511-
$normalizedMethodCall,
5512-
$this,
5513-
);
5514-
if ($resolvedType === null) {
5515-
continue;
5516-
}
5517-
5518-
$resolvedTypes[] = $resolvedType;
5519-
}
5520-
}
5521-
5522-
if (count($resolvedTypes) > 0) {
5523-
return TypeCombinator::union(...$resolvedTypes);
5501+
$returnType = $this->methodCallReturnTypeHelper->constructorReturnType(
5502+
$parametersAcceptor,
5503+
$this,
5504+
$methodCall,
5505+
$constructorMethod,
5506+
$classReflection->getName(),
5507+
);
5508+
if ($returnType !== null) {
5509+
return $returnType;
55245510
}
55255511

55265512
$methodResult = $this->getType($methodCall);
@@ -5619,34 +5605,10 @@ private function exactInstantiation(New_ $node, string $className): ?Type
56195605
);
56205606
}
56215607

5622-
private function filterTypeWithMethod(Type $typeWithMethod, string $methodName): ?Type
5623-
{
5624-
if ($typeWithMethod instanceof UnionType) {
5625-
$newTypes = [];
5626-
foreach ($typeWithMethod->getTypes() as $innerType) {
5627-
if (!$innerType->hasMethod($methodName)->yes()) {
5628-
continue;
5629-
}
5630-
5631-
$newTypes[] = $innerType;
5632-
}
5633-
if (count($newTypes) === 0) {
5634-
return null;
5635-
}
5636-
$typeWithMethod = TypeCombinator::union(...$newTypes);
5637-
}
5638-
5639-
if (!$typeWithMethod->hasMethod($methodName)->yes()) {
5640-
return null;
5641-
}
5642-
5643-
return $typeWithMethod;
5644-
}
5645-
56465608
/** @api */
56475609
public function getMethodReflection(Type $typeWithMethod, string $methodName): ?ExtendedMethodReflection
56485610
{
5649-
$type = $this->filterTypeWithMethod($typeWithMethod, $methodName);
5611+
$type = $this->methodCallReturnTypeHelper->filterTypeWithMethod($typeWithMethod, $methodName);
56505612
if ($type === null) {
56515613
return null;
56525614
}
@@ -5657,7 +5619,7 @@ public function getMethodReflection(Type $typeWithMethod, string $methodName): ?
56575619
/** @api */
56585620
public function getNakedMethod(Type $typeWithMethod, string $methodName): ?ExtendedMethodReflection
56595621
{
5660-
$type = $this->filterTypeWithMethod($typeWithMethod, $methodName);
5622+
$type = $this->methodCallReturnTypeHelper->filterTypeWithMethod($typeWithMethod, $methodName);
56615623
if ($type === null) {
56625624
return null;
56635625
}
@@ -5670,7 +5632,7 @@ public function getNakedMethod(Type $typeWithMethod, string $methodName): ?Exten
56705632
*/
56715633
private function methodCallReturnType(Type $typeWithMethod, string $methodName, Expr $methodCall): ?Type
56725634
{
5673-
$typeWithMethod = $this->filterTypeWithMethod($typeWithMethod, $methodName);
5635+
$typeWithMethod = $this->methodCallReturnTypeHelper->filterTypeWithMethod($typeWithMethod, $methodName);
56745636
if ($typeWithMethod === null) {
56755637
return null;
56765638
}
@@ -5682,55 +5644,20 @@ private function methodCallReturnType(Type $typeWithMethod, string $methodName,
56825644
$methodReflection->getVariants(),
56835645
$methodReflection->getNamedArgumentsVariants(),
56845646
);
5685-
if ($methodCall instanceof MethodCall) {
5686-
$normalizedMethodCall = ArgumentsNormalizer::reorderMethodArguments($parametersAcceptor, $methodCall);
5687-
} else {
5688-
$normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $methodCall);
5689-
}
5690-
if ($normalizedMethodCall === null) {
5691-
return $this->transformVoidToNull($parametersAcceptor->getReturnType(), $methodCall);
5692-
}
5693-
5694-
$resolvedTypes = [];
5695-
foreach ($typeWithMethod->getObjectClassNames() as $className) {
5696-
if ($normalizedMethodCall instanceof MethodCall) {
5697-
foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicMethodReturnTypeExtensionsForClass($className) as $dynamicMethodReturnTypeExtension) {
5698-
if (!$dynamicMethodReturnTypeExtension->isMethodSupported($methodReflection)) {
5699-
continue;
5700-
}
5701-
5702-
$resolvedType = $dynamicMethodReturnTypeExtension->getTypeFromMethodCall($methodReflection, $normalizedMethodCall, $this);
5703-
if ($resolvedType === null) {
5704-
continue;
5705-
}
5706-
5707-
$resolvedTypes[] = $resolvedType;
5708-
}
5709-
} else {
5710-
foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicStaticMethodReturnTypeExtensionsForClass($className) as $dynamicStaticMethodReturnTypeExtension) {
5711-
if (!$dynamicStaticMethodReturnTypeExtension->isStaticMethodSupported($methodReflection)) {
5712-
continue;
5713-
}
5714-
5715-
$resolvedType = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall(
5716-
$methodReflection,
5717-
$normalizedMethodCall,
5718-
$this,
5719-
);
5720-
if ($resolvedType === null) {
5721-
continue;
5722-
}
57235647

5724-
$resolvedTypes[] = $resolvedType;
5725-
}
5726-
}
5727-
}
5648+
$returnType = $this->methodCallReturnTypeHelper->methodCallReturnType(
5649+
$parametersAcceptor,
5650+
$this,
5651+
$typeWithMethod,
5652+
$methodName,
5653+
$methodCall,
5654+
);
57285655

5729-
if (count($resolvedTypes) > 0) {
5730-
return $this->transformVoidToNull(TypeCombinator::union(...$resolvedTypes), $methodCall);
5656+
if ($returnType !== null) {
5657+
return $this->transformVoidToNull($returnType, $methodCall);
57315658
}
57325659

5733-
return $this->transformVoidToNull($parametersAcceptor->getReturnType(), $methodCall);
5660+
return null;
57345661
}
57355662

57365663
/** @api */

src/Testing/PHPStanTestCase.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PHPStan\Analyser\ConstantResolver;
66
use PHPStan\Analyser\DirectInternalScopeFactory;
77
use PHPStan\Analyser\Error;
8+
use PHPStan\Analyser\MethodCallReturnTypeHelper;
89
use PHPStan\Analyser\MutatingScope;
910
use PHPStan\Analyser\NodeScopeResolver;
1011
use PHPStan\Analyser\ScopeFactory;
@@ -188,6 +189,7 @@ public static function createScopeFactory(ReflectionProvider $reflectionProvider
188189
self::getParser(),
189190
$container->getByType(NodeScopeResolver::class),
190191
$container->getByType(PhpVersion::class),
192+
$container->getByType(MethodCallReturnTypeHelper::class),
191193
$container->getParameter('featureToggles')['explicitMixedInUnknownGenericNew'],
192194
$container->getParameter('featureToggles')['explicitMixedForGlobalVariables'],
193195
$constantResolver,

0 commit comments

Comments
 (0)