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