diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 2724ba1fca..90b3fdd465 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -46,7 +46,6 @@ use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Node\PropertyAssignNode; use PHPStan\Parser\ArrayMapArgVisitor; -use PHPStan\Parser\ImmediatelyInvokedClosureVisitor; use PHPStan\Parser\NewAssignedToPropertyVisitor; use PHPStan\Parser\Parser; use PHPStan\Php\PhpVersion; @@ -4795,7 +4794,6 @@ private function processFinallyScopeVariableTypeHolders( * @param Expr\ClosureUse[] $byRefUses */ public function processClosureScope( - Expr\Closure $expr, self $closureScope, ?self $prevScope, array $byRefUses, @@ -4828,9 +4826,7 @@ public function processClosureScope( $prevVariableType = $prevScope->getVariableType($variableName); if (!$variableType->equals($prevVariableType)) { $variableType = TypeCombinator::union($variableType, $prevVariableType); - if ($expr->getAttribute(ImmediatelyInvokedClosureVisitor::ATTRIBUTE_NAME) !== true) { - $variableType = self::generalizeType($variableType, $prevVariableType, 0); - } + $variableType = self::generalizeType($variableType, $prevVariableType, 0); } } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index fd6ac13fc3..fc7b1e9df7 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -119,6 +119,7 @@ use PHPStan\Node\VarTagChangedExpressionTypeNode; use PHPStan\Parser\ArrowFunctionArgVisitor; use PHPStan\Parser\ClosureArgVisitor; +use PHPStan\Parser\ImmediatelyInvokedClosureVisitor; use PHPStan\Parser\Parser; use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\PhpDocInheritanceResolver; @@ -4232,7 +4233,7 @@ private function processClosureNode( } $closureScope = $scope->enterAnonymousFunction($expr, $callableParameters); - $closureScope = $closureScope->processClosureScope($expr, $scope, null, $byRefUses); + $closureScope = $closureScope->processClosureScope($scope, null, $byRefUses); $closureType = $closureScope->getAnonymousFunctionReflection(); if (!$closureType instanceof ClosureType) { throw new ShouldNotHappenException(); @@ -4277,6 +4278,7 @@ private function processClosureNode( $gatheredReturnStatements[] = new ReturnStatement($scope, $node); }; + if (count($byRefUses) === 0) { $statementResult = $this->processStmtNodes($expr, $expr->stmts, $closureScope, $closureStmtsCallback, StatementContext::createTopLevel()); $nodeCallback(new ClosureReturnStatementsNode( @@ -4292,6 +4294,7 @@ private function processClosureNode( } $count = 0; + $closureResultScope = null; do { $prevScope = $closureScope; @@ -4301,8 +4304,15 @@ private function processClosureNode( foreach ($intermediaryClosureScopeResult->getExitPoints() as $exitPoint) { $intermediaryClosureScope = $intermediaryClosureScope->mergeWith($exitPoint->getScope()); } + + if ($expr->getAttribute(ImmediatelyInvokedClosureVisitor::ATTRIBUTE_NAME) === true) { + $closureResultScope = $intermediaryClosureScope; + break; + } + $closureScope = $scope->enterAnonymousFunction($expr, $callableParameters); - $closureScope = $closureScope->processClosureScope($expr, $intermediaryClosureScope, $prevScope, $byRefUses); + $closureScope = $closureScope->processClosureScope($intermediaryClosureScope, $prevScope, $byRefUses); + if ($closureScope->equals($prevScope)) { break; } @@ -4312,6 +4322,10 @@ private function processClosureNode( $count++; } while ($count < self::LOOP_SCOPE_ITERATIONS); + if ($closureResultScope === null) { + $closureResultScope = $closureScope; + } + $statementResult = $this->processStmtNodes($expr, $expr->stmts, $closureScope, $closureStmtsCallback, StatementContext::createTopLevel()); $nodeCallback(new ClosureReturnStatementsNode( $expr, @@ -4322,7 +4336,7 @@ private function processClosureNode( array_merge($statementResult->getImpurePoints(), $closureImpurePoints), ), $closureScope); - return new ProcessClosureResult($scope->processClosureScope($expr, $closureScope, null, $byRefUses), $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions); + return new ProcessClosureResult($scope->processClosureScope($closureResultScope, null, $byRefUses), $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions); } /** diff --git a/tests/PHPStan/Analyser/nsrt/bug-11561.php b/tests/PHPStan/Analyser/nsrt/bug-11561.php index 1a01e5f97a..f6894d4724 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11561.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11561.php @@ -12,13 +12,13 @@ function main(mixed $c): void{ assertType('array{date: DateTime, id: 1}', $c); $x = (function() use (&$c) { - assertType("array{date: DateTime, id: 1, name?: 'ruud'}", $c); + assertType("array{date: DateTime, id: 1}", $c); $c['name'] = 'ruud'; assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c); return 'x'; })(); - assertType("array{date: DateTime, id: 1, name?: 'ruud'}", $c); + assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c); } @@ -30,11 +30,51 @@ function main2(mixed $c): void{ assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c); $x = (function() use (&$c) { - assertType("array{date: DateTime, id: 1, name: 'ruud'|'staabm'}", $c); + assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c); $c['name'] = 'ruud'; assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c); return 'x'; })(); + assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c); +} + +/** @param array{date: DateTime} $c */ +function main3(mixed $c): void{ + assertType('array{date: DateTime}', $c); + $c['id']=1; + $c['name'] = 'staabm'; + assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c); + + $x = (function() use (&$c) { + assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c); + if (rand(0,1)) { + $c['name'] = 'ruud'; + } + assertType("array{date: DateTime, id: 1, name: 'ruud'|'staabm'}", $c); + return 'x'; + })(); + + assertType("array{date: DateTime, id: 1, name: 'ruud'|'staabm'}", $c); +} + +/** @param array{date: DateTime} $c */ +function main4(mixed $c): void{ + assertType('array{date: DateTime}', $c); + $c['id']=1; + $c['name'] = 'staabm'; + assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c); + + $x = (function() use (&$c) { + assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c); + if (rand(0,1)) { + $c['name'] = 'ruud'; + assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c); + return 'y'; + } + assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c); + return 'x'; + })(); + assertType("array{date: DateTime, id: 1, name: 'ruud'|'staabm'}", $c); }