Skip to content

Commit 8d2b7d4

Browse files
phpstan-botclaude
andcommitted
Use dimFetch flag instead of checking assigned expression type for $this reassignment
$this = $value should still be reported even when $value implements ArrayAccess. The fix now tracks whether the VariableAssignNode originated from a dimension-fetch assignment ($this[$key] = $value) vs a direct assignment ($this = $value). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 97e4965 commit 8d2b7d4

5 files changed

Lines changed: 42 additions & 7 deletions

File tree

src/Analyser/ExprHandler/AssignHandler.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ public function processAssignVar(
463463

464464
if ($varType->isArray()->yes() || !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->yes()) {
465465
if ($var instanceof Variable && is_string($var->name)) {
466-
$nodeScopeResolver->callNodeCallback($nodeCallback, new VariableAssignNode($var, new TypeExpr($valueToWrite)), $scopeBeforeAssignEval, $storage);
466+
$nodeScopeResolver->callNodeCallback($nodeCallback, new VariableAssignNode($var, new TypeExpr($valueToWrite), true), $scopeBeforeAssignEval, $storage);
467467
$scope = $scope->assignVariable($var->name, $valueToWrite, $nativeValueToWrite, TrinaryLogic::createYes());
468468
} else {
469469
if ($var instanceof PropertyFetch || $var instanceof StaticPropertyFetch) {
@@ -480,7 +480,7 @@ public function processAssignVar(
480480
}
481481
} else {
482482
if ($var instanceof Variable) {
483-
$nodeScopeResolver->callNodeCallback($nodeCallback, new VariableAssignNode($var, $assignedPropertyExpr), $scopeBeforeAssignEval, $storage);
483+
$nodeScopeResolver->callNodeCallback($nodeCallback, new VariableAssignNode($var, $assignedPropertyExpr, true), $scopeBeforeAssignEval, $storage);
484484
} elseif ($var instanceof PropertyFetch || $var instanceof StaticPropertyFetch) {
485485
$nodeScopeResolver->callNodeCallback($nodeCallback, new PropertyAssignNode($var, $assignedPropertyExpr, $isAssignOp), $scopeBeforeAssignEval, $storage);
486486
if ($var instanceof PropertyFetch && $var->name instanceof Node\Identifier && !$isAssignOp) {
@@ -799,7 +799,7 @@ public function processAssignVar(
799799
}
800800

801801
if ($var instanceof Variable && is_string($var->name)) {
802-
$nodeScopeResolver->callNodeCallback($nodeCallback, new VariableAssignNode($var, $assignedPropertyExpr), $scope, $storage);
802+
$nodeScopeResolver->callNodeCallback($nodeCallback, new VariableAssignNode($var, $assignedPropertyExpr, true), $scope, $storage);
803803
$scope = $scope->assignVariable($var->name, $valueToWrite, $nativeValueToWrite, TrinaryLogic::createYes());
804804
} else {
805805
if ($var instanceof PropertyFetch || $var instanceof StaticPropertyFetch) {

src/Node/VariableAssignNode.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ final class VariableAssignNode extends NodeAbstract implements VirtualNode
1212
public function __construct(
1313
private Expr\Variable $variable,
1414
private Expr $assignedExpr,
15+
private bool $dimFetch = false,
1516
)
1617
{
1718
parent::__construct($variable->getAttributes());
@@ -27,6 +28,11 @@ public function getAssignedExpr(): Expr
2728
return $this->assignedExpr;
2829
}
2930

31+
public function isDimFetch(): bool
32+
{
33+
return $this->dimFetch;
34+
}
35+
3036
#[Override]
3137
public function getType(): string
3238
{

src/Rules/Variables/InvalidVariableAssignRule.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,7 @@ public function processNode(Node $node, Scope $scope): array
3232
}
3333

3434
if ($variable->name === 'this') {
35-
$expr = $node->getAssignedExpr();
36-
$type = $scope->getType($expr);
37-
38-
if ((new ObjectType(ArrayAccess::class))->isSuperTypeOf($type)->yes()) {
35+
if ($node->isDimFetch() && (new ObjectType(ArrayAccess::class))->isSuperTypeOf($scope->getType($variable))->yes()) {
3936
return [];
4037
}
4138

tests/PHPStan/Rules/Variables/InvalidVariableAssignRuleTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ public function testBug14352(): void
6363
'Cannot re-assign $this.',
6464
37,
6565
],
66+
[
67+
'Cannot re-assign $this.',
68+
61,
69+
],
70+
[
71+
'Cannot re-assign $this.',
72+
69,
73+
],
6674
]);
6775
}
6876

tests/PHPStan/Rules/Variables/data/bug-14352.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,30 @@ public function offsetUnset(mixed $offset): void
3030
}
3131
}
3232

33+
class TestArrayAccessReassign implements ArrayAccess
34+
{
35+
public function doFoo(self $other): void
36+
{
37+
$this = $other;
38+
}
39+
40+
public function offsetExists(mixed $offset): bool
41+
{
42+
}
43+
44+
public function offsetGet(mixed $offset): mixed
45+
{
46+
}
47+
48+
public function offsetSet(mixed $offset, mixed $value): void
49+
{
50+
}
51+
52+
public function offsetUnset(mixed $offset): void
53+
{
54+
}
55+
}
56+
3357
final class FinalTestPlain
3458
{
3559
public function doFoo(string $key, string $value): void

0 commit comments

Comments
 (0)