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