Skip to content

Commit 824abe6

Browse files
committed
Support for endless loops
1 parent 6ac62d3 commit 824abe6

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
@@ -1193,10 +1193,8 @@ private function processStmtNode(
11931193

11941194
if ($alwaysIterates) {
11951195
$isAlwaysTerminating = count($finalScopeResult->getExitPointsByType(Break_::class)) === 0;
1196-
} elseif ($isIterableAtLeastOnce) {
1197-
$isAlwaysTerminating = $finalScopeResult->isAlwaysTerminating();
11981196
} else {
1199-
$isAlwaysTerminating = false;
1197+
$isAlwaysTerminating = $isIterableAtLeastOnce && $finalScopeResult->isAlwaysTerminating();
12001198
}
12011199
$condScope = $condResult->getFalseyScope();
12021200
if (!$isIterableAtLeastOnce) {
@@ -1313,6 +1311,7 @@ private function processStmtNode(
13131311
}
13141312

13151313
$bodyScope = $initScope;
1314+
$alwaysIterates = $stmt->cond === [] && $context->isTopLevel();
13161315
$isIterableAtLeastOnce = TrinaryLogic::createYes();
13171316
foreach ($stmt->cond as $condExpr) {
13181317
$condResult = $this->processExprNode($stmt, $condExpr, $bodyScope, static function (): void {
@@ -1410,10 +1409,16 @@ private function processStmtNode(
14101409
}
14111410
}
14121411

1412+
if ($alwaysIterates) {
1413+
$isAlwaysTerminating = count($finalScopeResult->getExitPointsByType(Break_::class)) === 0;
1414+
} else {
1415+
$isAlwaysTerminating = false; // $finalScopeResult->isAlwaysTerminating() && $isAlwaysIterable
1416+
}
1417+
14131418
return new StatementResult(
14141419
$finalScope,
14151420
$finalScopeResult->hasYield() || $hasYield,
1416-
false/* $finalScopeResult->isAlwaysTerminating() && $isAlwaysIterable*/,
1421+
$isAlwaysTerminating,
14171422
$finalScopeResult->getExitPointsForOuterLoop(),
14181423
array_merge($throwPoints, $finalScopeResult->getThrowPoints()),
14191424
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
@@ -107,12 +107,12 @@ public function testStrictComparison(): void
107107
140,
108108
],
109109
[
110-
'Strict comparison using !== between StrictComparison\Foo|null and 1 will always evaluate to true.',
111-
154,
110+
'Strict comparison using === between non-empty-array and null will always evaluate to false.',
111+
150,
112112
],
113113
[
114-
'Strict comparison using === between non-empty-array and null will always evaluate to false.',
115-
164,
114+
'Strict comparison using !== between StrictComparison\Foo|null and 1 will always evaluate to true.',
115+
161,
116116
],
117117
[
118118
'Strict comparison using !== between StrictComparison\Node|null and false will always evaluate to true.',
@@ -333,7 +333,7 @@ public function testStrictComparisonWithoutAlwaysTrue(): void
333333
],
334334
[
335335
'Strict comparison using === between non-empty-array and null will always evaluate to false.',
336-
164,
336+
150,
337337
],
338338
[
339339
'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)