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