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