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