Skip to content

Commit 6671089

Browse files
phpstan-botclaude
andcommitted
Do not carry forward any property fetch types into closure scope
Objects are always references in PHP, even when captured by value via `use`. Properties (both dynamic and declared) can be modified between closure definition and invocation, so their types should not be frozen at closure definition time. Simplify shouldNotCarryForwardPropertyFetchInClosure to skip all PropertyFetch expressions, not just dynamic properties. Add test case with declared property class (Foo with list<string> $items). Update specified-types-closure-use.php expectations accordingly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 25046b7 commit 6671089

3 files changed

Lines changed: 20 additions & 20 deletions

File tree

src/Analyser/MutatingScope.php

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2224,24 +2224,7 @@ public function enterAnonymousFunctionWithoutReflection(
22242224

22252225
private function shouldNotCarryForwardPropertyFetchInClosure(Expr $expr): bool
22262226
{
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;
2227+
return $expr instanceof PropertyFetch;
22452228
}
22462229

22472230
private function expressionTypeIsUnchangeable(ExpressionTypeHolder $typeHolder): bool

tests/PHPStan/Analyser/nsrt/specified-types-closure-use.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public function doFoo(MethodCall $call, MethodCall $bar): void
1313
{
1414
if ($call->name instanceof Identifier && $bar->name instanceof Identifier) {
1515
function () use ($call): void {
16-
assertType('PhpParser\Node\Identifier', $call->name);
16+
assertType('PhpParser\Node\Expr|PhpParser\Node\Identifier', $call->name);
1717
assertType('mixed', $bar->name);
1818
};
1919

@@ -26,7 +26,7 @@ public function doBar(MethodCall $call, MethodCall $bar): void
2626
if ($call->name instanceof Identifier && $bar->name instanceof Identifier) {
2727
$a = 1;
2828
function () use ($call, &$a): void {
29-
assertType('PhpParser\Node\Identifier', $call->name);
29+
assertType('PhpParser\Node\Expr|PhpParser\Node\Identifier', $call->name);
3030
assertType('mixed', $bar->name);
3131
};
3232

tests/PHPStan/Rules/Arrays/data/bug-10345.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,20 @@
1313
$container->items[] = '1';
1414

1515
$a = $func();
16+
17+
class Foo {
18+
/** @var list<string> */
19+
public array $items = [];
20+
}
21+
22+
$container2 = new Foo();
23+
$container2->items = [];
24+
25+
$func2 = function() use ($container2): int {
26+
foreach ($container2->items as $item) {}
27+
return 1;
28+
};
29+
30+
$container2->items[] = '1';
31+
32+
$a2 = $func2();

0 commit comments

Comments
 (0)