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