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