@@ -247,6 +247,10 @@ public static function union(Type ...$types): Type
247247 unset($ types [$ i ]);
248248 }
249249
250+ foreach ($ scalarTypes as $ classType => $ scalarTypeItems ) {
251+ $ scalarTypes [$ classType ] = array_values ($ scalarTypeItems );
252+ }
253+
250254 /** @var ArrayType[] $arrayTypes */
251255 $ arrayTypes = $ arrayTypes ;
252256
@@ -280,105 +284,68 @@ public static function union(Type ...$types): Type
280284
281285 foreach ($ scalarTypes as $ classType => $ scalarTypeItems ) {
282286 if (isset ($ hasGenericScalarTypes [$ classType ])) {
287+ unset($ scalarTypes [$ classType ]);
283288 continue ;
284289 }
285290 if ($ classType === ConstantBooleanType::class && count ($ scalarTypeItems ) === 2 ) {
286291 $ types [] = new BooleanType ();
292+ unset($ scalarTypes [$ classType ]);
287293 continue ;
288294 }
289- foreach ($ scalarTypeItems as $ type ) {
290- $ types [] = $ type ;
291- }
292- }
293295
294- // transform A | A to A
295- // transform A | never to A
296- for ($ i = 0 ; $ i < count ($ types ); $ i ++) {
297- for ($ j = $ i + 1 ; $ j < count ($ types ); $ j ++) {
298- if ($ types [$ i ] instanceof IntegerRangeType) {
299- $ type = $ types [$ i ]->tryUnion ($ types [$ j ]);
300- if ($ type !== null ) {
301- $ types [$ i ] = $ type ;
302- $ i --;
303- array_splice ($ types , $ j , 1 );
304- continue 2 ;
296+ for ($ i = 0 ; $ i < count ($ scalarTypeItems ); $ i ++) {
297+ for ($ j = 0 ; $ j < count ($ types ); $ j ++) {
298+ $ compareResult = self ::compareTypesInUnion ($ scalarTypeItems [$ i ], $ types [$ j ]);
299+ if ($ compareResult === null ) {
300+ continue ;
305301 }
306- }
307302
308- if ($ types [$ i ] instanceof SubtractableType) {
309- $ typeWithoutSubtractedTypeA = $ types [$ i ]->getTypeWithoutSubtractedType ();
310- if ($ typeWithoutSubtractedTypeA instanceof MixedType && $ types [$ j ] instanceof MixedType) {
311- $ isSuperType = $ typeWithoutSubtractedTypeA ->isSuperTypeOfMixed ($ types [$ j ]);
312- } else {
313- $ isSuperType = $ typeWithoutSubtractedTypeA ->isSuperTypeOf ($ types [$ j ]);
314- }
315- if ($ isSuperType ->yes ()) {
316- $ subtractedType = null ;
317- if ($ types [$ j ] instanceof SubtractableType) {
318- $ subtractedType = $ types [$ j ]->getSubtractedType ();
319- }
320- $ types [$ i ] = self ::intersectWithSubtractedType ($ types [$ i ], $ subtractedType );
303+ [$ a , $ b ] = $ compareResult ;
304+ if ($ a !== null ) {
305+ $ scalarTypeItems [$ i ] = $ a ;
321306 array_splice ($ types , $ j --, 1 );
322307 continue 1 ;
323308 }
324- }
325-
326- if ($ types [$ j ] instanceof SubtractableType) {
327- $ typeWithoutSubtractedTypeB = $ types [$ j ]->getTypeWithoutSubtractedType ();
328- if ($ typeWithoutSubtractedTypeB instanceof MixedType && $ types [$ i ] instanceof MixedType) {
329- $ isSuperType = $ typeWithoutSubtractedTypeB ->isSuperTypeOfMixed ($ types [$ i ]);
330- } else {
331- $ isSuperType = $ typeWithoutSubtractedTypeB ->isSuperTypeOf ($ types [$ i ]);
332- }
333- if ($ isSuperType ->yes ()) {
334- $ subtractedType = null ;
335- if ($ types [$ i ] instanceof SubtractableType) {
336- $ subtractedType = $ types [$ i ]->getSubtractedType ();
337- }
338- $ types [$ j ] = self ::intersectWithSubtractedType ($ types [$ j ], $ subtractedType );
339- array_splice ($ types , $ i --, 1 );
309+ if ($ b !== null ) {
310+ $ types [$ j ] = $ b ;
311+ array_splice ($ scalarTypeItems , $ i --, 1 );
340312 continue 2 ;
341313 }
342314 }
315+ }
343316
344- if (
345- !$ types [$ j ] instanceof ConstantArrayType
346- && $ types [$ j ]->isSuperTypeOf ($ types [$ i ])->yes ()
347- ) {
348- array_splice ($ types , $ i --, 1 );
349- continue 2 ;
350- }
317+ $ scalarTypes [$ classType ] = $ scalarTypeItems ;
318+ }
351319
352- if (
353- !$ types [$ i ] instanceof ConstantArrayType
354- && $ types [$ i ]->isSuperTypeOf ($ types [$ j ])->yes ()
355- ) {
356- array_splice ($ types , $ j --, 1 );
357- continue 1 ;
320+ // transform A | A to A
321+ // transform A | never to A
322+ for ($ i = 0 ; $ i < count ($ types ); $ i ++) {
323+ for ($ j = $ i + 1 ; $ j < count ($ types ); $ j ++) {
324+ $ compareResult = self ::compareTypesInUnion ($ types [$ i ], $ types [$ j ]);
325+ if ($ compareResult === null ) {
326+ continue ;
358327 }
359328
360- if (
361- $ types [$ i ] instanceof ConstantStringType
362- && $ types [$ i ]->getValue () === ''
363- && $ types [$ j ]->describe (VerbosityLevel::value ()) === 'non-empty-string '
364- ) {
365- $ types [$ i ] = new StringType ();
329+ [$ a , $ b ] = $ compareResult ;
330+ if ($ a !== null ) {
331+ $ types [$ i ] = $ a ;
366332 array_splice ($ types , $ j --, 1 );
367333 continue 1 ;
368334 }
369-
370- if (
371- $ types [$ j ] instanceof ConstantStringType
372- && $ types [$ j ]->getValue () === ''
373- && $ types [$ i ]->describe (VerbosityLevel::value ()) === 'non-empty-string '
374- ) {
375- $ types [$ j ] = new StringType ();
335+ if ($ b !== null ) {
336+ $ types [$ j ] = $ b ;
376337 array_splice ($ types , $ i --, 1 );
377338 continue 2 ;
378339 }
379340 }
380341 }
381342
343+ foreach ($ scalarTypes as $ scalarTypeItems ) {
344+ foreach ($ scalarTypeItems as $ scalarType ) {
345+ $ types [] = $ scalarType ;
346+ }
347+ }
348+
382349 if (count ($ types ) === 0 ) {
383350 return new NeverType ();
384351
@@ -408,6 +375,95 @@ public static function union(Type ...$types): Type
408375 return new UnionType ($ types );
409376 }
410377
378+ /**
379+ * @param Type $a
380+ * @param Type $b
381+ * @return array{Type, null}|array{null, Type}|null
382+ */
383+ private static function compareTypesInUnion (Type $ a , Type $ b ): ?array
384+ {
385+ if ($ a instanceof IntegerRangeType) {
386+ $ type = $ a ->tryUnion ($ b );
387+ if ($ type !== null ) {
388+ $ a = $ type ;
389+ return [$ a , null ];
390+ }
391+ }
392+ if ($ b instanceof IntegerRangeType) {
393+ $ type = $ b ->tryUnion ($ a );
394+ if ($ type !== null ) {
395+ $ b = $ type ;
396+ return [null , $ b ];
397+ }
398+ }
399+
400+ if ($ a instanceof SubtractableType) {
401+ $ typeWithoutSubtractedTypeA = $ a ->getTypeWithoutSubtractedType ();
402+ if ($ typeWithoutSubtractedTypeA instanceof MixedType && $ b instanceof MixedType) {
403+ $ isSuperType = $ typeWithoutSubtractedTypeA ->isSuperTypeOfMixed ($ b );
404+ } else {
405+ $ isSuperType = $ typeWithoutSubtractedTypeA ->isSuperTypeOf ($ b );
406+ }
407+ if ($ isSuperType ->yes ()) {
408+ $ subtractedType = null ;
409+ if ($ b instanceof SubtractableType) {
410+ $ subtractedType = $ b ->getSubtractedType ();
411+ }
412+ $ a = self ::intersectWithSubtractedType ($ a , $ subtractedType );
413+ return [$ a , null ];
414+ }
415+ }
416+
417+ if ($ b instanceof SubtractableType) {
418+ $ typeWithoutSubtractedTypeB = $ b ->getTypeWithoutSubtractedType ();
419+ if ($ typeWithoutSubtractedTypeB instanceof MixedType && $ a instanceof MixedType) {
420+ $ isSuperType = $ typeWithoutSubtractedTypeB ->isSuperTypeOfMixed ($ a );
421+ } else {
422+ $ isSuperType = $ typeWithoutSubtractedTypeB ->isSuperTypeOf ($ a );
423+ }
424+ if ($ isSuperType ->yes ()) {
425+ $ subtractedType = null ;
426+ if ($ a instanceof SubtractableType) {
427+ $ subtractedType = $ a ->getSubtractedType ();
428+ }
429+ $ b = self ::intersectWithSubtractedType ($ b , $ subtractedType );
430+ return [null , $ b ];
431+ }
432+ }
433+
434+ if (
435+ !$ b instanceof ConstantArrayType
436+ && $ b ->isSuperTypeOf ($ a )->yes ()
437+ ) {
438+ return [null , $ b ];
439+ }
440+
441+ if (
442+ !$ a instanceof ConstantArrayType
443+ && $ a ->isSuperTypeOf ($ b )->yes ()
444+ ) {
445+ return [$ a , null ];
446+ }
447+
448+ if (
449+ $ a instanceof ConstantStringType
450+ && $ a ->getValue () === ''
451+ && $ b ->describe (VerbosityLevel::value ()) === 'non-empty-string '
452+ ) {
453+ return [null , new StringType ()];
454+ }
455+
456+ if (
457+ $ b instanceof ConstantStringType
458+ && $ b ->getValue () === ''
459+ && $ a ->describe (VerbosityLevel::value ()) === 'non-empty-string '
460+ ) {
461+ return [new StringType (), null ];
462+ }
463+
464+ return null ;
465+ }
466+
411467 private static function unionWithSubtractedType (
412468 Type $ type ,
413469 ?Type $ subtractedType
0 commit comments