Skip to content

Commit 94b6012

Browse files
committed
Fix while loop inside another loop during scope stabilization phase
1 parent 932f403 commit 94b6012

File tree

2 files changed

+68
-2
lines changed

2 files changed

+68
-2
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,6 +1288,22 @@ private function processStmtNode(
12881288
} elseif ($stmt instanceof While_) {
12891289
$condResult = $this->processExprNode($stmt, $stmt->cond, $scope, static function (): void {
12901290
}, ExpressionContext::createDeep(), $context);
1291+
$beforeCondBooleanType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($stmt->cond) : $scope->getNativeType($stmt->cond))->toBoolean();
1292+
$condScope = $condResult->getFalseyScope();
1293+
if (!$context->isTopLevel() && $beforeCondBooleanType->isFalse()->yes()) {
1294+
if (!$this->polluteScopeWithLoopInitialAssignments) {
1295+
$scope = $condScope->mergeWith($scope);
1296+
}
1297+
1298+
return new StatementResult(
1299+
$scope,
1300+
$condResult->hasYield(),
1301+
false,
1302+
[],
1303+
$condResult->getThrowPoints(),
1304+
$condResult->getImpurePoints(),
1305+
);
1306+
}
12911307
$bodyScope = $condResult->getTruthyScope();
12921308

12931309
if ($context->isTopLevel()) {
@@ -1334,7 +1350,6 @@ private function processStmtNode(
13341350
$finalScope = $finalScope->mergeWith($breakExitPoint->getScope());
13351351
}
13361352

1337-
$beforeCondBooleanType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($stmt->cond) : $scope->getNativeType($stmt->cond))->toBoolean();
13381353
$isIterableAtLeastOnce = $beforeCondBooleanType->isTrue()->yes();
13391354
$nodeCallback(new BreaklessWhileLoopNode($stmt, $finalScopeResult->getExitPoints()), $bodyScopeMaybeRan);
13401355

@@ -1345,7 +1360,7 @@ private function processStmtNode(
13451360
} else {
13461361
$isAlwaysTerminating = false;
13471362
}
1348-
$condScope = $condResult->getFalseyScope();
1363+
13491364
if (!$isIterableAtLeastOnce) {
13501365
if (!$this->polluteScopeWithLoopInitialAssignments) {
13511366
$condScope = $condScope->mergeWith($scope);
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug13385;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
interface Operator {
8+
public function priority(): int;
9+
public function calculate(int $a, int $b): int;
10+
}
11+
12+
class HelloWorld
13+
{
14+
/**
15+
* @param list<Operator|int> $children
16+
*/
17+
public function calculate(array $children): int {
18+
$operands = [];
19+
$operators = [];
20+
21+
foreach ($children as $child) {
22+
if ($child instanceof Operator) {
23+
while ($operators !== [] && end($operators)->priority() >= $child->priority()) {
24+
$op = array_pop($operators);
25+
$left = array_pop($operands) ?? 0;
26+
$right = array_pop($operands) ?? 0;
27+
28+
assert(is_int($left));
29+
assert(is_int($right));
30+
31+
$value = $op->calculate($left, $right);
32+
33+
assertType(Operator::class, $op);
34+
assertType('int', $left);
35+
assertType('int', $right);
36+
assertType('int', $value);
37+
38+
$operands[] = $value;
39+
40+
assertType('non-empty-list<int>', $operands);
41+
}
42+
43+
$operators[] = $child;
44+
} else {
45+
$operands[] = $child;
46+
}
47+
}
48+
49+
return count($operands) === 1 ? reset($operands) : 0;
50+
}
51+
}

0 commit comments

Comments
 (0)