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 UnionType([new IntegerType(), new StringType()]);
581: }
582:
583: public function isIterable(): TrinaryLogic
584: {
585: if ($this->subtractedType !== null) {
586: if ($this->subtractedType->isSuperTypeOf(new IterableType(new MixedType(), new MixedType()))->yes()) {
587: return TrinaryLogic::createNo();
588: }
589: }
590:
591: return TrinaryLogic::createMaybe();
592: }
593:
594: public function isIterableAtLeastOnce(): TrinaryLogic
595: {
596: return $this->isIterable();
597: }
598:
599: public function getArraySize(): Type
600: {
601: if ($this->isIterable()->no()) {
602: return new ErrorType();
603: }
604:
605: return IntegerRangeType::fromInterval(0, null);
606: }
607:
608: public function getIterableKeyType(): Type
609: {
610: return new self($this->isExplicitMixed);
611: }
612:
613: public function getFirstIterableKeyType(): Type
614: {
615: return new self($this->isExplicitMixed);
616: }
617:
618: public function getLastIterableKeyType(): Type
619: {
620: return new self($this->isExplicitMixed);
621: }
622:
623: public function getIterableValueType(): Type
624: {
625: return new self($this->isExplicitMixed);
626: }
627:
628: public function getFirstIterableValueType(): Type
629: {
630: return new self($this->isExplicitMixed);
631: }
632:
633: public function getLastIterableValueType(): Type
634: {
635: return new self($this->isExplicitMixed);
636: }
637:
638: public function isOffsetAccessible(): TrinaryLogic
639: {
640: if ($this->subtractedType !== null) {
641: $offsetAccessibles = new UnionType([
642: new StringType(),
643: new ArrayType(new MixedType(), new MixedType()),
644: new ObjectType(ArrayAccess::class),
645: ]);
646:
647: if ($this->subtractedType->isSuperTypeOf($offsetAccessibles)->yes()) {
648: return TrinaryLogic::createNo();
649: }
650: }
651: return TrinaryLogic::createMaybe();
652: }
653:
654: public function isOffsetAccessLegal(): TrinaryLogic
655: {
656: if ($this->subtractedType !== null) {
657: if ($this->subtractedType->isSuperTypeOf(new ObjectWithoutClassType())->yes()) {
658: return TrinaryLogic::createYes();
659: }
660: }
661: return TrinaryLogic::createMaybe();
662: }
663:
664: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
665: {
666: if ($this->isOffsetAccessible()->no()) {
667: return TrinaryLogic::createNo();
668: }
669:
670: return TrinaryLogic::createMaybe();
671: }
672:
673: public function getOffsetValueType(Type $offsetType): Type
674: {
675: return new self($this->isExplicitMixed);
676: }
677:
678: public function isExplicitMixed(): bool
679: {
680: return $this->isExplicitMixed;
681: }
682:
683: public function subtract(Type $type): Type
684: {
685: if ($type instanceof self && !$type instanceof TemplateType) {
686: return new NeverType();
687: }
688: if ($this->subtractedType !== null) {
689: $type = TypeCombinator::union($this->subtractedType, $type);
690: }
691:
692: return new self($this->isExplicitMixed, $type);
693: }
694:
695: public function getTypeWithoutSubtractedType(): Type
696: {
697: return new self($this->isExplicitMixed);
698: }
699:
700: public function changeSubtractedType(?Type $subtractedType): Type
701: {
702: return new self($this->isExplicitMixed, $subtractedType);
703: }
704:
705: public function getSubtractedType(): ?Type
706: {
707: return $this->subtractedType;
708: }
709:
710: public function traverse(callable $cb): Type
711: {
712: return $this;
713: }
714:
715: public function traverseSimultaneously(Type $right, callable $cb): Type
716: {
717: return $this;
718: }
719:
720: public function isArray(): TrinaryLogic
721: {
722: if ($this->subtractedType !== null) {
723: if ($this->subtractedType->isSuperTypeOf(new ArrayType(new MixedType(), new MixedType()))->yes()) {
724: return TrinaryLogic::createNo();
725: }
726: }
727:
728: return TrinaryLogic::createMaybe();
729: }
730:
731: public function isConstantArray(): TrinaryLogic
732: {
733: return $this->isArray();
734: }
735:
736: public function isOversizedArray(): TrinaryLogic
737: {
738: if ($this->subtractedType !== null) {
739: $oversizedArray = TypeCombinator::intersect(
740: new ArrayType(new MixedType(), new MixedType()),
741: new OversizedArrayType(),
742: );
743:
744: if ($this->subtractedType->isSuperTypeOf($oversizedArray)->yes()) {
745: return TrinaryLogic::createNo();
746: }
747: }
748:
749: return TrinaryLogic::createMaybe();
750: }
751:
752: public function isList(): TrinaryLogic
753: {
754: if ($this->subtractedType !== null) {
755: $list = TypeCombinator::intersect(
756: new ArrayType(new IntegerType(), new MixedType()),
757: new AccessoryArrayListType(),
758: );
759:
760: if ($this->subtractedType->isSuperTypeOf($list)->yes()) {
761: return TrinaryLogic::createNo();
762: }
763: }
764:
765: return TrinaryLogic::createMaybe();
766: }
767:
768: public function isNull(): TrinaryLogic
769: {
770: if ($this->subtractedType !== null) {
771: if ($this->subtractedType->isSuperTypeOf(new NullType())->yes()) {
772: return TrinaryLogic::createNo();
773: }
774: }
775:
776: return TrinaryLogic::createMaybe();
777: }
778:
779: public function isConstantValue(): TrinaryLogic
780: {
781: return TrinaryLogic::createNo();
782: }
783:
784: public function isConstantScalarValue(): TrinaryLogic
785: {
786: return TrinaryLogic::createNo();
787: }
788:
789: public function getConstantScalarTypes(): array
790: {
791: return [];
792: }
793:
794: public function getConstantScalarValues(): array
795: {
796: return [];
797: }
798:
799: public function isTrue(): TrinaryLogic
800: {
801: if ($this->subtractedType !== null) {
802: if ($this->subtractedType->isSuperTypeOf(new ConstantBooleanType(true))->yes()) {
803: return TrinaryLogic::createNo();
804: }
805: }
806:
807: return TrinaryLogic::createMaybe();
808: }
809:
810: public function isFalse(): TrinaryLogic
811: {
812: if ($this->subtractedType !== null) {
813: if ($this->subtractedType->isSuperTypeOf(new ConstantBooleanType(false))->yes()) {
814: return TrinaryLogic::createNo();
815: }
816: }
817:
818: return TrinaryLogic::createMaybe();
819: }
820:
821: public function isBoolean(): TrinaryLogic
822: {
823: if ($this->subtractedType !== null) {
824: if ($this->subtractedType->isSuperTypeOf(new BooleanType())->yes()) {
825: return TrinaryLogic::createNo();
826: }
827: }
828:
829: return TrinaryLogic::createMaybe();
830: }
831:
832: public function isFloat(): TrinaryLogic
833: {
834: if ($this->subtractedType !== null) {
835: if ($this->subtractedType->isSuperTypeOf(new FloatType())->yes()) {
836: return TrinaryLogic::createNo();
837: }
838: }
839:
840: return TrinaryLogic::createMaybe();
841: }
842:
843: public function isInteger(): TrinaryLogic
844: {
845: if ($this->subtractedType !== null) {
846: if ($this->subtractedType->isSuperTypeOf(new IntegerType())->yes()) {
847: return TrinaryLogic::createNo();
848: }
849: }
850:
851: return TrinaryLogic::createMaybe();
852: }
853:
854: public function isString(): TrinaryLogic
855: {
856: if ($this->subtractedType !== null) {
857: if ($this->subtractedType->isSuperTypeOf(new StringType())->yes()) {
858: return TrinaryLogic::createNo();
859: }
860: }
861: return TrinaryLogic::createMaybe();
862: }
863:
864: public function isNumericString(): TrinaryLogic
865: {
866: if ($this->subtractedType !== null) {
867: $numericString = TypeCombinator::intersect(
868: new StringType(),
869: new AccessoryNumericStringType(),
870: );
871:
872: if ($this->subtractedType->isSuperTypeOf($numericString)->yes()) {
873: return TrinaryLogic::createNo();
874: }
875: }
876:
877: return TrinaryLogic::createMaybe();
878: }
879:
880: public function isNonEmptyString(): TrinaryLogic
881: {
882: if ($this->subtractedType !== null) {
883: $nonEmptyString = TypeCombinator::intersect(
884: new StringType(),
885: new AccessoryNonEmptyStringType(),
886: );
887:
888: if ($this->subtractedType->isSuperTypeOf($nonEmptyString)->yes()) {
889: return TrinaryLogic::createNo();
890: }
891: }
892:
893: return TrinaryLogic::createMaybe();
894: }
895:
896: public function isNonFalsyString(): TrinaryLogic
897: {
898: if ($this->subtractedType !== null) {
899: $nonFalsyString = TypeCombinator::intersect(
900: new StringType(),
901: new AccessoryNonFalsyStringType(),
902: );
903:
904: if ($this->subtractedType->isSuperTypeOf($nonFalsyString)->yes()) {
905: return TrinaryLogic::createNo();
906: }
907: }
908:
909: return TrinaryLogic::createMaybe();
910: }
911:
912: public function isLiteralString(): TrinaryLogic
913: {
914: if ($this->subtractedType !== null) {
915: $literalString = TypeCombinator::intersect(
916: new StringType(),
917: new AccessoryLiteralStringType(),
918: );
919:
920: if ($this->subtractedType->isSuperTypeOf($literalString)->yes()) {
921: return TrinaryLogic::createNo();
922: }
923: }
924:
925: return TrinaryLogic::createMaybe();
926: }
927:
928: public function isLowercaseString(): TrinaryLogic
929: {
930: if ($this->subtractedType !== null) {
931: $lowercaseString = TypeCombinator::intersect(
932: new StringType(),
933: new AccessoryLowercaseStringType(),
934: );
935:
936: if ($this->subtractedType->isSuperTypeOf($lowercaseString)->yes()) {
937: return TrinaryLogic::createNo();
938: }
939: }
940:
941: return TrinaryLogic::createMaybe();
942: }
943:
944: public function isUppercaseString(): TrinaryLogic
945: {
946: if ($this->subtractedType !== null) {
947: $uppercaseString = TypeCombinator::intersect(
948: new StringType(),
949: new AccessoryUppercaseStringType(),
950: );
951:
952: if ($this->subtractedType->isSuperTypeOf($uppercaseString)->yes()) {
953: return TrinaryLogic::createNo();
954: }
955: }
956:
957: return TrinaryLogic::createMaybe();
958: }
959:
960: public function isClassString(): TrinaryLogic
961: {
962: if ($this->subtractedType !== null) {
963: if ($this->subtractedType->isSuperTypeOf(new StringType())->yes()) {
964: return TrinaryLogic::createNo();
965: }
966: if ($this->subtractedType->isSuperTypeOf(new ClassStringType())->yes()) {
967: return TrinaryLogic::createNo();
968: }
969: }
970:
971: return TrinaryLogic::createMaybe();
972: }
973:
974: public function getClassStringObjectType(): Type
975: {
976: if (!$this->isClassString()->no()) {
977: return new ObjectWithoutClassType();
978: }
979:
980: return new ErrorType();
981: }
982:
983: public function getObjectTypeOrClassStringObjectType(): Type
984: {
985: $objectOrClass = new UnionType([
986: new ObjectWithoutClassType(),
987: new ClassStringType(),
988: ]);
989: if (!$this->isSuperTypeOf($objectOrClass)->no()) {
990: return new ObjectWithoutClassType();
991: }
992:
993: return new ErrorType();
994: }
995:
996: public function isVoid(): TrinaryLogic
997: {
998: if ($this->subtractedType !== null) {
999: if ($this->subtractedType->isSuperTypeOf(new VoidType())->yes()) {
1000: return TrinaryLogic::createNo();
1001: }
1002: }
1003:
1004: return TrinaryLogic::createMaybe();
1005: }
1006:
1007: public function isScalar(): TrinaryLogic
1008: {
1009: if ($this->subtractedType !== null) {
1010: if ($this->subtractedType->isSuperTypeOf(new UnionType([new BooleanType(), new FloatType(), new IntegerType(), new StringType()]))->yes()) {
1011: return TrinaryLogic::createNo();
1012: }
1013: }
1014:
1015: return TrinaryLogic::createMaybe();
1016: }
1017:
1018: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
1019: {
1020: return new BooleanType();
1021: }
1022:
1023: public function tryRemove(Type $typeToRemove): ?Type
1024: {
1025: if ($this->isSuperTypeOf($typeToRemove)->yes()) {
1026: return $this->subtract($typeToRemove);
1027: }
1028:
1029: return null;
1030: }
1031:
1032: public function exponentiate(Type $exponent): Type
1033: {
1034: return new BenevolentUnionType([
1035: new FloatType(),
1036: new IntegerType(),
1037: ]);
1038: }
1039:
1040: public function getFiniteTypes(): array
1041: {
1042: return [];
1043: }
1044:
1045: public function toPhpDocNode(): TypeNode
1046: {
1047: return new IdentifierTypeNode('mixed');
1048: }
1049:
1050: }
1051: