Skip to content

Commit 19faea6

Browse files
committed
resolveType for method call handlers
1 parent a4b28bc commit 19faea6

File tree

8 files changed

+247
-225
lines changed

8 files changed

+247
-225
lines changed

src/Analyser/DirectInternalScopeFactory.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use PhpParser\Node;
66
use PHPStan\Analyser\Fiber\FiberScope;
77
use PHPStan\DependencyInjection\Container;
8-
use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider;
98
use PHPStan\DependencyInjection\Type\ExpressionTypeResolverExtensionRegistryProvider;
109
use PHPStan\Node\Printer\ExprPrinter;
1110
use PHPStan\Parser\Parser;
@@ -28,7 +27,6 @@ public function __construct(
2827
private Container $container,
2928
private ReflectionProvider $reflectionProvider,
3029
private InitializerExprTypeResolver $initializerExprTypeResolver,
31-
private DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider,
3230
private ExpressionTypeResolverExtensionRegistryProvider $expressionTypeResolverExtensionRegistryProvider,
3331
private ExprPrinter $exprPrinter,
3432
private TypeSpecifier $typeSpecifier,
@@ -73,7 +71,6 @@ public function create(
7371
$this,
7472
$this->reflectionProvider,
7573
$this->initializerExprTypeResolver,
76-
$this->dynamicReturnTypeExtensionRegistryProvider->getRegistry(),
7774
$this->expressionTypeResolverExtensionRegistryProvider->getRegistry(),
7875
$this->exprPrinter,
7976
$this->typeSpecifier,
@@ -109,7 +106,6 @@ public function toFiberFactory(): InternalScopeFactory
109106
$this->container,
110107
$this->reflectionProvider,
111108
$this->initializerExprTypeResolver,
112-
$this->dynamicReturnTypeExtensionRegistryProvider,
113109
$this->expressionTypeResolverExtensionRegistryProvider,
114110
$this->exprPrinter,
115111
$this->typeSpecifier,
@@ -130,7 +126,6 @@ public function toMutatingFactory(): InternalScopeFactory
130126
$this->container,
131127
$this->reflectionProvider,
132128
$this->initializerExprTypeResolver,
133-
$this->dynamicReturnTypeExtensionRegistryProvider,
134129
$this->expressionTypeResolverExtensionRegistryProvider,
135130
$this->exprPrinter,
136131
$this->typeSpecifier,

src/Analyser/DirectInternalScopeFactoryFactory.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
use PhpParser\Node;
66
use PHPStan\DependencyInjection\Container;
7-
use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider;
87
use PHPStan\DependencyInjection\Type\ExpressionTypeResolverExtensionRegistryProvider;
98
use PHPStan\Node\Printer\ExprPrinter;
109
use PHPStan\Parser\Parser;
@@ -24,7 +23,6 @@ public function __construct(
2423
private Container $container,
2524
private ReflectionProvider $reflectionProvider,
2625
private InitializerExprTypeResolver $initializerExprTypeResolver,
27-
private DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider,
2826
private ExpressionTypeResolverExtensionRegistryProvider $expressionTypeResolverExtensionRegistryProvider,
2927
private ExprPrinter $exprPrinter,
3028
private TypeSpecifier $typeSpecifier,
@@ -47,7 +45,6 @@ public function create(?callable $nodeCallback): DirectInternalScopeFactory
4745
$this->container,
4846
$this->reflectionProvider,
4947
$this->initializerExprTypeResolver,
50-
$this->dynamicReturnTypeExtensionRegistryProvider,
5148
$this->expressionTypeResolverExtensionRegistryProvider,
5249
$this->exprPrinter,
5350
$this->typeSpecifier,
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\ExprHandler\Helper;
4+
5+
use PhpParser\Node\Expr;
6+
use PhpParser\Node\Expr\MethodCall;
7+
use PHPStan\Analyser\ArgumentsNormalizer;
8+
use PHPStan\Analyser\MutatingScope;
9+
use PHPStan\DependencyInjection\AutowiredService;
10+
use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider;
11+
use PHPStan\Reflection\ParametersAcceptorSelector;
12+
use PHPStan\Type\Type;
13+
use PHPStan\Type\TypeCombinator;
14+
use function count;
15+
16+
#[AutowiredService]
17+
final class MethodCallReturnTypeHelper
18+
{
19+
20+
public function __construct(
21+
private DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider,
22+
)
23+
{
24+
}
25+
26+
public function methodCallReturnType(
27+
MutatingScope $scope,
28+
Type $typeWithMethod,
29+
string $methodName,
30+
MethodCall|Expr\StaticCall $methodCall,
31+
): ?Type
32+
{
33+
$typeWithMethod = $scope->filterTypeWithMethod($typeWithMethod, $methodName);
34+
if ($typeWithMethod === null) {
35+
return null;
36+
}
37+
38+
$methodReflection = $typeWithMethod->getMethod($methodName, $scope);
39+
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
40+
$scope,
41+
$methodCall->getArgs(),
42+
$methodReflection->getVariants(),
43+
$methodReflection->getNamedArgumentsVariants(),
44+
);
45+
if ($methodCall instanceof MethodCall) {
46+
$normalizedMethodCall = ArgumentsNormalizer::reorderMethodArguments($parametersAcceptor, $methodCall);
47+
} else {
48+
$normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $methodCall);
49+
}
50+
if ($normalizedMethodCall === null) {
51+
return VoidToNullTypeTransfomer::transform($parametersAcceptor->getReturnType(), $methodCall);
52+
}
53+
54+
$resolvedTypes = [];
55+
foreach ($typeWithMethod->getObjectClassNames() as $className) {
56+
if ($normalizedMethodCall instanceof MethodCall) {
57+
foreach ($this->dynamicReturnTypeExtensionRegistryProvider->getRegistry()->getDynamicMethodReturnTypeExtensionsForClass($className) as $dynamicMethodReturnTypeExtension) {
58+
if (!$dynamicMethodReturnTypeExtension->isMethodSupported($methodReflection)) {
59+
continue;
60+
}
61+
62+
$resolvedType = $dynamicMethodReturnTypeExtension->getTypeFromMethodCall($methodReflection, $normalizedMethodCall, $scope);
63+
if ($resolvedType === null) {
64+
continue;
65+
}
66+
67+
$resolvedTypes[] = $resolvedType;
68+
}
69+
} else {
70+
foreach ($this->dynamicReturnTypeExtensionRegistryProvider->getRegistry()->getDynamicStaticMethodReturnTypeExtensionsForClass($className) as $dynamicStaticMethodReturnTypeExtension) {
71+
if (!$dynamicStaticMethodReturnTypeExtension->isStaticMethodSupported($methodReflection)) {
72+
continue;
73+
}
74+
75+
$resolvedType = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall(
76+
$methodReflection,
77+
$normalizedMethodCall,
78+
$scope,
79+
);
80+
if ($resolvedType === null) {
81+
continue;
82+
}
83+
84+
$resolvedTypes[] = $resolvedType;
85+
}
86+
}
87+
}
88+
89+
if (count($resolvedTypes) > 0) {
90+
return VoidToNullTypeTransfomer::transform(TypeCombinator::union(...$resolvedTypes), $methodCall);
91+
}
92+
93+
return VoidToNullTypeTransfomer::transform($parametersAcceptor->getReturnType(), $methodCall);
94+
}
95+
96+
}

src/Analyser/ExprHandler/MethodCallHandler.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33
namespace PHPStan\Analyser\ExprHandler;
44

55
use PhpParser\Node\Expr;
6+
use PhpParser\Node\Expr\BinaryOp\Identical;
67
use PhpParser\Node\Expr\MethodCall;
78
use PhpParser\Node\Identifier;
9+
use PhpParser\Node\Scalar\String_;
810
use PhpParser\Node\Stmt;
911
use PHPStan\Analyser\ArgumentsNormalizer;
1012
use PHPStan\Analyser\ExpressionContext;
1113
use PHPStan\Analyser\ExpressionResult;
1214
use PHPStan\Analyser\ExpressionResultStorage;
1315
use PHPStan\Analyser\ExprHandler;
16+
use PHPStan\Analyser\ExprHandler\Helper\MethodCallReturnTypeHelper;
17+
use PHPStan\Analyser\ExprHandler\Helper\NullsafeShortCircuitingHelper;
1418
use PHPStan\Analyser\ImpurePoint;
1519
use PHPStan\Analyser\InternalThrowPoint;
1620
use PHPStan\Analyser\MutatingScope;
@@ -25,17 +29,22 @@
2529
use PHPStan\Reflection\MethodReflection;
2630
use PHPStan\Reflection\ParametersAcceptor;
2731
use PHPStan\Reflection\ParametersAcceptorSelector;
32+
use PHPStan\Type\ErrorType;
2833
use PHPStan\Type\Generic\TemplateTypeHelper;
2934
use PHPStan\Type\Generic\TemplateTypeVariance;
3035
use PHPStan\Type\Generic\TemplateTypeVarianceMap;
3136
use PHPStan\Type\MixedType;
3237
use PHPStan\Type\NeverType;
3338
use PHPStan\Type\ObjectType;
39+
use PHPStan\Type\Type;
40+
use PHPStan\Type\TypeCombinator;
3441
use PHPStan\Type\TypeUtils;
3542
use ReflectionFunction;
3643
use ReflectionMethod;
3744
use Throwable;
45+
use function array_map;
3846
use function array_merge;
47+
use function count;
3948
use function in_array;
4049
use function sprintf;
4150
use function strtolower;
@@ -49,6 +58,7 @@ final class MethodCallHandler implements ExprHandler
4958

5059
public function __construct(
5160
private DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider,
61+
private MethodCallReturnTypeHelper $methodCallReturnTypeHelper,
5262
#[AutowiredParameter(ref: '%exceptions.implicitThrows%')]
5363
private bool $implicitThrows,
5464
#[AutowiredParameter]
@@ -277,4 +287,48 @@ private function getMethodThrowPoint(MethodReflection $methodReflection, Paramet
277287
return null;
278288
}
279289

290+
/**
291+
* @param MethodCall $expr
292+
*/
293+
public function resolveType(MutatingScope $scope, Expr $expr): Type
294+
{
295+
if ($expr->name instanceof Identifier) {
296+
if ($scope->nativeTypesPromoted) {
297+
$methodReflection = $scope->getMethodReflection(
298+
$scope->getNativeType($expr->var),
299+
$expr->name->name,
300+
);
301+
if ($methodReflection === null) {
302+
$returnType = new ErrorType();
303+
} else {
304+
$returnType = ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants())->getNativeReturnType();
305+
}
306+
307+
return NullsafeShortCircuitingHelper::getType($scope, $expr->var, $returnType);
308+
}
309+
310+
$returnType = $this->methodCallReturnTypeHelper->methodCallReturnType(
311+
$scope,
312+
$scope->getType($expr->var),
313+
$expr->name->name,
314+
$expr,
315+
);
316+
if ($returnType === null) {
317+
$returnType = new ErrorType();
318+
}
319+
return NullsafeShortCircuitingHelper::getType($scope, $expr->var, $returnType);
320+
}
321+
322+
$nameType = $scope->getType($expr->name);
323+
if (count($nameType->getConstantStrings()) > 0) {
324+
return TypeCombinator::union(
325+
...array_map(static fn ($constantString) => $constantString->getValue() === '' ? new ErrorType() : $scope
326+
->filterByTruthyValue(new Identical($expr->name, new String_($constantString->getValue())))
327+
->getType(new MethodCall($expr->var, new Identifier($constantString->getValue()), $expr->args)), $nameType->getConstantStrings()),
328+
);
329+
}
330+
331+
return new MixedType();
332+
}
333+
280334
}

