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 makeListMaybe(): Type
311: {
312: // `mixed` doesn't track list-ness; nothing to weaken.
313: return $this;
314: }
315:
316: public function mapValueType(callable $cb): Type
317: {
318: if ($this->isArray()->no()) {
319: return new ErrorType();
320: }
321:
322: return new ArrayType(
323: new MixedType($this->isExplicitMixed),
324: $cb(new MixedType($this->isExplicitMixed)),
325: );
326: }
327:
328: public function mapKeyType(callable $cb): Type
329: {
330: if ($this->isArray()->no()) {
331: return new ErrorType();
332: }
333:
334: return new ArrayType(
335: $cb(new MixedType($this->isExplicitMixed)),
336: new MixedType($this->isExplicitMixed),
337: );
338: }
339:
340: public function makeAllArrayKeysOptional(): Type
341: {
342: // `mixed` is already arbitrary; nothing to weaken.
343: return $this;
344: }
345:
346: public function changeKeyCaseArray(?int $case): Type
347: {
348: if ($this->isArray()->no()) {
349: return new ErrorType();
350: }
351:
352: return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed));
353: }
354:
355: public function filterArrayRemovingFalsey(): 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 isCallable(): TrinaryLogic
365: {
366: if ($this->subtractedType !== null) {
367: if ($this->subtractedType->isSuperTypeOf(new CallableType())->yes()) {
368: return TrinaryLogic::createNo();
369: }
370: }
371:
372: return TrinaryLogic::createMaybe();
373: }
374:
375: public function getEnumCases(): array
376: {
377: return [];
378: }
379:
380: public function getEnumCaseObject(): ?EnumCaseObjectType
381: {
382: return null;
383: }
384:
385: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
386: {
387: return [new TrivialParametersAcceptor()];
388: }
389:
390: public function equals(Type $type): bool
391: {
392: if (get_class($type) !== static::class) {
393: return false;
394: }
395:
396: if ($this->subtractedType === null) {
397: if ($type->subtractedType === null) {
398: return true;
399: }
400:
401: return false;
402: }
403:
404: if ($type->subtractedType === null) {
405: return false;
406: }
407:
408: return $this->subtractedType->equals($type->subtractedType);
409: }
410:
411: public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult
412: {
413: if ($otherType instanceof self && !$otherType instanceof TemplateMixedType) {
414: return IsSuperTypeOfResult::createYes();
415: }
416:
417: if ($this->subtractedType !== null) {
418: $isSuperType = $this->subtractedType->isSuperTypeOf($otherType);
419: if ($isSuperType->yes()) {
420: return IsSuperTypeOfResult::createNo();
421: }
422: }
423:
424: return IsSuperTypeOfResult::createMaybe();
425: }
426:
427: public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult
428: {
429: $isSuperType = $this->isSuperTypeOf($acceptingType)->toAcceptsResult();
430: if ($isSuperType->no()) {
431: return $isSuperType;
432: }
433: return AcceptsResult::createYes();
434: }
435:
436: public function getTemplateType(string $ancestorClassName, string $templateTypeName): Type
437: {
438: return new self();
439: }
440:
441: public function isObject(): TrinaryLogic
442: {
443: if ($this->subtractedType !== null) {
444: if ($this->subtractedType->isSuperTypeOf(new ObjectWithoutClassType())->yes()) {
445: return TrinaryLogic::createNo();
446: }
447: }
448: return TrinaryLogic::createMaybe();
449: }
450:
451: public function getClassStringType(): Type
452: {
453: return new ClassStringType();
454: }
455:
456: public function isEnum(): TrinaryLogic
457: {
458: if ($this->subtractedType !== null) {
459: if ($this->subtractedType->isSuperTypeOf(new ObjectWithoutClassType())->yes()) {
460: return TrinaryLogic::createNo();
461: }
462: }
463: return TrinaryLogic::createMaybe();
464: }
465:
466: public function canAccessProperties(): TrinaryLogic
467: {
468: return TrinaryLogic::createYes();
469: }
470:
471: public function hasProperty(string $propertyName): TrinaryLogic
472: {
473: return TrinaryLogic::createYes();
474: }
475:
476: public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
477: {
478: return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty();
479: }
480:
481: public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
482: {
483: $property = new DummyPropertyReflection($propertyName);
484: return new CallbackUnresolvedPropertyPrototypeReflection(
485: $property,
486: $property->getDeclaringClass(),
487: false,
488: static fn (Type $type): Type => $type,
489: );
490: }
491:
492: public function hasInstanceProperty(string $propertyName): TrinaryLogic
493: {
494: return TrinaryLogic::createYes();
495: }
496:
497: public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
498: {
499: return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty();
500: }
501:
502: public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
503: {
504: $property = new DummyPropertyReflection($propertyName);
505: return new CallbackUnresolvedPropertyPrototypeReflection(
506: $property,
507: $property->getDeclaringClass(),
508: false,
509: static fn (Type $type): Type => $type,
510: );
511: }
512:
513: public function hasStaticProperty(string $propertyName): TrinaryLogic
514: {
515: return TrinaryLogic::createYes();
516: }
517:
518: public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
519: {
520: return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty();
521: }
522:
523: public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
524: {
525: $property = new DummyPropertyReflection($propertyName);
526: return new CallbackUnresolvedPropertyPrototypeReflection(
527: $property,
528: $property->getDeclaringClass(),
529: false,
530: static fn (Type $type): Type => $type,
531: );
532: }
533:
534: public function canCallMethods(): TrinaryLogic
535: {
536: return TrinaryLogic::createYes();
537: }
538:
539: public function hasMethod(string $methodName): TrinaryLogic
540: {
541: return TrinaryLogic::createYes();
542: }
543:
544: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
545: {
546: return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
547: }
548:
549: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
550: {
551: $method = new DummyMethodReflection($methodName);
552: return new CallbackUnresolvedMethodPrototypeReflection(
553: $method,
554: $method->getDeclaringClass(),
555: false,
556: static fn (Type $type): Type => $type,
557: );
558: }
559:
560: public function canAccessConstants(): TrinaryLogic
561: {
562: return TrinaryLogic::createYes();
563: }
564:
565: public function hasConstant(string $constantName): TrinaryLogic
566: {
567: return TrinaryLogic::createYes();
568: }
569:
570: public function getConstant(string $constantName): ClassConstantReflection
571: {
572: return new DummyClassConstantReflection($constantName);
573: }
574:
575: public function isCloneable(): TrinaryLogic
576: {
577: return TrinaryLogic::createYes();
578: }
579:
580: public function describe(VerbosityLevel $level): string
581: {
582: return $level->handle(
583: static fn (): string => 'mixed',
584: static fn (): string => 'mixed',
585: fn (): string => 'mixed' . $this->describeSubtractedType($this->subtractedType, $level),
586: function () use ($level): string {
587: $description = 'mixed' . $this->describeSubtractedType($this->subtractedType, $level);
588:
589: if ($this->isExplicitMixed) {
590: $description .= '=explicit';
591: } else {
592: $description .= '=implicit';
593: }
594:
595: return $description;
596: },
597: );
598: }
599:
600: public function toBoolean(): BooleanType
601: {
602: if ($this->subtractedType !== null) {
603: if ($this->subtractedType->isSuperTypeOf(StaticTypeFactory::falsey())->yes()) {
604: return new ConstantBooleanType(true);
605: }
606: }
607:
608: return new BooleanType();
609: }
610:
611: public function toNumber(): Type
612: {
613: return TypeCombinator::union(
614: $this->toInteger(),
615: $this->toFloat(),
616: );
617: }
618:
619: public function toBitwiseNotType(): Type
620: {
621: return new ErrorType();
622: }
623:
624: public function toGetClassResultType(): Type
625: {
626: $isObject = $this->isObject();
627: if ($isObject->no()) {
628: return new ConstantBooleanType(false);
629: }
630:
631: $classString = $this->getClassStringType();
632: if ($isObject->yes()) {
633: return $classString;
634: }
635:
636: return new UnionType([$classString, new ConstantBooleanType(false)]);
637: }
638:
639: public function toClassConstantType(ReflectionProvider $reflectionProvider): Type
640: {
641: // `mixed::class` is undefined — the original `TypeTraverser` cb fell
642: // through to `ErrorType` for any leaf that wasn't a definite object.
643: return new ErrorType();
644: }
645:
646: public function toObjectTypeForInstanceofCheck(): ClassNameToObjectTypeResult
647: {
648: return new ClassNameToObjectTypeResult(new MixedType(), false);
649: }
650:
651: public function toObjectTypeForIsACheck(Type $objectOrClassType, bool $allowString, bool $allowSameClass): ClassNameToObjectTypeResult
652: {
653: if ($allowString) {
654: return new ClassNameToObjectTypeResult(
655: new UnionType([new ObjectWithoutClassType(), new ClassStringType()]),
656: false,
657: );
658: }
659:
660: return new ClassNameToObjectTypeResult(new ObjectWithoutClassType(), false);
661: }
662:
663: public function toAbsoluteNumber(): Type
664: {
665: return $this->toNumber()->toAbsoluteNumber();
666: }
667:
668: public function toInteger(): Type
669: {
670: $castsToZero = new UnionType([
671: new NullType(),
672: new ConstantBooleanType(false),
673: new ConstantIntegerType(0),
674: new ConstantArrayType([], []),
675: new StringType(),
676: new FloatType(), // every 0.x float casts to int(0)
677: ]);
678: if (
679: $this->subtractedType !== null
680: && $this->subtractedType->isSuperTypeOf($castsToZero)->yes()
681: ) {
682: return new UnionType([
683: IntegerRangeType::fromInterval(null, -1),
684: IntegerRangeType::fromInterval(1, null),
685: ]);
686: }
687:
688: return new IntegerType();
689: }
690:
691: public function toFloat(): Type
692: {
693: return new FloatType();
694: }
695:
696: public function toString(): Type
697: {
698: if ($this->subtractedType !== null) {
699: $castsToEmptyString = new UnionType([
700: new NullType(),
701: new ConstantBooleanType(false),
702: new ConstantStringType(''),
703: ]);
704: if ($this->subtractedType->isSuperTypeOf($castsToEmptyString)->yes()) {
705: $accessories = [
706: new StringType(),
707: new AccessoryNonEmptyStringType(),
708: ];
709:
710: $castsToZeroString = new UnionType([
711: new ConstantFloatType(0.0),
712: new ConstantStringType('0'),
713: new ConstantIntegerType(0),
714: ]);
715: if ($this->subtractedType->isSuperTypeOf($castsToZeroString)->yes()) {
716: $accessories[] = new AccessoryNonFalsyStringType();
717: }
718: return new IntersectionType(
719: $accessories,
720: );
721: }
722: }
723:
724: return new StringType();
725: }
726:
727: public function toArray(): Type
728: {
729: $mixed = new self($this->isExplicitMixed);
730:
731: return new ArrayType($mixed, $mixed);
732: }
733:
734: public function toArrayKey(): Type
735: {
736: return new BenevolentUnionType([new IntegerType(), new StringType()]);
737: }
738:
739: public function toCoercedArgumentType(bool $strictTypes): Type
740: {
741: return $this;
742: }
743:
744: public function isIterable(): TrinaryLogic
745: {
746: if ($this->subtractedType !== null) {
747: if ($this->subtractedType->isSuperTypeOf(new IterableType(new MixedType(), new MixedType()))->yes()) {
748: return TrinaryLogic::createNo();
749: }
750: }
751:
752: return TrinaryLogic::createMaybe();
753: }
754:
755: public function isIterableAtLeastOnce(): TrinaryLogic
756: {
757: return $this->isIterable();
758: }
759:
760: public function getArraySize(): Type
761: {
762: if ($this->isIterable()->no()) {
763: return new ErrorType();
764: }
765:
766: return IntegerRangeType::fromInterval(0, null);
767: }
768:
769: public function getIterableKeyType(): Type
770: {
771: return new self($this->isExplicitMixed);
772: }
773:
774: public function getFirstIterableKeyType(): Type
775: {
776: return new self($this->isExplicitMixed);
777: }
778:
779: public function getLastIterableKeyType(): Type
780: {
781: return new self($this->isExplicitMixed);
782: }
783:
784: public function getIterableValueType(): Type
785: {
786: return new self($this->isExplicitMixed);
787: }
788:
789: public function getFirstIterableValueType(): Type
790: {
791: return new self($this->isExplicitMixed);
792: }
793:
794: public function getLastIterableValueType(): Type
795: {
796: return new self($this->isExplicitMixed);
797: }
798:
799: public function isOffsetAccessible(): TrinaryLogic
800: {
801: if ($this->subtractedType !== null) {
802: $offsetAccessibles = new UnionType([
803: new StringType(),
804: new ArrayType(new MixedType(), new MixedType()),
805: new ObjectType(ArrayAccess::class),
806: ]);
807:
808: if ($this->subtractedType->isSuperTypeOf($offsetAccessibles)->yes()) {
809: return TrinaryLogic::createNo();
810: }
811: }
812: return TrinaryLogic::createMaybe();
813: }
814:
815: public function isOffsetAccessLegal(): TrinaryLogic
816: {
817: if ($this->subtractedType !== null) {
818: if ($this->subtractedType->isSuperTypeOf(new ObjectWithoutClassType())->yes()) {
819: return TrinaryLogic::createYes();
820: }
821: }
822: return TrinaryLogic::createMaybe();
823: }
824:
825: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
826: {
827: if ($this->isOffsetAccessible()->no()) {
828: return TrinaryLogic::createNo();
829: }
830:
831: return TrinaryLogic::createMaybe();
832: }
833:
834: public function getOffsetValueType(Type $offsetType): Type
835: {
836: return new self($this->isExplicitMixed);
837: }
838:
839: public function isExplicitMixed(): bool
840: {
841: return $this->isExplicitMixed;
842: }
843:
844: public function subtract(Type $type): Type
845: {
846: if ($type instanceof self && !$type instanceof TemplateType) {
847: return new NeverType();
848: }
849: if ($this->subtractedType !== null) {
850: $type = TypeCombinator::union($this->subtractedType, $type);
851: }
852:
853: return new self($this->isExplicitMixed, $type);
854: }
855:
856: public function getTypeWithoutSubtractedType(): Type
857: {
858: return new self($this->isExplicitMixed);
859: }
860:
861: public function changeSubtractedType(?Type $subtractedType): Type
862: {
863: return new self($this->isExplicitMixed, $subtractedType);
864: }
865:
866: public function getSubtractedType(): ?Type
867: {
868: return $this->subtractedType;
869: }
870:
871: public function traverse(callable $cb): Type
872: {
873: return $this;
874: }
875:
876: public function traverseSimultaneously(Type $right, callable $cb): Type
877: {
878: return $this;
879: }
880:
881: public function isArray(): TrinaryLogic
882: {
883: if ($this->subtractedType !== null) {
884: if ($this->subtractedType->isSuperTypeOf(new ArrayType(new MixedType(), new MixedType()))->yes()) {
885: return TrinaryLogic::createNo();
886: }
887: }
888:
889: return TrinaryLogic::createMaybe();
890: }
891:
892: public function isConstantArray(): TrinaryLogic
893: {
894: return $this->isArray();
895: }
896:
897: public function isOversizedArray(): TrinaryLogic
898: {
899: if ($this->subtractedType !== null) {
900: $oversizedArray = new IntersectionType([
901: new ArrayType(new MixedType(), new MixedType()),
902: new OversizedArrayType(),
903: ]);
904:
905: if ($this->subtractedType->isSuperTypeOf($oversizedArray)->yes()) {
906: return TrinaryLogic::createNo();
907: }
908: }
909:
910: return TrinaryLogic::createMaybe();
911: }
912:
913: public function isList(): TrinaryLogic
914: {
915: if ($this->subtractedType !== null) {
916: $list = new IntersectionType([
917: new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new MixedType()),
918: new AccessoryArrayListType(),
919: ]);
920:
921: if ($this->subtractedType->isSuperTypeOf($list)->yes()) {
922: return TrinaryLogic::createNo();
923: }
924: }
925:
926: return TrinaryLogic::createMaybe();
927: }
928:
929: public function isNull(): TrinaryLogic
930: {
931: if ($this->subtractedType !== null) {
932: if ($this->subtractedType->isSuperTypeOf(new NullType())->yes()) {
933: return TrinaryLogic::createNo();
934: }
935: }
936:
937: return TrinaryLogic::createMaybe();
938: }
939:
940: public function isConstantValue(): TrinaryLogic
941: {
942: return TrinaryLogic::createNo();
943: }
944:
945: public function isConstantScalarValue(): TrinaryLogic
946: {
947: return TrinaryLogic::createNo();
948: }
949:
950: public function getConstantScalarTypes(): array
951: {
952: return [];
953: }
954:
955: public function getConstantScalarValues(): array
956: {
957: return [];
958: }
959:
960: public function isTrue(): TrinaryLogic
961: {
962: if ($this->subtractedType !== null) {
963: if ($this->subtractedType->isSuperTypeOf(new ConstantBooleanType(true))->yes()) {
964: return TrinaryLogic::createNo();
965: }
966: }
967:
968: return TrinaryLogic::createMaybe();
969: }
970:
971: public function isFalse(): TrinaryLogic
972: {
973: if ($this->subtractedType !== null) {
974: if ($this->subtractedType->isSuperTypeOf(new ConstantBooleanType(false))->yes()) {
975: return TrinaryLogic::createNo();
976: }
977: }
978:
979: return TrinaryLogic::createMaybe();
980: }
981:
982: public function isBoolean(): TrinaryLogic
983: {
984: if ($this->subtractedType !== null) {
985: if ($this->subtractedType->isSuperTypeOf(new BooleanType())->yes()) {
986: return TrinaryLogic::createNo();
987: }
988: }
989:
990: return TrinaryLogic::createMaybe();
991: }
992:
993: public function isFloat(): TrinaryLogic
994: {
995: if ($this->subtractedType !== null) {
996: if ($this->subtractedType->isSuperTypeOf(new FloatType())->yes()) {
997: return TrinaryLogic::createNo();
998: }
999: }
1000:
1001: return TrinaryLogic::createMaybe();
1002: }
1003:
1004: public function isInteger(): TrinaryLogic
1005: {
1006: if ($this->subtractedType !== null) {
1007: if ($this->subtractedType->isSuperTypeOf(new IntegerType())->yes()) {
1008: return TrinaryLogic::createNo();
1009: }
1010: }
1011:
1012: return TrinaryLogic::createMaybe();
1013: }
1014:
1015: public function isString(): TrinaryLogic
1016: {
1017: if ($this->subtractedType !== null) {
1018: if ($this->subtractedType->isSuperTypeOf(new StringType())->yes()) {
1019: return TrinaryLogic::createNo();
1020: }
1021: }
1022: return TrinaryLogic::createMaybe();
1023: }
1024:
1025: public function isNumericString(): TrinaryLogic
1026: {
1027: if ($this->subtractedType !== null) {
1028: $numericString = new IntersectionType([
1029: new StringType(),
1030: new AccessoryNumericStringType(),
1031: ]);
1032:
1033: if ($this->subtractedType->isSuperTypeOf($numericString)->yes()) {
1034: return TrinaryLogic::createNo();
1035: }
1036: }
1037:
1038: return TrinaryLogic::createMaybe();
1039: }
1040:
1041: public function isDecimalIntegerString(): TrinaryLogic
1042: {
1043: if ($this->subtractedType !== null) {
1044: $decimalIntegerString = new IntersectionType([
1045: new StringType(),
1046: new AccessoryDecimalIntegerStringType(),
1047: ]);
1048:
1049: if ($this->subtractedType->isSuperTypeOf($decimalIntegerString)->yes()) {
1050: return TrinaryLogic::createNo();
1051: }
1052: }
1053:
1054: return TrinaryLogic::createMaybe();
1055: }
1056:
1057: public function isNonEmptyString(): TrinaryLogic
1058: {
1059: if ($this->subtractedType !== null) {
1060: $nonEmptyString = new IntersectionType([
1061: new StringType(),
1062: new AccessoryNonEmptyStringType(),
1063: ]);
1064:
1065: if ($this->subtractedType->isSuperTypeOf($nonEmptyString)->yes()) {
1066: return TrinaryLogic::createNo();
1067: }
1068: }
1069:
1070: return TrinaryLogic::createMaybe();
1071: }
1072:
1073: public function isNonFalsyString(): TrinaryLogic
1074: {
1075: if ($this->subtractedType !== null) {
1076: $nonFalsyString = new IntersectionType([
1077: new StringType(),
1078: new AccessoryNonFalsyStringType(),
1079: ]);
1080:
1081: if ($this->subtractedType->isSuperTypeOf($nonFalsyString)->yes()) {
1082: return TrinaryLogic::createNo();
1083: }
1084: }
1085:
1086: return TrinaryLogic::createMaybe();
1087: }
1088:
1089: public function isLiteralString(): TrinaryLogic
1090: {
1091: if ($this->subtractedType !== null) {
1092: $literalString = new IntersectionType([
1093: new StringType(),
1094: new AccessoryLiteralStringType(),
1095: ]);
1096:
1097: if ($this->subtractedType->isSuperTypeOf($literalString)->yes()) {
1098: return TrinaryLogic::createNo();
1099: }
1100: }
1101:
1102: return TrinaryLogic::createMaybe();
1103: }
1104:
1105: public function isLowercaseString(): TrinaryLogic
1106: {
1107: if ($this->subtractedType !== null) {
1108: $lowercaseString = new IntersectionType([
1109: new StringType(),
1110: new AccessoryLowercaseStringType(),
1111: ]);
1112:
1113: if ($this->subtractedType->isSuperTypeOf($lowercaseString)->yes()) {
1114: return TrinaryLogic::createNo();
1115: }
1116: }
1117:
1118: return TrinaryLogic::createMaybe();
1119: }
1120:
1121: public function isUppercaseString(): TrinaryLogic
1122: {
1123: if ($this->subtractedType !== null) {
1124: $uppercaseString = new IntersectionType([
1125: new StringType(),
1126: new AccessoryUppercaseStringType(),
1127: ]);
1128:
1129: if ($this->subtractedType->isSuperTypeOf($uppercaseString)->yes()) {
1130: return TrinaryLogic::createNo();
1131: }
1132: }
1133:
1134: return TrinaryLogic::createMaybe();
1135: }
1136:
1137: public function isClassString(): TrinaryLogic
1138: {
1139: if ($this->subtractedType !== null) {
1140: if ($this->subtractedType->isSuperTypeOf(new StringType())->yes()) {
1141: return TrinaryLogic::createNo();
1142: }
1143: if ($this->subtractedType->isSuperTypeOf(new ClassStringType())->yes()) {
1144: return TrinaryLogic::createNo();
1145: }
1146: }
1147:
1148: return TrinaryLogic::createMaybe();
1149: }
1150:
1151: public function getClassStringObjectType(): Type
1152: {
1153: if (!$this->isClassString()->no()) {
1154: return new ObjectWithoutClassType();
1155: }
1156:
1157: return new ErrorType();
1158: }
1159:
1160: public function getObjectTypeOrClassStringObjectType(): Type
1161: {
1162: $objectOrClass = new UnionType([
1163: new ObjectWithoutClassType(),
1164: new ClassStringType(),
1165: ]);
1166: if (!$this->isSuperTypeOf($objectOrClass)->no()) {
1167: return new ObjectWithoutClassType();
1168: }
1169:
1170: return new ErrorType();
1171: }
1172:
1173: public function isVoid(): TrinaryLogic
1174: {
1175: if ($this->subtractedType !== null) {
1176: if ($this->subtractedType->isSuperTypeOf(new VoidType())->yes()) {
1177: return TrinaryLogic::createNo();
1178: }
1179: }
1180:
1181: return TrinaryLogic::createMaybe();
1182: }
1183:
1184: public function isScalar(): TrinaryLogic
1185: {
1186: if ($this->subtractedType !== null) {
1187: if ($this->subtractedType->isSuperTypeOf(new UnionType([new BooleanType(), new FloatType(), new IntegerType(), new StringType()]))->yes()) {
1188: return TrinaryLogic::createNo();
1189: }
1190: }
1191:
1192: return TrinaryLogic::createMaybe();
1193: }
1194:
1195: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
1196: {
1197: return new BooleanType();
1198: }
1199:
1200: public function tryRemove(Type $typeToRemove): ?Type
1201: {
1202: if ($this->isSuperTypeOf($typeToRemove)->yes()) {
1203: return $this->subtract($typeToRemove);
1204: }
1205:
1206: return null;
1207: }
1208:
1209: public function exponentiate(Type $exponent): Type
1210: {
1211: return new BenevolentUnionType([
1212: new FloatType(),
1213: new IntegerType(),
1214: ]);
1215: }
1216:
1217: public function getFiniteTypes(): array
1218: {
1219: return [];
1220: }
1221:
1222: public function toPhpDocNode(): TypeNode
1223: {
1224: return new IdentifierTypeNode('mixed');
1225: }
1226:
1227: public function hasTemplateOrLateResolvableType(): bool
1228: {
1229: return false;
1230: }
1231:
1232: }
1233: