|
50 | 50 | use PhpParser\Node\Stmt\TryCatch; |
51 | 51 | use PhpParser\Node\Stmt\Unset_; |
52 | 52 | use PhpParser\Node\Stmt\While_; |
| 53 | +use PhpParser\NodeTraverser; |
| 54 | +use PhpParser\NodeVisitor\CloningVisitor; |
| 55 | +use PhpParser\NodeVisitorAbstract; |
53 | 56 | use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass; |
54 | 57 | use PHPStan\BetterReflection\Reflection\ReflectionEnum; |
55 | 58 | use PHPStan\BetterReflection\Reflector\Reflector; |
|
72 | 75 | use PHPStan\Node\DoWhileLoopConditionNode; |
73 | 76 | use PHPStan\Node\ExecutionEndNode; |
74 | 77 | use PHPStan\Node\Expr\AlwaysRememberedExpr; |
| 78 | +use PHPStan\Node\Expr\ExistingArrayDimFetch; |
75 | 79 | use PHPStan\Node\Expr\GetIterableKeyTypeExpr; |
76 | 80 | use PHPStan\Node\Expr\GetIterableValueTypeExpr; |
77 | 81 | use PHPStan\Node\Expr\GetOffsetValueTypeExpr; |
78 | 82 | use PHPStan\Node\Expr\OriginalPropertyTypeExpr; |
79 | 83 | use PHPStan\Node\Expr\PropertyInitializationExpr; |
| 84 | +use PHPStan\Node\Expr\SetExistingOffsetValueTypeExpr; |
80 | 85 | use PHPStan\Node\Expr\SetOffsetValueTypeExpr; |
81 | 86 | use PHPStan\Node\Expr\UnsetOffsetExpr; |
82 | 87 | use PHPStan\Node\FinallyExitPointsNode; |
@@ -1518,10 +1523,35 @@ private function processStmtNode( |
1518 | 1523 | $hasYield = $hasYield || $exprResult->hasYield(); |
1519 | 1524 | $throwPoints = array_merge($throwPoints, $exprResult->getThrowPoints()); |
1520 | 1525 | if ($var instanceof ArrayDimFetch && $var->dim !== null) { |
| 1526 | + $cloningTraverser = new NodeTraverser(); |
| 1527 | + $cloningTraverser->addVisitor(new CloningVisitor()); |
| 1528 | + |
| 1529 | + /** @var Expr $clonedVar */ |
| 1530 | + [$clonedVar] = $cloningTraverser->traverse([$var->var]); |
| 1531 | + |
| 1532 | + $traverser = new NodeTraverser(); |
| 1533 | + $traverser->addVisitor(new class () extends NodeVisitorAbstract { |
| 1534 | + |
| 1535 | + /** |
| 1536 | + * @return ExistingArrayDimFetch|null |
| 1537 | + */ |
| 1538 | + public function leaveNode(Node $node) |
| 1539 | + { |
| 1540 | + if (!$node instanceof ArrayDimFetch || $node->dim === null) { |
| 1541 | + return null; |
| 1542 | + } |
| 1543 | + |
| 1544 | + return new ExistingArrayDimFetch($node->var, $node->dim); |
| 1545 | + } |
| 1546 | + |
| 1547 | + }); |
| 1548 | + |
| 1549 | + /** @var Expr $clonedVar */ |
| 1550 | + [$clonedVar] = $traverser->traverse([$clonedVar]); |
1521 | 1551 | $scope = $this->processAssignVar( |
1522 | 1552 | $scope, |
1523 | 1553 | $stmt, |
1524 | | - $var->var, |
| 1554 | + $clonedVar, |
1525 | 1555 | new UnsetOffsetExpr($var->var, $var->dim), |
1526 | 1556 | static function (Node $node, Scope $scope) use ($nodeCallback): void { |
1527 | 1557 | if (!$node instanceof PropertyAssignNode) { |
@@ -4209,6 +4239,72 @@ static function (): void { |
4209 | 4239 | $hasYield = $hasYield || $result->hasYield(); |
4210 | 4240 | $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); |
4211 | 4241 | } |
| 4242 | + } elseif ($var instanceof ExistingArrayDimFetch) { |
| 4243 | + $dimFetchStack = []; |
| 4244 | + $assignedPropertyExpr = $assignedExpr; |
| 4245 | + while ($var instanceof ExistingArrayDimFetch) { |
| 4246 | + $varForSetOffsetValue = $var->getVar(); |
| 4247 | + if ($varForSetOffsetValue instanceof PropertyFetch || $varForSetOffsetValue instanceof StaticPropertyFetch) { |
| 4248 | + $varForSetOffsetValue = new OriginalPropertyTypeExpr($varForSetOffsetValue); |
| 4249 | + } |
| 4250 | + $assignedPropertyExpr = new SetExistingOffsetValueTypeExpr( |
| 4251 | + $varForSetOffsetValue, |
| 4252 | + $var->getDim(), |
| 4253 | + $assignedPropertyExpr, |
| 4254 | + ); |
| 4255 | + $dimFetchStack[] = $var; |
| 4256 | + $var = $var->getVar(); |
| 4257 | + } |
| 4258 | + |
| 4259 | + $offsetTypes = []; |
| 4260 | + $offsetNativeTypes = []; |
| 4261 | + foreach (array_reverse($dimFetchStack) as $dimFetch) { |
| 4262 | + $dimExpr = $dimFetch->getDim(); |
| 4263 | + $offsetTypes[] = $scope->getType($dimExpr); |
| 4264 | + $offsetNativeTypes[] = $scope->getNativeType($dimExpr); |
| 4265 | + } |
| 4266 | + |
| 4267 | + $valueToWrite = $scope->getType($assignedExpr); |
| 4268 | + $nativeValueToWrite = $scope->getNativeType($assignedExpr); |
| 4269 | + $varType = $scope->getType($var); |
| 4270 | + $varNativeType = $scope->getNativeType($var); |
| 4271 | + |
| 4272 | + $offsetValueType = $varType; |
| 4273 | + $offsetNativeValueType = $varNativeType; |
| 4274 | + $offsetValueTypeStack = [$offsetValueType]; |
| 4275 | + $offsetValueNativeTypeStack = [$offsetNativeValueType]; |
| 4276 | + foreach (array_slice($offsetTypes, 0, -1) as $offsetType) { |
| 4277 | + $offsetValueType = $offsetValueType->getOffsetValueType($offsetType); |
| 4278 | + $offsetValueTypeStack[] = $offsetValueType; |
| 4279 | + } |
| 4280 | + foreach (array_slice($offsetNativeTypes, 0, -1) as $offsetNativeType) { |
| 4281 | + $offsetNativeValueType = $offsetNativeValueType->getOffsetValueType($offsetNativeType); |
| 4282 | + $offsetValueNativeTypeStack[] = $offsetNativeValueType; |
| 4283 | + } |
| 4284 | + |
| 4285 | + foreach (array_reverse($offsetTypes) as $offsetType) { |
| 4286 | + /** @var Type $offsetValueType */ |
| 4287 | + $offsetValueType = array_pop($offsetValueTypeStack); |
| 4288 | + $valueToWrite = $offsetValueType->setExistingOffsetValueType($offsetType, $valueToWrite); |
| 4289 | + } |
| 4290 | + foreach (array_reverse($offsetNativeTypes) as $offsetNativeType) { |
| 4291 | + /** @var Type $offsetNativeValueType */ |
| 4292 | + $offsetNativeValueType = array_pop($offsetValueNativeTypeStack); |
| 4293 | + $nativeValueToWrite = $offsetNativeValueType->setExistingOffsetValueType($offsetNativeType, $nativeValueToWrite); |
| 4294 | + } |
| 4295 | + |
| 4296 | + if ($var instanceof Variable && is_string($var->name)) { |
| 4297 | + $scope = $scope->assignVariable($var->name, $valueToWrite, $nativeValueToWrite); |
| 4298 | + } else { |
| 4299 | + if ($var instanceof PropertyFetch || $var instanceof StaticPropertyFetch) { |
| 4300 | + $nodeCallback(new PropertyAssignNode($var, $assignedPropertyExpr, $isAssignOp), $scope); |
| 4301 | + } |
| 4302 | + $scope = $scope->assignExpression( |
| 4303 | + $var, |
| 4304 | + $valueToWrite, |
| 4305 | + $nativeValueToWrite, |
| 4306 | + ); |
| 4307 | + } |
4212 | 4308 | } |
4213 | 4309 |
|
4214 | 4310 | return new ExpressionResult($scope, $hasYield, $throwPoints); |
|
0 commit comments