src/Analyser/ExprHandler/StaticCallHandler.php

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,21 @@
33
namespace PHPStan\Analyser\ExprHandler;
44

55
use PhpParser\Node\Expr;
6+
use PhpParser\Node\Expr\BinaryOp\Identical;
67
use PhpParser\Node\Expr\New_;
78
use PhpParser\Node\Expr\StaticCall;
89
use PhpParser\Node\Expr\Variable;
10+
use PhpParser\Node\Identifier;
911
use PhpParser\Node\Name;
12+
use PhpParser\Node\Scalar\String_;
1013
use PhpParser\Node\Stmt;
1114
use PHPStan\Analyser\ArgumentsNormalizer;
1215
use PHPStan\Analyser\ExpressionContext;
1316
use PHPStan\Analyser\ExpressionResult;
1417
use PHPStan\Analyser\ExpressionResultStorage;
1518
use PHPStan\Analyser\ExprHandler;
19+
use PHPStan\Analyser\ExprHandler\Helper\MethodCallReturnTypeHelper;
20+
use PHPStan\Analyser\ExprHandler\Helper\NullsafeShortCircuitingHelper;
1621
use PHPStan\Analyser\ImpurePoint;
1722
use PHPStan\Analyser\InternalThrowPoint;
1823
use PHPStan\Analyser\MutatingScope;
@@ -26,14 +31,20 @@
2631
use PHPStan\Reflection\MethodReflection;
2732
use PHPStan\Reflection\ParametersAcceptor;
2833
use PHPStan\Reflection\ParametersAcceptorSelector;
34+
use PHPStan\Type\ErrorType;
2935
use PHPStan\Type\MixedType;
3036
use PHPStan\Type\NeverType;
3137
use PHPStan\Type\ObjectType;
38+
use PHPStan\Type\StaticType;
39+
use PHPStan\Type\Type;
3240
use PHPStan\Type\TypeCombinator;
41+
use PHPStan\Type\TypeWithClassName;
3342
use ReflectionProperty;
3443
use Throwable;
44+
use function array_map;
3545
use function array_merge;
3646
use function count;
47+
use function in_array;
3748
use function sprintf;
3849
use function strtolower;
3950

