Skip to content

Commit fe94d1d

Browse files
committed
Support nested assigns
1 parent 14c48fc commit fe94d1d

File tree

2 files changed

+44
-26
lines changed

2 files changed

+44
-26
lines changed

src/Analyser/NodeScopeResolver.php

+31-26
Original file line numberDiff line numberDiff line change
@@ -5450,14 +5450,15 @@ private function processAssignVar(
54505450
$offsetValueType = $varType;
54515451
$offsetNativeValueType = $varNativeType;
54525452

5453-
$valueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetTypes, $offsetValueType, $valueToWrite);
5453+
$valueToWrite = $this->produceArrayDimFetchAssignValueToWrite($dimFetchStack, $offsetTypes, $offsetValueType, $valueToWrite, $scope);
54545454

54555455
if (!$offsetValueType->equals($offsetNativeValueType) || !$valueToWrite->equals($nativeValueToWrite)) {
5456-
$nativeValueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite);
5456+
$nativeValueToWrite = $this->produceArrayDimFetchAssignValueToWrite($dimFetchStack, $offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite, $scope);
54575457
} else {
54585458
$rewritten = false;
54595459
foreach ($offsetTypes as $i => $offsetType) {
54605460
$offsetNativeType = $offsetNativeTypes[$i];
5461+
54615462
if ($offsetType === null) {
54625463
if ($offsetNativeType !== null) {
54635464
throw new ShouldNotHappenException();
@@ -5471,7 +5472,7 @@ private function processAssignVar(
54715472
continue;
54725473
}
54735474

5474-
$nativeValueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite);
5475+
$nativeValueToWrite = $this->produceArrayDimFetchAssignValueToWrite($dimFetchStack, $offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite, $scope);
54755476
$rewritten = true;
54765477
break;
54775478
}
@@ -5482,28 +5483,6 @@ private function processAssignVar(
54825483
}
54835484

54845485
if ($varType->isArray()->yes() || !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->yes()) {
5485-
if ($varType->isList()->yes()) {
5486-
if ($scope->hasExpressionType($originalVar)->yes()) { // keep list for $list[$index] assignments
5487-
$valueToWrite = TypeCombinator::intersect($valueToWrite, new AccessoryArrayListType());
5488-
} elseif ($originalVar->dim instanceof BinaryOp\Plus) {
5489-
if ( // keep list for $list[$index + 1] assignments
5490-
$originalVar->dim->right instanceof Variable
5491-
&& $originalVar->dim->left instanceof Node\Scalar\Int_
5492-
&& $originalVar->dim->left->value === 1
5493-
&& $scope->hasExpressionType(new ArrayDimFetch($originalVar->var, $originalVar->dim->right))->yes()
5494-
) {
5495-
$valueToWrite = TypeCombinator::intersect($valueToWrite, new AccessoryArrayListType());
5496-
} elseif ( // keep list for $list[1 + $index] assignments
5497-
$originalVar->dim->left instanceof Variable
5498-
&& $originalVar->dim->right instanceof Node\Scalar\Int_
5499-
&& $originalVar->dim->right->value === 1
5500-
&& $scope->hasExpressionType(new ArrayDimFetch($originalVar->var, $originalVar->dim->left))->yes()
5501-
) {
5502-
$valueToWrite = TypeCombinator::intersect($valueToWrite, new AccessoryArrayListType());
5503-
}
5504-
}
5505-
}
5506-
55075486
if ($var instanceof Variable && is_string($var->name)) {
55085487
$nodeCallback(new VariableAssignNode($var, $assignedPropertyExpr, $isAssignOp), $scope);
55095488
$scope = $scope->assignVariable($var->name, $valueToWrite, $nativeValueToWrite, TrinaryLogic::createYes());
@@ -5806,9 +5785,10 @@ static function (): void {
58065785
}
58075786

58085787
/**
5788+
* @param list<ArrayDimFetch> $dimFetchStack
58095789
* @param list<Type|null> $offsetTypes
58105790
*/
5811-
private function produceArrayDimFetchAssignValueToWrite(array $offsetTypes, Type $offsetValueType, Type $valueToWrite): Type
5791+
private function produceArrayDimFetchAssignValueToWrite(array $dimFetchStack, array $offsetTypes, Type $offsetValueType, Type $valueToWrite, Scope $scope): Type
58125792
{
58135793
$offsetValueTypeStack = [$offsetValueType];
58145794
foreach (array_slice($offsetTypes, 0, -1) as $offsetType) {
@@ -5843,6 +5823,31 @@ private function produceArrayDimFetchAssignValueToWrite(array $offsetTypes, Type
58435823
$offsetValueType = TypeCombinator::intersect($offsetValueType, TypeCombinator::union(...$types));
58445824
}
58455825
$valueToWrite = $offsetValueType->setOffsetValueType($offsetType, $valueToWrite, $i === 0);
5826+
5827+
$arrayDimFetch = $dimFetchStack[$i] ?? null;
5828+
if ($arrayDimFetch === null || !$offsetValueType->isList()->yes()) {
5829+
continue;
5830+
}
5831+
5832+
if ($scope->hasExpressionType($arrayDimFetch)->yes()) { // keep list for $list[$index] assignments
5833+
$valueToWrite = TypeCombinator::intersect($valueToWrite, new AccessoryArrayListType());
5834+
} elseif ($arrayDimFetch->dim instanceof BinaryOp\Plus) {
5835+
if ( // keep list for $list[$index + 1] assignments
5836+
$arrayDimFetch->dim->right instanceof Variable
5837+
&& $arrayDimFetch->dim->left instanceof Node\Scalar\Int_
5838+
&& $arrayDimFetch->dim->left->value === 1
5839+
&& $scope->hasExpressionType(new ArrayDimFetch($arrayDimFetch->var, $arrayDimFetch->dim->right))->yes()
5840+
) {
5841+
$valueToWrite = TypeCombinator::intersect($valueToWrite, new AccessoryArrayListType());
5842+
} elseif ( // keep list for $list[1 + $index] assignments
5843+
$arrayDimFetch->dim->left instanceof Variable
5844+
&& $arrayDimFetch->dim->right instanceof Node\Scalar\Int_
5845+
&& $arrayDimFetch->dim->right->value === 1
5846+
&& $scope->hasExpressionType(new ArrayDimFetch($arrayDimFetch->var, $arrayDimFetch->dim->left))->yes()
5847+
) {
5848+
$valueToWrite = TypeCombinator::intersect($valueToWrite, new AccessoryArrayListType());
5849+
}
5850+
}
58465851
}
58475852

58485853
return $valueToWrite;

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

+13
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,19 @@ function testKeepListAfterIssetIndex(array $list, int $i): void
4949
assertType('list<int>', $list);
5050
}
5151

52+
/** @param list<list<int>> $nestedList */
53+
function testKeepNestedListAfterIssetIndex(array $nestedList, int $i, int $j): void
54+
{
55+
if (isset($nestedList[$i][$j])) {
56+
assertType('list<list<int>>', $nestedList);
57+
assertType('list<int>', $nestedList[$i]);
58+
$nestedList[$i][$j] = 21;
59+
assertType('non-empty-list<non-empty-list<int>>', $nestedList);
60+
assertType('non-empty-list<int>', $nestedList[$i]);
61+
}
62+
assertType('list<list<int>>', $nestedList);
63+
}
64+
5265
/** @param list<int> $list */
5366
function testKeepListAfterIssetIndexPlusOne(array $list, int $i): void
5467
{

0 commit comments

Comments
 (0)