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