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