1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use DateTime;
6: use DateTimeImmutable;
7: use DateTimeInterface;
8: use Error;
9: use Exception;
10: use PHPStan\DependencyInjection\ReportUnsafeArrayStringKeyCastingToggle;
11: use PHPStan\Php\PhpVersion;
12: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
13: use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
14: use PHPStan\Reflection\ClassConstantReflection;
15: use PHPStan\Reflection\ClassMemberAccessAnswerer;
16: use PHPStan\Reflection\ExtendedMethodReflection;
17: use PHPStan\Reflection\ExtendedPropertyReflection;
18: use PHPStan\Reflection\InitializerExprTypeResolver;
19: use PHPStan\Reflection\MissingMethodFromReflectionException;
20: use PHPStan\Reflection\MissingPropertyFromReflectionException;
21: use PHPStan\Reflection\ReflectionProvider;
22: use PHPStan\Reflection\Type\UnionTypeUnresolvedMethodPrototypeReflection;
23: use PHPStan\Reflection\Type\UnionTypeUnresolvedPropertyPrototypeReflection;
24: use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
25: use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
26: use PHPStan\ShouldNotHappenException;
27: use PHPStan\TrinaryLogic;
28: use PHPStan\Type\Enum\EnumCaseObjectType;
29: use PHPStan\Type\Generic\GenericClassStringType;
30: use PHPStan\Type\Generic\TemplateIterableType;
31: use PHPStan\Type\Generic\TemplateMixedType;
32: use PHPStan\Type\Generic\TemplateType;
33: use PHPStan\Type\Generic\TemplateTypeMap;
34: use PHPStan\Type\Generic\TemplateTypeVariance;
35: use PHPStan\Type\Generic\TemplateUnionType;
36: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
37: use Throwable;
38: use function array_diff_assoc;
39: use function array_fill_keys;
40: use function array_intersect;
41: use function array_keys;
42: use function array_map;
43: use function array_merge;
44: use function array_slice;
45: use function array_unique;
46: use function array_values;
47: use function count;
48: use function implode;
49: use function sprintf;
50: use function str_contains;
51:
52: /** @api */
53: class UnionType implements CompoundType
54: {
55:
56: use NonGeneralizableTypeTrait;
57:
58: public const EQUAL_UNION_CLASSES = [
59: DateTimeInterface::class => [DateTimeImmutable::class, DateTime::class],
60: Throwable::class => [Error::class, Exception::class], // phpcs:ignore SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException
61: ];
62:
63: private bool $sortedTypes = false;
64:
65: /** @var array<int, string> */
66: private array $cachedDescriptions = [];
67:
68: /**
69: * @api
70: * @param list<Type> $types
71: */
72: public function __construct(private array $types, private bool $normalized = false)
73: {
74: $throwException = static function () use ($types): void {
75: throw new ShouldNotHappenException(sprintf(
76: 'Cannot create %s with: %s',
77: self::class,
78: implode(', ', array_map(static fn (Type $type): string => $type->describe(VerbosityLevel::value()), $types)),
79: ));
80: };
81: if (count($types) < 2) {
82: $throwException();
83: }
84: foreach ($types as $type) {
85: if (!($type instanceof UnionType)) {
86: continue;
87: }
88: if ($type instanceof TemplateType) {
89: continue;
90: }
91:
92: $throwException();
93: }
94: }
95:
96: /**
97: * @return list<Type>
98: */
99: public function getTypes(): array
100: {
101: return $this->types;
102: }
103:
104: /**
105: * @param callable(Type $type): bool $filterCb
106: */
107: public function filterTypes(callable $filterCb): Type
108: {
109: $newTypes = [];
110: $changed = false;
111: foreach ($this->getTypes() as $innerType) {
112: if (!$filterCb($innerType)) {
113: $changed = true;
114: continue;
115: }
116:
117: $newTypes[] = $innerType;
118: }
119:
120: if (!$changed) {
121: return $this;
122: }
123:
124: return TypeCombinator::union(...$newTypes);
125: }
126:
127: public function isNormalized(): bool
128: {
129: return $this->normalized;
130: }
131:
132: /**
133: * @return list<Type>
134: */
135: protected function getSortedTypes(): array
136: {
137: if ($this->sortedTypes) {
138: return $this->types;
139: }
140:
141: $this->types = UnionTypeHelper::sortTypes($this->types);
142: $this->sortedTypes = true;
143:
144: return $this->types;
145: }
146:
147: public function getReferencedClasses(): array
148: {
149: $classes = [];
150: foreach ($this->types as $type) {
151: foreach ($type->getReferencedClasses() as $className) {
152: $classes[] = $className;
153: }
154: }
155:
156: return $classes;
157: }
158:
159: public function getObjectClassNames(): array
160: {
161: return array_values(array_unique($this->pickFromTypes(
162: static fn (Type $type) => $type->getObjectClassNames(),
163: static fn (Type $type) => $type->isObject()->yes(),
164: )));
165: }
166:
167: public function getObjectClassReflections(): array
168: {
169: return $this->pickFromTypes(
170: static fn (Type $type) => $type->getObjectClassReflections(),
171: static fn (Type $type) => $type->isObject()->yes(),
172: );
173: }
174:
175: public function getArrays(): array
176: {
177: return $this->pickFromTypes(
178: static fn (Type $type) => $type->getArrays(),
179: static fn (Type $type) => $type->isArray()->yes(),
180: );
181: }
182:
183: public function getConstantArrays(): array
184: {
185: return $this->pickFromTypes(
186: static fn (Type $type) => $type->getConstantArrays(),
187: static fn (Type $type) => $type->isArray()->yes(),
188: );
189: }
190:
191: public function getConstantStrings(): array
192: {
193: return $this->pickFromTypes(
194: static fn (Type $type) => $type->getConstantStrings(),
195: static fn (Type $type) => $type->isString()->yes(),
196: );
197: }
198:
199: public function accepts(Type $type, bool $strictTypes): AcceptsResult
200: {
201: if ($type instanceof IterableType) {
202: return $this->accepts($type->toArrayOrTraversable(), $strictTypes);
203: }
204:
205: foreach (self::EQUAL_UNION_CLASSES as $baseClass => $classes) {
206: if (!$type->equals(new ObjectType($baseClass))) {
207: continue;
208: }
209:
210: $union = TypeCombinator::union(
211: ...array_map(static fn (string $objectClass): Type => new ObjectType($objectClass), $classes),
212: );
213: if ($this->accepts($union, $strictTypes)->yes()) {
214: return AcceptsResult::createYes();
215: }
216: break;
217: }
218:
219: $innerAccepts = [];
220: $result = AcceptsResult::createNo();
221: foreach ($this->getSortedTypes() as $i => $innerType) {
222: $innerResult = $innerType->accepts($type, $strictTypes);
223: $innerAccepts[$i] = $innerResult;
224: $result = $result->or($innerResult->decorateReasons(static fn (string $reason) => sprintf('Type #%d from the union: %s', $i + 1, $reason)));
225: }
226: if ($result->yes()) {
227: return $result;
228: }
229:
230: $commonReasons = null;
231: foreach ($innerAccepts as $innerResult) {
232: if ($commonReasons === null) {
233: $commonReasons = $innerResult->reasons;
234: continue;
235: }
236: $commonReasons = array_values(array_intersect($commonReasons, $innerResult->reasons));
237: }
238: if ($commonReasons !== null && count($commonReasons) > 0) {
239: $decorated = [];
240: foreach (array_keys($innerAccepts) as $i) {
241: foreach ($commonReasons as $reason) {
242: $decorated[] = sprintf('Type #%d from the union: %s', $i + 1, $reason);
243: }
244: }
245: $result = new AcceptsResult($result->result, $decorated);
246: }
247:
248: if ($type instanceof CompoundType && !$type instanceof CallableType && !$type instanceof TemplateType && !$type instanceof IntersectionType) {
249: return $type->isAcceptedBy($this, $strictTypes);
250: }
251:
252: if ($type instanceof TemplateUnionType) {
253: return $result->or($type->isAcceptedBy($this, $strictTypes));
254: }
255:
256: if ($type->isEnum()->yes() && !$this->isEnum()->no()) {
257: $enumCasesUnion = TypeCombinator::union(...$type->getEnumCases());
258: if (!$type->equals($enumCasesUnion)) {
259: return $this->accepts($enumCasesUnion, $strictTypes);
260: }
261: }
262:
263: return $result;
264: }
265:
266: public function isSuperTypeOf(Type $otherType): IsSuperTypeOfResult
267: {
268: if (
269: ($otherType instanceof self && !$otherType instanceof TemplateUnionType)
270: || ($otherType instanceof IterableType && !$otherType instanceof TemplateIterableType)
271: || $otherType instanceof NeverType
272: || $otherType instanceof IntegerRangeType
273: ) {
274: return $otherType->isSubTypeOf($this);
275: }
276:
277: $results = [];
278: foreach ($this->types as $innerType) {
279: $result = $innerType->isSuperTypeOf($otherType);
280: if ($result->yes()) {
281: return $result;
282: }
283: $results[] = $result;
284: }
285: $result = IsSuperTypeOfResult::createNo()->or(...$results);
286:
287: if (
288: $otherType instanceof TemplateUnionType
289: || ($otherType instanceof LateResolvableType && $otherType instanceof CompoundType && !$otherType instanceof TemplateType)
290: ) {
291: return $result->or($otherType->isSubTypeOf($this));
292: }
293:
294: return $result;
295: }
296:
297: public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult
298: {
299: return IsSuperTypeOfResult::extremeIdentity(...array_map(static fn (Type $innerType) => $otherType->isSuperTypeOf($innerType), $this->types));
300: }
301:
302: public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult
303: {
304: return AcceptsResult::extremeIdentity(...array_map(static fn (Type $innerType) => $acceptingType->accepts($innerType, $strictTypes), $this->types));
305: }
306:
307: public function equals(Type $type): bool
308: {
309: if (!$type instanceof static) {
310: return false;
311: }
312:
313: if (count($this->types) !== count($type->types)) {
314: return false;
315: }
316:
317: $otherTypes = $type->types;
318: foreach ($this->types as $innerType) {
319: $match = false;
320: foreach ($otherTypes as $i => $otherType) {
321: if (!$innerType->equals($otherType)) {
322: continue;
323: }
324:
325: $match = true;
326: unset($otherTypes[$i]);
327: break;
328: }
329:
330: if (!$match) {
331: return false;
332: }
333: }
334:
335: return count($otherTypes) === 0;
336: }
337:
338: public function describe(VerbosityLevel $level): string
339: {
340: if (isset($this->cachedDescriptions[$level->getLevelValue()])) {
341: return $this->cachedDescriptions[$level->getLevelValue()];
342: }
343: $joinTypes = static function (array $types) use ($level): string {
344: $typeNames = [];
345: foreach ($types as $i => $type) {
346: if ($type instanceof ClosureType || $type instanceof CallableType || $type instanceof TemplateUnionType) {
347: $typeNames[] = sprintf('(%s)', $type->describe($level));
348: } elseif ($type instanceof TemplateType) {
349: $isLast = $i >= count($types) - 1;
350: $bound = $type->getBound();
351: if (
352: !$isLast
353: && ($level->isTypeOnly() || $level->isValue())
354: && !($bound instanceof MixedType && $bound->getSubtractedType() === null && !$bound instanceof TemplateMixedType)
355: ) {
356: $typeNames[] = sprintf('(%s)', $type->describe($level));
357: } else {
358: $typeNames[] = $type->describe($level);
359: }
360: } elseif ($type instanceof IntersectionType) {
361: $intersectionDescription = $type->describe($level);
362: if (str_contains($intersectionDescription, '&')) {
363: $typeNames[] = sprintf('(%s)', $intersectionDescription);
364: } else {
365: $typeNames[] = $intersectionDescription;
366: }
367: } else {
368: $typeNames[] = $type->describe($level);
369: }
370: }
371:
372: if ($level->isPrecise() || $level->isCache()) {
373: $duplicates = array_diff_assoc($typeNames, array_unique($typeNames));
374: if (count($duplicates) > 0) {
375: $indexByDuplicate = array_fill_keys($duplicates, 0);
376: foreach ($typeNames as $key => $typeName) {
377: if (!isset($indexByDuplicate[$typeName])) {
378: continue;
379: }
380:
381: $typeNames[$key] = $typeName . '#' . ++$indexByDuplicate[$typeName];
382: }
383: }
384: } else {
385: $typeNames = array_unique($typeNames);
386: }
387:
388: if (count($typeNames) > 1024) {
389: return implode('|', array_slice($typeNames, 0, 1024)) . "|\u{2026}";
390: }
391:
392: return implode('|', $typeNames);
393: };
394:
395: return $this->cachedDescriptions[$level->getLevelValue()] = $level->handle(
396: function () use ($joinTypes): string {
397: $types = TypeCombinator::union(...array_map(static function (Type $type): Type {
398: if (
399: $type->isConstantValue()->yes()
400: && $type->isTrue()->or($type->isFalse())->no()
401: ) {
402: return $type->generalize(GeneralizePrecision::lessSpecific());
403: }
404:
405: return $type;
406: }, $this->getSortedTypes()));
407:
408: if ($types instanceof UnionType) {
409: return $joinTypes($types->getSortedTypes());
410: }
411:
412: return $joinTypes([$types]);
413: },
414: fn (): string => $joinTypes($this->getSortedTypes()),
415: );
416: }
417:
418: /**
419: * @param callable(Type $type): TrinaryLogic $canCallback
420: * @param callable(Type $type): TrinaryLogic $hasCallback
421: */
422: private function hasInternal(
423: callable $canCallback,
424: callable $hasCallback,
425: ): TrinaryLogic
426: {
427: return TrinaryLogic::lazyExtremeIdentity($this->types, static function (Type $type) use ($canCallback, $hasCallback): TrinaryLogic {
428: if ($canCallback($type)->no()) {
429: return TrinaryLogic::createNo();
430: }
431:
432: return $hasCallback($type);
433: });
434: }
435:
436: /**
437: * @template TObject of object
438: * @param callable(Type $type): TrinaryLogic $hasCallback
439: * @param callable(Type $type): TObject $getCallback
440: * @return TObject
441: */
442: private function getInternal(
443: callable $hasCallback,
444: callable $getCallback,
445: ): object
446: {
447: /** @var TrinaryLogic|null $result */
448: $result = null;
449:
450: /** @var TObject|null $object */
451: $object = null;
452: foreach ($this->types as $type) {
453: $has = $hasCallback($type);
454: if (!$has->yes()) {
455: continue;
456: }
457: if ($result !== null && $result->compareTo($has) !== $has) {
458: continue;
459: }
460:
461: $get = $getCallback($type);
462: $result = $has;
463: $object = $get;
464: }
465:
466: if ($object === null) {
467: throw new ShouldNotHappenException();
468: }
469:
470: return $object;
471: }
472:
473: public function getTemplateType(string $ancestorClassName, string $templateTypeName): Type
474: {
475: return $this->unionTypes(static fn (Type $type): Type => $type->getTemplateType($ancestorClassName, $templateTypeName));
476: }
477:
478: public function isObject(): TrinaryLogic
479: {
480: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isObject());
481: }
482:
483: public function getClassStringType(): Type
484: {
485: return $this->unionTypes(static fn (Type $type): Type => $type->getClassStringType());
486: }
487:
488: public function isEnum(): TrinaryLogic
489: {
490: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isEnum());
491: }
492:
493: public function canAccessProperties(): TrinaryLogic
494: {
495: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->canAccessProperties());
496: }
497:
498: public function hasProperty(string $propertyName): TrinaryLogic
499: {
500: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->hasProperty($propertyName));
501: }
502:
503: public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
504: {
505: return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty();
506: }
507:
508: public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
509: {
510: $propertyPrototypes = [];
511: foreach ($this->types as $type) {
512: if (!$type->hasProperty($propertyName)->yes()) {
513: continue;
514: }
515:
516: $propertyPrototypes[] = $type->getUnresolvedPropertyPrototype($propertyName, $scope)->withFechedOnType($this);
517: }
518:
519: $propertiesCount = count($propertyPrototypes);
520: if ($propertiesCount === 0) {
521: throw new MissingPropertyFromReflectionException($this->describe(VerbosityLevel::typeOnly()), $propertyName);
522: }
523:
524: if ($propertiesCount === 1) {
525: return $propertyPrototypes[0];
526: }
527:
528: return new UnionTypeUnresolvedPropertyPrototypeReflection($propertyPrototypes);
529: }
530:
531: public function hasInstanceProperty(string $propertyName): TrinaryLogic
532: {
533: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->hasInstanceProperty($propertyName));
534: }
535:
536: public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
537: {
538: return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty();
539: }
540:
541: public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
542: {
543: $propertyPrototypes = [];
544: foreach ($this->types as $type) {
545: if (!$type->hasInstanceProperty($propertyName)->yes()) {
546: continue;
547: }
548:
549: $propertyPrototypes[] = $type->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->withFechedOnType($this);
550: }
551:
552: $propertiesCount = count($propertyPrototypes);
553: if ($propertiesCount === 0) {
554: throw new MissingPropertyFromReflectionException($this->describe(VerbosityLevel::typeOnly()), $propertyName);
555: }
556:
557: if ($propertiesCount === 1) {
558: return $propertyPrototypes[0];
559: }
560:
561: return new UnionTypeUnresolvedPropertyPrototypeReflection($propertyPrototypes);
562: }
563:
564: public function hasStaticProperty(string $propertyName): TrinaryLogic
565: {
566: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->hasStaticProperty($propertyName));
567: }
568:
569: public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
570: {
571: return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty();
572: }
573:
574: public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
575: {
576: $propertyPrototypes = [];
577: foreach ($this->types as $type) {
578: if (!$type->hasStaticProperty($propertyName)->yes()) {
579: continue;
580: }
581:
582: $propertyPrototypes[] = $type->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->withFechedOnType($this);
583: }
584:
585: $propertiesCount = count($propertyPrototypes);
586: if ($propertiesCount === 0) {
587: throw new MissingPropertyFromReflectionException($this->describe(VerbosityLevel::typeOnly()), $propertyName);
588: }
589:
590: if ($propertiesCount === 1) {
591: return $propertyPrototypes[0];
592: }
593:
594: return new UnionTypeUnresolvedPropertyPrototypeReflection($propertyPrototypes);
595: }
596:
597: public function canCallMethods(): TrinaryLogic
598: {
599: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->canCallMethods());
600: }
601:
602: public function hasMethod(string $methodName): TrinaryLogic
603: {
604: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->hasMethod($methodName));
605: }
606:
607: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
608: {
609: return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
610: }
611:
612: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
613: {
614: $methodPrototypes = [];
615: foreach ($this->types as $type) {
616: if (!$type->hasMethod($methodName)->yes()) {
617: continue;
618: }
619:
620: $prototype = $type->getUnresolvedMethodPrototype($methodName, $scope);
621: if ($this instanceof TemplateType) {
622: $prototype = $prototype->withCalledOnType($this);
623: }
624: $methodPrototypes[] = $prototype;
625: }
626:
627: $methodsCount = count($methodPrototypes);
628: if ($methodsCount === 0) {
629: throw new MissingMethodFromReflectionException($this->describe(VerbosityLevel::typeOnly()), $methodName);
630: }
631:
632: if ($methodsCount === 1) {
633: return $methodPrototypes[0];
634: }
635:
636: return new UnionTypeUnresolvedMethodPrototypeReflection($methodName, $methodPrototypes);
637: }
638:
639: public function canAccessConstants(): TrinaryLogic
640: {
641: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->canAccessConstants());
642: }
643:
644: public function hasConstant(string $constantName): TrinaryLogic
645: {
646: return $this->hasInternal(
647: static fn (Type $type): TrinaryLogic => $type->canAccessConstants(),
648: static fn (Type $type): TrinaryLogic => $type->hasConstant($constantName),
649: );
650: }
651:
652: public function getConstant(string $constantName): ClassConstantReflection
653: {
654: return $this->getInternal(
655: static fn (Type $type): TrinaryLogic => $type->hasConstant($constantName),
656: static fn (Type $type): ClassConstantReflection => $type->getConstant($constantName),
657: );
658: }
659:
660: public function isIterable(): TrinaryLogic
661: {
662: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isIterable());
663: }
664:
665: public function isIterableAtLeastOnce(): TrinaryLogic
666: {
667: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isIterableAtLeastOnce());
668: }
669:
670: public function getArraySize(): Type
671: {
672: return $this->unionTypes(static fn (Type $type): Type => $type->getArraySize());
673: }
674:
675: public function getIterableKeyType(): Type
676: {
677: return $this->unionTypes(static fn (Type $type): Type => $type->getIterableKeyType());
678: }
679:
680: public function getFirstIterableKeyType(): Type
681: {
682: return $this->unionTypes(static fn (Type $type): Type => $type->getIterableKeyType());
683: }
684:
685: public function getLastIterableKeyType(): Type
686: {
687: return $this->unionTypes(static fn (Type $type): Type => $type->getIterableKeyType());
688: }
689:
690: public function getIterableValueType(): Type
691: {
692: return $this->unionTypes(static fn (Type $type): Type => $type->getIterableValueType());
693: }
694:
695: public function getFirstIterableValueType(): Type
696: {
697: return $this->unionTypes(static fn (Type $type): Type => $type->getIterableValueType());
698: }
699:
700: public function getLastIterableValueType(): Type
701: {
702: return $this->unionTypes(static fn (Type $type): Type => $type->getIterableValueType());
703: }
704:
705: public function isArray(): TrinaryLogic
706: {
707: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isArray());
708: }
709:
710: public function isConstantArray(): TrinaryLogic
711: {
712: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isConstantArray());
713: }
714:
715: public function isOversizedArray(): TrinaryLogic
716: {
717: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isOversizedArray());
718: }
719:
720: public function isList(): TrinaryLogic
721: {
722: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isList());
723: }
724:
725: public function isString(): TrinaryLogic
726: {
727: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isString());
728: }
729:
730: public function isNumericString(): TrinaryLogic
731: {
732: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isNumericString());
733: }
734:
735: public function isDecimalIntegerString(): TrinaryLogic
736: {
737: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isDecimalIntegerString());
738: }
739:
740: public function isNonEmptyString(): TrinaryLogic
741: {
742: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isNonEmptyString());
743: }
744:
745: public function isNonFalsyString(): TrinaryLogic
746: {
747: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isNonFalsyString());
748: }
749:
750: public function isLiteralString(): TrinaryLogic
751: {
752: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isLiteralString());
753: }
754:
755: public function isLowercaseString(): TrinaryLogic
756: {
757: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isLowercaseString());
758: }
759:
760: public function isUppercaseString(): TrinaryLogic
761: {
762: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isUppercaseString());
763: }
764:
765: public function isClassString(): TrinaryLogic
766: {
767: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isClassString());
768: }
769:
770: public function getClassStringObjectType(): Type
771: {
772: return $this->unionTypes(static fn (Type $type): Type => $type->getClassStringObjectType());
773: }
774:
775: public function getObjectTypeOrClassStringObjectType(): Type
776: {
777: return $this->unionTypes(static fn (Type $type): Type => $type->getObjectTypeOrClassStringObjectType());
778: }
779:
780: public function isVoid(): TrinaryLogic
781: {
782: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isVoid());
783: }
784:
785: public function isScalar(): TrinaryLogic
786: {
787: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isScalar());
788: }
789:
790: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
791: {
792: return $this->notBenevolentUnionResults(
793: static fn (Type $innerType): TrinaryLogic => $innerType->looseCompare($type, $phpVersion)->toTrinaryLogic(),
794: )->toBooleanType();
795: }
796:
797: public function isOffsetAccessible(): TrinaryLogic
798: {
799: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isOffsetAccessible());
800: }
801:
802: public function isOffsetAccessLegal(): TrinaryLogic
803: {
804: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isOffsetAccessLegal());
805: }
806:
807: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
808: {
809: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->hasOffsetValueType($offsetType));
810: }
811:
812: public function getOffsetValueType(Type $offsetType): Type
813: {
814: $types = [];
815: foreach ($this->types as $innerType) {
816: $valueType = $innerType->getOffsetValueType($offsetType);
817: if ($valueType instanceof ErrorType) {
818: continue;
819: }
820:
821: $types[] = $valueType;
822: }
823:
824: if (count($types) === 0) {
825: return new ErrorType();
826: }
827:
828: return TypeCombinator::union(...$types);
829: }
830:
831: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
832: {
833: return $this->unionTypes(static fn (Type $type): Type => $type->setOffsetValueType($offsetType, $valueType, $unionValues));
834: }
835:
836: public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
837: {
838: return $this->unionTypes(static fn (Type $type): Type => $type->setExistingOffsetValueType($offsetType, $valueType));
839: }
840:
841: public function unsetOffset(Type $offsetType): Type
842: {
843: return $this->unionTypes(static fn (Type $type): Type => $type->unsetOffset($offsetType));
844: }
845:
846: public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
847: {
848: return $this->unionTypes(static fn (Type $type): Type => $type->getKeysArrayFiltered($filterValueType, $strict));
849: }
850:
851: public function getKeysArray(): Type
852: {
853: return $this->unionTypes(static fn (Type $type): Type => $type->getKeysArray());
854: }
855:
856: public function getValuesArray(): Type
857: {
858: return $this->unionTypes(static fn (Type $type): Type => $type->getValuesArray());
859: }
860:
861: public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type
862: {
863: return $this->unionTypes(static fn (Type $type): Type => $type->chunkArray($lengthType, $preserveKeys));
864: }
865:
866: public function fillKeysArray(Type $valueType): Type
867: {
868: return $this->unionTypes(static fn (Type $type): Type => $type->fillKeysArray($valueType));
869: }
870:
871: public function flipArray(): Type
872: {
873: return $this->unionTypes(static fn (Type $type): Type => $type->flipArray());
874: }
875:
876: public function intersectKeyArray(Type $otherArraysType): Type
877: {
878: return $this->unionTypes(static fn (Type $type): Type => $type->intersectKeyArray($otherArraysType));
879: }
880:
881: public function popArray(): Type
882: {
883: return $this->unionTypes(static fn (Type $type): Type => $type->popArray());
884: }
885:
886: public function reverseArray(TrinaryLogic $preserveKeys): Type
887: {
888: return $this->unionTypes(static fn (Type $type): Type => $type->reverseArray($preserveKeys));
889: }
890:
891: public function searchArray(Type $needleType, ?TrinaryLogic $strict = null): Type
892: {
893: return $this->unionTypes(static fn (Type $type): Type => $type->searchArray($needleType, $strict));
894: }
895:
896: public function shiftArray(): Type
897: {
898: return $this->unionTypes(static fn (Type $type): Type => $type->shiftArray());
899: }
900:
901: public function shuffleArray(): Type
902: {
903: return $this->unionTypes(static fn (Type $type): Type => $type->shuffleArray());
904: }
905:
906: public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type
907: {
908: return $this->unionTypes(static fn (Type $type): Type => $type->sliceArray($offsetType, $lengthType, $preserveKeys));
909: }
910:
911: public function spliceArray(Type $offsetType, Type $lengthType, Type $replacementType): Type
912: {
913: return $this->unionTypes(static fn (Type $type): Type => $type->spliceArray($offsetType, $lengthType, $replacementType));
914: }
915:
916: public function truncateListToSize(Type $sizeType): Type
917: {
918: return $this->unionTypes(static fn (Type $type): Type => $type->truncateListToSize($sizeType));
919: }
920:
921: public function makeListMaybe(): Type
922: {
923: return $this->unionTypes(static fn (Type $type): Type => $type->makeListMaybe());
924: }
925:
926: public function mapValueType(callable $cb): Type
927: {
928: return $this->unionTypes(static fn (Type $type): Type => $type->mapValueType($cb));
929: }
930:
931: public function mapKeyType(callable $cb): Type
932: {
933: return $this->unionTypes(static fn (Type $type): Type => $type->mapKeyType($cb));
934: }
935:
936: public function makeAllArrayKeysOptional(): Type
937: {
938: return $this->unionTypes(static fn (Type $type): Type => $type->makeAllArrayKeysOptional());
939: }
940:
941: public function changeKeyCaseArray(?int $case): Type
942: {
943: return $this->unionTypes(static fn (Type $type): Type => $type->changeKeyCaseArray($case));
944: }
945:
946: public function filterArrayRemovingFalsey(): Type
947: {
948: return $this->unionTypes(static fn (Type $type): Type => $type->filterArrayRemovingFalsey());
949: }
950:
951: public function getEnumCases(): array
952: {
953: return $this->pickFromTypes(
954: static fn (Type $type) => $type->getEnumCases(),
955: static fn (Type $type) => $type->isObject()->yes(),
956: );
957: }
958:
959: public function getEnumCaseObject(): ?EnumCaseObjectType
960: {
961: $cases = $this->getEnumCases();
962:
963: if (count($cases) === 1) {
964: return $cases[0];
965: }
966:
967: return null;
968: }
969:
970: public function isCallable(): TrinaryLogic
971: {
972: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isCallable());
973: }
974:
975: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
976: {
977: $acceptors = [];
978:
979: foreach ($this->types as $type) {
980: if ($type->isCallable()->no()) {
981: continue;
982: }
983:
984: $acceptors = array_merge($acceptors, $type->getCallableParametersAcceptors($scope));
985: }
986:
987: if (count($acceptors) === 0) {
988: throw new ShouldNotHappenException();
989: }
990:
991: return $acceptors;
992: }
993:
994: public function isCloneable(): TrinaryLogic
995: {
996: return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isCloneable());
997: }
998:
999: public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic
1000: {
1001: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isSmallerThan($otherType, $phpVersion));
1002: }
1003:
1004: public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic
1005: {
1006: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isSmallerThanOrEqual($otherType, $phpVersion));
1007: }
1008:
1009: public function isNull(): TrinaryLogic
1010: {
1011: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isNull());
1012: }
1013:
1014: public function isConstantValue(): TrinaryLogic
1015: {
1016: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isConstantValue());
1017: }
1018:
1019: public function isConstantScalarValue(): TrinaryLogic
1020: {
1021: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isConstantScalarValue());
1022: }
1023:
1024: public function getConstantScalarTypes(): array
1025: {
1026: return $this->notBenevolentPickFromTypes(static fn (Type $type) => $type->getConstantScalarTypes());
1027: }
1028:
1029: public function getConstantScalarValues(): array
1030: {
1031: return $this->notBenevolentPickFromTypes(static fn (Type $type) => $type->getConstantScalarValues());
1032: }
1033:
1034: public function isTrue(): TrinaryLogic
1035: {
1036: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isTrue());
1037: }
1038:
1039: public function isFalse(): TrinaryLogic
1040: {
1041: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isFalse());
1042: }
1043:
1044: public function isBoolean(): TrinaryLogic
1045: {
1046: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isBoolean());
1047: }
1048:
1049: public function isFloat(): TrinaryLogic
1050: {
1051: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isFloat());
1052: }
1053:
1054: public function isInteger(): TrinaryLogic
1055: {
1056: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isInteger());
1057: }
1058:
1059: public function getSmallerType(PhpVersion $phpVersion): Type
1060: {
1061: return $this->unionTypes(static fn (Type $type): Type => $type->getSmallerType($phpVersion));
1062: }
1063:
1064: public function getSmallerOrEqualType(PhpVersion $phpVersion): Type
1065: {
1066: return $this->unionTypes(static fn (Type $type): Type => $type->getSmallerOrEqualType($phpVersion));
1067: }
1068:
1069: public function getGreaterType(PhpVersion $phpVersion): Type
1070: {
1071: return $this->unionTypes(static fn (Type $type): Type => $type->getGreaterType($phpVersion));
1072: }
1073:
1074: public function getGreaterOrEqualType(PhpVersion $phpVersion): Type
1075: {
1076: return $this->unionTypes(static fn (Type $type): Type => $type->getGreaterOrEqualType($phpVersion));
1077: }
1078:
1079: public function isGreaterThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic
1080: {
1081: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $otherType->isSmallerThan($type, $phpVersion));
1082: }
1083:
1084: public function isGreaterThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic
1085: {
1086: return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $otherType->isSmallerThanOrEqual($type, $phpVersion));
1087: }
1088:
1089: public function toBoolean(): BooleanType
1090: {
1091: /** @var BooleanType $type */
1092: $type = $this->unionTypes(static fn (Type $type): BooleanType => $type->toBoolean());
1093:
1094: return $type;
1095: }
1096:
1097: public function toNumber(): Type
1098: {
1099: $type = $this->unionTypes(static fn (Type $type): Type => $type->toNumber());
1100:
1101: return $type;
1102: }
1103:
1104: public function toBitwiseNotType(): Type
1105: {
1106: return $this->unionTypes(static fn (Type $type): Type => $type->toBitwiseNotType());
1107: }
1108:
1109: public function toGetClassResultType(): Type
1110: {
1111: return $this->unionTypes(static fn (Type $type): Type => $type->toGetClassResultType());
1112: }
1113:
1114: public function toClassConstantType(ReflectionProvider $reflectionProvider): Type
1115: {
1116: return $this->unionTypes(static fn (Type $type): Type => $type->toClassConstantType($reflectionProvider));
1117: }
1118:
1119: public function toObjectTypeForInstanceofCheck(): ClassNameToObjectTypeResult
1120: {
1121: $types = [];
1122: $uncertainty = false;
1123: foreach ($this->getTypes() as $innerType) {
1124: $result = $innerType->toObjectTypeForInstanceofCheck();
1125: $types[] = $result->type;
1126: if (!$result->uncertainty) {
1127: continue;
1128: }
1129:
1130: $uncertainty = true;
1131: }
1132:
1133: return new ClassNameToObjectTypeResult(TypeCombinator::union(...$types), $uncertainty);
1134: }
1135:
1136: public function toObjectTypeForIsACheck(Type $objectOrClassType, bool $allowString, bool $allowSameClass): ClassNameToObjectTypeResult
1137: {
1138: $types = [];
1139: $uncertainty = false;
1140: foreach ($this->getTypes() as $innerType) {
1141: $result = $innerType->toObjectTypeForIsACheck($objectOrClassType, $allowString, $allowSameClass);
1142: $types[] = $result->type;
1143: if (!$result->uncertainty) {
1144: continue;
1145: }
1146:
1147: $uncertainty = true;
1148: }
1149:
1150: return new ClassNameToObjectTypeResult(TypeCombinator::union(...$types), $uncertainty);
1151: }
1152:
1153: public function toAbsoluteNumber(): Type
1154: {
1155: $type = $this->unionTypes(static fn (Type $type): Type => $type->toAbsoluteNumber());
1156:
1157: return $type;
1158: }
1159:
1160: public function toString(): Type
1161: {
1162: $type = $this->unionTypes(static fn (Type $type): Type => $type->toString());
1163:
1164: return $type;
1165: }
1166:
1167: public function toInteger(): Type
1168: {
1169: $type = $this->unionTypes(static fn (Type $type): Type => $type->toInteger());
1170:
1171: return $type;
1172: }
1173:
1174: public function toFloat(): Type
1175: {
1176: $type = $this->unionTypes(static fn (Type $type): Type => $type->toFloat());
1177:
1178: return $type;
1179: }
1180:
1181: public function toArray(): Type
1182: {
1183: $type = $this->unionTypes(static fn (Type $type): Type => $type->toArray());
1184:
1185: return $type;
1186: }
1187:
1188: public function toArrayKey(): Type
1189: {
1190: $level = ReportUnsafeArrayStringKeyCastingToggle::getLevel();
1191: if ($level !== ReportUnsafeArrayStringKeyCastingToggle::PREVENT || $this->isInteger()->no()) {
1192: return $this->unionTypes(static fn (Type $type): Type => $type->toArrayKey());
1193: }
1194:
1195: return $this->unionTypes(static function (Type $type): Type {
1196: if ($type instanceof StringType) { // @phpstan-ignore phpstanApi.instanceofType
1197: return $type;
1198: }
1199:
1200: return $type->toArrayKey();
1201: });
1202: }
1203:
1204: public function toCoercedArgumentType(bool $strictTypes): Type
1205: {
1206: return $this->unionTypes(static fn (Type $type): Type => $type->toCoercedArgumentType($strictTypes));
1207: }
1208:
1209: public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
1210: {
1211: if ($receivedType instanceof IterableType) {
1212: $receivedType = $receivedType->toArrayOrTraversable();
1213: }
1214:
1215: $types = TemplateTypeMap::createEmpty();
1216: if ($receivedType instanceof UnionType) {
1217: $myTypes = [];
1218: $remainingReceivedTypes = [];
1219: foreach ($receivedType->getTypes() as $receivedInnerType) {
1220: foreach ($this->types as $type) {
1221: if ($type->isSuperTypeOf($receivedInnerType)->yes()) {
1222: $types = $types->union($type->inferTemplateTypes($receivedInnerType));
1223: continue 2;
1224: }
1225: $myTypes[] = $type;
1226: }
1227: $remainingReceivedTypes[] = $receivedInnerType;
1228: }
1229: if (count($remainingReceivedTypes) === 0) {
1230: return $types;
1231: }
1232: $receivedType = TypeCombinator::union(...$remainingReceivedTypes);
1233: } else {
1234: $myTypes = $this->types;
1235: }
1236:
1237: foreach ($myTypes as $type) {
1238: if ($type instanceof TemplateType || ($type instanceof GenericClassStringType && $type->getGenericType() instanceof TemplateType)) {
1239: continue;
1240: }
1241: $types = $types->union($type->inferTemplateTypes($receivedType));
1242: }
1243:
1244: if (!$types->isEmpty()) {
1245: return $types;
1246: }
1247:
1248: foreach ($myTypes as $type) {
1249: $types = $types->union($type->inferTemplateTypes($receivedType));
1250: }
1251:
1252: return $types;
1253: }
1254:
1255: public function inferTemplateTypesOn(Type $templateType): TemplateTypeMap
1256: {
1257: $types = TemplateTypeMap::createEmpty();
1258:
1259: foreach ($this->types as $type) {
1260: $types = $types->union($templateType->inferTemplateTypes($type));
1261: }
1262:
1263: return $types;
1264: }
1265:
1266: public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array
1267: {
1268: $references = [];
1269:
1270: foreach ($this->types as $type) {
1271: foreach ($type->getReferencedTemplateTypes($positionVariance) as $reference) {
1272: $references[] = $reference;
1273: }
1274: }
1275:
1276: return $references;
1277: }
1278:
1279: public function traverse(callable $cb): Type
1280: {
1281: $types = [];
1282: $changed = false;
1283:
1284: foreach ($this->types as $type) {
1285: $newType = $cb($type);
1286: if ($type !== $newType) {
1287: $changed = true;
1288: }
1289: $types[] = $newType;
1290: }
1291:
1292: if ($changed) {
1293: return TypeCombinator::union(...$types);
1294: }
1295:
1296: return $this;
1297: }
1298:
1299: public function traverseSimultaneously(Type $right, callable $cb): Type
1300: {
1301: $rightTypes = TypeUtils::flattenTypes($right);
1302: $newTypes = [];
1303: $changed = false;
1304: foreach ($this->types as $innerType) {
1305: $candidates = [];
1306: foreach ($rightTypes as $i => $rightType) {
1307: if (!$innerType->isSuperTypeOf($rightType)->yes()) {
1308: continue;
1309: }
1310:
1311: $candidates[] = $rightType;
1312: unset($rightTypes[$i]);
1313: }
1314:
1315: if (count($candidates) === 0) {
1316: $newTypes[] = $innerType;
1317: continue;
1318: }
1319:
1320: $newType = $cb($innerType, TypeCombinator::union(...$candidates));
1321: if ($innerType !== $newType) {
1322: $changed = true;
1323: }
1324:
1325: $newTypes[] = $newType;
1326: }
1327:
1328: if ($changed) {
1329: return TypeCombinator::union(...$newTypes);
1330: }
1331:
1332: return $this;
1333: }
1334:
1335: public function tryRemove(Type $typeToRemove): ?Type
1336: {
1337: $innerTypes = [];
1338: $changed = false;
1339: foreach ($this->types as $innerType) {
1340: $removed = TypeCombinator::remove($innerType, $typeToRemove);
1341: if (!$removed->equals($innerType)) {
1342: $changed = true;
1343: }
1344: if ($removed instanceof NeverType) {
1345: continue;
1346: }
1347: if ($removed instanceof self && !$removed instanceof TemplateType) {
1348: foreach ($removed->getTypes() as $removedInnerType) {
1349: $innerTypes[] = $removedInnerType;
1350: }
1351: } else {
1352: $innerTypes[] = $removed;
1353: }
1354: }
1355:
1356: if (!$changed) {
1357: return null;
1358: }
1359:
1360: if (count($innerTypes) === 0) {
1361: return new NeverType();
1362: }
1363:
1364: if (count($innerTypes) === 1) {
1365: return $innerTypes[0];
1366: }
1367:
1368: return new UnionType($innerTypes);
1369: }
1370:
1371: public function exponentiate(Type $exponent): Type
1372: {
1373: return $this->unionTypes(static fn (Type $type): Type => $type->exponentiate($exponent));
1374: }
1375:
1376: public function getFiniteTypes(): array
1377: {
1378: $types = $this->notBenevolentPickFromTypes(static fn (Type $type) => $type->getFiniteTypes());
1379: $uniquedTypes = [];
1380: foreach ($types as $type) {
1381: $uniquedTypes[$type->describe(VerbosityLevel::cache())] = $type;
1382: }
1383:
1384: if (count($uniquedTypes) > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT) {
1385: return [];
1386: }
1387:
1388: return array_values($uniquedTypes);
1389: }
1390:
1391: /**
1392: * @param callable(Type $type): TrinaryLogic $getResult
1393: */
1394: protected function unionResults(callable $getResult): TrinaryLogic
1395: {
1396: return TrinaryLogic::lazyExtremeIdentity($this->types, $getResult);
1397: }
1398:
1399: /**
1400: * @param callable(Type $type): TrinaryLogic $getResult
1401: */
1402: private function notBenevolentUnionResults(callable $getResult): TrinaryLogic
1403: {
1404: return TrinaryLogic::lazyExtremeIdentity($this->types, $getResult);
1405: }
1406:
1407: /**
1408: * @param callable(Type $type): Type $getType
1409: */
1410: protected function unionTypes(callable $getType): Type
1411: {
1412: $newTypes = [];
1413: foreach ($this->types as $type) {
1414: $newTypes[] = $getType($type);
1415: }
1416:
1417: return TypeCombinator::union(...$newTypes);
1418: }
1419:
1420: /**
1421: * @template T
1422: * @param callable(Type $type): list<T> $getValues
1423: * @param callable(Type $type): bool $criteria
1424: * @return list<T>
1425: */
1426: protected function pickFromTypes(
1427: callable $getValues,
1428: callable $criteria,
1429: ): array
1430: {
1431: $values = [];
1432: foreach ($this->types as $type) {
1433: $innerValues = $getValues($type);
1434: if ($innerValues === []) {
1435: return [];
1436: }
1437:
1438: foreach ($innerValues as $innerType) {
1439: $values[] = $innerType;
1440: }
1441: }
1442:
1443: return $values;
1444: }
1445:
1446: public function toPhpDocNode(): TypeNode
1447: {
1448: return new UnionTypeNode(array_map(static fn (Type $type) => $type->toPhpDocNode(), $this->getSortedTypes()));
1449: }
1450:
1451: /**
1452: * @template T
1453: * @param callable(Type $type): list<T> $getValues
1454: * @return list<T>
1455: */
1456: private function notBenevolentPickFromTypes(callable $getValues): array
1457: {
1458: $values = [];
1459: foreach ($this->types as $type) {
1460: $innerValues = $getValues($type);
1461: if ($innerValues === []) {
1462: return [];
1463: }
1464:
1465: foreach ($innerValues as $innerType) {
1466: $values[] = $innerType;
1467: }
1468: }
1469:
1470: return $values;
1471: }
1472:
1473: public function hasTemplateOrLateResolvableType(): bool
1474: {
1475: foreach ($this->types as $type) {
1476: if (!$type->hasTemplateOrLateResolvableType()) {
1477: continue;
1478: }
1479:
1480: return true;
1481: }
1482:
1483: return false;
1484: }
1485:
1486: }
1487: