Skip to content

Commit

Permalink
Improve for loop handling
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jan 12, 2022
1 parent 4fb1a77 commit 12b6bf7
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 2 deletions.
27 changes: 25 additions & 2 deletions src/Analyser/NodeScopeResolver.php
Expand Up @@ -978,9 +978,17 @@ private function processStmtNode(
}

$bodyScope = $initScope;
$isIterableAtLeastOnce = TrinaryLogic::createYes();
foreach ($stmt->cond as $condExpr) {
$condResult = $this->processExprNode($condExpr, $bodyScope, static function (): void {
}, ExpressionContext::createDeep());
$condTruthiness = $condResult->getScope()->getType($condExpr)->toBoolean();
if ($condTruthiness instanceof ConstantBooleanType) {
$condTruthinessTrinary = TrinaryLogic::createFromBoolean($condTruthiness->getValue());
} else {
$condTruthinessTrinary = TrinaryLogic::createMaybe();
}
$isIterableAtLeastOnce = $isIterableAtLeastOnce->and($condTruthinessTrinary);
$hasYield = $hasYield || $condResult->hasYield();
$throwPoints = array_merge($throwPoints, $condResult->getThrowPoints());
$bodyScope = $condResult->getTruthyScope();
Expand Down Expand Up @@ -1043,8 +1051,23 @@ private function processStmtNode(
$finalScope = $breakExitPoint->getScope()->mergeWith($finalScope);
}

if (!$this->polluteScopeWithLoopInitialAssignments) {
$finalScope = $finalScope->mergeWith($scope);
if ($isIterableAtLeastOnce->no() || $finalScopeResult->isAlwaysTerminating()) {
if ($this->polluteScopeWithLoopInitialAssignments) {
$finalScope = $initScope;
} else {
$finalScope = $scope;
}

} elseif ($isIterableAtLeastOnce->maybe()) {
if ($this->polluteScopeWithLoopInitialAssignments) {
$finalScope = $finalScope->mergeWith($initScope);
} else {
$finalScope = $finalScope->mergeWith($scope);
}
} else {
if (!$this->polluteScopeWithLoopInitialAssignments) {
$finalScope = $finalScope->mergeWith($scope);
}
}

return new StatementResult(
Expand Down
25 changes: 25 additions & 0 deletions tests/PHPStan/Analyser/data/for-loop-i-type.php
Expand Up @@ -8,11 +8,14 @@ class Foo
{

public function doBar() {
$foo = null;
for($i = 1; $i < 50; $i++) {
$foo = new \stdClass();
assertType('int<1, 49>', $i);
}

assertType('50', $i);
assertType(\stdClass::class, $foo);

for($i = 50; $i > 0; $i--) {
assertType('int<1, max>', $i); // could be int<1, 50>
Expand All @@ -21,6 +24,28 @@ public function doBar() {
assertType('0', $i);
}

public function doCount(array $a) {
$foo = null;
for($i = 1; $i < count($a); $i++) {
$foo = new \stdClass();
assertType('int<1, max>', $i);
}

assertType('int<1, max>', $i);
assertType(\stdClass::class . '|null', $foo);
}

public function doCount2() {
$foo = null;
for($i = 1; $i < count([]); $i++) {
$foo = new \stdClass();
assertType('string', $i); // should be *NEVER*
}

assertType('1', $i);
assertType('null', $foo);
}

public function doBaz() {
for($i = 1; $i < 50; $i += 2) {
assertType('int<1, 49>', $i);
Expand Down

0 comments on commit 12b6bf7

Please sign in to comment.