Skip to content

Commit c9d33e8

Browse files
phpstan-botclaudestaabm
authored
Invalidate property types after dynamic method calls (#5679)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Markus Staab <maggus.staab@googlemail.com>
1 parent 4fa4568 commit c9d33e8

3 files changed

Lines changed: 103 additions & 0 deletions

File tree

src/Analyser/ExprHandler/MethodCallHandler.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
176176
}
177177

178178
} else {
179+
$nodeScopeResolver->callNodeCallback($nodeCallback, new InvalidateExprNode($normalizedExpr->var), $scope, $storage);
180+
$scope = $scope->invalidateExpression($normalizedExpr->var, true);
179181
$throwPoints[] = InternalThrowPoint::createImplicit($scope, $expr);
180182
}
181183
$hasYield = $hasYield || $argsResult->hasYield();
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug3831Nsrt;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class DynamicMethodCall
8+
{
9+
public int $counter = 0;
10+
11+
/** @var array<string> */
12+
public array $footer = [];
13+
14+
public function test(): void
15+
{
16+
$this->counter = 0;
17+
assertType('0', $this->counter);
18+
19+
$this->{'increment'}();
20+
assertType('int', $this->counter);
21+
}
22+
23+
public function testDynamicVar(): void
24+
{
25+
$this->footer = [];
26+
assertType('array{}', $this->footer);
27+
28+
$method = 'compileSection';
29+
$this->{$method}();
30+
assertType('array<string>', $this->footer);
31+
}
32+
33+
private function increment(): int
34+
{
35+
$this->counter++;
36+
return 0;
37+
}
38+
39+
private function compileSection(): void
40+
{
41+
$this->footer[] = 'section-name';
42+
}
43+
}
44+
45+
class Template
46+
{
47+
/** @var array<string> */
48+
public $footer = [];
49+
50+
public function render(): string
51+
{
52+
$content = '';
53+
$this->footer = [];
54+
55+
assertType('array{}', $this->footer);
56+
$this->{'compileSection'}();
57+
assertType('array<string>', $this->footer);
58+
59+
if (count($this->footer) > 0) {
60+
$content = str_replace('some', 'thing', $content);
61+
}
62+
return $content;
63+
}
64+
65+
private function compileSection(): void
66+
{
67+
$this->footer[] = 'section-name';
68+
}
69+
}
70+
71+
class TemplateDynamicVar
72+
{
73+
/** @var array<string> */
74+
public $footer = [];
75+
76+
public function render(): string
77+
{
78+
$content = '';
79+
$this->footer = [];
80+
81+
assertType('array{}', $this->footer);
82+
$method = 'compileSection';
83+
$this->{$method}();
84+
assertType('array<string>', $this->footer);
85+
86+
if (count($this->footer) > 0) {
87+
$content = str_replace('some', 'thing', $content);
88+
}
89+
return $content;
90+
}
91+
92+
private function compileSection(): void
93+
{
94+
$this->footer[] = 'section-name';
95+
}
96+
}

tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,4 +308,9 @@ public function testBug11146(): void
308308
$this->analyse([__DIR__ . '/data/bug-11146.php'], []);
309309
}
310310

311+
public function testBug3831(): void
312+
{
313+
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-3831.php'], []);
314+
}
315+
311316
}

0 commit comments

Comments
 (0)