Skip to content

Commit 4b4836a

Browse files
committed
Fix false positive about never written property when assigned in foreach key or value
1 parent 4d119ba commit 4b4836a

File tree

3 files changed

+41
-16
lines changed

3 files changed

+41
-16
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,12 +1210,12 @@ private function processStmtNode(
12101210

12111211
if ($context->isTopLevel()) {
12121212
$originalScope = $this->polluteScopeWithAlwaysIterableForeach ? $scope->filterByTruthyValue($arrayComparisonExpr) : $scope;
1213-
$bodyScope = $this->enterForeach($originalScope, $originalScope, $stmt);
1213+
$bodyScope = $this->enterForeach($originalScope, $originalScope, $stmt, $nodeCallback);
12141214
$count = 0;
12151215
do {
12161216
$prevScope = $bodyScope;
12171217
$bodyScope = $bodyScope->mergeWith($this->polluteScopeWithAlwaysIterableForeach ? $scope->filterByTruthyValue($arrayComparisonExpr) : $scope);
1218-
$bodyScope = $this->enterForeach($bodyScope, $originalScope, $stmt);
1218+
$bodyScope = $this->enterForeach($bodyScope, $originalScope, $stmt, $nodeCallback);
12191219
$bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void {
12201220
}, $context->enterDeep())->filterOutLoopExitPoints();
12211221
$bodyScope = $bodyScopeResult->getScope();
@@ -1234,7 +1234,7 @@ private function processStmtNode(
12341234
}
12351235

12361236
$bodyScope = $bodyScope->mergeWith($this->polluteScopeWithAlwaysIterableForeach ? $scope->filterByTruthyValue($arrayComparisonExpr) : $scope);
1237-
$bodyScope = $this->enterForeach($bodyScope, $originalScope, $stmt);
1237+
$bodyScope = $this->enterForeach($bodyScope, $originalScope, $stmt, $nodeCallback);
12381238
$finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback, $context)->filterOutLoopExitPoints();
12391239
$finalScope = $finalScopeResult->getScope();
12401240
foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) {
@@ -6420,7 +6420,10 @@ private function processVarAnnotation(MutatingScope $scope, array $variableNames
64206420
return $scope;
64216421
}
64226422

6423-
private function enterForeach(MutatingScope $scope, MutatingScope $originalScope, Foreach_ $stmt): MutatingScope
6423+
/**
6424+
* @param callable(Node $node, Scope $scope): void $nodeCallback
6425+
*/
6426+
private function enterForeach(MutatingScope $scope, MutatingScope $originalScope, Foreach_ $stmt, callable $nodeCallback): MutatingScope
64246427
{
64256428
if ($stmt->expr instanceof Variable && is_string($stmt->expr->name)) {
64266429
$scope = $this->processVarAnnotation($scope, [$stmt->expr->name], $stmt);
@@ -6446,16 +6449,12 @@ private function enterForeach(MutatingScope $scope, MutatingScope $originalScope
64466449
$vars[] = $keyVarName;
64476450
}
64486451
} else {
6449-
$scope = $this->processAssignVar(
6452+
$scope = $this->processVirtualAssign(
64506453
$scope,
64516454
$stmt,
64526455
$stmt->valueVar,
64536456
new GetIterableValueTypeExpr($stmt->expr),
6454-
static function (): void {
6455-
},
6456-
ExpressionContext::createDeep(),
6457-
static fn (MutatingScope $scope): ExpressionResult => new ExpressionResult($scope, false, false, [], []),
6458-
true,
6457+
$nodeCallback,
64596458
)->getScope();
64606459
$vars = $this->getAssignedVariables($stmt->valueVar);
64616460
if (
@@ -6464,16 +6463,12 @@ static function (): void {
64646463
$scope = $scope->enterForeachKey($originalScope, $stmt->expr, $stmt->keyVar->name);
64656464
$vars[] = $stmt->keyVar->name;
64666465
} elseif ($stmt->keyVar !== null) {
6467-
$scope = $this->processAssignVar(
6466+
$scope = $this->processVirtualAssign(
64686467
$scope,
64696468
$stmt,
64706469
$stmt->keyVar,
64716470
new GetIterableKeyTypeExpr($stmt->expr),
6472-
static function (): void {
6473-
},
6474-
ExpressionContext::createDeep(),
6475-
static fn (MutatingScope $scope): ExpressionResult => new ExpressionResult($scope, false, false, [], []),
6476-
true,
6471+
$nodeCallback,
64776472
)->getScope();
64786473
$vars = array_merge($vars, $this->getAssignedVariables($stmt->keyVar));
64796474
}

tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,4 +402,12 @@ public function testBug12702(): void
402402
]);
403403
}
404404

405+
public function testBug9213(): void
406+
{
407+
$this->alwaysWrittenTags = [];
408+
$this->alwaysReadTags = [];
409+
410+
$this->analyse([__DIR__ . '/data/bug-9213.php'], []);
411+
}
412+
405413
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Bug9213;
4+
5+
class IterateSomething
6+
{
7+
/** @var string|null */
8+
private $currentKey;
9+
10+
/** @var string|null */
11+
private $currentValue;
12+
13+
/** @param iterable<string, string> $collection */
14+
public function __construct(private iterable $collection) {}
15+
16+
public function printAllTheThings(): void
17+
{
18+
foreach ($this->collection as $this->currentKey => $this->currentValue) {
19+
echo "{$this->currentKey} => {$this->currentValue}\n";
20+
}
21+
}
22+
}

0 commit comments

Comments
 (0)