Skip to content

Commit 73521c3

Browse files
authored
Final scope from Continue_ points should not be used for while (true)
1 parent 80b46e3 commit 73521c3

File tree

3 files changed

+60
-5
lines changed

3 files changed

+60
-5
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,19 +1104,23 @@ private function processStmtNode(
11041104
$bodyScope = $this->processExprNode($stmt, $stmt->cond, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope();
11051105
$finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback, $context)->filterOutLoopExitPoints();
11061106
$finalScope = $finalScopeResult->getScope()->filterByFalseyValue($stmt->cond);
1107-
foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) {
1108-
$finalScope = $finalScope->mergeWith($continueExitPoint->getScope());
1107+
1108+
$condBooleanType = ($this->treatPhpDocTypesAsCertain ? $bodyScopeMaybeRan->getType($stmt->cond) : $bodyScopeMaybeRan->getNativeType($stmt->cond))->toBoolean();
1109+
$alwaysIterates = $condBooleanType->isTrue()->yes() && $context->isTopLevel();
1110+
$neverIterates = $condBooleanType->isFalse()->yes() && $context->isTopLevel();
1111+
if (!$alwaysIterates) {
1112+
foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) {
1113+
$finalScope = $finalScope->mergeWith($continueExitPoint->getScope());
1114+
}
11091115
}
1116+
11101117
$breakExitPoints = $finalScopeResult->getExitPointsByType(Break_::class);
11111118
foreach ($breakExitPoints as $breakExitPoint) {
11121119
$finalScope = $finalScope->mergeWith($breakExitPoint->getScope());
11131120
}
11141121

11151122
$beforeCondBooleanType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($stmt->cond) : $scope->getNativeType($stmt->cond))->toBoolean();
1116-
$condBooleanType = ($this->treatPhpDocTypesAsCertain ? $bodyScopeMaybeRan->getType($stmt->cond) : $bodyScopeMaybeRan->getNativeType($stmt->cond))->toBoolean();
11171123
$isIterableAtLeastOnce = $beforeCondBooleanType->isTrue()->yes();
1118-
$alwaysIterates = $condBooleanType->isTrue()->yes() && $context->isTopLevel();
1119-
$neverIterates = $condBooleanType->isFalse()->yes() && $context->isTopLevel();
11201124
$nodeCallback(new BreaklessWhileLoopNode($stmt, $finalScopeResult->getExitPoints()), $bodyScopeMaybeRan);
11211125

11221126
if ($alwaysIterates) {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser;
4+
5+
use PHPStan\Testing\TypeInferenceTestCase;
6+
7+
class Bug10980Test extends TypeInferenceTestCase
8+
{
9+
10+
public function dataFileAsserts(): iterable
11+
{
12+
yield from self::gatherAssertTypes(__DIR__ . '/data/bug-10980.php');
13+
}
14+
15+
/**
16+
* @dataProvider dataFileAsserts
17+
* @param mixed ...$args
18+
*/
19+
public function testFileAsserts(
20+
string $assertType,
21+
string $file,
22+
...$args,
23+
): void
24+
{
25+
$this->assertFileAsserts($assertType, $file, ...$args);
26+
}
27+
28+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Bug10980;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
class A {}
10+
11+
class B extends A {}
12+
13+
function a(): A {}
14+
15+
while (true) {
16+
$type = a();
17+
if (!$type instanceof B) {
18+
continue;
19+
}
20+
break;
21+
}
22+
23+
assertType('Bug10980\B', $type);

0 commit comments

Comments
 (0)