diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index dda335f566..1a6a069d28 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1384,7 +1384,10 @@ private function processStmtNode( $loopScope = $this->processExprNode($stmt, $loopExpr, $loopScope, $nodeCallback, ExpressionContext::createTopLevel())->getScope(); } $finalScope = $finalScope->generalizeWith($loopScope); + + $alwaysIterates = TrinaryLogic::createFromBoolean($context->isTopLevel()); if ($lastCondExpr !== null) { + $alwaysIterates = $alwaysIterates->and($finalScope->getType($lastCondExpr)->toBoolean()->isTrue()); $finalScope = $finalScope->filterByFalseyValue($lastCondExpr); } @@ -1411,10 +1414,18 @@ private function processStmtNode( } } + if ($alwaysIterates->yes()) { + $isAlwaysTerminating = count($finalScopeResult->getExitPointsByType(Break_::class)) === 0; + } elseif ($isIterableAtLeastOnce->yes()) { + $isAlwaysTerminating = $finalScopeResult->isAlwaysTerminating(); + } else { + $isAlwaysTerminating = false; + } + return new StatementResult( $finalScope, $finalScopeResult->hasYield() || $hasYield, - false/* $finalScopeResult->isAlwaysTerminating() && $isAlwaysIterable*/, + $isAlwaysTerminating, $finalScopeResult->getExitPointsForOuterLoop(), array_merge($throwPoints, $finalScopeResult->getThrowPoints()), array_merge($impurePoints, $finalScopeResult->getImpurePoints()), diff --git a/tests/PHPStan/Analyser/StatementResultTest.php b/tests/PHPStan/Analyser/StatementResultTest.php index fb3a9dd5b1..b86de8617e 100644 --- a/tests/PHPStan/Analyser/StatementResultTest.php +++ b/tests/PHPStan/Analyser/StatementResultTest.php @@ -173,6 +173,130 @@ public function dataIsAlwaysTerminating(): array 'while (true) { break; }', false, ], + [ + 'while (true) { exit; }', + true, + ], + [ + 'while (true) { while (true) { } }', + true, + ], + [ + 'while (true) { while (true) { return; } }', + true, + ], + [ + 'while (true) { while (true) { break; } }', + true, + ], + [ + 'while (true) { while (true) { exit; } }', + true, + ], + [ + 'while (true) { while (true) { break 2; } }', + false, + ], + [ + 'while (true) { while ($x) { } }', + true, + ], + [ + 'while (true) { while ($x) { return; } }', + true, + ], + [ + 'while (true) { while ($x) { break; } }', + true, + ], + [ + 'while (true) { while ($x) { exit; } }', + true, + ], + [ + 'while (true) { while ($x) { break 2; } }', + false, + ], + [ + 'for (;;) { }', + true, + ], + [ + 'for (;;) { return; }', + true, + ], + [ + 'for (;;) { break; }', + false, + ], + [ + 'for (;;) { exit; }', + true, + ], + [ + 'for (;;) { for (;;) { } }', + true, + ], + [ + 'for (;;) { for (;;) { return; } }', + true, + ], + [ + 'for (;;) { for (;;) { break; } }', + true, + ], + [ + 'for (;;) { for (;;) { exit; } }', + true, + ], + [ + 'for (;;) { for (;;) { break 2; } }', + false, + ], + [ + 'for (;;) { for ($i = 0; $i< 5; $i++) { } }', + true, + ], + [ + 'for (;;) { for ($i = 0; $i< 5; $i++) { return; } }', + true, + ], + [ + 'for (;;) { for ($i = 0; $i< 5; $i++) { break; } }', + true, + ], + [ + 'for (;;) { for ($i = 0; $i< 5; $i++) { exit; } }', + true, + ], + [ + 'for (;;) { for ($i = 0; $i< 5; $i++) { break 2; } }', + false, + ], + [ + 'for ($i = 0; $i < 5;) { }', + true, + ], + [ + 'for ($i = 0; $i < 5; $i--) { }', + true, + ], + [ + 'for (; 0, 1;) { }', + true, + ], + [ + 'for (; 1, 0;) { }', + false, + ], + [ + 'for (; "", "a";) { }', + true, + ], + [ + 'for (; "a", "";) { }', + false, + ], [ 'do { } while (doFoo());', false, @@ -231,7 +355,7 @@ public function dataIsAlwaysTerminating(): array ], [ 'for ($i = 0; $i < 10; $i++) { return; }', - false, // will be true with range types + true, ], [ 'for ($i = 0; $i < 0; $i++) { return; }', diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 01157d82e1..48de5a95b4 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -109,12 +109,12 @@ public function testStrictComparison(): void 140, ], [ - 'Strict comparison using !== between StrictComparison\Foo|null and 1 will always evaluate to true.', - 154, + 'Strict comparison using === between non-empty-array and null will always evaluate to false.', + 150, ], [ - 'Strict comparison using === between non-empty-array and null will always evaluate to false.', - 164, + 'Strict comparison using !== between StrictComparison\Foo|null and 1 will always evaluate to true.', + 161, ], [ 'Strict comparison using !== between StrictComparison\Node|null and false will always evaluate to true.', @@ -352,7 +352,7 @@ public function testStrictComparisonWithoutAlwaysTrue(): void ], [ 'Strict comparison using === between non-empty-array and null will always evaluate to false.', - 164, + 150, ], [ 'Strict comparison using === between 1 and 2 will always evaluate to false.', diff --git a/tests/PHPStan/Rules/Comparison/data/strict-comparison.php b/tests/PHPStan/Rules/Comparison/data/strict-comparison.php index 70882c4ca4..f98e7e11aa 100644 --- a/tests/PHPStan/Rules/Comparison/data/strict-comparison.php +++ b/tests/PHPStan/Rules/Comparison/data/strict-comparison.php @@ -146,6 +146,13 @@ public function whileWithTypeChange() public function forWithTypeChange() { + for (; $val = $this->returnArray();) { + if ($val === null) { + + } + $val = null; + } + $foo = null; for (;;) { if ($foo !== null) { @@ -159,13 +166,6 @@ public function forWithTypeChange() $foo = new self(); } } - - for (; $val = $this->returnArray();) { - if ($val === null) { - - } - $val = null; - } } private function returnArray(): array diff --git a/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php b/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php index ad9fc94be5..f99d9352d9 100644 --- a/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php +++ b/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php @@ -325,4 +325,22 @@ public function testBug9309(): void $this->analyse([__DIR__ . '/data/bug-9309.php'], []); } + public function testBug6807(): void + { + $this->checkExplicitMixedMissingReturn = true; + $this->analyse([__DIR__ . '/data/bug-6807.php'], []); + } + + public function testBug8463(): void + { + $this->checkExplicitMixedMissingReturn = true; + $this->analyse([__DIR__ . '/data/bug-8463.php'], []); + } + + public function testBug9374(): void + { + $this->checkExplicitMixedMissingReturn = true; + $this->analyse([__DIR__ . '/data/bug-9374.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Missing/data/bug-6807.php b/tests/PHPStan/Rules/Missing/data/bug-6807.php new file mode 100644 index 0000000000..d7ffdde9dd --- /dev/null +++ b/tests/PHPStan/Rules/Missing/data/bug-6807.php @@ -0,0 +1,16 @@ + 5) + throw new Exception(); + + if (rand() == 1) + return 5; + } +} diff --git a/tests/PHPStan/Rules/Missing/data/bug-8463.php b/tests/PHPStan/Rules/Missing/data/bug-8463.php new file mode 100644 index 0000000000..c27592782f --- /dev/null +++ b/tests/PHPStan/Rules/Missing/data/bug-8463.php @@ -0,0 +1,26 @@ +