|
27 | 27 | use PHPStan\Analyser\ExprHandler\Helper\DynamicReturnTypeStoragePrimer; |
28 | 28 | use PHPStan\Analyser\ExprHandler\Helper\EarlyTerminatingCallHelper; |
29 | 29 | use PHPStan\Analyser\ExprHandler\Helper\FuncCallScopeEffectsHelper; |
| 30 | +use PHPStan\Analyser\Generics\ClosureSignatureInference; |
30 | 31 | use PHPStan\Analyser\Generics\TemplateArgumentFrame; |
31 | 32 | use PHPStan\Analyser\ImpurePoint; |
32 | 33 | use PHPStan\Analyser\InternalThrowPoint; |
|
52 | 53 | use PHPStan\Reflection\Callables\SimpleThrowPoint; |
53 | 54 | use PHPStan\Reflection\ExtendedParametersAcceptor; |
54 | 55 | use PHPStan\Reflection\FunctionReflection; |
| 56 | +use PHPStan\Reflection\Native\NativeParameterReflection; |
55 | 57 | use PHPStan\Reflection\ParametersAcceptor; |
56 | 58 | use PHPStan\Reflection\ParametersAcceptorSelector; |
| 59 | +use PHPStan\Reflection\PassedByReference; |
57 | 60 | use PHPStan\Reflection\ReflectionProvider; |
58 | 61 | use PHPStan\Rules\Comparison\ImpossibleCheckTypeHelper; |
59 | 62 | use PHPStan\ShouldNotHappenException; |
60 | 63 | use PHPStan\Turbo\ShadowedByTurboExtension; |
61 | 64 | use PHPStan\Type\Accessory\HasPropertyType; |
62 | 65 | use PHPStan\Type\Accessory\NonEmptyArrayType; |
| 66 | +use PHPStan\Type\CallableType; |
| 67 | +use PHPStan\Type\ClosureType; |
63 | 68 | use PHPStan\Type\Constant\ConstantBooleanType; |
64 | 69 | use PHPStan\Type\DynamicFunctionThrowTypeExtension; |
65 | 70 | use PHPStan\Type\DynamicReturnTypeExtensionRegistry; |
66 | 71 | use PHPStan\Type\ErrorType; |
67 | 72 | use PHPStan\Type\Generic\TemplateTypeHelper; |
68 | 73 | use PHPStan\Type\Generic\TemplateTypeVariance; |
69 | 74 | use PHPStan\Type\Generic\TemplateTypeVarianceMap; |
| 75 | +use PHPStan\Type\MixedType; |
70 | 76 | use PHPStan\Type\NeverType; |
71 | 77 | use PHPStan\Type\ObjectType; |
72 | 78 | use PHPStan\Type\Type; |
@@ -112,6 +118,7 @@ public function __construct( |
112 | 118 | private ArgumentsHandler $argumentsHandler, |
113 | 119 | private ClosureProcessor $closureProcessor, |
114 | 120 | private AssignHandler $assignHandler, |
| 121 | + private ClosureSignatureInference $closureSignatureInference, |
115 | 122 | ) |
116 | 123 | { |
117 | 124 | } |
@@ -341,6 +348,17 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex |
341 | 348 | } |
342 | 349 | } |
343 | 350 | $resolvedParametersAcceptor = $argsResult->getResolvedParametersAcceptor(); |
| 351 | + if ( |
| 352 | + $resolvedParametersAcceptor !== null |
| 353 | + && $nameResult !== null |
| 354 | + && $expr->name instanceof Expr\Variable |
| 355 | + && is_string($expr->name->name) |
| 356 | + ) { |
| 357 | + $invokedClosureType = $this->resolveInvokedClosureType($nodeScopeResolver, $normalizedExpr, $expr->name->name, $nameResult->getType(), $argsResult->getScope(), $storage); |
| 358 | + if ($invokedClosureType !== null) { |
| 359 | + $resolvedParametersAcceptor = $invokedClosureType->getCallableParametersAcceptors($scope)[0]; |
| 360 | + } |
| 361 | + } |
344 | 362 | // arguments walked ahead of the callee: the scope already carries them |
345 | 363 | // and the callee walk's own effects (a closure's by-ref uses) on top |
346 | 364 | if ($argumentsWalkedAhead === null) { |
@@ -764,6 +782,63 @@ private function resolveReturnType(NodeScopeResolver $nodeScopeResolver, Mutatin |
764 | 782 | return TemplateArgumentFrame::returnTypeOfCall($parametersAcceptor, $reflectionScope, $expr); |
765 | 783 | } |
766 | 784 |
|
| 785 | + /** |
| 786 | + * A closure whose signature is inferred from its invocations (see |
| 787 | + * ClosureSignatureInference) joins the arguments of all of them; invoked |
| 788 | + * with narrower ones, it returns what its body returns for these. The |
| 789 | + * variable holds the closure it was assigned when the type it holds is the |
| 790 | + * type the closure was created with. |
| 791 | + */ |
| 792 | + private function resolveInvokedClosureType(NodeScopeResolver $nodeScopeResolver, FuncCall $call, string $name, Type $calleeType, MutatingScope $scope, ExpressionResultStorage $storage): ?ClosureType |
| 793 | + { |
| 794 | + if (!$calleeType instanceof ClosureType || !$this->closureSignatureInference->infersInvocationReturnType($scope, $calleeType)) { |
| 795 | + return null; |
| 796 | + } |
| 797 | + $parameters = $calleeType->getParameters(); |
| 798 | + $args = $call->getArgs(); |
| 799 | + if ($args === [] || count($args) > count($parameters)) { |
| 800 | + return null; |
| 801 | + } |
| 802 | + |
| 803 | + $narrower = false; |
| 804 | + $callableParameters = []; |
| 805 | + foreach ($parameters as $i => $parameter) { |
| 806 | + if ($parameter->isVariadic() || !$parameter->passedByReference()->no()) { |
| 807 | + return null; |
| 808 | + } |
| 809 | + $type = $parameter->getType(); |
| 810 | + if (isset($args[$i])) { |
| 811 | + if ($args[$i]->name !== null || $args[$i]->unpack) { |
| 812 | + return null; |
| 813 | + } |
| 814 | + $argumentType = $nodeScopeResolver->readTypeOfMaybeStored($args[$i]->value, $scope); |
| 815 | + // an argument the signature rejects is reported against it |
| 816 | + if (!$type->isSuperTypeOf($argumentType)->yes()) { |
| 817 | + return null; |
| 818 | + } |
| 819 | + if (!$argumentType->isSuperTypeOf($type)->yes()) { |
| 820 | + $narrower = true; |
| 821 | + $type = $argumentType; |
| 822 | + } |
| 823 | + } |
| 824 | + $callableParameters[] = new NativeParameterReflection($parameter->getName(), $parameter->isOptional(), $type, PassedByReference::createNo(), false, null); |
| 825 | + } |
| 826 | + if (!$narrower) { |
| 827 | + return null; |
| 828 | + } |
| 829 | + |
| 830 | + foreach ($this->closureSignatureInference->findAssignedClosures($scope, $name) as $closure) { |
| 831 | + $creationResult = $storage->findExpressionResult($closure); |
| 832 | + if ($creationResult === null || !$creationResult->getType()->equals($calleeType)) { |
| 833 | + continue; |
| 834 | + } |
| 835 | + |
| 836 | + return $this->closureTypeResolver->getClosureType($creationResult->getBeforeScope(), $closure, false, $storage, new CallableType($callableParameters, new MixedType(), false)); |
| 837 | + } |
| 838 | + |
| 839 | + return null; |
| 840 | + } |
| 841 | + |
767 | 842 | /** |
768 | 843 | * Ported inside-out from the old TypeResolvingExprHandler::specifyTypes(): the |
769 | 844 | * FunctionTypeSpecifyingExtensions, conditional-return-type and @phpstan-assert |
|
0 commit comments