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