|
10 | 10 | use PHPStan\Analyser\ExprHandler\ArrowFunctionHandler; |
11 | 11 | use PHPStan\Analyser\ExprHandler\Helper\ClosureParameterResolver; |
12 | 12 | use PHPStan\Analyser\ExprHandler\Helper\ClosureTypeResolver; |
| 13 | +use PHPStan\Analyser\ExprHandler\Helper\ContextualClosureParameterResolver; |
| 14 | +use PHPStan\Analyser\Generics\ClosureSignatureInference; |
13 | 15 | use PHPStan\DependencyInjection\AutowiredService; |
14 | 16 | use PHPStan\DependencyInjection\Container; |
15 | 17 | use PHPStan\Node\ClosureReturnStatementsNode; |
@@ -51,6 +53,8 @@ public function __construct( |
51 | 53 | private ExpressionResultFactory $expressionResultFactory, |
52 | 54 | private ClosureParameterResolver $closureParameterResolver, |
53 | 55 | private ClosureTypeResolver $closureTypeResolver, |
| 56 | + private ContextualClosureParameterResolver $contextualClosureParameterResolver, |
| 57 | + private ClosureSignatureInference $closureSignatureInference, |
54 | 58 | ) |
55 | 59 | { |
56 | 60 | } |
@@ -107,6 +111,12 @@ private function processClosureNodeInternal( |
107 | 111 | $parameterTypes = $this->closureParameterResolver->resolve($scope, $expr, $storage, $closureCallArgs, $passedToType, $nativePassedToType); |
108 | 112 | $callableParameters = $parameterTypes->parameters; |
109 | 113 | $nativeCallableParameters = $parameterTypes->nativeParameters; |
| 114 | + [$expectedReturnType, $nativeExpectedReturnType] = $this->contextualClosureParameterResolver->resolveExpectedReturnTypes($scope, $expr, $passedToType, $nativePassedToType); |
| 115 | + // the closure's own body may invoke it (use (&$self)): its sites must |
| 116 | + // exist before the body is observed |
| 117 | + if ($this->closureSignatureInference->isObserving($scope)) { |
| 118 | + $scope = $scope->addTemplateArgumentConstraints($this->closureSignatureInference->collectSites($scope, $this->closureTypeResolver->getClosureType($scope, $expr, true, $storage))); |
| 119 | + } |
110 | 120 |
|
111 | 121 | $useScope = $scope; |
112 | 122 | foreach ($expr->uses as $use) { |
@@ -219,7 +229,7 @@ private function processClosureNodeInternal( |
219 | 229 | if (count($byRefUses) === 0) { |
220 | 230 | $nodeScopeResolver->pushNodeGatherer($closureStmtsGatherer); |
221 | 231 | try { |
222 | | - $statementResult = $nodeScopeResolver->processStmtNodesInternal($expr, $expr->stmts, $closureScope, $storage, $nodeCallback, StatementContext::createTopLevel($context->shouldResolveTemplateArguments())); |
| 232 | + $statementResult = $nodeScopeResolver->processStmtNodesInternal($expr, $expr->stmts, $closureScope, $storage, $nodeCallback, StatementContext::createTopLevel($context->shouldResolveTemplateArguments())->withExpectedReturnType($expectedReturnType, $nativeExpectedReturnType)); |
223 | 233 | } finally { |
224 | 234 | $nodeScopeResolver->popNodeGatherer(); |
225 | 235 | } |
@@ -266,7 +276,7 @@ private function processClosureNodeInternal( |
266 | 276 | // loops walk single-pass here and only the final walk below (top-level) |
267 | 277 | // runs their full convergence - otherwise every closure-convergence |
268 | 278 | // pass would re-converge every inner loop from scratch |
269 | | - $intermediaryClosureScopeResult = $nodeScopeResolver->processStmtNodesInternal($expr, $expr->stmts, $closureScope, $storage, $bodyRecording, StatementContext::createDeep(resolveTemplateArguments: false)); |
| 279 | + $intermediaryClosureScopeResult = $nodeScopeResolver->processStmtNodesInternal($expr, $expr->stmts, $closureScope, $storage, $bodyRecording, StatementContext::createDeep(resolveTemplateArguments: false)->withExpectedReturnType($expectedReturnType, $nativeExpectedReturnType)); |
270 | 280 | // the candidate to replace the final walk when this pass's entry |
271 | 281 | // turns out to be the fixpoint |
272 | 282 | if ($bodyRecording instanceof RecordingNodeCallback) { |
@@ -320,7 +330,7 @@ private function processClosureNodeInternal( |
320 | 330 | $nodeScopeResolver->replayRecording($replayBodyRecording, $nodeCallback, $originalStorage, $closureScope); |
321 | 331 | $statementResult = $replayPassResult; |
322 | 332 | } else { |
323 | | - $statementResult = $nodeScopeResolver->processStmtNodesInternal($expr, $expr->stmts, $closureScope, $storage, $nodeCallback, StatementContext::createTopLevel($context->shouldResolveTemplateArguments())); |
| 333 | + $statementResult = $nodeScopeResolver->processStmtNodesInternal($expr, $expr->stmts, $closureScope, $storage, $nodeCallback, StatementContext::createTopLevel($context->shouldResolveTemplateArguments())->withExpectedReturnType($expectedReturnType, $nativeExpectedReturnType)); |
324 | 334 | } |
325 | 335 | } finally { |
326 | 336 | $nodeScopeResolver->popNodeGatherer(); |
@@ -445,6 +455,7 @@ public function processArrowFunctionNode( |
445 | 455 | $parameterTypes = $this->closureParameterResolver->resolve($scope, $expr, $storage, $arrowFunctionCallArgs, $passedToType, $nativePassedToType); |
446 | 456 | $callableParameters = $parameterTypes->parameters; |
447 | 457 | $nativeCallableParameters = $parameterTypes->nativeParameters; |
| 458 | + [$expectedReturnType, $nativeExpectedReturnType] = $this->contextualClosureParameterResolver->resolveExpectedReturnTypes($scope, $expr, $passedToType, $nativePassedToType); |
448 | 459 | $arrowFunctionScope = $scope->enterArrowFunction($expr, $callableParameters, $nativeCallableParameters); |
449 | 460 | if ($arrowFunctionScope->getAnonymousFunctionReflection() === null) { |
450 | 461 | throw new ShouldNotHappenException(); |
@@ -482,7 +493,7 @@ public function processArrowFunctionNode( |
482 | 493 |
|
483 | 494 | $nodeScopeResolver->pushNodeGatherer($arrowFunctionStmtsGatherer); |
484 | 495 | try { |
485 | | - $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $arrowFunctionScope, $storage, $nodeCallback, ExpressionContext::createTopLevel($context->shouldResolveTemplateArguments())); |
| 496 | + $exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $arrowFunctionScope, $storage, $nodeCallback, ExpressionContext::createTopLevel($context->shouldResolveTemplateArguments())->enterPassedToType($expectedReturnType, $nativeExpectedReturnType)); |
486 | 497 | } finally { |
487 | 498 | $nodeScopeResolver->popNodeGatherer(); |
488 | 499 | } |
|
0 commit comments