2020use PHPStan \Type \Generic \TemplateUnionType ;
2121use function array_intersect_key ;
2222use function array_key_exists ;
23+ use function array_keys ;
2324use function array_map ;
2425use function array_merge ;
2526use function array_slice ;
2930use function get_class ;
3031use function is_int ;
3132use function md5 ;
33+ use function sprintf ;
3234use function usort ;
3335
3436/** @api */
@@ -171,7 +173,12 @@ public static function union(Type ...$types): Type
171173 continue ;
172174 }
173175 if ($ innerType instanceof AccessoryType || $ innerType instanceof CallableType) {
174- $ intermediateAccessoryTypes [$ innerType ->describe (VerbosityLevel::cache ())] = $ innerType ;
176+ if ($ innerType instanceof HasOffsetValueType) {
177+ $ intermediateAccessoryTypes [sprintf ('hasOffsetValue(%s) ' , $ innerType ->getOffsetType ()->describe (VerbosityLevel::cache ()))][] = $ innerType ;
178+ continue ;
179+ }
180+
181+ $ intermediateAccessoryTypes [$ innerType ->describe (VerbosityLevel::cache ())][] = $ innerType ;
175182 continue ;
176183 }
177184 }
@@ -191,7 +198,7 @@ public static function union(Type ...$types): Type
191198
192199 if ($ types [$ i ]->isIterableAtLeastOnce ()->yes ()) {
193200 $ nonEmpty = new NonEmptyArrayType ();
194- $ arrayAccessoryTypes [] = [$ nonEmpty ->describe (VerbosityLevel::cache ()) => $ nonEmpty ];
201+ $ arrayAccessoryTypes [] = [$ nonEmpty ->describe (VerbosityLevel::cache ()) => [ $ nonEmpty] ];
195202 } else {
196203 $ arrayAccessoryTypes [] = [];
197204 }
@@ -205,11 +212,22 @@ public static function union(Type ...$types): Type
205212 /** @var ArrayType[] $arrayTypes */
206213 $ arrayTypes = $ arrayTypes ;
207214
208- $ arrayAccessoryTypesToProcess = [];
215+ $ commonArrayAccessoryTypesKeys = [];
209216 if (count ($ arrayAccessoryTypes ) > 1 ) {
210- $ arrayAccessoryTypesToProcess = array_values (array_intersect_key (...$ arrayAccessoryTypes ));
217+ $ commonArrayAccessoryTypesKeys = array_keys (array_intersect_key (...$ arrayAccessoryTypes ));
211218 } elseif (count ($ arrayAccessoryTypes ) > 0 ) {
212- $ arrayAccessoryTypesToProcess = array_values ($ arrayAccessoryTypes [0 ]);
219+ $ commonArrayAccessoryTypesKeys = array_keys ($ arrayAccessoryTypes [0 ]);
220+ }
221+
222+ $ arrayAccessoryTypesToProcess = [];
223+ foreach ($ commonArrayAccessoryTypesKeys as $ commonKey ) {
224+ $ typesToUnion = [];
225+ foreach ($ arrayAccessoryTypes as $ array ) {
226+ foreach ($ array [$ commonKey ] as $ arrayAccessoryType ) {
227+ $ typesToUnion [] = $ arrayAccessoryType ;
228+ }
229+ }
230+ $ arrayAccessoryTypesToProcess [] = self ::union (...$ typesToUnion );
213231 }
214232
215233 $ types = array_values (
@@ -366,6 +384,11 @@ private static function compareTypesInUnion(Type $a, Type $b): ?array
366384 if ($ a instanceof IntegerRangeType && $ b instanceof IntegerRangeType) {
367385 return null ;
368386 }
387+ if ($ a instanceof HasOffsetValueType && $ b instanceof HasOffsetValueType) {
388+ if ($ a ->getOffsetType ()->equals ($ b ->getOffsetType ())) {
389+ return [new HasOffsetValueType ($ a ->getOffsetType (), self ::union ($ a ->getValueType (), $ b ->getValueType ())), null ];
390+ }
391+ }
369392
370393 if ($ a instanceof SubtractableType) {
371394 $ typeWithoutSubtractedTypeA = $ a ->getTypeWithoutSubtractedType ();
0 commit comments