Skip to content

Commit

Permalink
Final scope from Continue_ points should not be used for `while (tr…
Browse files Browse the repository at this point in the history
…ue)`
  • Loading branch information
greew committed May 9, 2024
1 parent 80b46e3 commit 73521c3
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 5 deletions.
14 changes: 9 additions & 5 deletions src/Analyser/NodeScopeResolver.php
Expand Up @@ -1104,19 +1104,23 @@ private function processStmtNode(
$bodyScope = $this->processExprNode($stmt, $stmt->cond, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope();
$finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback, $context)->filterOutLoopExitPoints();
$finalScope = $finalScopeResult->getScope()->filterByFalseyValue($stmt->cond);
foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) {
$finalScope = $finalScope->mergeWith($continueExitPoint->getScope());

$condBooleanType = ($this->treatPhpDocTypesAsCertain ? $bodyScopeMaybeRan->getType($stmt->cond) : $bodyScopeMaybeRan->getNativeType($stmt->cond))->toBoolean();
$alwaysIterates = $condBooleanType->isTrue()->yes() && $context->isTopLevel();
$neverIterates = $condBooleanType->isFalse()->yes() && $context->isTopLevel();
if (!$alwaysIterates) {
foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) {
$finalScope = $finalScope->mergeWith($continueExitPoint->getScope());
}
}

$breakExitPoints = $finalScopeResult->getExitPointsByType(Break_::class);
foreach ($breakExitPoints as $breakExitPoint) {
$finalScope = $finalScope->mergeWith($breakExitPoint->getScope());
}

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

if ($alwaysIterates) {
Expand Down
28 changes: 28 additions & 0 deletions tests/PHPStan/Analyser/Bug10980Test.php
@@ -0,0 +1,28 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

use PHPStan\Testing\TypeInferenceTestCase;

class Bug10980Test extends TypeInferenceTestCase
{

public function dataFileAsserts(): iterable
{
yield from self::gatherAssertTypes(__DIR__ . '/data/bug-10980.php');
}

/**
* @dataProvider dataFileAsserts
* @param mixed ...$args
*/
public function testFileAsserts(
string $assertType,
string $file,
...$args,
): void
{
$this->assertFileAsserts($assertType, $file, ...$args);
}

}
23 changes: 23 additions & 0 deletions tests/PHPStan/Analyser/data/bug-10980.php
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Bug10980;

use function PHPStan\Testing\assertType;

class A {}

class B extends A {}

function a(): A {}

while (true) {
$type = a();
if (!$type instanceof B) {
continue;
}
break;
}

assertType('Bug10980\B', $type);

0 comments on commit 73521c3

Please sign in to comment.