|
24 | 24 | use PHPStan\Node\LiteralArrayNode; |
25 | 25 | use PHPStan\Reflection\InitializerExprTypeResolver; |
26 | 26 | use PHPStan\Type\CallableType; |
| 27 | +use PHPStan\Type\Constant\ConstantIntegerType; |
27 | 28 | use PHPStan\Type\Type; |
28 | 29 | use PHPStan\Type\TypeCombinator; |
29 | 30 | use function array_merge; |
30 | 31 | use function count; |
| 32 | +use function is_int; |
31 | 33 |
|
32 | 34 | /** |
33 | 35 | * @implements ExprHandler<Array_> |
@@ -72,27 +74,47 @@ public function resolveType(MutatingScope $scope, Expr $expr): Type |
72 | 74 | return $type; |
73 | 75 | } |
74 | 76 |
|
75 | | - public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult |
| 77 | + public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context, ?Type $overriddenType): ExpressionResult |
76 | 78 | { |
77 | 79 | $beforeScope = $scope; |
78 | 80 | $itemNodes = []; |
79 | 81 | $hasYield = false; |
80 | 82 | $throwPoints = []; |
81 | 83 | $impurePoints = []; |
82 | 84 | $isAlwaysTerminating = false; |
| 85 | + $nextAutoIndex = 0; |
83 | 86 | foreach ($expr->items as $arrayItem) { |
84 | 87 | $itemNodes[] = new LiteralArrayItem($scope, $arrayItem); |
85 | 88 | $nodeScopeResolver->callNodeCallback($nodeCallback, $arrayItem, $scope, $storage); |
| 89 | + $keyType = new ConstantIntegerType($nextAutoIndex); |
86 | 90 | if ($arrayItem->key !== null) { |
87 | | - $keyResult = $nodeScopeResolver->processExprNode($stmt, $arrayItem->key, $scope, $storage, $nodeCallback, $context->enterDeep()); |
| 91 | + $keyType = $scope->getType($arrayItem->key); |
| 92 | + $keyResult = $nodeScopeResolver->processExprNode($stmt, $arrayItem->key, $scope, $storage, $nodeCallback, $context->enterDeep(), null); |
88 | 93 | $hasYield = $hasYield || $keyResult->hasYield(); |
89 | 94 | $throwPoints = array_merge($throwPoints, $keyResult->getThrowPoints()); |
90 | 95 | $impurePoints = array_merge($impurePoints, $keyResult->getImpurePoints()); |
91 | 96 | $isAlwaysTerminating = $isAlwaysTerminating || $keyResult->isAlwaysTerminating(); |
92 | 97 | $scope = $keyResult->getScope(); |
93 | 98 | } |
94 | 99 |
|
95 | | - $valueResult = $nodeScopeResolver->processExprNode($stmt, $arrayItem->value, $scope, $storage, $nodeCallback, $context->enterDeep()); |
| 100 | + // an overridden array type prices each item against the type declared |
| 101 | + // for its key, so a nested closure is walked with the parameters the |
| 102 | + // extension announced instead of the declared (contravariant) ones |
| 103 | + $overriddenValueType = null; |
| 104 | + if ($overriddenType !== null && $overriddenType->hasOffsetValueType($keyType)->yes()) { |
| 105 | + $overriddenValueType = $overriddenType->getOffsetValueType($keyType); |
| 106 | + } |
| 107 | + |
| 108 | + if ($arrayItem->key === null) { |
| 109 | + $nextAutoIndex++; |
| 110 | + } else { |
| 111 | + $keyIntegers = $keyType->getConstantScalarValues(); |
| 112 | + if (count($keyIntegers) === 1 && is_int($keyIntegers[0])) { |
| 113 | + $nextAutoIndex = $keyIntegers[0] + 1; |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + $valueResult = $nodeScopeResolver->processExprNode($stmt, $arrayItem->value, $scope, $storage, $nodeCallback, $context->enterDeep(), $overriddenValueType); |
96 | 118 | $hasYield = $hasYield || $valueResult->hasYield(); |
97 | 119 | $throwPoints = array_merge($throwPoints, $valueResult->getThrowPoints()); |
98 | 120 | $impurePoints = array_merge($impurePoints, $valueResult->getImpurePoints()); |
|
0 commit comments