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