Skip to content

Commit

Permalink
Foreach can append to the array but it does not change the number of …
Browse files Browse the repository at this point in the history
…iterations

Closes phpstan/phpstan#8924
  • Loading branch information
ondrejmirtes committed Jun 30, 2023
1 parent 2df1a59 commit 27085c5
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 23 deletions.
26 changes: 13 additions & 13 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -3121,37 +3121,37 @@ public function enterMatch(Expr\Match_ $expr): self
return $this->assignExpression($condExpr, $type, $nativeType);
}

public function enterForeach(Expr $iteratee, string $valueName, ?string $keyName): self
public function enterForeach(self $originalScope, Expr $iteratee, string $valueName, ?string $keyName): self
{
$iterateeType = $this->getType($iteratee);
$nativeIterateeType = $this->getNativeType($iteratee);
$iterateeType = $originalScope->getType($iteratee);
$nativeIterateeType = $originalScope->getNativeType($iteratee);
$scope = $this->assignVariable(
$valueName,
$this->getIterableValueType($iterateeType),
$this->getIterableValueType($nativeIterateeType),
$originalScope->getIterableValueType($iterateeType),
$originalScope->getIterableValueType($nativeIterateeType),
);
if ($keyName !== null) {
$scope = $scope->enterForeachKey($iteratee, $keyName);
$scope = $scope->enterForeachKey($originalScope, $iteratee, $keyName);
}

return $scope;
}

public function enterForeachKey(Expr $iteratee, string $keyName): self
public function enterForeachKey(self $originalScope, Expr $iteratee, string $keyName): self
{
$iterateeType = $this->getType($iteratee);
$nativeIterateeType = $this->getNativeType($iteratee);
$iterateeType = $originalScope->getType($iteratee);
$nativeIterateeType = $originalScope->getNativeType($iteratee);
$scope = $this->assignVariable(
$keyName,
$this->getIterableKeyType($iterateeType),
$this->getIterableKeyType($nativeIterateeType),
$originalScope->getIterableKeyType($iterateeType),
$originalScope->getIterableKeyType($nativeIterateeType),
);

if ($iterateeType->isArray()->yes()) {
$scope = $scope->assignExpression(
new Expr\ArrayDimFetch($iteratee, new Variable($keyName)),
$this->getIterableValueType($iterateeType),
$this->getIterableValueType($nativeIterateeType),
$originalScope->getIterableValueType($iterateeType),
$originalScope->getIterableValueType($nativeIterateeType),
);
}

Expand Down
20 changes: 11 additions & 9 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -835,20 +835,21 @@ private function processStmtNode(
$stmt->expr,
new Array_([]),
);
$inForeachScope = $scope;
if ($stmt->expr instanceof Variable && is_string($stmt->expr->name)) {
$inForeachScope = $this->processVarAnnotation($scope, [$stmt->expr->name], $stmt);
$scope = $this->processVarAnnotation($scope, [$stmt->expr->name], $stmt);
}
$nodeCallback(new InForeachNode($stmt), $inForeachScope);
$nodeCallback(new InForeachNode($stmt), $scope);
$originalScope = $scope;
$bodyScope = $scope;

if ($context->isTopLevel()) {
$bodyScope = $this->polluteScopeWithAlwaysIterableForeach ? $this->enterForeach($scope->filterByTruthyValue($arrayComparisonExpr), $stmt) : $this->enterForeach($scope, $stmt);
$originalScope = $this->polluteScopeWithAlwaysIterableForeach ? $scope->filterByTruthyValue($arrayComparisonExpr) : $scope;
$bodyScope = $this->enterForeach($originalScope, $originalScope, $stmt);
$count = 0;
do {
$prevScope = $bodyScope;
$bodyScope = $bodyScope->mergeWith($this->polluteScopeWithAlwaysIterableForeach ? $scope->filterByTruthyValue($arrayComparisonExpr) : $scope);
$bodyScope = $this->enterForeach($bodyScope, $stmt);
$bodyScope = $this->enterForeach($bodyScope, $originalScope, $stmt);
$bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void {
}, $context->enterDeep())->filterOutLoopExitPoints();
$bodyScope = $bodyScopeResult->getScope();
Expand All @@ -867,7 +868,7 @@ private function processStmtNode(
}

$bodyScope = $bodyScope->mergeWith($this->polluteScopeWithAlwaysIterableForeach ? $scope->filterByTruthyValue($arrayComparisonExpr) : $scope);
$bodyScope = $this->enterForeach($bodyScope, $stmt);
$bodyScope = $this->enterForeach($bodyScope, $originalScope, $stmt);
$finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback, $context)->filterOutLoopExitPoints();
$finalScope = $finalScopeResult->getScope();
foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) {
Expand Down Expand Up @@ -4142,12 +4143,12 @@ private function processVarAnnotation(MutatingScope $scope, array $variableNames
return $scope;
}

private function enterForeach(MutatingScope $scope, Foreach_ $stmt): MutatingScope
private function enterForeach(MutatingScope $scope, MutatingScope $originalScope, Foreach_ $stmt): MutatingScope
{
if ($stmt->expr instanceof Variable && is_string($stmt->expr->name)) {
$scope = $this->processVarAnnotation($scope, [$stmt->expr->name], $stmt);
}
$iterateeType = $scope->getType($stmt->expr);
$iterateeType = $originalScope->getType($stmt->expr);
if (
($stmt->valueVar instanceof Variable && is_string($stmt->valueVar->name))
&& ($stmt->keyVar === null || ($stmt->keyVar instanceof Variable && is_string($stmt->keyVar->name)))
Expand All @@ -4157,6 +4158,7 @@ private function enterForeach(MutatingScope $scope, Foreach_ $stmt): MutatingSco
$keyVarName = $stmt->keyVar->name;
}
$scope = $scope->enterForeach(
$originalScope,
$stmt->expr,
$stmt->valueVar->name,
$keyVarName,
Expand All @@ -4180,7 +4182,7 @@ static function (): void {
if (
$stmt->keyVar instanceof Variable && is_string($stmt->keyVar->name)
) {
$scope = $scope->enterForeachKey($stmt->expr, $stmt->keyVar->name);
$scope = $scope->enterForeachKey($originalScope, $stmt->expr, $stmt->keyVar->name);
$vars[] = $stmt->keyVar->name;
} elseif ($stmt->keyVar !== null) {
$scope = $this->processAssignVar(
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-4504.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public function sayHello($models): void
assertType('Bug4504TypeInference\A', $v);
}

assertType('array{}|Iterator<mixed, Bug4504TypeInference\A>', $models);
assertType('Iterator<mixed, Bug4504TypeInference\A>', $models);
}

}
Expand Down
15 changes: 15 additions & 0 deletions tests/PHPStan/Analyser/data/bug-8924.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,18 @@ function foo(array $array): void {
$array = null;
}
}

function makeValidNumbers(): array
{
$validNumbers = [1, 2];
foreach ($validNumbers as $k => $v) {
assertType("non-empty-list<-2|-1|1|2|' 1'|' 2'>", $validNumbers);
assertType('0|1', $k);
assertType('1|2', $v);
$validNumbers[] = -$v;
$validNumbers[] = ' ' . (string)$v;
assertType("non-empty-list<-2|-1|1|2|' 1'|' 2'>", $validNumbers);
}

return $validNumbers;
}

0 comments on commit 27085c5

Please sign in to comment.