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