Skip to content

Commit f1f6eb7

Browse files
phpstan-botclaude
andcommitted
Move intertwined ref preservation from assignVariable to invalidateExpression
Instead of preserving and restoring non-variable-to-variable intertwined refs in assignVariable, handle them in invalidateExpression by skipping refs whose endpoint variables (variableName, root of expr, root of assignedExpr) match the variable being invalidated. Refs are still invalidated when a non-endpoint variable (e.g. a dim index like $k in $a[$k]) changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f6a987e commit f1f6eb7

2 files changed

Lines changed: 18 additions & 61 deletions

File tree

src/Analyser/MutatingScope.php

Lines changed: 18 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2573,29 +2573,6 @@ public function isUndefinedExpressionAllowed(Expr $expr): bool
25732573
public function assignVariable(string $variableName, Type $type, Type $nativeType, TrinaryLogic $certainty, array $intertwinedPropagatedFrom = []): self
25742574
{
25752575
$node = new Variable($variableName);
2576-
2577-
// Collect non-variable-to-variable intertwined refs for this variable before invalidation,
2578-
// as they may be lost during assignExpression and recursive propagation
2579-
$preservedIntertwinedRefs = [];
2580-
$preservedNativeIntertwinedRefs = [];
2581-
foreach ($this->expressionTypes as $exprString => $exprTypeHolder) {
2582-
$exprExpr = $exprTypeHolder->getExpr();
2583-
if (
2584-
!($exprExpr instanceof IntertwinedVariableByReferenceWithExpr)
2585-
|| $exprExpr->getVariableName() !== $variableName
2586-
|| $exprExpr->isVariableToVariableReference()
2587-
) {
2588-
continue;
2589-
}
2590-
2591-
$preservedIntertwinedRefs[$exprString] = $exprTypeHolder;
2592-
if (!array_key_exists($exprString, $this->nativeExpressionTypes)) {
2593-
continue;
2594-
}
2595-
2596-
$preservedNativeIntertwinedRefs[$exprString] = $this->nativeExpressionTypes[$exprString];
2597-
}
2598-
25992576
$scope = $this->assignExpression($node, $type, $nativeType);
26002577
if ($certainty->no()) {
26012578
throw new ShouldNotHappenException();
@@ -2653,33 +2630,6 @@ public function assignVariable(string $variableName, Type $type, Type $nativeTyp
26532630
foreach ($invalidatedIntertwinedRefs as $exprString) {
26542631
unset($scope->expressionTypes[$exprString]);
26552632
unset($scope->nativeExpressionTypes[$exprString]);
2656-
unset($preservedIntertwinedRefs[$exprString]);
2657-
unset($preservedNativeIntertwinedRefs[$exprString]);
2658-
}
2659-
2660-
// Re-add intertwined refs that were lost during propagation
2661-
foreach ($preservedIntertwinedRefs as $exprString => $exprTypeHolder) {
2662-
if (array_key_exists($exprString, $scope->expressionTypes)) {
2663-
continue;
2664-
}
2665-
2666-
$intertwinedExpr = $exprTypeHolder->getExpr();
2667-
if ($intertwinedExpr instanceof IntertwinedVariableByReferenceWithExpr) {
2668-
$assignedExpr = $intertwinedExpr->getAssignedExpr();
2669-
if ($assignedExpr instanceof Expr\ArrayDimFetch && $assignedExpr->var instanceof Expr\ArrayDimFetch && !$this->isNestedDimFetchPathValid($scope, $assignedExpr)) {
2670-
unset($preservedNativeIntertwinedRefs[$exprString]);
2671-
continue;
2672-
}
2673-
}
2674-
2675-
$scope->expressionTypes[$exprString] = $exprTypeHolder;
2676-
}
2677-
foreach ($preservedNativeIntertwinedRefs as $exprString => $exprTypeHolder) {
2678-
if (array_key_exists($exprString, $scope->nativeExpressionTypes)) {
2679-
continue;
2680-
}
2681-
2682-
$scope->nativeExpressionTypes[$exprString] = $exprTypeHolder;
26832633
}
26842634

26852635
return $scope;
@@ -2918,7 +2868,13 @@ public function invalidateExpression(Expr $expressionToInvalidate, bool $require
29182868
$exprExpr = $exprTypeHolder->getExpr();
29192869
if (
29202870
$exprExpr instanceof IntertwinedVariableByReferenceWithExpr
2921-
&& $exprExpr->isVariableToVariableReference()
2871+
&& $expressionToInvalidate instanceof Variable
2872+
&& is_string($expressionToInvalidate->name)
2873+
&& (
2874+
$exprExpr->getVariableName() === $expressionToInvalidate->name
2875+
|| $this->getIntertwinedRefRootVariableName($exprExpr->getExpr()) === $expressionToInvalidate->name
2876+
|| $this->getIntertwinedRefRootVariableName($exprExpr->getAssignedExpr()) === $expressionToInvalidate->name
2877+
)
29222878
) {
29232879
continue;
29242880
}
@@ -2989,6 +2945,17 @@ public function invalidateExpression(Expr $expressionToInvalidate, bool $require
29892945
);
29902946
}
29912947

2948+
private function getIntertwinedRefRootVariableName(Expr $expr): ?string
2949+
{
2950+
if ($expr instanceof Variable && is_string($expr->name)) {
2951+
return $expr->name;
2952+
}
2953+
if ($expr instanceof Expr\ArrayDimFetch) {
2954+
return $this->getIntertwinedRefRootVariableName($expr->var);
2955+
}
2956+
return null;
2957+
}
2958+
29922959
private function shouldInvalidateExpression(string $exprStringToInvalidate, Expr $exprToInvalidate, Expr $expr, string $exprString, bool $requireMoreCharacters = false, ?ClassReflection $invalidatingClass = null): bool
29932960
{
29942961
if ($requireMoreCharacters && $exprStringToInvalidate === $exprString) {

src/Node/Expr/IntertwinedVariableByReferenceWithExpr.php

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44

55
use Override;
66
use PhpParser\Node\Expr;
7-
use PhpParser\Node\Expr\Variable;
87
use PHPStan\Node\VirtualNode;
9-
use function is_string;
108

119
final class IntertwinedVariableByReferenceWithExpr extends Expr implements VirtualNode
1210
{
@@ -31,14 +29,6 @@ public function getAssignedExpr(): Expr
3129
return $this->assignedExpr;
3230
}
3331

34-
public function isVariableToVariableReference(): bool
35-
{
36-
return $this->expr instanceof Variable
37-
&& is_string($this->expr->name)
38-
&& $this->assignedExpr instanceof Variable
39-
&& is_string($this->assignedExpr->name);
40-
}
41-
4232
#[Override]
4333
public function getType(): string
4434
{

0 commit comments

Comments
 (0)