Skip to content

Commit d1de9eb

Browse files
github-actions[bot]phpstan-bot
authored andcommitted
Fix false positive "Empty array passed to foreach" in closure with object use
- Don't carry forward property fetch expression types for dynamic (undeclared) properties on objects captured by value in closures - Objects are references in PHP, so their properties can change between closure definition and invocation - Only affects dynamic properties (e.g. stdClass); declared/native properties still carry forward type narrowings - New regression test in tests/PHPStan/Rules/Arrays/data/bug-10345.php Closes phpstan/phpstan#10345
1 parent 3222c52 commit d1de9eb

File tree

3 files changed

+46
-0
lines changed

3 files changed

+46
-0
lines changed

src/Analyser/MutatingScope.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2152,6 +2152,10 @@ public function enterAnonymousFunctionWithoutReflection(
21522152
}
21532153
}
21542154

2155+
if ($this->shouldNotCarryForwardPropertyFetchInClosure($expr)) {
2156+
continue;
2157+
}
2158+
21552159
$expressionTypes[$exprString] = $typeHolder;
21562160
}
21572161

@@ -2218,6 +2222,28 @@ public function enterAnonymousFunctionWithoutReflection(
22182222
);
22192223
}
22202224

2225+
private function shouldNotCarryForwardPropertyFetchInClosure(Expr $expr): bool
2226+
{
2227+
if (!$expr instanceof PropertyFetch) {
2228+
return false;
2229+
}
2230+
2231+
if (!$expr->name instanceof Identifier) {
2232+
return false;
2233+
}
2234+
2235+
$objectType = $this->getType($expr->var);
2236+
$propertyName = $expr->name->name;
2237+
2238+
foreach ($objectType->getObjectClassReflections() as $classReflection) {
2239+
if ($classReflection->hasNativeProperty($propertyName)) {
2240+
return false;
2241+
}
2242+
}
2243+
2244+
return true;
2245+
}
2246+
22212247
private function expressionTypeIsUnchangeable(ExpressionTypeHolder $typeHolder): bool
22222248
{
22232249
$expr = $typeHolder->getExpr();

tests/PHPStan/Rules/Arrays/DeadForeachRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,9 @@ public function testBug2457(): void
5555
$this->analyse([__DIR__ . '/data/bug-2457.php'], []);
5656
}
5757

58+
public function testBug10345(): void
59+
{
60+
$this->analyse([__DIR__ . '/data/bug-10345.php'], []);
61+
}
62+
5863
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug10345;
4+
5+
$container = new \stdClass();
6+
$container->items = [];
7+
8+
$func = function() use ($container): int {
9+
foreach ($container->items as $item) {}
10+
return 1;
11+
};
12+
13+
$container->items[] = '1';
14+
15+
$a = $func();

0 commit comments

Comments
 (0)