|
91 | 91 | use PHPStan\Type\IntersectionType; |
92 | 92 | use PHPStan\Type\IterableType; |
93 | 93 | use PHPStan\Type\KeyOfType; |
| 94 | +use PHPStan\Type\LateResolvableArrayShapeType; |
94 | 95 | use PHPStan\Type\MixedType; |
95 | 96 | use PHPStan\Type\NewObjectType; |
96 | 97 | use PHPStan\Type\NonAcceptingNeverType; |
@@ -1139,94 +1140,43 @@ private function resolveCallableReturnTypeAssertions(CallableTypeNode $typeNode, |
1139 | 1140 |
|
1140 | 1141 | private function resolveArrayShapeNode(ArrayShapeNode $typeNode, NameScope $nameScope): Type |
1141 | 1142 | { |
1142 | | - $builder = ConstantArrayTypeBuilder::createEmpty(); |
1143 | | - $builder->disableArrayDegradation(); |
1144 | | - |
1145 | | - $explicitKeyValues = []; |
| 1143 | + $items = []; |
1146 | 1144 | foreach ($typeNode->items as $itemNode) { |
1147 | | - if ($itemNode->valueType instanceof CallableTypeNode) { |
1148 | | - $builder->disableClosureDegradation(); |
1149 | | - } |
1150 | | - |
1151 | | - $offsetType = $this->resolveArrayShapeOffsetType($itemNode, $nameScope); |
1152 | | - if ($offsetType instanceof ConstantIntegerType || $offsetType instanceof ConstantStringType) { |
1153 | | - $explicitKeyValues[] = $offsetType->getValue(); |
1154 | | - } |
1155 | | - $builder->setOffsetValueType($offsetType, $this->resolve($itemNode->valueType, $nameScope), $itemNode->optional); |
| 1145 | + $items[] = [ |
| 1146 | + $this->resolveArrayShapeOffsetType($itemNode, $nameScope), |
| 1147 | + $this->resolve($itemNode->valueType, $nameScope), |
| 1148 | + $itemNode->optional, |
| 1149 | + ]; |
1156 | 1150 | } |
1157 | 1151 |
|
1158 | | - $isList = in_array($typeNode->kind, [ |
1159 | | - ArrayShapeNode::KIND_LIST, |
1160 | | - ArrayShapeNode::KIND_NON_EMPTY_LIST, |
1161 | | - ], true); |
1162 | | - |
| 1152 | + $unsealed = null; |
1163 | 1153 | if (!$typeNode->sealed) { |
1164 | | - if ($typeNode->unsealedType === null) { |
1165 | | - if ($isList) { |
1166 | | - $unsealedKeyType = IntegerRangeType::createAllGreaterThanOrEqualTo(0); |
1167 | | - } else { |
1168 | | - $unsealedKeyType = (new BenevolentUnionType([new IntegerType(), new StringType()]))->toArrayKey(); |
1169 | | - } |
1170 | | - $builder->makeUnsealed( |
1171 | | - $unsealedKeyType, |
1172 | | - new MixedType(), |
1173 | | - ); |
1174 | | - } else { |
1175 | | - if ($typeNode->unsealedType->keyType === null) { |
1176 | | - if ($isList) { |
1177 | | - $unsealedKeyType = IntegerRangeType::createAllGreaterThanOrEqualTo(0); |
1178 | | - } else { |
1179 | | - $unsealedKeyType = (new BenevolentUnionType([new IntegerType(), new StringType()]))->toArrayKey(); |
1180 | | - } |
1181 | | - } else { |
1182 | | - $unsealedKeyType = $this->transformUnsafeArrayKey($this->resolve($typeNode->unsealedType->keyType, $nameScope)); |
1183 | | - } |
1184 | | - $unsealedKeyFiniteTypes = $unsealedKeyType->getFiniteTypes(); |
1185 | | - $unsealedValueType = $this->resolve($typeNode->unsealedType->valueType, $nameScope); |
1186 | | - if (count($unsealedKeyFiniteTypes) > 0) { |
1187 | | - foreach ($unsealedKeyFiniteTypes as $unsealedKeyFiniteType) { |
1188 | | - // Explicit keys own their slot — the unsealed extras |
1189 | | - // describe entries at keys NOT in the explicit set. |
1190 | | - if ( |
1191 | | - ($unsealedKeyFiniteType instanceof ConstantIntegerType || $unsealedKeyFiniteType instanceof ConstantStringType) |
1192 | | - && in_array($unsealedKeyFiniteType->getValue(), $explicitKeyValues, true) |
1193 | | - ) { |
1194 | | - continue; |
1195 | | - } |
1196 | | - $builder->setOffsetValueType($unsealedKeyFiniteType, $unsealedValueType, true); |
1197 | | - } |
1198 | | - } else { |
1199 | | - $builder->makeUnsealed($unsealedKeyType, $unsealedValueType); |
1200 | | - } |
1201 | | - } |
1202 | | - } |
| 1154 | + // A key type that is not written down is derived from the shape kind |
| 1155 | + // when the shape gets built, so that it can be printed back as `...`. |
| 1156 | + $unsealedKeyType = $typeNode->unsealedType === null || $typeNode->unsealedType->keyType === null |
| 1157 | + ? null |
| 1158 | + : $this->transformUnsafeArrayKey($this->resolve($typeNode->unsealedType->keyType, $nameScope)); |
1203 | 1159 |
|
1204 | | - $arrayType = $builder->getArray(); |
| 1160 | + $unsealedValueType = $typeNode->unsealedType === null |
| 1161 | + ? new MixedType() |
| 1162 | + : $this->resolve($typeNode->unsealedType->valueType, $nameScope); |
1205 | 1163 |
|
1206 | | - $accessories = []; |
1207 | | - if ($isList) { |
1208 | | - $accessories[] = new AccessoryArrayListType(); |
| 1164 | + $unsealed = [$unsealedKeyType, $unsealedValueType]; |
1209 | 1165 | } |
1210 | 1166 |
|
1211 | | - if (in_array($typeNode->kind, [ |
1212 | | - ArrayShapeNode::KIND_NON_EMPTY_ARRAY, |
1213 | | - ArrayShapeNode::KIND_NON_EMPTY_LIST, |
1214 | | - ], true)) { |
1215 | | - $accessories[] = new NonEmptyArrayType(); |
1216 | | - } |
1217 | | - |
1218 | | - if (count($accessories) > 0) { |
1219 | | - return TypeCombinator::intersect($arrayType, ...$accessories); |
1220 | | - } |
1221 | | - |
1222 | | - return $arrayType; |
| 1167 | + return LateResolvableArrayShapeType::create($items, $unsealed, $typeNode->kind); |
1223 | 1168 | } |
1224 | 1169 |
|
1225 | 1170 | private function resolveArrayShapeOffsetType(ArrayShapeItemNode $itemNode, NameScope $nameScope): ?Type |
1226 | 1171 | { |
1227 | 1172 | if ($itemNode->keyName instanceof ConstExprIntegerNode) { |
1228 | 1173 | return new ConstantIntegerType((int) $itemNode->keyName->value); |
1229 | 1174 | } elseif ($itemNode->keyName instanceof IdentifierTypeNode) { |
| 1175 | + $templateType = $nameScope->resolveTemplateTypeName($itemNode->keyName->name); |
| 1176 | + if ($templateType !== null) { |
| 1177 | + return $templateType; |
| 1178 | + } |
| 1179 | + |
1230 | 1180 | return new ConstantStringType($itemNode->keyName->name); |
1231 | 1181 | } elseif ($itemNode->keyName instanceof ConstExprStringNode) { |
1232 | 1182 | return new ConstantStringType($itemNode->keyName->value); |
|
0 commit comments