Skip to content

Commit 43de8fd

Browse files
phpstan-botclaude
authored andcommitted
Fix expressionHasNewInChain crash on Name nodes, add tests for all expression types
The recursive call in expressionHasNewInChain() could receive a Name node (e.g. from Foo::bar()) instead of an Expr, causing a type error. Added an instanceof Expr guard before recursing on $expr->class. Also added test coverage for all expression types handled by expressionHasNewInChain(): nullsafe method calls, property fetches, nullsafe property fetches, array dim fetches, static calls, and chained expressions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 97a045a commit 43de8fd

File tree

2 files changed

+62
-4
lines changed

2 files changed

+62
-4
lines changed

src/Analyser/MutatingScope.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1006,7 +1006,7 @@ private function expressionHasNewInChain(Expr $expr): bool
10061006
|| $expr instanceof Expr\StaticPropertyFetch
10071007
|| $expr instanceof Expr\ClassConstFetch
10081008
) {
1009-
return $expr->class instanceof Expr\New_ || $this->expressionHasNewInChain($expr->class);
1009+
return $expr->class instanceof Expr\New_ || ($expr->class instanceof Expr && $this->expressionHasNewInChain($expr->class));
10101010
}
10111011

10121012
return false;

tests/PHPStan/Analyser/nsrt/bug-8985.php

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88

99
class Entity
1010
{
11-
public function __construct(private string $value)
11+
public string $value;
12+
13+
public function __construct(string $value)
1214
{
15+
$this->value = $value;
1316
}
1417

1518
public function getValue(): string
@@ -25,12 +28,67 @@ public function getAll(): array
2528
{
2629
return [new Entity('test')];
2730
}
31+
32+
public string $name = 'default';
33+
34+
/** @return array<int, Entity> */
35+
public static function staticGetAll(): array
36+
{
37+
return [new Entity('test')];
38+
}
39+
40+
public function getEntity(): Entity
41+
{
42+
return new Entity('test');
43+
}
2844
}
2945

30-
function () : void {
46+
function testMethodCall(): void {
3147
assert((new Repository())->getAll() === []);
3248

3349
$all = (new Repository())->getAll();
3450
assertType('array<int, Bug8985\Entity>', $all);
3551
$value = $all[0]->getValue();
36-
};
52+
}
53+
54+
function testNullsafeMethodCall(): void {
55+
assert((new Repository())?->getEntity()?->getValue() === 'specific');
56+
57+
assertType('string', (new Repository())?->getEntity()?->getValue());
58+
}
59+
60+
function testPropertyFetch(): void {
61+
assert((new Repository())->name === 'foo');
62+
63+
assertType('string', (new Repository())->name);
64+
}
65+
66+
function testNullsafePropertyFetch(): void {
67+
assert((new Repository())?->name === 'foo');
68+
69+
assertType('string', (new Repository())?->name);
70+
}
71+
72+
function testArrayDimFetch(): void {
73+
assert((new Repository())->getAll()[0]->getValue() === 'specific');
74+
75+
assertType('string', (new Repository())->getAll()[0]->getValue());
76+
}
77+
78+
function testStaticCall(): void {
79+
assert((new Repository())::staticGetAll() === []);
80+
81+
assertType('array<int, Bug8985\Entity>', (new Repository())::staticGetAll());
82+
}
83+
84+
function testChainedMethodCalls(): void {
85+
assert((new Repository())->getEntity()->getValue() === 'specific');
86+
87+
assertType('string', (new Repository())->getEntity()->getValue());
88+
}
89+
90+
function testChainedPropertyOnMethodCall(): void {
91+
assert((new Repository())->getEntity()->value === 'specific');
92+
93+
assertType('string', (new Repository())->getEntity()->value);
94+
}

0 commit comments

Comments
 (0)