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