diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 8a807ea745..9a090bbae5 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1517,8 +1517,17 @@ private function processStmtNode( } $breakExitPoints = $finalScopeResult->getExitPointsByType(Break_::class); - foreach ($breakExitPoints as $breakExitPoint) { - $finalScope = $finalScope->mergeWith($breakExitPoint->getScope()); + if ($alwaysIterates && count($breakExitPoints) > 0) { + $finalScope = null; + foreach ($breakExitPoints as $breakExitPoint) { + $finalScope = $finalScope === null + ? $breakExitPoint->getScope() + : $finalScope->mergeWith($breakExitPoint->getScope()); + } + } else { + foreach ($breakExitPoints as $breakExitPoint) { + $finalScope = $finalScope->mergeWith($breakExitPoint->getScope()); + } } $isIterableAtLeastOnce = $beforeCondBooleanType->isTrue()->yes(); @@ -1624,8 +1633,18 @@ private function processStmtNode( } else { $this->processExprNode($stmt, $stmt->cond, $bodyScope, $storage, $nodeCallback, ExpressionContext::createDeep()); } - foreach ($bodyScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { - $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); + $breakExitPoints = $bodyScopeResult->getExitPointsByType(Break_::class); + if ($alwaysIterates && count($breakExitPoints) > 0) { + $finalScope = null; + foreach ($breakExitPoints as $breakExitPoint) { + $finalScope = $finalScope === null + ? $breakExitPoint->getScope() + : $finalScope->mergeWith($breakExitPoint->getScope()); + } + } else { + foreach ($breakExitPoints as $breakExitPoint) { + $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); + } } return new InternalStatementResult( @@ -1736,8 +1755,18 @@ private function processStmtNode( $finalScope = $finalScope->filterByFalseyValue($lastCondExpr); } - foreach ($finalScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { - $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); + $breakExitPoints = $finalScopeResult->getExitPointsByType(Break_::class); + if ($alwaysIterates->yes() && count($breakExitPoints) > 0) { + $finalScope = null; + foreach ($breakExitPoints as $breakExitPoint) { + $finalScope = $finalScope === null + ? $breakExitPoint->getScope() + : $finalScope->mergeWith($breakExitPoint->getScope()); + } + } else { + foreach ($breakExitPoints as $breakExitPoint) { + $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); + } } if ($isIterableAtLeastOnce->no() || $finalScopeResult->isAlwaysTerminating()) { @@ -1753,7 +1782,7 @@ private function processStmtNode( } else { $finalScope = $finalScope->mergeWith($scope); } - } else { + } elseif (!$alwaysIterates->yes()) { if (!$this->polluteScopeWithLoopInitialAssignments) { $finalScope = $finalScope->mergeWith($scope); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-10245.php b/tests/PHPStan/Analyser/nsrt/bug-10245.php new file mode 100644 index 0000000000..2fc5b5e78d --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-10245.php @@ -0,0 +1,25 @@ += 8.0 + +namespace NsrtBug10245; + +use function PHPStan\Testing\assertType; + +/** + * @throws \Exception + */ +function produceInt(): int +{ + return 1; +} + +function testTryCatchInWhileTrue(): void +{ + while (true) { + try { + $a = produceInt(); + break; + } catch (\Throwable) {} + } + + assertType('int', $a); +} diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 112a57d4cc..f566e0af2f 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1257,4 +1257,64 @@ public function testBug12944(): void $this->analyse([__DIR__ . '/data/bug-12944.php'], []); } + public function testBug11545(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = true; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + + $this->analyse([__DIR__ . '/data/bug-11545.php'], []); + } + + public function testBug11984(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = true; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + + $this->analyse([__DIR__ . '/data/bug-11984.php'], []); + } + + public function testBug10245(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = true; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + + $this->analyse([__DIR__ . '/data/bug-10245.php'], []); + } + + public function testBug9023(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = true; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + + $this->analyse([__DIR__ . '/data/bug-9023.php'], []); + } + + public function testBug5919(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = true; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + + $this->analyse([__DIR__ . '/data/bug-5919.php'], []); + } + + public function testBug5477(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = true; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + + $this->analyse([__DIR__ . '/data/bug-5477.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/bug-10245.php b/tests/PHPStan/Rules/Variables/data/bug-10245.php new file mode 100644 index 0000000000..e522e4cca3 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-10245.php @@ -0,0 +1,36 @@ + $max) { + $result = 'done'; + break; + } + ++$i; + } + print $result; +} diff --git a/tests/PHPStan/Rules/Variables/data/bug-11545.php b/tests/PHPStan/Rules/Variables/data/bug-11545.php new file mode 100644 index 0000000000..5c10a8c9f4 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-11545.php @@ -0,0 +1,37 @@ + $max) { + $result = 'done'; + break; + } + ++$i; + } + print $result; +} + +function foo_for(int $max): void { + for ($i = 0;; ++$i) { + if ($i > $max) { + $result = 'done'; + break; + } + } + print $result; +} + +function foo_do_while(int $max): void { + $i = 0; + do { + if ($i > $max) { + $result = 'done'; + break; + } + ++$i; + } while (true); + print $result; +} diff --git a/tests/PHPStan/Rules/Variables/data/bug-11984.php b/tests/PHPStan/Rules/Variables/data/bug-11984.php new file mode 100644 index 0000000000..da45fb4f3c --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-11984.php @@ -0,0 +1,31 @@ + + */ + public function loadFromFile(): array + { + return ['x' => 1]; + } + + /** + * @return array + */ + public function test(): array + { + while (true) { + try { + $data = $this->loadFromFile(); + + break; + } catch (\Exception $ex) { + } + } + + return $data; + } +} diff --git a/tests/PHPStan/Rules/Variables/data/bug-5477.php b/tests/PHPStan/Rules/Variables/data/bug-5477.php new file mode 100644 index 0000000000..222efd3d30 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-5477.php @@ -0,0 +1,17 @@ +