diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index a745d920f1..1165c81b72 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1029,19 +1029,24 @@ private function processStmtNode( foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { $finalScope = $continueExitPoint->getScope()->mergeWith($finalScope); } + + $loopScope = $finalScope; foreach ($stmt->loop as $loopExpr) { - $finalScope = $this->processExprNode($loopExpr, $finalScope, $nodeCallback, ExpressionContext::createTopLevel())->getScope(); + $loopScope = $this->processExprNode($loopExpr, $loopScope, $nodeCallback, ExpressionContext::createTopLevel())->getScope(); + } + $finalScope = $loopScope->generalizeWith($finalScope); + foreach ($stmt->cond as $condExpr) { + $finalScope = $finalScope->filterByFalseyValue($condExpr); } + foreach ($finalScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); } - if ($this->polluteScopeWithLoopInitialAssignments) { - $scope = $initScope; + if (!$this->polluteScopeWithLoopInitialAssignments) { + $finalScope = $finalScope->mergeWith($scope); } - $finalScope = $finalScope->mergeWith($scope); - return new StatementResult( $finalScope, $finalScopeResult->hasYield() || $hasYield, diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 199537c33c..d28ee02b4c 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -421,7 +421,7 @@ public function dataAssignInIf(): array $testScope, 'previousI', TrinaryLogic::createYes(), - '0|1', + 'int<1, max>', ], [ $testScope, @@ -582,14 +582,14 @@ public function dataAssignInIf(): array [ $testScope, 'nonexistentVariableOutsideFor', - TrinaryLogic::createMaybe(), + TrinaryLogic::createYes(), '1', ], [ $testScope, 'integerOrNullFromFor', TrinaryLogic::createYes(), - '1|null', + '1', ], [ $testScope, @@ -816,17 +816,17 @@ public function dataConstantTypes(): array [ $testScope, 'incrementInForLoop', - 'int<1, max>', + 'int<2, max>', ], [ $testScope, 'valueOverwrittenInForLoop', - '1|2', + '2', ], [ $testScope, 'arrayOverwrittenInForLoop', - 'array{a: int<1, max>, b: \'bar\'|\'foo\'}', + 'array{a: int<2, max>, b: \'bar\'}', ], [ $testScope, @@ -836,12 +836,12 @@ public function dataConstantTypes(): array [ $testScope, 'intProperty', - 'int<1, max>', + 'int<2, max>', ], [ $testScope, 'staticIntProperty', - 'int<1, max>', + 'int<2, max>', ], [ $testScope, @@ -6922,11 +6922,6 @@ public function dataLoopVariables(): array '$foo', "'end'", ], - [ - 'LoopVariables\Bar|LoopVariables\Foo|LoopVariables\Lorem|null', - '$foo', - "'afterLoop'", - ], [ 'int<1, max>|null', '$nullableVal', @@ -6942,11 +6937,6 @@ public function dataLoopVariables(): array '$nullableVal', "'nullableValElse'", ], - [ - '1|int<10, max>|null', - '$nullableVal', - "'afterLoop'", - ], [ 'LoopVariables\Foo|false', '$falseOrObject', @@ -6957,11 +6947,6 @@ public function dataLoopVariables(): array '$falseOrObject', "'end'", ], - [ - 'LoopVariables\Foo|false', - '$falseOrObject', - "'afterLoop'", - ], ]; } @@ -7043,6 +7028,21 @@ public function dataForeachLoopVariables(): array '$i', "'afterLoop'", ], + [ + 'LoopVariables\Bar|LoopVariables\Foo|LoopVariables\Lorem|null', + '$foo', + "'afterLoop'", + ], + [ + '1|int<10, max>|null', + '$nullableVal', + "'afterLoop'", + ], + [ + 'LoopVariables\Foo|false', + '$falseOrObject', + "'afterLoop'", + ], ]; } @@ -7064,6 +7064,21 @@ public function dataWhileLoopVariables(): array '$i', "'afterLoop'", ], + [ + 'LoopVariables\Bar|LoopVariables\Foo|LoopVariables\Lorem|null', + '$foo', + "'afterLoop'", + ], + [ + '1|int<10, max>|null', + '$nullableVal', + "'afterLoop'", + ], + [ + 'LoopVariables\Foo|false', + '$falseOrObject', + "'afterLoop'", + ], ]; } @@ -7086,6 +7101,21 @@ public function dataForLoopVariables(): array '$i', "'afterLoop'", ], + [ + 'LoopVariables\Bar|LoopVariables\Foo|LoopVariables\Lorem', + '$foo', + "'afterLoop'", + ], + [ + '1|int<10, max>', + '$nullableVal', + "'afterLoop'", + ], + [ + 'LoopVariables\Foo', + '$falseOrObject', + "'afterLoop'", + ], ]; } diff --git a/tests/PHPStan/Analyser/data/for-loop-i-type.php b/tests/PHPStan/Analyser/data/for-loop-i-type.php index 983738189f..692c30585d 100644 --- a/tests/PHPStan/Analyser/data/for-loop-i-type.php +++ b/tests/PHPStan/Analyser/data/for-loop-i-type.php @@ -11,9 +11,30 @@ public function doBar() { for($i = 1; $i < 50; $i++) { assertType('int<1, 49>', $i); } + + assertType('50', $i); + for($i = 50; $i > 0; $i--) { assertType('int<1, max>', $i); // could be int<1, 50> } + + assertType('0', $i); + } + + public function doBaz() { + for($i = 1; $i < 50; $i += 2) { + assertType('int<1, 49>', $i); + } + + assertType('int<50, 51>', $i); + } + + public function doLOrem() { + for($i = 1; $i < 50; $i++) { + break; + } + + assertType('int<1, 50>', $i); } }