1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use PHPStan\Type\Accessory\AccessoryArrayListType;
6: use PHPStan\Type\Accessory\AccessoryLowercaseStringType;
7: use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
8: use PHPStan\Type\Accessory\AccessoryType;
9: use PHPStan\Type\Accessory\AccessoryUppercaseStringType;
10: use PHPStan\Type\Accessory\HasOffsetType;
11: use PHPStan\Type\Accessory\HasOffsetValueType;
12: use PHPStan\Type\Accessory\HasPropertyType;
13: use PHPStan\Type\Accessory\NonEmptyArrayType;
14: use PHPStan\Type\Accessory\OversizedArrayType;
15: use PHPStan\Type\Constant\ConstantArrayType;
16: use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
17: use PHPStan\Type\Constant\ConstantBooleanType;
18: use PHPStan\Type\Constant\ConstantFloatType;
19: use PHPStan\Type\Constant\ConstantIntegerType;
20: use PHPStan\Type\Constant\ConstantStringType;
21: use PHPStan\Type\Generic\GenericClassStringType;
22: use PHPStan\Type\Generic\TemplateArrayType;
23: use PHPStan\Type\Generic\TemplateBenevolentUnionType;
24: use PHPStan\Type\Generic\TemplateType;
25: use PHPStan\Type\Generic\TemplateTypeFactory;
26: use PHPStan\Type\Generic\TemplateUnionType;
27: use function array_key_exists;
28: use function array_key_first;
29: use function array_map;
30: use function array_merge;
31: use function array_slice;
32: use function array_splice;
33: use function array_values;
34: use function count;
35: use function get_class;
36: use function is_int;
37: use function md5;
38: use function sprintf;
39: use function usort;
40: use const PHP_INT_MAX;
41: use const PHP_INT_MIN;
42:
43: /**
44: * @api
45: */
46: final class TypeCombinator
47: {
48:
49: public static function addNull(Type $type): Type
50: {
51: $nullType = new NullType();
52:
53: if ($nullType->isSuperTypeOf($type)->no()) {
54: return self::union($type, $nullType);
55: }
56:
57: return $type;
58: }
59:
60: public static function remove(Type $fromType, Type $typeToRemove): Type
61: {
62: if ($typeToRemove instanceof UnionType) {
63: foreach ($typeToRemove->getTypes() as $unionTypeToRemove) {
64: $fromType = self::remove($fromType, $unionTypeToRemove);
65: }
66: return $fromType;
67: }
68:
69: $isSuperType = $typeToRemove->isSuperTypeOf($fromType);
70: if ($isSuperType->yes()) {
71: return new NeverType();
72: }
73: if ($isSuperType->no()) {
74: return $fromType;
75: }
76:
77: if ($typeToRemove instanceof MixedType) {
78: $typeToRemoveSubtractedType = $typeToRemove->getSubtractedType();
79: if ($typeToRemoveSubtractedType !== null) {
80: return self::intersect($fromType, $typeToRemoveSubtractedType);
81: }
82: }
83:
84: $removed = $fromType->tryRemove($typeToRemove);
85: if ($removed !== null) {
86: return $removed;
87: }
88:
89: $fromFiniteTypes = $fromType->getFiniteTypes();
90: if (count($fromFiniteTypes) > 0) {
91: $finiteTypesToRemove = $typeToRemove->getFiniteTypes();
92: if (count($finiteTypesToRemove) === 1) {
93: $result = [];
94: foreach ($fromFiniteTypes as $finiteType) {
95: if ($finiteType->equals($finiteTypesToRemove[0])) {
96: continue;
97: }
98:
99: $result[] = $finiteType;
100: }
101:
102: if (count($result) === count($fromFiniteTypes)) {
103: return $fromType;
104: }
105:
106: if (count($result) === 0) {
107: return new NeverType();
108: }
109:
110: if (count($result) === 1) {
111: return $result[0];
112: }
113:
114: return new UnionType($result);
115: }
116: }
117:
118: return $fromType;
119: }
120:
121: public static function removeNull(Type $type): Type
122: {
123: if (self::containsNull($type)) {
124: return self::remove($type, new NullType());
125: }
126:
127: return $type;
128: }
129:
130: public static function containsNull(Type $type): bool
131: {
132: if ($type instanceof UnionType) {
133: foreach ($type->getTypes() as $innerType) {
134: if ($innerType instanceof NullType) {
135: return true;
136: }
137: }
138:
139: return false;
140: }
141:
142: return $type instanceof NullType;
143: }
144:
145: public static function union(Type ...$types): Type
146: {
147: $typesCount = count($types);
148: if ($typesCount === 0) {
149: return new NeverType();
150: }
151:
152: $benevolentTypes = [];
153: $benevolentUnionObject = null;
154: // transform A | (B | C) to A | B | C
155: for ($i = 0; $i < $typesCount; $i++) {
156: if ($types[$i] instanceof BenevolentUnionType) {
157: if ($types[$i] instanceof TemplateBenevolentUnionType && $benevolentUnionObject === null) {
158: $benevolentUnionObject = $types[$i];
159: }
160: $benevolentTypesCount = 0;
161: $typesInner = $types[$i]->getTypes();
162: foreach ($typesInner as $benevolentInnerType) {
163: $benevolentTypesCount++;
164: $benevolentTypes[$benevolentInnerType->describe(VerbosityLevel::value())] = $benevolentInnerType;
165: }
166: array_splice($types, $i, 1, $typesInner);
167: $typesCount += $benevolentTypesCount - 1;
168: continue;
169: }
170: if (!($types[$i] instanceof UnionType)) {
171: continue;
172: }
173: if ($types[$i] instanceof TemplateType) {
174: continue;
175: }
176:
177: $typesInner = $types[$i]->getTypes();
178: array_splice($types, $i, 1, $typesInner);
179: $typesCount += count($typesInner) - 1;
180: }
181:
182: if ($typesCount === 1) {
183: return $types[0];
184: }
185:
186: $arrayTypes = [];
187: $scalarTypes = [];
188: $hasGenericScalarTypes = [];
189: $enumCaseTypes = [];
190: $integerRangeTypes = [];
191: for ($i = 0; $i < $typesCount; $i++) {
192: if ($types[$i] instanceof ConstantScalarType) {
193: $type = $types[$i];
194: $scalarTypes[get_class($type)][md5($type->describe(VerbosityLevel::cache()))] = $type;
195: unset($types[$i]);
196: continue;
197: }
198: if ($types[$i] instanceof BooleanType) {
199: $hasGenericScalarTypes[ConstantBooleanType::class] = true;
200: }
201: if ($types[$i] instanceof FloatType) {
202: $hasGenericScalarTypes[ConstantFloatType::class] = true;
203: }
204: if ($types[$i] instanceof IntegerType && !$types[$i] instanceof IntegerRangeType) {
205: $hasGenericScalarTypes[ConstantIntegerType::class] = true;
206: }
207: if ($types[$i] instanceof StringType && !$types[$i] instanceof ClassStringType) {
208: $hasGenericScalarTypes[ConstantStringType::class] = true;
209: }
210: $enumCases = $types[$i]->getEnumCases();
211: if (count($enumCases) === 1) {
212: $enumCaseTypes[$types[$i]->describe(VerbosityLevel::cache())] = $types[$i];
213:
214: unset($types[$i]);
215: continue;
216: }
217:
218: if ($types[$i] instanceof IntegerRangeType) {
219: $integerRangeTypes[] = $types[$i];
220: unset($types[$i]);
221:
222: continue;
223: }
224:
225: if (!$types[$i]->isArray()->yes()) {
226: continue;
227: }
228:
229: $arrayTypes[] = $types[$i];
230: unset($types[$i]);
231: }
232:
233: foreach ($scalarTypes as $classType => $scalarTypeItems) {
234: $scalarTypes[$classType] = array_values($scalarTypeItems);
235: }
236:
237: $enumCaseTypes = array_values($enumCaseTypes);
238: usort(
239: $integerRangeTypes,
240: static fn (IntegerRangeType $a, IntegerRangeType $b): int => ($a->getMin() ?? PHP_INT_MIN) <=> ($b->getMin() ?? PHP_INT_MIN)
241: ?: ($a->getMax() ?? PHP_INT_MAX) <=> ($b->getMax() ?? PHP_INT_MAX)
242: );
243: $types = array_merge($types, $integerRangeTypes);
244: $types = array_values($types);
245: $typesCount = count($types);
246:
247: foreach ($scalarTypes as $classType => $scalarTypeItems) {
248: if (isset($hasGenericScalarTypes[$classType])) {
249: unset($scalarTypes[$classType]);
250: continue;
251: }
252: if ($classType === ConstantBooleanType::class && count($scalarTypeItems) === 2) {
253: $types[] = new BooleanType();
254: $typesCount++;
255: unset($scalarTypes[$classType]);
256: continue;
257: }
258:
259: $scalarTypeItemsCount = count($scalarTypeItems);
260: for ($i = 0; $i < $typesCount; $i++) {
261: for ($j = 0; $j < $scalarTypeItemsCount; $j++) {
262: $compareResult = self::compareTypesInUnion($types[$i], $scalarTypeItems[$j]);
263: if ($compareResult === null) {
264: continue;
265: }
266:
267: [$a, $b] = $compareResult;
268: if ($a !== null) {
269: $types[$i] = $a;
270: array_splice($scalarTypeItems, $j--, 1);
271: $scalarTypeItemsCount--;
272: continue 1;
273: }
274: if ($b !== null) {
275: $scalarTypeItems[$j] = $b;
276: array_splice($types, $i--, 1);
277: $typesCount--;
278: continue 2;
279: }
280: }
281: }
282:
283: $scalarTypes[$classType] = $scalarTypeItems;
284: }
285:
286: if (count($types) > 16) {
287: $newTypes = [];
288: foreach ($types as $type) {
289: $newTypes[$type->describe(VerbosityLevel::cache())] = $type;
290: }
291: $types = array_values($newTypes);
292: }
293:
294: $types = array_merge(
295: $types,
296: self::processArrayTypes($arrayTypes),
297: );
298: $typesCount = count($types);
299:
300: // transform A | A to A
301: // transform A | never to A
302: for ($i = 0; $i < $typesCount; $i++) {
303: for ($j = $i + 1; $j < $typesCount; $j++) {
304: $compareResult = self::compareTypesInUnion($types[$i], $types[$j]);
305: if ($compareResult === null) {
306: continue;
307: }
308:
309: [$a, $b] = $compareResult;
310: if ($a !== null) {
311: $types[$i] = $a;
312: array_splice($types, $j--, 1);
313: $typesCount--;
314: continue 1;
315: }
316: if ($b !== null) {
317: $types[$j] = $b;
318: array_splice($types, $i--, 1);
319: $typesCount--;
320: continue 2;
321: }
322: }
323: }
324:
325: $enumCasesCount = count($enumCaseTypes);
326: for ($i = 0; $i < $typesCount; $i++) {
327: for ($j = 0; $j < $enumCasesCount; $j++) {
328: $compareResult = self::compareTypesInUnion($types[$i], $enumCaseTypes[$j]);
329: if ($compareResult === null) {
330: continue;
331: }
332:
333: [$a, $b] = $compareResult;
334: if ($a !== null) {
335: $types[$i] = $a;
336: array_splice($enumCaseTypes, $j--, 1);
337: $enumCasesCount--;
338: continue 1;
339: }
340: if ($b !== null) {
341: $enumCaseTypes[$j] = $b;
342: array_splice($types, $i--, 1);
343: $typesCount--;
344: continue 2;
345: }
346: }
347: }
348:
349: foreach ($enumCaseTypes as $enumCaseType) {
350: $types[] = $enumCaseType;
351: $typesCount++;
352: }
353:
354: foreach ($scalarTypes as $scalarTypeItems) {
355: foreach ($scalarTypeItems as $scalarType) {
356: $types[] = $scalarType;
357: $typesCount++;
358: }
359: }
360:
361: if ($typesCount === 0) {
362: return new NeverType();
363: }
364: if ($typesCount === 1) {
365: return $types[0];
366: }
367:
368: if ($benevolentTypes !== []) {
369: $tempTypes = $types;
370: foreach ($tempTypes as $i => $type) {
371: if (!isset($benevolentTypes[$type->describe(VerbosityLevel::value())])) {
372: break;
373: }
374:
375: unset($tempTypes[$i]);
376: }
377:
378: if ($tempTypes === []) {
379: if ($benevolentUnionObject instanceof TemplateBenevolentUnionType) {
380: return $benevolentUnionObject->withTypes($types);
381: }
382:
383: return new BenevolentUnionType($types, true);
384: }
385: }
386:
387: return new UnionType($types, true);
388: }
389:
390: /**
391: * @return array{Type, null}|array{null, Type}|null
392: */
393: private static function compareTypesInUnion(Type $a, Type $b): ?array
394: {
395: if ($a instanceof IntegerRangeType) {
396: $type = $a->tryUnion($b);
397: if ($type !== null) {
398: $a = $type;
399: return [$a, null];
400: }
401: }
402: if ($b instanceof IntegerRangeType) {
403: $type = $b->tryUnion($a);
404: if ($type !== null) {
405: $b = $type;
406: return [null, $b];
407: }
408: }
409: if ($a instanceof IntegerRangeType && $b instanceof IntegerRangeType) {
410: return null;
411: }
412: if ($a instanceof HasOffsetValueType && $b instanceof HasOffsetValueType) {
413: if ($a->getOffsetType()->equals($b->getOffsetType())) {
414: return [new HasOffsetValueType($a->getOffsetType(), self::union($a->getValueType(), $b->getValueType())), null];
415: }
416: }
417: if ($a->isConstantArray()->yes() && $b->isConstantArray()->yes()) {
418: return null;
419: }
420:
421: // simplify string[] | int[] to (string|int)[]
422: if ($a instanceof IterableType && $b instanceof IterableType) {
423: return [
424: new IterableType(
425: self::union($a->getIterableKeyType(), $b->getIterableKeyType()),
426: self::union($a->getIterableValueType(), $b->getIterableValueType()),
427: ),
428: null,
429: ];
430: }
431:
432: if ($a instanceof SubtractableType) {
433: $typeWithoutSubtractedTypeA = $a->getTypeWithoutSubtractedType();
434: if ($typeWithoutSubtractedTypeA instanceof MixedType && $b instanceof MixedType) {
435: $isSuperType = $typeWithoutSubtractedTypeA->isSuperTypeOfMixed($b);
436: } else {
437: $isSuperType = $typeWithoutSubtractedTypeA->isSuperTypeOf($b);
438: }
439: if ($isSuperType->yes()) {
440: $a = self::intersectWithSubtractedType($a, $b);
441: return [$a, null];
442: }
443: }
444:
445: if ($b instanceof SubtractableType) {
446: $typeWithoutSubtractedTypeB = $b->getTypeWithoutSubtractedType();
447: if ($typeWithoutSubtractedTypeB instanceof MixedType && $a instanceof MixedType) {
448: $isSuperType = $typeWithoutSubtractedTypeB->isSuperTypeOfMixed($a);
449: } else {
450: $isSuperType = $typeWithoutSubtractedTypeB->isSuperTypeOf($a);
451: }
452: if ($isSuperType->yes()) {
453: $b = self::intersectWithSubtractedType($b, $a);
454: return [null, $b];
455: }
456: }
457:
458: if ($b->isSuperTypeOf($a)->yes()) {
459: return [null, $b];
460: }
461:
462: if ($a->isSuperTypeOf($b)->yes()) {
463: return [$a, null];
464: }
465:
466: if (
467: $a instanceof ConstantStringType
468: && $a->getValue() === ''
469: && ($b->describe(VerbosityLevel::value()) === 'non-empty-string'
470: || $b->describe(VerbosityLevel::value()) === 'non-falsy-string')
471: ) {
472: return [null, self::intersect(
473: new StringType(),
474: ...self::getAccessoryCaseStringTypes($b),
475: )];
476: }
477:
478: if (
479: $b instanceof ConstantStringType
480: && $b->getValue() === ''
481: && ($a->describe(VerbosityLevel::value()) === 'non-empty-string'
482: || $a->describe(VerbosityLevel::value()) === 'non-falsy-string')
483: ) {
484: return [self::intersect(
485: new StringType(),
486: ...self::getAccessoryCaseStringTypes($a),
487: ), null];
488: }
489:
490: if (
491: $a instanceof ConstantStringType
492: && $a->getValue() === '0'
493: && $b->describe(VerbosityLevel::value()) === 'non-falsy-string'
494: ) {
495: return [null, self::intersect(
496: new StringType(),
497: new AccessoryNonEmptyStringType(),
498: ...self::getAccessoryCaseStringTypes($b),
499: )];
500: }
501:
502: if (
503: $b instanceof ConstantStringType
504: && $b->getValue() === '0'
505: && $a->describe(VerbosityLevel::value()) === 'non-falsy-string'
506: ) {
507: return [self::intersect(
508: new StringType(),
509: new AccessoryNonEmptyStringType(),
510: ...self::getAccessoryCaseStringTypes($a),
511: ), null];
512: }
513:
514: return null;
515: }
516:
517: /**
518: * @return array<Type>
519: */
520: private static function getAccessoryCaseStringTypes(Type $type): array
521: {
522: $accessory = [];
523: if ($type->isLowercaseString()->yes()) {
524: $accessory[] = new AccessoryLowercaseStringType();
525: }
526: if ($type->isUppercaseString()->yes()) {
527: $accessory[] = new AccessoryUppercaseStringType();
528: }
529:
530: return $accessory;
531: }
532:
533: private static function unionWithSubtractedType(
534: Type $type,
535: ?Type $subtractedType,
536: ): Type
537: {
538: if ($subtractedType === null) {
539: return $type;
540: }
541:
542: if ($type instanceof SubtractableType) {
543: $subtractedType = $type->getSubtractedType() === null
544: ? $subtractedType
545: : self::union($type->getSubtractedType(), $subtractedType);
546: if ($subtractedType instanceof NeverType) {
547: $subtractedType = null;
548: }
549:
550: return $type->changeSubtractedType($subtractedType);
551: }
552:
553: if ($subtractedType->isSuperTypeOf($type)->yes()) {
554: return new NeverType();
555: }
556:
557: return self::remove($type, $subtractedType);
558: }
559:
560: private static function intersectWithSubtractedType(
561: SubtractableType $a,
562: Type $b,
563: ): Type
564: {
565: if ($a->getSubtractedType() === null) {
566: return $a;
567: }
568:
569: if ($b instanceof IntersectionType) {
570: $subtractableTypes = [];
571: foreach ($b->getTypes() as $innerType) {
572: if (!$innerType instanceof SubtractableType) {
573: continue;
574: }
575:
576: $subtractableTypes[] = $innerType;
577: }
578:
579: if (count($subtractableTypes) === 0) {
580: return $a->getTypeWithoutSubtractedType();
581: }
582:
583: $subtractedTypes = [];
584: foreach ($subtractableTypes as $subtractableType) {
585: if ($subtractableType->getSubtractedType() === null) {
586: continue;
587: }
588:
589: $subtractedTypes[] = $subtractableType->getSubtractedType();
590: }
591:
592: if (count($subtractedTypes) === 0) {
593: return $a->getTypeWithoutSubtractedType();
594:
595: }
596:
597: $subtractedType = self::union(...$subtractedTypes);
598: } elseif ($b instanceof SubtractableType) {
599: $subtractedType = $b->getSubtractedType();
600: if ($subtractedType === null) {
601: return $a->getTypeWithoutSubtractedType();
602: }
603: } else {
604: $subtractedTypeTmp = self::intersect($a->getTypeWithoutSubtractedType(), $a->getSubtractedType());
605: if ($b->isSuperTypeOf($subtractedTypeTmp)->yes()) {
606: return $a->getTypeWithoutSubtractedType();
607: }
608: $subtractedType = new MixedType(false, $b);
609: }
610:
611: $subtractedType = self::intersect(
612: $a->getSubtractedType(),
613: $subtractedType,
614: );
615: if ($subtractedType instanceof NeverType) {
616: $subtractedType = null;
617: }
618:
619: return $a->changeSubtractedType($subtractedType);
620: }
621:
622: /**
623: * @param Type[] $arrayTypes
624: * @return Type[]
625: */
626: private static function processArrayAccessoryTypes(array $arrayTypes): array
627: {
628: $accessoryTypes = [];
629: foreach ($arrayTypes as $i => $arrayType) {
630: if ($arrayType instanceof IntersectionType) {
631: foreach ($arrayType->getTypes() as $innerType) {
632: if ($innerType instanceof TemplateType) {
633: break;
634: }
635: if (!($innerType instanceof AccessoryType) && !($innerType instanceof CallableType)) {
636: continue;
637: }
638: if ($innerType instanceof HasOffsetType) {
639: $offset = $innerType->getOffsetType();
640: if ($offset instanceof ConstantStringType || $offset instanceof ConstantIntegerType) {
641: $innerType = new HasOffsetValueType($offset, $arrayType->getIterableValueType());
642: }
643: }
644: if ($innerType instanceof HasOffsetValueType) {
645: $accessoryTypes[sprintf('hasOffsetValue(%s)', $innerType->getOffsetType()->describe(VerbosityLevel::cache()))][$i] = $innerType;
646: continue;
647: }
648:
649: $accessoryTypes[$innerType->describe(VerbosityLevel::cache())][$i] = $innerType;
650: }
651: }
652:
653: if (!$arrayType->isConstantArray()->yes()) {
654: continue;
655: }
656: $constantArrays = $arrayType->getConstantArrays();
657:
658: foreach ($constantArrays as $constantArray) {
659: if ($constantArray->isList()->yes()) {
660: $list = new AccessoryArrayListType();
661: $accessoryTypes[$list->describe(VerbosityLevel::cache())][$i] = $list;
662: }
663:
664: if (!$constantArray->isIterableAtLeastOnce()->yes()) {
665: continue;
666: }
667:
668: $nonEmpty = new NonEmptyArrayType();
669: $accessoryTypes[$nonEmpty->describe(VerbosityLevel::cache())][$i] = $nonEmpty;
670: }
671: }
672:
673: $commonAccessoryTypes = [];
674: $arrayTypeCount = count($arrayTypes);
675: foreach ($accessoryTypes as $accessoryType) {
676: if (count($accessoryType) !== $arrayTypeCount) {
677: $firstKey = array_key_first($accessoryType);
678: if ($accessoryType[$firstKey] instanceof OversizedArrayType) {
679: $commonAccessoryTypes[] = $accessoryType[$firstKey];
680: }
681: continue;
682: }
683:
684: if ($accessoryType[0] instanceof HasOffsetValueType) {
685: $commonAccessoryTypes[] = self::union(...$accessoryType);
686: continue;
687: }
688:
689: $commonAccessoryTypes[] = $accessoryType[0];
690: }
691:
692: return $commonAccessoryTypes;
693: }
694:
695: /**
696: * @param list<Type> $arrayTypes
697: * @return Type[]
698: */
699: private static function processArrayTypes(array $arrayTypes): array
700: {
701: if ($arrayTypes === []) {
702: return [];
703: }
704:
705: $accessoryTypes = self::processArrayAccessoryTypes($arrayTypes);
706:
707: if (count($arrayTypes) === 1) {
708: return [
709: self::intersect(...$arrayTypes, ...$accessoryTypes),
710: ];
711: }
712:
713: $keyTypesForGeneralArray = [];
714: $valueTypesForGeneralArray = [];
715: $generalArrayOccurred = false;
716: $constantKeyTypesNumbered = [];
717: $filledArrays = 0;
718: $overflowed = false;
719:
720: /** @var int|float $nextConstantKeyTypeIndex */
721: $nextConstantKeyTypeIndex = 1;
722: $constantArraysMap = array_map(
723: static fn (Type $t) => $t->getConstantArrays(),
724: $arrayTypes,
725: );
726:
727: foreach ($arrayTypes as $arrayIdx => $arrayType) {
728: $constantArrays = $constantArraysMap[$arrayIdx];
729: $isConstantArray = $constantArrays !== [];
730: if (!$isConstantArray || !$arrayType->isIterableAtLeastOnce()->no()) {
731: $filledArrays++;
732: }
733:
734: if ($generalArrayOccurred || !$isConstantArray) {
735: foreach ($arrayType->getArrays() as $type) {
736: $keyTypesForGeneralArray[] = $type->getIterableKeyType();
737: $valueTypesForGeneralArray[] = $type->getItemType();
738: $generalArrayOccurred = true;
739: }
740: continue;
741: }
742:
743: $constantArrays = $arrayType->getConstantArrays();
744: foreach ($constantArrays as $constantArray) {
745: foreach ($constantArray->getKeyTypes() as $i => $keyType) {
746: $keyTypesForGeneralArray[] = $keyType;
747: $valueTypesForGeneralArray[] = $constantArray->getValueTypes()[$i];
748:
749: $keyTypeValue = $keyType->getValue();
750: if (array_key_exists($keyTypeValue, $constantKeyTypesNumbered)) {
751: continue;
752: }
753:
754: $constantKeyTypesNumbered[$keyTypeValue] = $nextConstantKeyTypeIndex;
755: $nextConstantKeyTypeIndex *= 2;
756: if (!is_int($nextConstantKeyTypeIndex)) {
757: $generalArrayOccurred = true;
758: $overflowed = true;
759: continue 2;
760: }
761: }
762: }
763: }
764:
765: if ($generalArrayOccurred && (!$overflowed || $filledArrays > 1)) {
766: $reducedArrayTypes = self::reduceArrays($arrayTypes, false);
767: if (count($reducedArrayTypes) === 1) {
768: return [self::intersect($reducedArrayTypes[0], ...$accessoryTypes)];
769: }
770: $scopes = [];
771: $useTemplateArray = true;
772: foreach ($arrayTypes as $arrayType) {
773: if (!$arrayType instanceof TemplateArrayType) {
774: $useTemplateArray = false;
775: break;
776: }
777:
778: $scopes[$arrayType->getScope()->describe()] = $arrayType;
779: }
780:
781: $arrayType = new ArrayType(
782: self::union(...$keyTypesForGeneralArray),
783: self::union(...self::optimizeConstantArrays($valueTypesForGeneralArray)),
784: );
785:
786: if ($useTemplateArray && count($scopes) === 1) {
787: $templateArray = array_values($scopes)[0];
788: $arrayType = new TemplateArrayType(
789: $templateArray->getScope(),
790: $templateArray->getStrategy(),
791: $templateArray->getVariance(),
792: $templateArray->getName(),
793: $arrayType,
794: $templateArray->getDefault(),
795: );
796: }
797:
798: return [
799: self::intersect($arrayType, ...$accessoryTypes),
800: ];
801: }
802:
803: $reducedArrayTypes = self::reduceArrays($arrayTypes, true);
804:
805: return array_map(
806: static fn (Type $arrayType) => self::intersect($arrayType, ...$accessoryTypes),
807: self::optimizeConstantArrays($reducedArrayTypes),
808: );
809: }
810:
811: /**
812: * @param Type[] $types
813: * @return Type[]
814: */
815: private static function optimizeConstantArrays(array $types): array
816: {
817: $constantArrayValuesCount = self::countConstantArrayValueTypes($types);
818:
819: if ($constantArrayValuesCount <= ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) {
820: return $types;
821: }
822:
823: $results = [];
824: $eachIsOversized = true;
825: foreach ($types as $type) {
826: $isOversized = false;
827: $result = TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$isOversized): Type {
828: if (!$type instanceof ConstantArrayType) {
829: return $traverse($type);
830: }
831:
832: if ($type->isIterableAtLeastOnce()->no()) {
833: return $type;
834: }
835:
836: $isOversized = true;
837:
838: $isList = true;
839: $valueTypes = [];
840: $keyTypes = [];
841: $nextAutoIndex = 0;
842: foreach ($type->getKeyTypes() as $i => $innerKeyType) {
843: if (!$innerKeyType instanceof ConstantIntegerType) {
844: $isList = false;
845: } elseif ($innerKeyType->getValue() !== $nextAutoIndex) {
846: $isList = false;
847: $nextAutoIndex = $innerKeyType->getValue() + 1;
848: } else {
849: $nextAutoIndex++;
850: }
851:
852: $generalizedKeyType = $innerKeyType->generalize(GeneralizePrecision::moreSpecific());
853: $keyTypes[$generalizedKeyType->describe(VerbosityLevel::precise())] = $generalizedKeyType;
854:
855: $innerValueType = $type->getValueTypes()[$i];
856: $generalizedValueType = TypeTraverser::map($innerValueType, static function (Type $type) use ($traverse): Type {
857: if ($type instanceof ArrayType || $type instanceof ConstantArrayType) {
858: return TypeCombinator::intersect($type, new OversizedArrayType());
859: }
860:
861: return $traverse($type);
862: });
863: $valueTypes[$generalizedValueType->describe(VerbosityLevel::precise())] = $generalizedValueType;
864: }
865:
866: $keyType = TypeCombinator::union(...array_values($keyTypes));
867: $valueType = TypeCombinator::union(...array_values($valueTypes));
868:
869: $arrayType = new ArrayType($keyType, $valueType);
870: if ($isList) {
871: $arrayType = TypeCombinator::intersect($arrayType, new AccessoryArrayListType());
872: }
873:
874: return TypeCombinator::intersect($arrayType, new NonEmptyArrayType(), new OversizedArrayType());
875: });
876:
877: if (!$isOversized) {
878: $eachIsOversized = false;
879: }
880:
881: $results[] = $result;
882: }
883:
884: if ($eachIsOversized) {
885: $eachIsList = true;
886: $keyTypes = [];
887: $valueTypes = [];
888: foreach ($results as $result) {
889: $keyTypes[] = $result->getIterableKeyType();
890: $valueTypes[] = $result->getLastIterableValueType();
891: if ($result->isList()->yes()) {
892: continue;
893: }
894: $eachIsList = false;
895: }
896:
897: $keyType = self::union(...$keyTypes);
898: $valueType = self::union(...$valueTypes);
899:
900: if ($valueType instanceof UnionType && count($valueType->getTypes()) > ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) {
901: $valueType = $valueType->generalize(GeneralizePrecision::lessSpecific());
902: }
903:
904: $arrayType = new ArrayType($keyType, $valueType);
905: if ($eachIsList) {
906: $arrayType = self::intersect($arrayType, new AccessoryArrayListType());
907: }
908:
909: return [self::intersect($arrayType, new NonEmptyArrayType(), new OversizedArrayType())];
910: }
911:
912: return $results;
913: }
914:
915: /**
916: * @param Type[] $types
917: */
918: public static function countConstantArrayValueTypes(array $types): int
919: {
920: $constantArrayValuesCount = 0;
921: foreach ($types as $type) {
922: TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$constantArrayValuesCount): Type {
923: if ($type instanceof ConstantArrayType) {
924: $constantArrayValuesCount += count($type->getValueTypes());
925: }
926:
927: return $traverse($type);
928: });
929: }
930: return $constantArrayValuesCount;
931: }
932:
933: /**
934: * @param list<Type> $constantArrays
935: * @return list<Type>
936: */
937: private static function reduceArrays(array $constantArrays, bool $preserveTaggedUnions): array
938: {
939: $newArrays = [];
940: $arraysToProcess = [];
941: $emptyArray = null;
942: foreach ($constantArrays as $constantArray) {
943: if (!$constantArray->isConstantArray()->yes()) {
944: // This is an optimization for current use-case of $preserveTaggedUnions=false, where we need
945: // one constant array as a result, or we generalize the $constantArrays.
946: if (!$preserveTaggedUnions) {
947: return $constantArrays;
948: }
949: $newArrays[] = $constantArray;
950: continue;
951: }
952:
953: if ($constantArray->isIterableAtLeastOnce()->no()) {
954: $emptyArray = $constantArray;
955: continue;
956: }
957:
958: $arraysToProcess = array_merge($arraysToProcess, $constantArray->getConstantArrays());
959: }
960:
961: if ($emptyArray !== null) {
962: $newArrays[] = $emptyArray;
963: }
964:
965: $arraysToProcessPerKey = [];
966: foreach ($arraysToProcess as $i => $arrayToProcess) {
967: foreach ($arrayToProcess->getKeyTypes() as $keyType) {
968: $arraysToProcessPerKey[$keyType->getValue()][] = $i;
969: }
970: }
971:
972: $eligibleCombinations = [];
973:
974: foreach ($arraysToProcessPerKey as $arrays) {
975: for ($i = 0, $arraysCount = count($arrays); $i < $arraysCount - 1; $i++) {
976: for ($j = $i + 1; $j < $arraysCount; $j++) {
977: $eligibleCombinations[$arrays[$i]][$arrays[$j]] ??= 0;
978: $eligibleCombinations[$arrays[$i]][$arrays[$j]]++;
979: }
980: }
981: }
982:
983: foreach ($eligibleCombinations as $i => $other) {
984: if (!array_key_exists($i, $arraysToProcess)) {
985: continue;
986: }
987:
988: foreach ($other as $j => $overlappingKeysCount) {
989: if (!array_key_exists($j, $arraysToProcess)) {
990: continue;
991: }
992:
993: if (
994: $preserveTaggedUnions
995: && $overlappingKeysCount === count($arraysToProcess[$i]->getKeyTypes())
996: && $arraysToProcess[$j]->isKeysSupersetOf($arraysToProcess[$i])
997: ) {
998: $arraysToProcess[$j] = $arraysToProcess[$j]->mergeWith($arraysToProcess[$i]);
999: unset($arraysToProcess[$i]);
1000: continue 2;
1001: }
1002:
1003: if (
1004: $preserveTaggedUnions
1005: && $overlappingKeysCount === count($arraysToProcess[$j]->getKeyTypes())
1006: && $arraysToProcess[$i]->isKeysSupersetOf($arraysToProcess[$j])
1007: ) {
1008: $arraysToProcess[$i] = $arraysToProcess[$i]->mergeWith($arraysToProcess[$j]);
1009: unset($arraysToProcess[$j]);
1010: continue 1;
1011: }
1012:
1013: if (
1014: !$preserveTaggedUnions
1015: // both arrays have same keys
1016: && $overlappingKeysCount === count($arraysToProcess[$i]->getKeyTypes())
1017: && $overlappingKeysCount === count($arraysToProcess[$j]->getKeyTypes())
1018: ) {
1019: $arraysToProcess[$j] = $arraysToProcess[$j]->mergeWith($arraysToProcess[$i]);
1020: unset($arraysToProcess[$i]);
1021: continue 2;
1022: }
1023: }
1024: }
1025:
1026: return array_merge($newArrays, $arraysToProcess);
1027: }
1028:
1029: public static function intersect(Type ...$types): Type
1030: {
1031: $types = array_values($types);
1032:
1033: $typesCount = count($types);
1034: if ($typesCount === 0) {
1035: return new NeverType();
1036: }
1037: if ($typesCount === 1) {
1038: return $types[0];
1039: }
1040:
1041: $sortTypes = static function (Type $a, Type $b): int {
1042: if (!$a instanceof UnionType || !$b instanceof UnionType) {
1043: return 0;
1044: }
1045:
1046: if ($a instanceof TemplateType) {
1047: return -1;
1048: }
1049: if ($b instanceof TemplateType) {
1050: return 1;
1051: }
1052:
1053: if ($a instanceof BenevolentUnionType) {
1054: return -1;
1055: }
1056: if ($b instanceof BenevolentUnionType) {
1057: return 1;
1058: }
1059:
1060: return 0;
1061: };
1062: usort($types, $sortTypes);
1063: // transform A & (B | C) to (A & B) | (A & C)
1064: foreach ($types as $i => $type) {
1065: if (!$type instanceof UnionType) {
1066: continue;
1067: }
1068:
1069: $topLevelUnionSubTypes = [];
1070: $innerTypes = $type->getTypes();
1071: usort($innerTypes, $sortTypes);
1072: $slice1 = array_slice($types, 0, $i);
1073: $slice2 = array_slice($types, $i + 1);
1074: foreach ($innerTypes as $innerUnionSubType) {
1075: $topLevelUnionSubTypes[] = self::intersect(
1076: $innerUnionSubType,
1077: ...$slice1,
1078: ...$slice2,
1079: );
1080: }
1081:
1082: $union = self::union(...$topLevelUnionSubTypes);
1083: if ($union instanceof NeverType) {
1084: return $union;
1085: }
1086:
1087: if ($type instanceof BenevolentUnionType) {
1088: $union = TypeUtils::toBenevolentUnion($union);
1089: }
1090:
1091: if ($type instanceof TemplateUnionType || $type instanceof TemplateBenevolentUnionType) {
1092: $union = TemplateTypeFactory::create(
1093: $type->getScope(),
1094: $type->getName(),
1095: $union,
1096: $type->getVariance(),
1097: $type->getStrategy(),
1098: $type->getDefault(),
1099: );
1100: }
1101:
1102: return $union;
1103: }
1104: $typesCount = count($types);
1105:
1106: // transform A & (B & C) to A & B & C
1107: for ($i = 0; $i < $typesCount; $i++) {
1108: $type = $types[$i];
1109:
1110: if (!($type instanceof IntersectionType)) {
1111: continue;
1112: }
1113:
1114: array_splice($types, $i--, 1, $type->getTypes());
1115: $typesCount = count($types);
1116: }
1117:
1118: $hasOffsetValueTypeCount = 0;
1119: $newTypes = [];
1120: foreach ($types as $type) {
1121: if (!$type instanceof HasOffsetValueType) {
1122: $newTypes[] = $type;
1123: continue;
1124: }
1125:
1126: $hasOffsetValueTypeCount++;
1127: }
1128:
1129: if ($hasOffsetValueTypeCount > 32) {
1130: $newTypes[] = new OversizedArrayType();
1131: $types = $newTypes;
1132: $typesCount = count($types);
1133: }
1134:
1135: usort($types, static function (Type $a, Type $b): int {
1136: // move subtractables with subtracts before those without to avoid loosing them in the union logic
1137: if ($a instanceof SubtractableType && $a->getSubtractedType() !== null) {
1138: return -1;
1139: }
1140: if ($b instanceof SubtractableType && $b->getSubtractedType() !== null) {
1141: return 1;
1142: }
1143:
1144: if ($a instanceof ConstantArrayType && !$b instanceof ConstantArrayType) {
1145: return -1;
1146: }
1147: if ($b instanceof ConstantArrayType && !$a instanceof ConstantArrayType) {
1148: return 1;
1149: }
1150:
1151: return 0;
1152: });
1153:
1154: // transform IntegerType & ConstantIntegerType to ConstantIntegerType
1155: // transform Child & Parent to Child
1156: // transform Object & ~null to Object
1157: // transform A & A to A
1158: // transform int[] & string to never
1159: // transform callable & int to never
1160: // transform A & ~A to never
1161: // transform int & string to never
1162: for ($i = 0; $i < $typesCount; $i++) {
1163: for ($j = $i + 1; $j < $typesCount; $j++) {
1164: if ($types[$j] instanceof SubtractableType) {
1165: $typeWithoutSubtractedTypeA = $types[$j]->getTypeWithoutSubtractedType();
1166:
1167: if ($typeWithoutSubtractedTypeA instanceof MixedType && $types[$i] instanceof MixedType) {
1168: $isSuperTypeSubtractableA = $typeWithoutSubtractedTypeA->isSuperTypeOfMixed($types[$i]);
1169: } else {
1170: $isSuperTypeSubtractableA = $typeWithoutSubtractedTypeA->isSuperTypeOf($types[$i]);
1171: }
1172: if ($isSuperTypeSubtractableA->yes()) {
1173: $types[$i] = self::unionWithSubtractedType($types[$i], $types[$j]->getSubtractedType());
1174: array_splice($types, $j--, 1);
1175: $typesCount--;
1176: continue 1;
1177: }
1178: }
1179:
1180: if ($types[$i] instanceof SubtractableType) {
1181: $typeWithoutSubtractedTypeB = $types[$i]->getTypeWithoutSubtractedType();
1182:
1183: if ($typeWithoutSubtractedTypeB instanceof MixedType && $types[$j] instanceof MixedType) {
1184: $isSuperTypeSubtractableB = $typeWithoutSubtractedTypeB->isSuperTypeOfMixed($types[$j]);
1185: } else {
1186: $isSuperTypeSubtractableB = $typeWithoutSubtractedTypeB->isSuperTypeOf($types[$j]);
1187: }
1188: if ($isSuperTypeSubtractableB->yes()) {
1189: $types[$j] = self::unionWithSubtractedType($types[$j], $types[$i]->getSubtractedType());
1190: array_splice($types, $i--, 1);
1191: $typesCount--;
1192: continue 2;
1193: }
1194: }
1195:
1196: if ($types[$i] instanceof IntegerRangeType) {
1197: $intersectionType = $types[$i]->tryIntersect($types[$j]);
1198: if ($intersectionType !== null) {
1199: $types[$j] = $intersectionType;
1200: array_splice($types, $i--, 1);
1201: $typesCount--;
1202: continue 2;
1203: }
1204: }
1205:
1206: if ($types[$j] instanceof IterableType) {
1207: $isSuperTypeA = $types[$j]->isSuperTypeOfMixed($types[$i]);
1208: } else {
1209: $isSuperTypeA = $types[$j]->isSuperTypeOf($types[$i]);
1210: }
1211:
1212: if ($isSuperTypeA->yes()) {
1213: array_splice($types, $j--, 1);
1214: $typesCount--;
1215: continue;
1216: }
1217:
1218: if ($types[$i] instanceof IterableType) {
1219: $isSuperTypeB = $types[$i]->isSuperTypeOfMixed($types[$j]);
1220: } else {
1221: $isSuperTypeB = $types[$i]->isSuperTypeOf($types[$j]);
1222: }
1223:
1224: if ($isSuperTypeB->maybe()) {
1225: if ($types[$i] instanceof ConstantArrayType && $types[$j] instanceof HasOffsetType) {
1226: $types[$i] = $types[$i]->makeOffsetRequired($types[$j]->getOffsetType());
1227: array_splice($types, $j--, 1);
1228: $typesCount--;
1229: continue;
1230: }
1231:
1232: if ($types[$j] instanceof ConstantArrayType && $types[$i] instanceof HasOffsetType) {
1233: $types[$j] = $types[$j]->makeOffsetRequired($types[$i]->getOffsetType());
1234: array_splice($types, $i--, 1);
1235: $typesCount--;
1236: continue 2;
1237: }
1238:
1239: if (
1240: $types[$i] instanceof ConstantArrayType
1241: && count($types[$i]->getKeyTypes()) === 1
1242: && $types[$i]->isOptionalKey(0)
1243: && $types[$j] instanceof NonEmptyArrayType
1244: ) {
1245: $types[$i] = $types[$i]->makeOffsetRequired($types[$i]->getKeyTypes()[0]);
1246: array_splice($types, $j--, 1);
1247: $typesCount--;
1248: continue;
1249: }
1250:
1251: if (
1252: $types[$j] instanceof ConstantArrayType
1253: && count($types[$j]->getKeyTypes()) === 1
1254: && $types[$j]->isOptionalKey(0)
1255: && $types[$i] instanceof NonEmptyArrayType
1256: ) {
1257: $types[$j] = $types[$j]->makeOffsetRequired($types[$j]->getKeyTypes()[0]);
1258: array_splice($types, $i--, 1);
1259: $typesCount--;
1260: continue 2;
1261: }
1262:
1263: if ($types[$i] instanceof ConstantArrayType && $types[$j] instanceof HasOffsetValueType) {
1264: $offsetType = $types[$j]->getOffsetType();
1265: $valueType = $types[$j]->getValueType();
1266: $newValueType = self::intersect($types[$i]->getOffsetValueType($offsetType), $valueType);
1267: if ($newValueType instanceof NeverType) {
1268: return $newValueType;
1269: }
1270: $types[$i] = $types[$i]->setOffsetValueType($offsetType, $newValueType);
1271: array_splice($types, $j--, 1);
1272: $typesCount--;
1273: continue;
1274: }
1275:
1276: if ($types[$j] instanceof ConstantArrayType && $types[$i] instanceof HasOffsetValueType) {
1277: $offsetType = $types[$i]->getOffsetType();
1278: $valueType = $types[$i]->getValueType();
1279: $newValueType = self::intersect($types[$j]->getOffsetValueType($offsetType), $valueType);
1280: if ($newValueType instanceof NeverType) {
1281: return $newValueType;
1282: }
1283:
1284: $types[$j] = $types[$j]->setOffsetValueType($offsetType, $newValueType);
1285: array_splice($types, $i--, 1);
1286: $typesCount--;
1287: continue 2;
1288: }
1289:
1290: if ($types[$i] instanceof OversizedArrayType && $types[$j] instanceof HasOffsetValueType) {
1291: array_splice($types, $j--, 1);
1292: $typesCount--;
1293: continue;
1294: }
1295:
1296: if ($types[$j] instanceof OversizedArrayType && $types[$i] instanceof HasOffsetValueType) {
1297: array_splice($types, $i--, 1);
1298: $typesCount--;
1299: continue 2;
1300: }
1301:
1302: if ($types[$i] instanceof ObjectShapeType && $types[$j] instanceof HasPropertyType) {
1303: $types[$i] = $types[$i]->makePropertyRequired($types[$j]->getPropertyName());
1304: array_splice($types, $j--, 1);
1305: $typesCount--;
1306: continue;
1307: }
1308:
1309: if ($types[$j] instanceof ObjectShapeType && $types[$i] instanceof HasPropertyType) {
1310: $types[$j] = $types[$j]->makePropertyRequired($types[$i]->getPropertyName());
1311: array_splice($types, $i--, 1);
1312: $typesCount--;
1313: continue 2;
1314: }
1315:
1316: if ($types[$i] instanceof ConstantArrayType && ($types[$j] instanceof ArrayType || $types[$j] instanceof ConstantArrayType)) {
1317: $newArray = ConstantArrayTypeBuilder::createEmpty();
1318: $valueTypes = $types[$i]->getValueTypes();
1319: foreach ($types[$i]->getKeyTypes() as $k => $keyType) {
1320: $newArray->setOffsetValueType(
1321: self::intersect($keyType, $types[$j]->getIterableKeyType()),
1322: self::intersect($valueTypes[$k], $types[$j]->getIterableValueType()),
1323: $types[$i]->isOptionalKey($k) && !$types[$j]->hasOffsetValueType($keyType)->yes(),
1324: );
1325: }
1326: $types[$i] = $newArray->getArray();
1327: array_splice($types, $j--, 1);
1328: $typesCount--;
1329: continue 2;
1330: }
1331:
1332: if ($types[$j] instanceof ConstantArrayType && ($types[$i] instanceof ArrayType || $types[$i] instanceof ConstantArrayType)) {
1333: $newArray = ConstantArrayTypeBuilder::createEmpty();
1334: $valueTypes = $types[$j]->getValueTypes();
1335: foreach ($types[$j]->getKeyTypes() as $k => $keyType) {
1336: $newArray->setOffsetValueType(
1337: self::intersect($keyType, $types[$i]->getIterableKeyType()),
1338: self::intersect($valueTypes[$k], $types[$i]->getIterableValueType()),
1339: $types[$j]->isOptionalKey($k) && !$types[$i]->hasOffsetValueType($keyType)->yes(),
1340: );
1341: }
1342: $types[$j] = $newArray->getArray();
1343: array_splice($types, $i--, 1);
1344: $typesCount--;
1345: continue 2;
1346: }
1347:
1348: if (
1349: ($types[$i] instanceof ArrayType || $types[$i] instanceof ConstantArrayType || $types[$i] instanceof IterableType) &&
1350: ($types[$j] instanceof ArrayType || $types[$j] instanceof ConstantArrayType || $types[$j] instanceof IterableType)
1351: ) {
1352: $keyType = self::intersect($types[$i]->getIterableKeyType(), $types[$j]->getKeyType());
1353: $itemType = self::intersect($types[$i]->getItemType(), $types[$j]->getItemType());
1354: if ($types[$i] instanceof IterableType && $types[$j] instanceof IterableType) {
1355: $types[$j] = new IterableType($keyType, $itemType);
1356: } else {
1357: $types[$j] = new ArrayType($keyType, $itemType);
1358: }
1359: array_splice($types, $i--, 1);
1360: $typesCount--;
1361: continue 2;
1362: }
1363:
1364: if ($types[$i] instanceof GenericClassStringType && $types[$j] instanceof GenericClassStringType) {
1365: $genericType = self::intersect($types[$i]->getGenericType(), $types[$j]->getGenericType());
1366: $types[$i] = new GenericClassStringType($genericType);
1367: array_splice($types, $j--, 1);
1368: $typesCount--;
1369: continue;
1370: }
1371:
1372: if (
1373: $types[$i] instanceof ArrayType
1374: && get_class($types[$i]) === ArrayType::class
1375: && $types[$j] instanceof AccessoryArrayListType
1376: && !$types[$j]->getIterableKeyType()->isSuperTypeOf($types[$i]->getIterableKeyType())->yes()
1377: ) {
1378: $keyType = self::intersect($types[$i]->getIterableKeyType(), $types[$j]->getIterableKeyType());
1379: if ($keyType instanceof NeverType) {
1380: return $keyType;
1381: }
1382: $types[$i] = new ArrayType($keyType, $types[$i]->getItemType());
1383: continue;
1384: }
1385:
1386: continue;
1387: }
1388:
1389: if ($isSuperTypeB->yes()) {
1390: array_splice($types, $i--, 1);
1391: $typesCount--;
1392: continue 2;
1393: }
1394:
1395: if ($isSuperTypeA->no()) {
1396: return new NeverType();
1397: }
1398: }
1399: }
1400:
1401: if ($typesCount === 1) {
1402: return $types[0];
1403: }
1404:
1405: return new IntersectionType($types);
1406: }
1407:
1408: public static function removeFalsey(Type $type): Type
1409: {
1410: return self::remove($type, StaticTypeFactory::falsey());
1411: }
1412:
1413: public static function removeTruthy(Type $type): Type
1414: {
1415: return self::remove($type, StaticTypeFactory::truthy());
1416: }
1417:
1418: }
1419: