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