@@ -46,6 +57,7 @@ final class StaticCallHandler implements ExprHandler
4657

4758
public function __construct(
4859
private DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider,
60+
private MethodCallReturnTypeHelper $methodCallReturnTypeHelper,
4961
#[AutowiredParameter(ref: '%exceptions.implicitThrows%')]
5062
private bool $implicitThrows,
5163
#[AutowiredParameter]
@@ -287,4 +299,88 @@ private function getStaticMethodThrowPoint(MethodReflection $methodReflection, P
287299
return null;
288300
}
289301

302+
/**
303+
* @param StaticCall $expr
304+
*/
305+
public function resolveType(MutatingScope $scope, Expr $expr): Type
306+
{
307+
if ($expr->name instanceof Identifier) {
308+
if ($scope->nativeTypesPromoted) {
309+
if ($expr->class instanceof Name) {
310+
$staticMethodCalledOnType = $this->resolveTypeByNameWithLateStaticBinding($scope, $expr->class, $expr->name);
311+
} else {
312+
$staticMethodCalledOnType = $scope->getNativeType($expr->class);
313+
}
314+
$methodReflection = $scope->getMethodReflection(
315+
$staticMethodCalledOnType,
316+
$expr->name->name,
317+
);
318+
if ($methodReflection === null) {
319+
$callType = new ErrorType();
320+
} else {
321+
$callType = ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants())->getNativeReturnType();
322+
}
323+
324+
if ($expr->class instanceof Expr) {
325+
return NullsafeShortCircuitingHelper::getType($scope, $expr->class, $callType);
326+
}
327+
328+
return $callType;
329+
}
330+
331+
if ($expr->class instanceof Name) {
332+
$staticMethodCalledOnType = $this->resolveTypeByNameWithLateStaticBinding($scope, $expr->class, $expr->name);
333+
} else {
334+
$staticMethodCalledOnType = TypeCombinator::removeNull($scope->getType($expr->class))->getObjectTypeOrClassStringObjectType();
335+
}
336+
337+
$callType = $this->methodCallReturnTypeHelper->methodCallReturnType(
338+
$scope,
339+
$staticMethodCalledOnType,
340+
$expr->name->toString(),
341+
$expr,
342+
);
343+
if ($callType === null) {
344+
$callType = new ErrorType();
345+
}
346+
347+
if ($expr->class instanceof Expr) {
348+
return NullsafeShortCircuitingHelper::getType($scope, $expr->class, $callType);
349+
}
350+
351+
return $callType;
352+
}
353+
354+
$nameType = $scope->getType($expr->name);
355+
if (count($nameType->getConstantStrings()) > 0) {
356+
return TypeCombinator::union(
357+
...array_map(static fn ($constantString) => $constantString->getValue() === '' ? new ErrorType() : $scope
358+
->filterByTruthyValue(new Identical($expr->name, new String_($constantString->getValue())))
359+
->getType(new Expr\StaticCall($expr->class, new Identifier($constantString->getValue()), $expr->args)), $nameType->getConstantStrings()),
360+
);
361+
}
362+
363+
return new MixedType();
364+
}
365+
366+
private function resolveTypeByNameWithLateStaticBinding(MutatingScope $scope, Name $class, Identifier $name): TypeWithClassName
367+
{
368+
$classType = $scope->resolveTypeByName($class);
369+
370+
if (
371+
$classType instanceof StaticType
372+
&& !in_array($class->toLowerString(), ['self', 'static', 'parent'], true)
373+
) {
374+
$methodReflectionCandidate = $scope->getMethodReflection(
375+
$classType,
376+
$name->name,
377+
);
378+
if ($methodReflectionCandidate !== null && $methodReflectionCandidate->isStatic()) {
379+
$classType = $classType->getStaticObjectType();
380+
}
381+
}
382+
383+
return $classType;
384+
}
385+
290386
}

0 commit comments

Comments
 (0)