1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use ArrayAccess;
6: use PHPStan\Php\PhpVersion;
7: use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
8: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
9: use PHPStan\Reflection\ClassConstantReflection;
10: use PHPStan\Reflection\ClassMemberAccessAnswerer;
11: use PHPStan\Reflection\Dummy\DummyClassConstantReflection;
12: use PHPStan\Reflection\Dummy\DummyMethodReflection;
13: use PHPStan\Reflection\Dummy\DummyPropertyReflection;
14: use PHPStan\Reflection\ExtendedMethodReflection;
15: use PHPStan\Reflection\ExtendedPropertyReflection;
16: use PHPStan\Reflection\ReflectionProvider;
17: use PHPStan\Reflection\TrivialParametersAcceptor;
18: use PHPStan\Reflection\Type\CallbackUnresolvedMethodPrototypeReflection;
19: use PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection;
20: use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
21: use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
22: use PHPStan\TrinaryLogic;
23: use PHPStan\Turbo\ShadowedByTurboExtension;
24: use PHPStan\Type\Accessory\AccessoryArrayListType;
25: use PHPStan\Type\Accessory\AccessoryDecimalIntegerStringType;
26: use PHPStan\Type\Accessory\AccessoryLiteralStringType;
27: use PHPStan\Type\Accessory\AccessoryLowercaseStringType;
28: use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
29: use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
30: use PHPStan\Type\Accessory\AccessoryNumericStringType;
31: use PHPStan\Type\Accessory\AccessoryUppercaseStringType;
32: use PHPStan\Type\Accessory\OversizedArrayType;
33: use PHPStan\Type\Constant\ConstantArrayType;
34: use PHPStan\Type\Constant\ConstantBooleanType;
35: use PHPStan\Type\Constant\ConstantFloatType;
36: use PHPStan\Type\Constant\ConstantIntegerType;
37: use PHPStan\Type\Constant\ConstantStringType;
38: use PHPStan\Type\Enum\EnumCaseObjectType;
39: use PHPStan\Type\Generic\TemplateMixedType;
40: use PHPStan\Type\Generic\TemplateType;
41: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
42: use PHPStan\Type\Traits\NonGenericTypeTrait;
43: use PHPStan\Type\Traits\SubstractableTypeTrait;
44: use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
45: use function get_class;
46: use function sprintf;
47:
48: /** @api */
49: #[ShadowedByTurboExtension(implementation: __DIR__ . '/../../turbo-ext/src/MixedType.cpp')]
50: class MixedType implements CompoundType, SubtractableType
51: {
52:
53: use NonGenericTypeTrait;
54: use UndecidedComparisonCompoundTypeTrait;
55: use NonGeneralizableTypeTrait;
56: use SubstractableTypeTrait;
57:
58: private ?Type $subtractedType;
59:
60: /** @api */
61: public function __construct(
62: private bool $isExplicitMixed = false,
63: ?Type $subtractedType = null,
64: )
65: {
66: if ($subtractedType instanceof NeverType) {
67: $subtractedType = null;
68: }
69:
70: $this->subtractedType = $subtractedType;
71: }
72:
73: public function getReferencedClasses(): array
74: {
75: return [];
76: }
77:
78: public function getObjectClassNames(): array
79: {
80: return [];
81: }
82:
83: public function getObjectClassReflections(): array
84: {
85: return [];
86: }
87:
88: public function getArrays(): array
89: {
90: return [];
91: }
92:
93: public function getConstantArrays(): array
94: {
95: return [];
96: }
97:
98: public function getConstantStrings(): array
99: {
100: return [];
101: }
102:
103: public function accepts(Type $type, bool $strictTypes): AcceptsResult
104: {
105: return AcceptsResult::createYes();
106: }
107:
108: public function isSuperTypeOfMixed(MixedType $type): IsSuperTypeOfResult
109: {
110: if ($this->subtractedType === null) {
111: if ($this->isExplicitMixed) {
112: if ($type->isExplicitMixed) {
113: return IsSuperTypeOfResult::createYes();
114: }
115: return IsSuperTypeOfResult::createMaybe();
116: }
117:
118: return IsSuperTypeOfResult::createYes();
119: }
120:
121: if ($type->subtractedType === null) {
122: return IsSuperTypeOfResult::createMaybe();
123: }
124:
125: $isSuperType = $type->subtractedType->isSuperTypeOf($this->subtractedType);
126: if ($isSuperType->yes()) {
127: if ($this->isExplicitMixed) {
128: if ($type->isExplicitMixed) {
129: return IsSuperTypeOfResult::createYes();
130: }
131: return IsSuperTypeOfResult::createMaybe();
132: }
133:
134: return IsSuperTypeOfResult::createYes();
135: }
136:
137: return IsSuperTypeOfResult::createMaybe();
138: }
139:
140: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
141: {
142: if ($this->subtractedType === null || $type instanceof NeverType) {
143: return IsSuperTypeOfResult::createYes();
144: }
145:
146: if ($type instanceof self) {
147: if ($type->subtractedType === null) {
148: return IsSuperTypeOfResult::createMaybe();
149: }
150: $isSuperType = $type->subtractedType->isSuperTypeOf($this->subtractedType);
151: if ($isSuperType->yes()) {
152: return $isSuperType;
153: }
154:
155: return IsSuperTypeOfResult::createMaybe();
156: }
157:
158: $result = $this->subtractedType->isSuperTypeOf($type)->negate();
159: if ($result->no()) {
160: return IsSuperTypeOfResult::createNo([
161: sprintf(
162: 'Type %s has already been eliminated from %s.',
163: $this->subtractedType->describe(VerbosityLevel::precise()),
164: $this->describe(VerbosityLevel::typeOnly()),
165: ),
166: ]);
167: }
168:
169: return $result;
170: }
171:
172: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
173: {
174: return new self($this->isExplicitMixed);
175: }
176:
177: public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
178: {
179: return new self($this->isExplicitMixed);
180: }
181:
182: public function unsetOffset(Type $offsetType): Type
183: {
184: if ($this->subtractedType !== null) {
185: return new self($this->isExplicitMixed, TypeCombinator::remove($this->subtractedType, new ConstantArrayType([], [])));
186: }
187: return $this;
188: }
189:
190: public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
191: {
192: return $this->getKeysArray();
193: }
194:
195: public function getKeysArray(): Type
196: {
197: if ($this->isArray()->no()) {
198: return new ErrorType();
199: }
200:
201: return new IntersectionType([new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new UnionType([new IntegerType(), new StringType()])), new AccessoryArrayListType()]);
202: }
203:
204: public function getValuesArray(): Type
205: {
206: if ($this->isArray()->no()) {
207: return new ErrorType();
208: }
209:
210: return new IntersectionType([new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new MixedType($this->isExplicitMixed)), new AccessoryArrayListType()]);
211: }
212:
213: public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type
214: {
215: if ($this->isArray()->no()) {
216: return new ErrorType();
217: }
218:
219: return new IntersectionType([new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new MixedType($this->isExplicitMixed)), new AccessoryArrayListType()]);
220: }
221:
222: public function fillKeysArray(Type $valueType): Type
223: {
224: if ($this->isArray()->no()) {
225: return new ErrorType();
226: }
227:
228: return new ArrayType($this->getIterableValueType()->toArrayKey(), $valueType);
229: }
230:
231: public function flipArray(): Type
232: {
233: if ($this->isArray()->no()) {
234: return new ErrorType();
235: }
236:
237: return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed));
238: }
239:
240: public function intersectKeyArray(Type $otherArraysType): Type
241: {
242: if ($this->isArray()->no()) {
243: return new ErrorType();
244: }
245:
246: return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed));
247: }
248:
249: public function popArray(): Type
250: {
251: if ($this->isArray()->no()) {
252: return new ErrorType();
253: }
254:
255: return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed));
256: }
257:
258: public function reverseArray(TrinaryLogic $preserveKeys): Type
259: {
260: if ($this->isArray()->no()) {
261: return new ErrorType();
262: }
263:
264: return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed));
265: }
266:
267: public function searchArray(Type $needleType, ?TrinaryLogic $strict = null): Type
268: {
269: if ($this->isArray()->no()) {
270: return new ErrorType();
271: }
272:
273: return new UnionType([new IntegerType(), new StringType(), new ConstantBooleanType(false)]);
274: }
275:
276: public function shiftArray(): Type
277: {
278: if ($this->isArray()->no()) {
279: return new ErrorType();
280: }
281:
282: return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed));
283: }
284:
285: public function shuffleArray(): Type
286: {
287: if ($this->isArray()->no()) {
288: return new ErrorType();
289: }
290:
291: return new IntersectionType([new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new MixedType($this->isExplicitMixed)), new AccessoryArrayListType()]);
292: }
293:
294: public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type
295: {
296: if ($this->isArray()->no()) {
297: return new ErrorType();
298: }
299:
300: return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed));
301: }
302:
303: public function spliceArray(Type $offsetType, Type $lengthType, Type $replacementType): Type
304: {
305: if ($this->isArray()->no()) {
306: return new ErrorType();
307: }
308:
309: return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed));
310: }
311:
312: public function truncateListToSize(Type $sizeType): Type
313: {
314: if ($this->isArray()->no()) {
315: return new ErrorType();
316: }
317:
318: return $this;
319: }
320:
321: public function makeListMaybe(): Type
322: {
323: // `mixed` doesn't track list-ness; nothing to weaken.
324: return $this;
325: }
326:
327: public function mapValueType(callable $cb): Type
328: {
329: if ($this->isArray()->no()) {
330: return new ErrorType();
331: }
332:
333: return new ArrayType(
334: new MixedType($this->isExplicitMixed),
335: $cb(new MixedType($this->isExplicitMixed)),
336: );
337: }
338:
339: public function mapKeyType(callable $cb): Type
340: {
341: if ($this->isArray()->no()) {
342: return new ErrorType();
343: }
344:
345: return new ArrayType(
346: $cb(new MixedType($this->isExplicitMixed)),
347: new MixedType($this->isExplicitMixed),
348: );
349: }
350:
351: public function makeAllArrayKeysOptional(): Type
352: {
353: // `mixed` is already arbitrary; nothing to weaken.
354: return $this;
355: }
356:
357: public function changeKeyCaseArray(?int $case): Type
358: {
359: if ($this->isArray()->no()) {
360: return new ErrorType();
361: }
362:
363: return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed));
364: }
365:
366: public function filterArrayRemovingFalsey(): Type
367: {
368: if ($this->isArray()->no()) {
369: return new ErrorType();
370: }
371:
372: return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed));
373: }
374:
375: public function isCallable(): TrinaryLogic
376: {
377: if ($this->subtractedType !== null) {
378: if ($this->subtractedType->isSuperTypeOf(new CallableType())->yes()) {
379: return TrinaryLogic::createNo();
380: }
381: }
382:
383: return TrinaryLogic::createMaybe();
384: }
385:
386: public function getEnumCases(): array
387: {
388: return [];
389: }
390:
391: public function getEnumCaseObject(): ?EnumCaseObjectType
392: {
393: return null;
394: }
395:
396: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
397: {
398: return [new TrivialParametersAcceptor()];
399: }
400:
401: public function equals(Type $type): bool
402: {
403: if (get_class($type) !== static::class) {
404: return false;
405: }
406:
407: if ($this->subtractedType === null) {
408: if ($type->subtractedType === null) {
409: return true;
410: }
411:
412: return false;
413: }
414:
415: if ($type->subtractedType === null) {
416: return false;
417: }
418:
419: return $this->subtractedType->equals($type->subtractedType);
420: }
421:
422: public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult
423: {
424: if ($otherType instanceof self && !$otherType instanceof TemplateMixedType) {
425: return IsSuperTypeOfResult::createYes();
426: }
427:
428: if ($this->subtractedType !== null) {
429: $isSuperType = $this->subtractedType->isSuperTypeOf($otherType);
430: if ($isSuperType->yes()) {
431: return IsSuperTypeOfResult::createNo();
432: }
433: }
434:
435: return IsSuperTypeOfResult::createMaybe();
436: }
437:
438: public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult
439: {
440: $isSuperType = $this->isSuperTypeOf($acceptingType)->toAcceptsResult();
441: if ($isSuperType->no()) {
442: return $isSuperType;
443: }
444: return AcceptsResult::createYes();
445: }
446:
447: public function getTemplateType(string $ancestorClassName, string $templateTypeName): Type
448: {
449: return new self();
450: }
451:
452: public function isObject(): TrinaryLogic
453: {
454: if ($this->subtractedType !== null) {
455: if ($this->subtractedType->isSuperTypeOf(new ObjectWithoutClassType())->yes()) {
456: return TrinaryLogic::createNo();
457: }
458: }
459: return TrinaryLogic::createMaybe();
460: }
461:
462: public function getClassStringType(): Type
463: {
464: return new ClassStringType();
465: }
466:
467: public function isEnum(): TrinaryLogic
468: {
469: if ($this->subtractedType !== null) {
470: if ($this->subtractedType->isSuperTypeOf(new ObjectWithoutClassType())->yes()) {
471: return TrinaryLogic::createNo();
472: }
473: }
474: return TrinaryLogic::createMaybe();
475: }
476:
477: public function canAccessProperties(): TrinaryLogic
478: {
479: return TrinaryLogic::createYes();
480: }
481:
482: public function hasProperty(string $propertyName): TrinaryLogic
483: {
484: return TrinaryLogic::createYes();
485: }
486:
487: public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
488: {
489: return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty();
490: }
491:
492: public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
493: {
494: $property = new DummyPropertyReflection($propertyName);
495: return new CallbackUnresolvedPropertyPrototypeReflection(
496: $property,
497: $property->getDeclaringClass(),
498: false,
499: static fn (Type $type): Type => $type,
500: );
501: }
502:
503: public function hasInstanceProperty(string $propertyName): TrinaryLogic
504: {
505: return TrinaryLogic::createYes();
506: }
507:
508: public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
509: {
510: return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty();
511: }
512:
513: public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
514: {
515: $property = new DummyPropertyReflection($propertyName);
516: return new CallbackUnresolvedPropertyPrototypeReflection(
517: $property,
518: $property->getDeclaringClass(),
519: false,
520: static fn (Type $type): Type => $type,
521: );
522: }
523:
524: public function hasStaticProperty(string $propertyName): TrinaryLogic
525: {
526: return TrinaryLogic::createYes();
527: }
528:
529: public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
530: {
531: return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty();
532: }
533:
534: public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
535: {
536: $property = new DummyPropertyReflection($propertyName);
537: return new CallbackUnresolvedPropertyPrototypeReflection(
538: $property,
539: $property->getDeclaringClass(),
540: false,
541: static fn (Type $type): Type => $type,
542: );
543: }
544:
545: public function canCallMethods(): TrinaryLogic
546: {
547: return TrinaryLogic::createYes();
548: }
549:
550: public function hasMethod(string $methodName): TrinaryLogic
551: {
552: return TrinaryLogic::createYes();
553: }
554:
555: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
556: {
557: return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
558: }
559:
560: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
561: {
562: $method = new DummyMethodReflection($methodName);
563: return new CallbackUnresolvedMethodPrototypeReflection(
564: $method,
565: $method->getDeclaringClass(),
566: false,
567: static fn (Type $type): Type => $type,
568: );
569: }
570:
571: public function canAccessConstants(): TrinaryLogic
572: {
573: return TrinaryLogic::createYes();
574: }
575:
576: public function hasConstant(string $constantName): TrinaryLogic
577: {
578: return TrinaryLogic::createYes();
579: }
580:
581: public function getConstant(string $constantName): ClassConstantReflection
582: {
583: return new DummyClassConstantReflection($constantName);
584: }
585:
586: public function isCloneable(): TrinaryLogic
587: {
588: return TrinaryLogic::createYes();
589: }
590:
591: public function describe(VerbosityLevel $level): string
592: {
593: return $level->handle(
594: static fn (): string => 'mixed',
595: static fn (): string => 'mixed',
596: fn (): string => 'mixed' . $this->describeSubtractedType($this->subtractedType, $level),
597: function () use ($level): string {
598: $description = 'mixed' . $this->describeSubtractedType($this->subtractedType, $level);
599:
600: if ($this->isExplicitMixed) {
601: $description .= '=explicit';
602: } else {
603: $description .= '=implicit';
604: }
605:
606: return $description;
607: },
608: );
609: }
610:
611: public function toBoolean(): BooleanType
612: {
613: if ($this->subtractedType !== null) {
614: if ($this->subtractedType->isSuperTypeOf(StaticTypeFactory::falsey())->yes()) {
615: return new ConstantBooleanType(true);
616: }
617: }
618:
619: return new BooleanType();
620: }
621:
622: public function toNumber(): Type
623: {
624: return TypeCombinator::union(
625: $this->toInteger(),
626: $this->toFloat(),
627: );
628: }
629:
630: public function toBitwiseNotType(): Type
631: {
632: return new ErrorType();
633: }
634:
635: public function toGetClassResultType(): Type
636: {
637: $isObject = $this->isObject();
638: if ($isObject->no()) {
639: return new ConstantBooleanType(false);
640: }
641:
642: $classString = $this->getClassStringType();
643: if ($isObject->yes()) {
644: return $classString;
645: }
646:
647: return new UnionType([$classString, new ConstantBooleanType(false)]);
648: }
649:
650: public function toClassConstantType(ReflectionProvider $reflectionProvider): Type
651: {
652: // `mixed::class` is undefined — the original `TypeTraverser` cb fell
653: // through to `ErrorType` for any leaf that wasn't a definite object.
654: return new ErrorType();
655: }
656:
657: public function toObjectTypeForInstanceofCheck(): ClassNameToObjectTypeResult
658: {
659: return new ClassNameToObjectTypeResult(new MixedType(), false);
660: }
661:
662: public function toObjectTypeForIsACheck(Type $objectOrClassType, bool $allowString, bool $allowSameClass): ClassNameToObjectTypeResult
663: {
664: if ($allowString) {
665: return new ClassNameToObjectTypeResult(
666: new UnionType([new ObjectWithoutClassType(), new ClassStringType()]),
667: false,
668: );
669: }
670:
671: return new ClassNameToObjectTypeResult(new ObjectWithoutClassType(), false);
672: }
673:
674: public function toAbsoluteNumber(): Type
675: {
676: return $this->toNumber()->toAbsoluteNumber();
677: }
678:
679: public function toInteger(): Type
680: {
681: $castsToZero = new UnionType([
682: new NullType(),
683: new ConstantBooleanType(false),
684: new ConstantIntegerType(0),
685: new ConstantArrayType([], []),
686: new StringType(),
687: new FloatType(), // every 0.x float casts to int(0)
688: ]);
689: if (
690: $this->subtractedType !== null
691: && $this->subtractedType->isSuperTypeOf($castsToZero)->yes()
692: ) {
693: return new UnionType([
694: IntegerRangeType::fromInterval(null, -1),
695: IntegerRangeType::fromInterval(1, null),
696: ]);
697: }
698:
699: return new IntegerType();
700: }
701:
702: public function toFloat(): Type
703: {
704: return new FloatType();
705: }
706:
707: public function toString(): Type
708: {
709: if ($this->subtractedType !== null) {
710: $castsToEmptyString = new UnionType([
711: new NullType(),
712: new ConstantBooleanType(false),
713: new ConstantStringType(''),
714: ]);
715: if ($this->subtractedType->isSuperTypeOf($castsToEmptyString)->yes()) {
716: $accessories = [
717: new StringType(),
718: new AccessoryNonEmptyStringType(),
719: ];
720:
721: $castsToZeroString = new UnionType([
722: new ConstantFloatType(0.0),
723: new ConstantStringType('0'),
724: new ConstantIntegerType(0),
725: ]);
726: if ($this->subtractedType->isSuperTypeOf($castsToZeroString)->yes()) {
727: $accessories[] = new AccessoryNonFalsyStringType();
728: }
729: return new IntersectionType(
730: $accessories,
731: );
732: }
733: }
734:
735: return new StringType();
736: }
737:
738: public function toArray(): Type
739: {
740: $mixed = new self($this->isExplicitMixed);
741:
742: return new ArrayType($mixed, $mixed);
743: }
744:
745: public function toArrayKey(): Type
746: {
747: return new BenevolentUnionType([new IntegerType(), new StringType()]);
748: }
749:
750: public function toCoercedArgumentType(bool $strictTypes): Type
751: {
752: return $this;
753: }
754:
755: public function isIterable(): TrinaryLogic
756: {
757: if ($this->subtractedType !== null) {
758: if ($this->subtractedType->isSuperTypeOf(new IterableType(new MixedType(), new MixedType()))->yes()) {
759: return TrinaryLogic::createNo();
760: }
761: }
762:
763: return TrinaryLogic::createMaybe();
764: }
765:
766: public function isIterableAtLeastOnce(): TrinaryLogic
767: {
768: return $this->isIterable();
769: }
770:
771: public function getArraySize(): Type
772: {
773: if ($this->isIterable()->no()) {
774: return new ErrorType();
775: }
776:
777: return IntegerRangeType::fromInterval(0, null);
778: }
779:
780: public function getIterableKeyType(): Type
781: {
782: return new self($this->isExplicitMixed);
783: }
784:
785: public function getFirstIterableKeyType(): Type
786: {
787: return new self($this->isExplicitMixed);
788: }
789:
790: public function getLastIterableKeyType(): Type
791: {
792: return new self($this->isExplicitMixed);
793: }
794:
795: public function getIterableValueType(): Type
796: {
797: return new self($this->isExplicitMixed);
798: }
799:
800: public function getFirstIterableValueType(): Type
801: {
802: return new self($this->isExplicitMixed);
803: }
804:
805: public function getLastIterableValueType(): Type
806: {
807: return new self($this->isExplicitMixed);
808: }
809:
810: public function isOffsetAccessible(): TrinaryLogic
811: {
812: if ($this->subtractedType !== null) {
813: $offsetAccessibles = new UnionType([
814: new StringType(),
815: new ArrayType(new MixedType(), new MixedType()),
816: new ObjectType(ArrayAccess::class),
817: ]);
818:
819: if ($this->subtractedType->isSuperTypeOf($offsetAccessibles)->yes()) {
820: return TrinaryLogic::createNo();
821: }
822: }
823: return TrinaryLogic::createMaybe();
824: }
825:
826: public function isOffsetAccessLegal(): TrinaryLogic
827: {
828: if ($this->subtractedType !== null) {
829: if ($this->subtractedType->isSuperTypeOf(new ObjectWithoutClassType())->yes()) {
830: return TrinaryLogic::createYes();
831: }
832: }
833: return TrinaryLogic::createMaybe();
834: }
835:
836: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
837: {
838: if ($this->isOffsetAccessible()->no()) {
839: return TrinaryLogic::createNo();
840: }
841:
842: return TrinaryLogic::createMaybe();
843: }
844:
845: public function getOffsetValueType(Type $offsetType): Type
846: {
847: return new self($this->isExplicitMixed);
848: }
849:
850: public function isExplicitMixed(): bool
851: {
852: return $this->isExplicitMixed;
853: }
854:
855: public function subtract(Type $type): Type
856: {
857: if ($type instanceof self && !$type instanceof TemplateType) {
858: return new NeverType();
859: }
860: if ($this->subtractedType !== null) {
861: $type = TypeCombinator::union($this->subtractedType, $type);
862: }
863:
864: return new self($this->isExplicitMixed, $type);
865: }
866:
867: public function getTypeWithoutSubtractedType(): Type
868: {
869: return new self($this->isExplicitMixed);
870: }
871:
872: public function changeSubtractedType(?Type $subtractedType): Type
873: {
874: return new self($this->isExplicitMixed, $subtractedType);
875: }
876:
877: public function getSubtractedType(): ?Type
878: {
879: return $this->subtractedType;
880: }
881:
882: public function traverse(callable $cb): Type
883: {
884: return $this;
885: }
886:
887: public function traverseSimultaneously(Type $right, callable $cb): Type
888: {
889: return $this;
890: }
891:
892: public function isArray(): TrinaryLogic
893: {
894: if ($this->subtractedType !== null) {
895: if ($this->subtractedType->isSuperTypeOf(new ArrayType(new MixedType(), new MixedType()))->yes()) {
896: return TrinaryLogic::createNo();
897: }
898: }
899:
900: return TrinaryLogic::createMaybe();
901: }
902:
903: public function isConstantArray(): TrinaryLogic
904: {
905: return $this->isArray();
906: }
907:
908: public function isOversizedArray(): TrinaryLogic
909: {
910: if ($this->subtractedType !== null) {
911: $oversizedArray = new IntersectionType([
912: new ArrayType(new MixedType(), new MixedType()),
913: new OversizedArrayType(),
914: ]);
915:
916: if ($this->subtractedType->isSuperTypeOf($oversizedArray)->yes()) {
917: return TrinaryLogic::createNo();
918: }
919: }
920:
921: return TrinaryLogic::createMaybe();
922: }
923:
924: public function isList(): TrinaryLogic
925: {
926: if ($this->subtractedType !== null) {
927: $list = new IntersectionType([
928: new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new MixedType()),
929: new AccessoryArrayListType(),
930: ]);
931:
932: if ($this->subtractedType->isSuperTypeOf($list)->yes()) {
933: return TrinaryLogic::createNo();
934: }
935: }
936:
937: return TrinaryLogic::createMaybe();
938: }
939:
940: public function isNull(): TrinaryLogic
941: {
942: if ($this->subtractedType !== null) {
943: if ($this->subtractedType->isSuperTypeOf(new NullType())->yes()) {
944: return TrinaryLogic::createNo();
945: }
946: }
947:
948: return TrinaryLogic::createMaybe();
949: }
950:
951: public function isConstantValue(): TrinaryLogic
952: {
953: return TrinaryLogic::createNo();
954: }
955:
956: public function isConstantScalarValue(): TrinaryLogic
957: {
958: return TrinaryLogic::createNo();
959: }
960:
961: public function getConstantScalarTypes(): array
962: {
963: return [];
964: }
965:
966: public function getConstantScalarValues(): array
967: {
968: return [];
969: }
970:
971: public function isTrue(): TrinaryLogic
972: {
973: if ($this->subtractedType !== null) {
974: if ($this->subtractedType->isSuperTypeOf(new ConstantBooleanType(true))->yes()) {
975: return TrinaryLogic::createNo();
976: }
977: }
978:
979: return TrinaryLogic::createMaybe();
980: }
981:
982: public function isFalse(): TrinaryLogic
983: {
984: if ($this->subtractedType !== null) {
985: if ($this->subtractedType->isSuperTypeOf(new ConstantBooleanType(false))->yes()) {
986: return TrinaryLogic::createNo();
987: }
988: }
989:
990: return TrinaryLogic::createMaybe();
991: }
992:
993: public function isBoolean(): TrinaryLogic
994: {
995: if ($this->subtractedType !== null) {
996: if ($this->subtractedType->isSuperTypeOf(new BooleanType())->yes()) {
997: return TrinaryLogic::createNo();
998: }
999: }
1000:
1001: return TrinaryLogic::createMaybe();
1002: }
1003:
1004: public function isFloat(): TrinaryLogic
1005: {
1006: if ($this->subtractedType !== null) {
1007: if ($this->subtractedType->isSuperTypeOf(new FloatType())->yes()) {
1008: return TrinaryLogic::createNo();
1009: }
1010: }
1011:
1012: return TrinaryLogic::createMaybe();
1013: }
1014:
1015: public function isInteger(): TrinaryLogic
1016: {
1017: if ($this->subtractedType !== null) {
1018: if ($this->subtractedType->isSuperTypeOf(new IntegerType())->yes()) {
1019: return TrinaryLogic::createNo();
1020: }
1021: }
1022:
1023: return TrinaryLogic::createMaybe();
1024: }
1025:
1026: public function isString(): TrinaryLogic
1027: {
1028: if ($this->subtractedType !== null) {
1029: if ($this->subtractedType->isSuperTypeOf(new StringType())->yes()) {
1030: return TrinaryLogic::createNo();
1031: }
1032: }
1033: return TrinaryLogic::createMaybe();
1034: }
1035:
1036: public function isNumericString(): TrinaryLogic
1037: {
1038: if ($this->subtractedType !== null) {
1039: $numericString = new IntersectionType([
1040: new StringType(),
1041: new AccessoryNumericStringType(),
1042: ]);
1043:
1044: if ($this->subtractedType->isSuperTypeOf($numericString)->yes()) {
1045: return TrinaryLogic::createNo();
1046: }
1047: }
1048:
1049: return TrinaryLogic::createMaybe();
1050: }
1051:
1052: public function isDecimalIntegerString(): TrinaryLogic
1053: {
1054: if ($this->subtractedType !== null) {
1055: $decimalIntegerString = new IntersectionType([
1056: new StringType(),
1057: new AccessoryDecimalIntegerStringType(),
1058: ]);
1059:
1060: if ($this->subtractedType->isSuperTypeOf($decimalIntegerString)->yes()) {
1061: return TrinaryLogic::createNo();
1062: }
1063: }
1064:
1065: return TrinaryLogic::createMaybe();
1066: }
1067:
1068: public function isNonEmptyString(): TrinaryLogic
1069: {
1070: if ($this->subtractedType !== null) {
1071: $nonEmptyString = new IntersectionType([
1072: new StringType(),
1073: new AccessoryNonEmptyStringType(),
1074: ]);
1075:
1076: if ($this->subtractedType->isSuperTypeOf($nonEmptyString)->yes()) {
1077: return TrinaryLogic::createNo();
1078: }
1079: }
1080:
1081: return TrinaryLogic::createMaybe();
1082: }
1083:
1084: public function isNonFalsyString(): TrinaryLogic
1085: {
1086: if ($this->subtractedType !== null) {
1087: $nonFalsyString = new IntersectionType([
1088: new StringType(),
1089: new AccessoryNonFalsyStringType(),
1090: ]);
1091:
1092: if ($this->subtractedType->isSuperTypeOf($nonFalsyString)->yes()) {
1093: return TrinaryLogic::createNo();
1094: }
1095: }
1096:
1097: return TrinaryLogic::createMaybe();
1098: }
1099:
1100: public function isLiteralString(): TrinaryLogic
1101: {
1102: if ($this->subtractedType !== null) {
1103: $literalString = new IntersectionType([
1104: new StringType(),
1105: new AccessoryLiteralStringType(),
1106: ]);
1107:
1108: if ($this->subtractedType->isSuperTypeOf($literalString)->yes()) {
1109: return TrinaryLogic::createNo();
1110: }
1111: }
1112:
1113: return TrinaryLogic::createMaybe();
1114: }
1115:
1116: public function isLowercaseString(): TrinaryLogic
1117: {
1118: if ($this->subtractedType !== null) {
1119: $lowercaseString = new IntersectionType([
1120: new StringType(),
1121: new AccessoryLowercaseStringType(),
1122: ]);
1123:
1124: if ($this->subtractedType->isSuperTypeOf($lowercaseString)->yes()) {
1125: return TrinaryLogic::createNo();
1126: }
1127: }
1128:
1129: return TrinaryLogic::createMaybe();
1130: }
1131:
1132: public function isUppercaseString(): TrinaryLogic
1133: {
1134: if ($this->subtractedType !== null) {
1135: $uppercaseString = new IntersectionType([
1136: new StringType(),
1137: new AccessoryUppercaseStringType(),
1138: ]);
1139:
1140: if ($this->subtractedType->isSuperTypeOf($uppercaseString)->yes()) {
1141: return TrinaryLogic::createNo();
1142: }
1143: }
1144:
1145: return TrinaryLogic::createMaybe();
1146: }
1147:
1148: public function isClassString(): TrinaryLogic
1149: {
1150: if ($this->subtractedType !== null) {
1151: if ($this->subtractedType->isSuperTypeOf(new StringType())->yes()) {
1152: return TrinaryLogic::createNo();
1153: }
1154: if ($this->subtractedType->isSuperTypeOf(new ClassStringType())->yes()) {
1155: return TrinaryLogic::createNo();
1156: }
1157: }
1158:
1159: return TrinaryLogic::createMaybe();
1160: }
1161:
1162: public function getClassStringObjectType(): Type
1163: {
1164: if (!$this->isClassString()->no()) {
1165: return new ObjectWithoutClassType();
1166: }
1167:
1168: return new ErrorType();
1169: }
1170:
1171: public function getObjectTypeOrClassStringObjectType(): Type
1172: {
1173: $objectOrClass = new UnionType([
1174: new ObjectWithoutClassType(),
1175: new ClassStringType(),
1176: ]);
1177: if (!$this->isSuperTypeOf($objectOrClass)->no()) {
1178: return new ObjectWithoutClassType();
1179: }
1180:
1181: return new ErrorType();
1182: }
1183:
1184: public function isVoid(): TrinaryLogic
1185: {
1186: if ($this->subtractedType !== null) {
1187: if ($this->subtractedType->isSuperTypeOf(new VoidType())->yes()) {
1188: return TrinaryLogic::createNo();
1189: }
1190: }
1191:
1192: return TrinaryLogic::createMaybe();
1193: }
1194:
1195: public function isScalar(): TrinaryLogic
1196: {
1197: if ($this->subtractedType !== null) {
1198: if ($this->subtractedType->isSuperTypeOf(new UnionType([new BooleanType(), new FloatType(), new IntegerType(), new StringType()]))->yes()) {
1199: return TrinaryLogic::createNo();
1200: }
1201: }
1202:
1203: return TrinaryLogic::createMaybe();
1204: }
1205:
1206: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
1207: {
1208: return new BooleanType();
1209: }
1210:
1211: public function tryRemove(Type $typeToRemove): ?Type
1212: {
1213: // mixed is the top type, so removal is exactly the subtraction - also
1214: // when an earlier subtraction already lowered isSuperTypeOf() from yes
1215: // to maybe, which used to give up and remove nothing at all
1216: if ($this->isSuperTypeOf($typeToRemove)->no()) {
1217: return null;
1218: }
1219:
1220: return $this->subtract($typeToRemove);
1221: }
1222:
1223: public function exponentiate(Type $exponent): Type
1224: {
1225: return new BenevolentUnionType([
1226: new FloatType(),
1227: new IntegerType(),
1228: ]);
1229: }
1230:
1231: public function getFiniteTypes(): array
1232: {
1233: return [];
1234: }
1235:
1236: public function toPhpDocNode(): TypeNode
1237: {
1238: return new IdentifierTypeNode('mixed');
1239: }
1240:
1241: public function hasTemplateOrLateResolvableType(): bool
1242: {
1243: return false;
1244: }
1245:
1246: }
1247: