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