Skip to content

Commit 16d0d80

Browse files
committed
Support for endless loops
1 parent 6dcda72 commit 16d0d80

File tree

6 files changed

+65
-16
lines changed

6 files changed

+65
-16
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,10 +1194,8 @@ private function processStmtNode(
11941194

11951195
if ($alwaysIterates) {
11961196
$isAlwaysTerminating = count($finalScopeResult->getExitPointsByType(Break_::class)) === 0;
1197-
} elseif ($isIterableAtLeastOnce) {
1198-
$isAlwaysTerminating = $finalScopeResult->isAlwaysTerminating();
11991197
} else {
1200-
$isAlwaysTerminating = false;
1198+
$isAlwaysTerminating = $isIterableAtLeastOnce && $finalScopeResult->isAlwaysTerminating();
12011199
}
12021200
$condScope = $condResult->getFalseyScope();
12031201
if (!$isIterableAtLeastOnce) {
@@ -1314,6 +1312,7 @@ private function processStmtNode(
13141312
}
13151313

13161314
$bodyScope = $initScope;
1315+
$alwaysIterates = $stmt->cond === [] && $context->isTopLevel();
13171316
$isIterableAtLeastOnce = TrinaryLogic::createYes();
13181317
$lastCondExpr = $stmt->cond[count($stmt->cond) - 1] ?? null;
13191318
foreach ($stmt->cond as $condExpr) {
@@ -1411,10 +1410,16 @@ private function processStmtNode(
14111410
}
14121411
}
14131412

1413+
if ($alwaysIterates) {
1414+
$isAlwaysTerminating = count($finalScopeResult->getExitPointsByType(Break_::class)) === 0;
1415+
} else {
1416+
$isAlwaysTerminating = false; // $finalScopeResult->isAlwaysTerminating() && $isAlwaysIterable
1417+
}
1418+
14141419
return new StatementResult(
14151420
$finalScope,
14161421
$finalScopeResult->hasYield() || $hasYield,
1417-
false/* $finalScopeResult->isAlwaysTerminating() && $isAlwaysIterable*/,
1422+
$isAlwaysTerminating,
14181423
$finalScopeResult->getExitPointsForOuterLoop(),
14191424
array_merge($throwPoints, $finalScopeResult->getThrowPoints()),
14201425
array_merge($impurePoints, $finalScopeResult->getImpurePoints()),

tests/PHPStan/Analyser/StatementResultTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,18 @@ public function dataIsAlwaysTerminating(): array
173173
'while (true) { break; }',
174174
false,
175175
],
176+
[
177+
'for (;;) { }',
178+
true,
179+
],
180+
[
181+
'for (;;) { return; }',
182+
true,
183+
],
184+
[
185+
'for (;;) { break; }',
186+
false,
187+
],
176188
[
177189
'do { } while (doFoo());',
178190
false,

tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,12 @@ public function testStrictComparison(): void
109109
140,
110110
],
111111
[
112-
'Strict comparison using !== between StrictComparison\Foo|null and 1 will always evaluate to true.',
113-
154,
112+
'Strict comparison using === between non-empty-array and null will always evaluate to false.',
113+
150,
114114
],
115115
[
116-
'Strict comparison using === between non-empty-array and null will always evaluate to false.',
117-
164,
116+
'Strict comparison using !== between StrictComparison\Foo|null and 1 will always evaluate to true.',
117+
161,
118118
],
119119
[
120120
'Strict comparison using !== between StrictComparison\Node|null and false will always evaluate to true.',
@@ -352,7 +352,7 @@ public function testStrictComparisonWithoutAlwaysTrue(): void
352352
],
353353
[
354354
'Strict comparison using === between non-empty-array and null will always evaluate to false.',
355-
164,
355+
150,
356356
],
357357
[
358358
'Strict comparison using === between 1 and 2 will always evaluate to false.',

tests/PHPStan/Rules/Comparison/data/strict-comparison.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,13 @@ public function whileWithTypeChange()
146146

147147
public function forWithTypeChange()
148148
{
149+
for (; $val = $this->returnArray();) {
150+
if ($val === null) {
151+
152+
}
153+
$val = null;
154+
}
155+
149156
$foo = null;
150157
for (;;) {
151158
if ($foo !== null) {
@@ -159,13 +166,6 @@ public function forWithTypeChange()
159166
$foo = new self();
160167
}
161168
}
162-
163-
for (; $val = $this->returnArray();) {
164-
if ($val === null) {
165-
166-
}
167-
$val = null;
168-
}
169169
}
170170

171171
private function returnArray(): array

tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,4 +325,10 @@ public function testBug9309(): void
325325
$this->analyse([__DIR__ . '/data/bug-9309.php'], []);
326326
}
327327

328+
public function testBug8463(): void
329+
{
330+
$this->checkExplicitMixedMissingReturn = true;
331+
$this->analyse([__DIR__ . '/data/bug-8463.php'], []);
332+
}
333+
328334
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Bug8463;
4+
5+
function f1() : int
6+
{
7+
while(true)
8+
{
9+
if(rand() === rand())
10+
{
11+
return 1;
12+
}
13+
}
14+
}
15+
16+
17+
function f2() : int
18+
{
19+
for(;;)
20+
{
21+
if(rand() === rand())
22+
{
23+
return 1;
24+
}
25+
}
26+
}

0 commit comments

Comments
 (0)