1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use DateTime;
6: use DateTimeImmutable;
7: use DateTimeInterface;
8: use PHPStan\Reflection\ClassMemberAccessAnswerer;
9: use PHPStan\Reflection\ConstantReflection;
10: use PHPStan\Reflection\ExtendedMethodReflection;
11: use PHPStan\Reflection\ParametersAcceptor;
12: use PHPStan\Reflection\PropertyReflection;
13: use PHPStan\Reflection\Type\UnionTypeUnresolvedMethodPrototypeReflection;
14: use PHPStan\Reflection\Type\UnionTypeUnresolvedPropertyPrototypeReflection;
15: use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
16: use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
17: use PHPStan\ShouldNotHappenException;
18: use PHPStan\TrinaryLogic;
19: use PHPStan\Type\Constant\ConstantBooleanType;
20: use PHPStan\Type\Generic\GenericClassStringType;
21: use PHPStan\Type\Generic\TemplateMixedType;
22: use PHPStan\Type\Generic\TemplateType;
23: use PHPStan\Type\Generic\TemplateTypeMap;
24: use PHPStan\Type\Generic\TemplateTypeVariance;
25: use PHPStan\Type\Generic\TemplateUnionType;
26: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
27: use function array_map;
28: use function count;
29: use function implode;
30: use function sprintf;
31: use function strpos;
32:
33: /** @api */
34: class UnionType implements CompoundType
35: {
36:
37: use NonGeneralizableTypeTrait;
38:
39: private bool $sortedTypes = false;
40:
41: /** @var array<int, string> */
42: private array $cachedDescriptions = [];
43:
44: /**
45: * @api
46: * @param Type[] $types
47: */
48: public function __construct(private array $types, private bool $normalized = false)
49: {
50: $throwException = static function () use ($types): void {
51: throw new ShouldNotHappenException(sprintf(
52: 'Cannot create %s with: %s',
53: self::class,
54: implode(', ', array_map(static fn (Type $type): string => $type->describe(VerbosityLevel::value()), $types)),
55: ));
56: };
57: if (count($types) < 2) {
58: $throwException();
59: }
60: foreach ($types as $type) {
61: if (!($type instanceof UnionType)) {
62: continue;
63: }
64: if ($type instanceof TemplateType) {
65: continue;
66: }
67:
68: $throwException();
69: }
70: }
71:
72: /**
73: * @return Type[]
74: */
75: public function getTypes(): array
76: {
77: return $this->types;
78: }
79:
80: public function isNormalized(): bool
81: {
82: return $this->normalized;
83: }
84:
85: /**
86: * @return Type[]
87: */
88: private function getSortedTypes(): array
89: {
90: if ($this->sortedTypes) {
91: return $this->types;
92: }
93:
94: $this->types = UnionTypeHelper::sortTypes($this->types);
95: $this->sortedTypes = true;
96:
97: return $this->types;
98: }
99:
100: /**
101: * @return string[]
102: */
103: public function getReferencedClasses(): array
104: {
105: $classes = [];
106: foreach ($this->types as $type) {
107: foreach ($type->getReferencedClasses() as $className) {
108: $classes[] = $className;
109: }
110: }
111:
112: return $classes;
113: }
114:
115: public function getArrays(): array
116: {
117: return $this->pickTypes(static fn (Type $type) => $type->getArrays());
118: }
119:
120: public function getConstantArrays(): array
121: {
122: return $this->pickTypes(static fn (Type $type) => $type->getConstantArrays());
123: }
124:
125: public function getConstantStrings(): array
126: {
127: return $this->pickTypes(static fn (Type $type) => $type->getConstantStrings());
128: }
129:
130: public function accepts(Type $type, bool $strictTypes): TrinaryLogic
131: {
132: if (
133: $type->equals(new ObjectType(DateTimeInterface::class))
134: && $this->accepts(
135: new UnionType([new ObjectType(DateTime::class), new ObjectType(DateTimeImmutable::class)]),
136: $strictTypes,
137: )->yes()
138: ) {
139: return TrinaryLogic::createYes();
140: }
141:
142: if ($type instanceof CompoundType && !$type instanceof CallableType && !$type instanceof TemplateType && !$type instanceof IntersectionType) {
143: return $type->isAcceptedBy($this, $strictTypes);
144: }
145:
146: $result = TrinaryLogic::createNo()->lazyOr($this->getTypes(), static fn (Type $innerType) => $innerType->accepts($type, $strictTypes));
147: if ($result->yes()) {
148: return $result;
149: }
150:
151: if ($type instanceof TemplateUnionType) {
152: return $result->or($type->isAcceptedBy($this, $strictTypes));
153: }
154:
155: return $result;
156: }
157:
158: public function isSuperTypeOf(Type $otherType): TrinaryLogic
159: {
160: if (
161: ($otherType instanceof self && !$otherType instanceof TemplateUnionType)
162: || $otherType instanceof IterableType
163: || $otherType instanceof NeverType
164: || $otherType instanceof ConditionalType
165: || $otherType instanceof ConditionalTypeForParameter
166: || $otherType instanceof IntegerRangeType
167: ) {
168: return $otherType->isSubTypeOf($this);
169: }
170:
171: $result = TrinaryLogic::createNo()->lazyOr($this->getTypes(), static fn (Type $innerType) => $innerType->isSuperTypeOf($otherType));
172: if ($result->yes()) {
173: return $result;
174: }
175:
176: if ($otherType instanceof TemplateUnionType) {
177: return $result->or($otherType->isSubTypeOf($this));
178: }
179:
180: return $result;
181: }
182:
183: public function isSubTypeOf(Type $otherType): TrinaryLogic
184: {
185: return TrinaryLogic::lazyExtremeIdentity($this->getTypes(), static fn (Type $innerType) => $otherType->isSuperTypeOf($innerType));
186: }
187:
188: public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic
189: {
190: return TrinaryLogic::lazyExtremeIdentity($this->getTypes(), static fn (Type $innerType) => $acceptingType->accepts($innerType, $strictTypes));
191: }
192:
193: public function equals(Type $type): bool
194: {
195: if (!$type instanceof static) {
196: return false;
197: }
198:
199: if (count($this->types) !== count($type->types)) {
200: return false;
201: }
202:
203: $otherTypes = $type->types;
204: foreach ($this->types as $innerType) {
205: $match = false;
206: foreach ($otherTypes as $i => $otherType) {
207: if (!$innerType->equals($otherType)) {
208: continue;
209: }
210:
211: $match = true;
212: unset($otherTypes[$i]);
213: break;
214: }
215:
216: if (!$match) {
217: return false;
218: }
219: }
220:
221: return count($otherTypes) === 0;
222: }
223:
224: public function describe(VerbosityLevel $level): string
225: {
226: if (isset($this->cachedDescriptions[$level->getLevelValue()])) {
227: return $this->cachedDescriptions[$level->getLevelValue()];
228: }
229: $joinTypes = static function (array $types) use ($level): string {
230: $typeNames = [];
231: foreach ($types as $i => $type) {
232: if ($type instanceof ClosureType || $type instanceof CallableType || $type instanceof TemplateUnionType) {
233: $typeNames[] = sprintf('(%s)', $type->describe($level));
234: } elseif ($type instanceof TemplateType) {
235: $isLast = $i >= count($types) - 1;
236: $bound = $type->getBound();
237: if (
238: !$isLast
239: && ($level->isTypeOnly() || $level->isValue())
240: && !($bound instanceof MixedType && $bound->getSubtractedType() === null && !$bound instanceof TemplateMixedType)
241: ) {
242: $typeNames[] = sprintf('(%s)', $type->describe($level));
243: } else {
244: $typeNames[] = $type->describe($level);
245: }
246: } elseif ($type instanceof IntersectionType) {
247: $intersectionDescription = $type->describe($level);
248: if (strpos($intersectionDescription, '&') !== false) {
249: $typeNames[] = sprintf('(%s)', $type->describe($level));
250: } else {
251: $typeNames[] = $intersectionDescription;
252: }
253: } else {
254: $typeNames[] = $type->describe($level);
255: }
256: }
257:
258: return implode('|', $typeNames);
259: };
260:
261: return $this->cachedDescriptions[$level->getLevelValue()] = $level->handle(
262: function () use ($joinTypes): string {
263: $types = TypeCombinator::union(...array_map(static function (Type $type): Type {
264: if (
265: $type instanceof ConstantType
266: && !$type instanceof ConstantBooleanType
267: ) {
268: return $type->generalize(GeneralizePrecision::lessSpecific());
269: }
270:
271: return $type;
272: }, $this->getSortedTypes()));
273:
274: if ($types instanceof UnionType) {
275: return $joinTypes($types->getSortedTypes());
276: }
277:
278: return $joinTypes([$types]);
279: },
280: fn (): string => $joinTypes($this->getSortedTypes()),
281: );
282: }
283:
284: /**
285: * @param callable(Type $type): TrinaryLogic $canCallback
286: * @param callable(Type $type): TrinaryLogic $hasCallback
287: */
288: private function hasInternal(
289: callable $canCallback,
290: callable $hasCallback,
291: ): TrinaryLogic
292: {
293: return TrinaryLogic::lazyExtremeIdentity($this->types, static function (Type $type) use ($canCallback, $hasCallback): TrinaryLogic {
294: if ($canCallback($type)->no()) {
295: return TrinaryLogic::createNo();
296: }
297:
298: return $hasCallback($type);
299: });
300: }
301:
302: /**
303: * @template TObject of object
304: * @param callable(Type $type): TrinaryLogic $hasCallback
305: * @param callable(Type $type): TObject $getCallback
306: * @return TObject
307: */
308: private function getInternal(
309: callable $hasCallback,
310: callable $getCallback,
311: ): object
312: {
313: /** @var TrinaryLogic|null $result */
314: $result = null;
315:
316: /** @var TObject|null $object */
317: $object = null;
318: foreach ($this->types as $type) {
319: $has = $hasCallback($type);
320: if (!$has->yes()) {
321: continue;
322: }
323: if ($result !== null && $result->compareTo($has) !== $has) {
324: continue;
325: }
326:
327: $get = $getCallback($type);
328: $result = $has;
329: $object = $get;
330: }
331:
332: if ($object === null) {
333: throw new ShouldNotHappenException();
334: }
335:
336: return $object;
337: }
338:
339: public function canAccessProperties(): TrinaryLogic
340: {
341: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->canAccessProperties());
342: }
343:
344: public function hasProperty(string $propertyName): TrinaryLogic
345: {
346: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->hasProperty($propertyName));
347: }
348:
349: public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection
350: {
351: return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty();
352: }
353:
354: public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
355: {
356: $propertyPrototypes = [];
357: foreach ($this->types as $type) {
358: if (!$type->hasProperty($propertyName)->yes()) {
359: continue;
360: }
361:
362: $propertyPrototypes[] = $type->getUnresolvedPropertyPrototype($propertyName, $scope)->withFechedOnType($this);
363: }
364:
365: $propertiesCount = count($propertyPrototypes);
366: if ($propertiesCount === 0) {
367: throw new ShouldNotHappenException();
368: }
369:
370: if ($propertiesCount === 1) {
371: return $propertyPrototypes[0];
372: }
373:
374: return new UnionTypeUnresolvedPropertyPrototypeReflection($propertyName, $propertyPrototypes);
375: }
376:
377: public function canCallMethods(): TrinaryLogic
378: {
379: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->canCallMethods());
380: }
381:
382: public function hasMethod(string $methodName): TrinaryLogic
383: {
384: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->hasMethod($methodName));
385: }
386:
387: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
388: {
389: return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
390: }
391:
392: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
393: {
394: $methodPrototypes = [];
395: foreach ($this->types as $type) {
396: if (!$type->hasMethod($methodName)->yes()) {
397: continue;
398: }
399:
400: $methodPrototypes[] = $type->getUnresolvedMethodPrototype($methodName, $scope)->withCalledOnType($this);
401: }
402:
403: $methodsCount = count($methodPrototypes);
404: if ($methodsCount === 0) {
405: throw new ShouldNotHappenException();
406: }
407:
408: if ($methodsCount === 1) {
409: return $methodPrototypes[0];
410: }
411:
412: return new UnionTypeUnresolvedMethodPrototypeReflection($methodName, $methodPrototypes);
413: }
414:
415: public function canAccessConstants(): TrinaryLogic
416: {
417: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->canAccessConstants());
418: }
419:
420: public function hasConstant(string $constantName): TrinaryLogic
421: {
422: return $this->hasInternal(
423: static fn (Type $type): TrinaryLogic => $type->canAccessConstants(),
424: static fn (Type $type): TrinaryLogic => $type->hasConstant($constantName),
425: );
426: }
427:
428: public function getConstant(string $constantName): ConstantReflection
429: {
430: return $this->getInternal(
431: static fn (Type $type): TrinaryLogic => $type->hasConstant($constantName),
432: static fn (Type $type): ConstantReflection => $type->getConstant($constantName),
433: );
434: }
435:
436: public function isIterable(): TrinaryLogic
437: {
438: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isIterable());
439: }
440:
441: public function isIterableAtLeastOnce(): TrinaryLogic
442: {
443: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isIterableAtLeastOnce());
444: }
445:
446: public function getArraySize(): Type
447: {
448: return $this->unionTypes(static fn (Type $type): Type => $type->getArraySize());
449: }
450:
451: public function getIterableKeyType(): Type
452: {
453: return $this->unionTypes(static fn (Type $type): Type => $type->getIterableKeyType());
454: }
455:
456: public function getFirstIterableKeyType(): Type
457: {
458: return $this->unionTypes(static fn (Type $type): Type => $type->getFirstIterableKeyType());
459: }
460:
461: public function getLastIterableKeyType(): Type
462: {
463: return $this->unionTypes(static fn (Type $type): Type => $type->getLastIterableKeyType());
464: }
465:
466: public function getIterableValueType(): Type
467: {
468: return $this->unionTypes(static fn (Type $type): Type => $type->getIterableValueType());
469: }
470:
471: public function getFirstIterableValueType(): Type
472: {
473: return $this->unionTypes(static fn (Type $type): Type => $type->getFirstIterableValueType());
474: }
475:
476: public function getLastIterableValueType(): Type
477: {
478: return $this->unionTypes(static fn (Type $type): Type => $type->getLastIterableValueType());
479: }
480:
481: public function isArray(): TrinaryLogic
482: {
483: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isArray());
484: }
485:
486: public function isConstantArray(): TrinaryLogic
487: {
488: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isConstantArray());
489: }
490:
491: public function isOversizedArray(): TrinaryLogic
492: {
493: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isOversizedArray());
494: }
495:
496: public function isList(): TrinaryLogic
497: {
498: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isList());
499: }
500:
501: public function isString(): TrinaryLogic
502: {
503: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isString());
504: }
505:
506: public function isNumericString(): TrinaryLogic
507: {
508: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isNumericString());
509: }
510:
511: public function isNonEmptyString(): TrinaryLogic
512: {
513: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isNonEmptyString());
514: }
515:
516: public function isNonFalsyString(): TrinaryLogic
517: {
518: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isNonFalsyString());
519: }
520:
521: public function isLiteralString(): TrinaryLogic
522: {
523: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isLiteralString());
524: }
525:
526: public function isClassStringType(): TrinaryLogic
527: {
528: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isClassStringType());
529: }
530:
531: public function isVoid(): TrinaryLogic
532: {
533: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isVoid());
534: }
535:
536: public function isScalar(): TrinaryLogic
537: {
538: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isScalar());
539: }
540:
541: public function isOffsetAccessible(): TrinaryLogic
542: {
543: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isOffsetAccessible());
544: }
545:
546: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
547: {
548: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->hasOffsetValueType($offsetType));
549: }
550:
551: public function getOffsetValueType(Type $offsetType): Type
552: {
553: $types = [];
554: foreach ($this->types as $innerType) {
555: $valueType = $innerType->getOffsetValueType($offsetType);
556: if ($valueType instanceof ErrorType) {
557: continue;
558: }
559:
560: $types[] = $valueType;
561: }
562:
563: if (count($types) === 0) {
564: return new ErrorType();
565: }
566:
567: return TypeCombinator::union(...$types);
568: }
569:
570: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
571: {
572: return $this->unionTypes(static fn (Type $type): Type => $type->setOffsetValueType($offsetType, $valueType, $unionValues));
573: }
574:
575: public function unsetOffset(Type $offsetType): Type
576: {
577: return $this->unionTypes(static fn (Type $type): Type => $type->unsetOffset($offsetType));
578: }
579:
580: public function getKeysArray(): Type
581: {
582: return $this->unionTypes(static fn (Type $type): Type => $type->getKeysArray());
583: }
584:
585: public function getValuesArray(): Type
586: {
587: return $this->unionTypes(static fn (Type $type): Type => $type->getValuesArray());
588: }
589:
590: public function fillKeysArray(Type $valueType): Type
591: {
592: return $this->unionTypes(static fn (Type $type): Type => $type->fillKeysArray($valueType));
593: }
594:
595: public function flipArray(): Type
596: {
597: return $this->unionTypes(static fn (Type $type): Type => $type->flipArray());
598: }
599:
600: public function intersectKeyArray(Type $otherArraysType): Type
601: {
602: return $this->unionTypes(static fn (Type $type): Type => $type->intersectKeyArray($otherArraysType));
603: }
604:
605: public function popArray(): Type
606: {
607: return $this->unionTypes(static fn (Type $type): Type => $type->popArray());
608: }
609:
610: public function searchArray(Type $needleType): Type
611: {
612: return $this->unionTypes(static fn (Type $type): Type => $type->searchArray($needleType));
613: }
614:
615: public function shiftArray(): Type
616: {
617: return $this->unionTypes(static fn (Type $type): Type => $type->shiftArray());
618: }
619:
620: public function shuffleArray(): Type
621: {
622: return $this->unionTypes(static fn (Type $type): Type => $type->shuffleArray());
623: }
624:
625: public function isCallable(): TrinaryLogic
626: {
627: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isCallable());
628: }
629:
630: /**
631: * @return ParametersAcceptor[]
632: */
633: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
634: {
635: foreach ($this->types as $type) {
636: if ($type->isCallable()->no()) {
637: continue;
638: }
639:
640: return $type->getCallableParametersAcceptors($scope);
641: }
642:
643: throw new ShouldNotHappenException();
644: }
645:
646: public function isCloneable(): TrinaryLogic
647: {
648: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isCloneable());
649: }
650:
651: public function isSmallerThan(Type $otherType): TrinaryLogic
652: {
653: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isSmallerThan($otherType));
654: }
655:
656: public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic
657: {
658: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isSmallerThanOrEqual($otherType));
659: }
660:
661: public function isNull(): TrinaryLogic
662: {
663: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isNull());
664: }
665:
666: public function isTrue(): TrinaryLogic
667: {
668: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isTrue());
669: }
670:
671: public function isFalse(): TrinaryLogic
672: {
673: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isFalse());
674: }
675:
676: public function isBoolean(): TrinaryLogic
677: {
678: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isBoolean());
679: }
680:
681: public function isFloat(): TrinaryLogic
682: {
683: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isFloat());
684: }
685:
686: public function isInteger(): TrinaryLogic
687: {
688: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isInteger());
689: }
690:
691: public function getSmallerType(): Type
692: {
693: return $this->unionTypes(static fn (Type $type): Type => $type->getSmallerType());
694: }
695:
696: public function getSmallerOrEqualType(): Type
697: {
698: return $this->unionTypes(static fn (Type $type): Type => $type->getSmallerOrEqualType());
699: }
700:
701: public function getGreaterType(): Type
702: {
703: return $this->unionTypes(static fn (Type $type): Type => $type->getGreaterType());
704: }
705:
706: public function getGreaterOrEqualType(): Type
707: {
708: return $this->unionTypes(static fn (Type $type): Type => $type->getGreaterOrEqualType());
709: }
710:
711: public function isGreaterThan(Type $otherType): TrinaryLogic
712: {
713: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $otherType->isSmallerThan($type));
714: }
715:
716: public function isGreaterThanOrEqual(Type $otherType): TrinaryLogic
717: {
718: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $otherType->isSmallerThanOrEqual($type));
719: }
720:
721: public function toBoolean(): BooleanType
722: {
723: /** @var BooleanType $type */
724: $type = $this->unionTypes(static fn (Type $type): BooleanType => $type->toBoolean());
725:
726: return $type;
727: }
728:
729: public function toNumber(): Type
730: {
731: $type = $this->unionTypes(static fn (Type $type): Type => $type->toNumber());
732:
733: return $type;
734: }
735:
736: public function toString(): Type
737: {
738: $type = $this->unionTypes(static fn (Type $type): Type => $type->toString());
739:
740: return $type;
741: }
742:
743: public function toInteger(): Type
744: {
745: $type = $this->unionTypes(static fn (Type $type): Type => $type->toInteger());
746:
747: return $type;
748: }
749:
750: public function toFloat(): Type
751: {
752: $type = $this->unionTypes(static fn (Type $type): Type => $type->toFloat());
753:
754: return $type;
755: }
756:
757: public function toArray(): Type
758: {
759: $type = $this->unionTypes(static fn (Type $type): Type => $type->toArray());
760:
761: return $type;
762: }
763:
764: public function toArrayKey(): Type
765: {
766: return $this->unionTypes(static fn (Type $type): Type => $type->toArrayKey());
767: }
768:
769: public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
770: {
771: $types = TemplateTypeMap::createEmpty();
772: if ($receivedType instanceof UnionType) {
773: $myTypes = [];
774: $remainingReceivedTypes = [];
775: foreach ($receivedType->getTypes() as $receivedInnerType) {
776: foreach ($this->types as $type) {
777: if ($type->isSuperTypeOf($receivedInnerType)->yes()) {
778: $types = $types->union($type->inferTemplateTypes($receivedInnerType));
779: continue 2;
780: }
781: $myTypes[] = $type;
782: }
783: $remainingReceivedTypes[] = $receivedInnerType;
784: }
785: if (count($remainingReceivedTypes) === 0) {
786: return $types;
787: }
788: $receivedType = TypeCombinator::union(...$remainingReceivedTypes);
789: } else {
790: $myTypes = $this->types;
791: }
792:
793: $myTemplateTypes = [];
794: foreach ($myTypes as $type) {
795: if ($type instanceof TemplateType || ($type instanceof GenericClassStringType && $type->getGenericType() instanceof TemplateType)) {
796: $myTemplateTypes[] = $type;
797: continue;
798: }
799: $types = $types->union($type->inferTemplateTypes($receivedType));
800: }
801:
802: if (!$types->isEmpty()) {
803: return $types;
804: }
805:
806: foreach ($myTypes as $type) {
807: $types = $types->union($type->inferTemplateTypes($receivedType));
808: }
809:
810: return $types;
811: }
812:
813: public function inferTemplateTypesOn(Type $templateType): TemplateTypeMap
814: {
815: $types = TemplateTypeMap::createEmpty();
816:
817: foreach ($this->types as $type) {
818: $types = $types->union($templateType->inferTemplateTypes($type));
819: }
820:
821: return $types;
822: }
823:
824: public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array
825: {
826: $references = [];
827:
828: foreach ($this->types as $type) {
829: foreach ($type->getReferencedTemplateTypes($positionVariance) as $reference) {
830: $references[] = $reference;
831: }
832: }
833:
834: return $references;
835: }
836:
837: public function traverse(callable $cb): Type
838: {
839: $types = [];
840: $changed = false;
841:
842: foreach ($this->types as $type) {
843: $newType = $cb($type);
844: if ($type !== $newType) {
845: $changed = true;
846: }
847: $types[] = $newType;
848: }
849:
850: if ($changed) {
851: return TypeCombinator::union(...$types);
852: }
853:
854: return $this;
855: }
856:
857: public function tryRemove(Type $typeToRemove): ?Type
858: {
859: return $this->unionTypes(static fn (Type $type): Type => TypeCombinator::remove($type, $typeToRemove));
860: }
861:
862: /**
863: * @param mixed[] $properties
864: */
865: public static function __set_state(array $properties): Type
866: {
867: return new self($properties['types'], $properties['normalized']);
868: }
869:
870: /**
871: * @param callable(Type $type): TrinaryLogic $getResult
872: */
873: protected function unionResults(callable $getResult): TrinaryLogic
874: {
875: return TrinaryLogic::lazyExtremeIdentity($this->types, $getResult);
876: }
877:
878: /**
879: * @param callable(Type $type): TrinaryLogic $getResult
880: */
881: private function notBenevolentUnionResults(callable $getResult): TrinaryLogic
882: {
883: return TrinaryLogic::lazyExtremeIdentity($this->types, $getResult);
884: }
885:
886: /**
887: * @param callable(Type $type): Type $getType
888: */
889: protected function unionTypes(callable $getType): Type
890: {
891: return TypeCombinator::union(...array_map($getType, $this->types));
892: }
893:
894: /**
895: * @template T of Type
896: * @param callable(Type $type): list<T> $getTypes
897: * @return list<T>
898: */
899: protected function pickTypes(callable $getTypes): array
900: {
901: $types = [];
902: foreach ($this->types as $type) {
903: $innerTypes = $getTypes($type);
904: if ($innerTypes === []) {
905: return [];
906: }
907:
908: foreach ($innerTypes as $innerType) {
909: $types[] = $innerType;
910: }
911: }
912:
913: return $types;
914: }
915:
916: }
917: