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