Skip to content

Commit bc76dc0

Browse files
phpstan-botclaude
andcommitted
Propagate types bidirectionally through by-ref array items
- Add reverse intertwined entries in AssignHandler so that assigning to $b[$key] propagates through to the referenced variable $a when $b = [&$a] - Preserve intertwined entries during invalidation when the invalidated variable is the entry's trigger or target variable - Widen referenced variables to mixed when a variable containing references is passed to an impure function - Update test assertions to reflect correct propagation behavior Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e013a69 commit bc76dc0

4 files changed

Lines changed: 61 additions & 5 deletions

File tree

src/Analyser/ExprHandler/AssignHandler.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,17 @@ static function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $contex
208208
$type,
209209
$nativeType,
210210
);
211+
212+
// When $targetVar is assigned (e.g. $b[0] = 42), update $refVarName
213+
$scope = $scope->assignExpression(
214+
new IntertwinedVariableByReferenceWithExpr(
215+
$targetVarName,
216+
new Variable($refVarName),
217+
new ArrayDimFetch(new Variable($targetVarName), $key),
218+
),
219+
$type,
220+
$nativeType,
221+
);
211222
}
212223
}
213224

src/Analyser/MutatingScope.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2835,7 +2835,21 @@ public function invalidateExpression(Expr $expressionToInvalidate, bool $require
28352835
$exprExpr = $exprTypeHolder->getExpr();
28362836
if (
28372837
$exprExpr instanceof IntertwinedVariableByReferenceWithExpr
2838-
&& $exprExpr->isVariableToVariableReference()
2838+
&& (
2839+
$exprExpr->isVariableToVariableReference()
2840+
|| (
2841+
$expressionToInvalidate instanceof Variable
2842+
&& is_string($expressionToInvalidate->name)
2843+
&& (
2844+
$exprExpr->getVariableName() === $expressionToInvalidate->name
2845+
|| (
2846+
$exprExpr->getExpr() instanceof Variable
2847+
&& is_string($exprExpr->getExpr()->name)
2848+
&& $exprExpr->getExpr()->name === $expressionToInvalidate->name
2849+
)
2850+
)
2851+
)
2852+
)
28392853
) {
28402854
continue;
28412855
}

src/Analyser/NodeScopeResolver.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
use PHPStan\Node\DoWhileLoopConditionNode;
7575
use PHPStan\Node\ExecutionEndNode;
7676
use PHPStan\Node\Expr\ExistingArrayDimFetch;
77+
use PHPStan\Node\Expr\IntertwinedVariableByReferenceWithExpr;
7778
use PHPStan\Node\Expr\ForeachValueByRefExpr;
7879
use PHPStan\Node\Expr\GetIterableKeyTypeExpr;
7980
use PHPStan\Node\Expr\GetIterableValueTypeExpr;
@@ -3556,6 +3557,36 @@ public function processArgs(
35563557
)->getScope();
35573558
}
35583559
}
3560+
3561+
if (
3562+
!$assignByReference
3563+
&& $calleeReflection !== null
3564+
&& !$calleeReflection->hasSideEffects()->no()
3565+
&& $arg->value instanceof Variable
3566+
&& is_string($arg->value->name)
3567+
) {
3568+
$argVarName = $arg->value->name;
3569+
foreach ($scope->expressionTypes as $exprTypeHolder) {
3570+
$exprExpr = $exprTypeHolder->getExpr();
3571+
if (!$exprExpr instanceof IntertwinedVariableByReferenceWithExpr) {
3572+
continue;
3573+
}
3574+
if ($exprExpr->getVariableName() !== $argVarName) {
3575+
continue;
3576+
}
3577+
if (!($exprExpr->getExpr() instanceof Variable) || !is_string($exprExpr->getExpr()->name)) {
3578+
continue;
3579+
}
3580+
$scope = $this->processVirtualAssign(
3581+
$scope,
3582+
$storage,
3583+
$stmt,
3584+
$exprExpr->getExpr(),
3585+
new TypeExpr(new MixedType()),
3586+
$nodeCallback,
3587+
)->getScope();
3588+
}
3589+
}
35593590
}
35603591
}
35613592

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ function testByRefInArray(): void
5454
assertType('array{}', $a);
5555

5656
foo($b);
57-
assertType('array{}', $a);
57+
assertType('mixed', $a);
5858
}
5959

6060
function testByRefInArrayWithKey(): void
@@ -66,7 +66,7 @@ function testByRefInArrayWithKey(): void
6666
assertType("'hello'", $a);
6767

6868
$b['key'] = 42;
69-
assertType("'hello'", $a);
69+
assertType('42', $a);
7070
}
7171

7272
function testMultipleByRefInArray(): void
@@ -82,6 +82,6 @@ function testMultipleByRefInArray(): void
8282
$b[1] = 'foo';
8383
$b[2] = 'bar';
8484

85-
assertType('1', $a);
86-
assertType("'test'", $c);
85+
assertType('2', $a);
86+
assertType("'bar'", $c);
8787
}

0 commit comments

Comments
 (0)