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