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