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