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