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 UnionType([new IntegerType(), new StringType()]); |
581: | } |
582: | |
583: | public function isIterable(): TrinaryLogic |
584: | { |
585: | if ($this->subtractedType !== null) { |
586: | if ($this->subtractedType->isSuperTypeOf(new IterableType(new MixedType(), new MixedType()))->yes()) { |
587: | return TrinaryLogic::createNo(); |
588: | } |
589: | } |
590: | |
591: | return TrinaryLogic::createMaybe(); |
592: | } |
593: | |
594: | public function isIterableAtLeastOnce(): TrinaryLogic |
595: | { |
596: | return $this->isIterable(); |
597: | } |
598: | |
599: | public function getArraySize(): Type |
600: | { |
601: | if ($this->isIterable()->no()) { |
602: | return new ErrorType(); |
603: | } |
604: | |
605: | return IntegerRangeType::fromInterval(0, null); |
606: | } |
607: | |
608: | public function getIterableKeyType(): Type |
609: | { |
610: | return new self($this->isExplicitMixed); |
611: | } |
612: | |
613: | public function getFirstIterableKeyType(): Type |
614: | { |
615: | return new self($this->isExplicitMixed); |
616: | } |
617: | |
618: | public function getLastIterableKeyType(): Type |
619: | { |
620: | return new self($this->isExplicitMixed); |
621: | } |
622: | |
623: | public function getIterableValueType(): Type |
624: | { |
625: | return new self($this->isExplicitMixed); |
626: | } |
627: | |
628: | public function getFirstIterableValueType(): Type |
629: | { |
630: | return new self($this->isExplicitMixed); |
631: | } |
632: | |
633: | public function getLastIterableValueType(): Type |
634: | { |
635: | return new self($this->isExplicitMixed); |
636: | } |
637: | |
638: | public function isOffsetAccessible(): TrinaryLogic |
639: | { |
640: | if ($this->subtractedType !== null) { |
641: | $offsetAccessibles = new UnionType([ |
642: | new StringType(), |
643: | new ArrayType(new MixedType(), new MixedType()), |
644: | new ObjectType(ArrayAccess::class), |
645: | ]); |
646: | |
647: | if ($this->subtractedType->isSuperTypeOf($offsetAccessibles)->yes()) { |
648: | return TrinaryLogic::createNo(); |
649: | } |
650: | } |
651: | return TrinaryLogic::createMaybe(); |
652: | } |
653: | |
654: | public function isOffsetAccessLegal(): TrinaryLogic |
655: | { |
656: | if ($this->subtractedType !== null) { |
657: | if ($this->subtractedType->isSuperTypeOf(new ObjectWithoutClassType())->yes()) { |
658: | return TrinaryLogic::createYes(); |
659: | } |
660: | } |
661: | return TrinaryLogic::createMaybe(); |
662: | } |
663: | |
664: | public function hasOffsetValueType(Type $offsetType): TrinaryLogic |
665: | { |
666: | if ($this->isOffsetAccessible()->no()) { |
667: | return TrinaryLogic::createNo(); |
668: | } |
669: | |
670: | return TrinaryLogic::createMaybe(); |
671: | } |
672: | |
673: | public function getOffsetValueType(Type $offsetType): Type |
674: | { |
675: | return new self($this->isExplicitMixed); |
676: | } |
677: | |
678: | public function isExplicitMixed(): bool |
679: | { |
680: | return $this->isExplicitMixed; |
681: | } |
682: | |
683: | public function subtract(Type $type): Type |
684: | { |
685: | if ($type instanceof self && !$type instanceof TemplateType) { |
686: | return new NeverType(); |
687: | } |
688: | if ($this->subtractedType !== null) { |
689: | $type = TypeCombinator::union($this->subtractedType, $type); |
690: | } |
691: | |
692: | return new self($this->isExplicitMixed, $type); |
693: | } |
694: | |
695: | public function getTypeWithoutSubtractedType(): Type |
696: | { |
697: | return new self($this->isExplicitMixed); |
698: | } |
699: | |
700: | public function changeSubtractedType(?Type $subtractedType): Type |
701: | { |
702: | return new self($this->isExplicitMixed, $subtractedType); |
703: | } |
704: | |
705: | public function getSubtractedType(): ?Type |
706: | { |
707: | return $this->subtractedType; |
708: | } |
709: | |
710: | public function traverse(callable $cb): Type |
711: | { |
712: | return $this; |
713: | } |
714: | |
715: | public function traverseSimultaneously(Type $right, callable $cb): Type |
716: | { |
717: | return $this; |
718: | } |
719: | |
720: | public function isArray(): TrinaryLogic |
721: | { |
722: | if ($this->subtractedType !== null) { |
723: | if ($this->subtractedType->isSuperTypeOf(new ArrayType(new MixedType(), new MixedType()))->yes()) { |
724: | return TrinaryLogic::createNo(); |
725: | } |
726: | } |
727: | |
728: | return TrinaryLogic::createMaybe(); |
729: | } |
730: | |
731: | public function isConstantArray(): TrinaryLogic |
732: | { |
733: | return $this->isArray(); |
734: | } |
735: | |
736: | public function isOversizedArray(): TrinaryLogic |
737: | { |
738: | if ($this->subtractedType !== null) { |
739: | $oversizedArray = TypeCombinator::intersect( |
740: | new ArrayType(new MixedType(), new MixedType()), |
741: | new OversizedArrayType(), |
742: | ); |
743: | |
744: | if ($this->subtractedType->isSuperTypeOf($oversizedArray)->yes()) { |
745: | return TrinaryLogic::createNo(); |
746: | } |
747: | } |
748: | |
749: | return TrinaryLogic::createMaybe(); |
750: | } |
751: | |
752: | public function isList(): TrinaryLogic |
753: | { |
754: | if ($this->subtractedType !== null) { |
755: | $list = TypeCombinator::intersect( |
756: | new ArrayType(new IntegerType(), new MixedType()), |
757: | new AccessoryArrayListType(), |
758: | ); |
759: | |
760: | if ($this->subtractedType->isSuperTypeOf($list)->yes()) { |
761: | return TrinaryLogic::createNo(); |
762: | } |
763: | } |
764: | |
765: | return TrinaryLogic::createMaybe(); |
766: | } |
767: | |
768: | public function isNull(): TrinaryLogic |
769: | { |
770: | if ($this->subtractedType !== null) { |
771: | if ($this->subtractedType->isSuperTypeOf(new NullType())->yes()) { |
772: | return TrinaryLogic::createNo(); |
773: | } |
774: | } |
775: | |
776: | return TrinaryLogic::createMaybe(); |
777: | } |
778: | |
779: | public function isConstantValue(): TrinaryLogic |
780: | { |
781: | return TrinaryLogic::createNo(); |
782: | } |
783: | |
784: | public function isConstantScalarValue(): TrinaryLogic |
785: | { |
786: | return TrinaryLogic::createNo(); |
787: | } |
788: | |
789: | public function getConstantScalarTypes(): array |
790: | { |
791: | return []; |
792: | } |
793: | |
794: | public function getConstantScalarValues(): array |
795: | { |
796: | return []; |
797: | } |
798: | |
799: | public function isTrue(): TrinaryLogic |
800: | { |
801: | if ($this->subtractedType !== null) { |
802: | if ($this->subtractedType->isSuperTypeOf(new ConstantBooleanType(true))->yes()) { |
803: | return TrinaryLogic::createNo(); |
804: | } |
805: | } |
806: | |
807: | return TrinaryLogic::createMaybe(); |
808: | } |
809: | |
810: | public function isFalse(): TrinaryLogic |
811: | { |
812: | if ($this->subtractedType !== null) { |
813: | if ($this->subtractedType->isSuperTypeOf(new ConstantBooleanType(false))->yes()) { |
814: | return TrinaryLogic::createNo(); |
815: | } |
816: | } |
817: | |
818: | return TrinaryLogic::createMaybe(); |
819: | } |
820: | |
821: | public function isBoolean(): TrinaryLogic |
822: | { |
823: | if ($this->subtractedType !== null) { |
824: | if ($this->subtractedType->isSuperTypeOf(new BooleanType())->yes()) { |
825: | return TrinaryLogic::createNo(); |
826: | } |
827: | } |
828: | |
829: | return TrinaryLogic::createMaybe(); |
830: | } |
831: | |
832: | public function isFloat(): TrinaryLogic |
833: | { |
834: | if ($this->subtractedType !== null) { |
835: | if ($this->subtractedType->isSuperTypeOf(new FloatType())->yes()) { |
836: | return TrinaryLogic::createNo(); |
837: | } |
838: | } |
839: | |
840: | return TrinaryLogic::createMaybe(); |
841: | } |
842: | |
843: | public function isInteger(): TrinaryLogic |
844: | { |
845: | if ($this->subtractedType !== null) { |
846: | if ($this->subtractedType->isSuperTypeOf(new IntegerType())->yes()) { |
847: | return TrinaryLogic::createNo(); |
848: | } |
849: | } |
850: | |
851: | return TrinaryLogic::createMaybe(); |
852: | } |
853: | |
854: | public function isString(): TrinaryLogic |
855: | { |
856: | if ($this->subtractedType !== null) { |
857: | if ($this->subtractedType->isSuperTypeOf(new StringType())->yes()) { |
858: | return TrinaryLogic::createNo(); |
859: | } |
860: | } |
861: | return TrinaryLogic::createMaybe(); |
862: | } |
863: | |
864: | public function isNumericString(): TrinaryLogic |
865: | { |
866: | if ($this->subtractedType !== null) { |
867: | $numericString = TypeCombinator::intersect( |
868: | new StringType(), |
869: | new AccessoryNumericStringType(), |
870: | ); |
871: | |
872: | if ($this->subtractedType->isSuperTypeOf($numericString)->yes()) { |
873: | return TrinaryLogic::createNo(); |
874: | } |
875: | } |
876: | |
877: | return TrinaryLogic::createMaybe(); |
878: | } |
879: | |
880: | public function isNonEmptyString(): TrinaryLogic |
881: | { |
882: | if ($this->subtractedType !== null) { |
883: | $nonEmptyString = TypeCombinator::intersect( |
884: | new StringType(), |
885: | new AccessoryNonEmptyStringType(), |
886: | ); |
887: | |
888: | if ($this->subtractedType->isSuperTypeOf($nonEmptyString)->yes()) { |
889: | return TrinaryLogic::createNo(); |
890: | } |
891: | } |
892: | |
893: | return TrinaryLogic::createMaybe(); |
894: | } |
895: | |
896: | public function isNonFalsyString(): TrinaryLogic |
897: | { |
898: | if ($this->subtractedType !== null) { |
899: | $nonFalsyString = TypeCombinator::intersect( |
900: | new StringType(), |
901: | new AccessoryNonFalsyStringType(), |
902: | ); |
903: | |
904: | if ($this->subtractedType->isSuperTypeOf($nonFalsyString)->yes()) { |
905: | return TrinaryLogic::createNo(); |
906: | } |
907: | } |
908: | |
909: | return TrinaryLogic::createMaybe(); |
910: | } |
911: | |
912: | public function isLiteralString(): TrinaryLogic |
913: | { |
914: | if ($this->subtractedType !== null) { |
915: | $literalString = TypeCombinator::intersect( |
916: | new StringType(), |
917: | new AccessoryLiteralStringType(), |
918: | ); |
919: | |
920: | if ($this->subtractedType->isSuperTypeOf($literalString)->yes()) { |
921: | return TrinaryLogic::createNo(); |
922: | } |
923: | } |
924: | |
925: | return TrinaryLogic::createMaybe(); |
926: | } |
927: | |
928: | public function isLowercaseString(): TrinaryLogic |
929: | { |
930: | if ($this->subtractedType !== null) { |
931: | $lowercaseString = TypeCombinator::intersect( |
932: | new StringType(), |
933: | new AccessoryLowercaseStringType(), |
934: | ); |
935: | |
936: | if ($this->subtractedType->isSuperTypeOf($lowercaseString)->yes()) { |
937: | return TrinaryLogic::createNo(); |
938: | } |
939: | } |
940: | |
941: | return TrinaryLogic::createMaybe(); |
942: | } |
943: | |
944: | public function isUppercaseString(): TrinaryLogic |
945: | { |
946: | if ($this->subtractedType !== null) { |
947: | $uppercaseString = TypeCombinator::intersect( |
948: | new StringType(), |
949: | new AccessoryUppercaseStringType(), |
950: | ); |
951: | |
952: | if ($this->subtractedType->isSuperTypeOf($uppercaseString)->yes()) { |
953: | return TrinaryLogic::createNo(); |
954: | } |
955: | } |
956: | |
957: | return TrinaryLogic::createMaybe(); |
958: | } |
959: | |
960: | public function isClassString(): TrinaryLogic |
961: | { |
962: | if ($this->subtractedType !== null) { |
963: | if ($this->subtractedType->isSuperTypeOf(new StringType())->yes()) { |
964: | return TrinaryLogic::createNo(); |
965: | } |
966: | if ($this->subtractedType->isSuperTypeOf(new ClassStringType())->yes()) { |
967: | return TrinaryLogic::createNo(); |
968: | } |
969: | } |
970: | |
971: | return TrinaryLogic::createMaybe(); |
972: | } |
973: | |
974: | public function getClassStringObjectType(): Type |
975: | { |
976: | if (!$this->isClassString()->no()) { |
977: | return new ObjectWithoutClassType(); |
978: | } |
979: | |
980: | return new ErrorType(); |
981: | } |
982: | |
983: | public function getObjectTypeOrClassStringObjectType(): Type |
984: | { |
985: | $objectOrClass = new UnionType([ |
986: | new ObjectWithoutClassType(), |
987: | new ClassStringType(), |
988: | ]); |
989: | if (!$this->isSuperTypeOf($objectOrClass)->no()) { |
990: | return new ObjectWithoutClassType(); |
991: | } |
992: | |
993: | return new ErrorType(); |
994: | } |
995: | |
996: | public function isVoid(): TrinaryLogic |
997: | { |
998: | if ($this->subtractedType !== null) { |
999: | if ($this->subtractedType->isSuperTypeOf(new VoidType())->yes()) { |
1000: | return TrinaryLogic::createNo(); |
1001: | } |
1002: | } |
1003: | |
1004: | return TrinaryLogic::createMaybe(); |
1005: | } |
1006: | |
1007: | public function isScalar(): TrinaryLogic |
1008: | { |
1009: | if ($this->subtractedType !== null) { |
1010: | if ($this->subtractedType->isSuperTypeOf(new UnionType([new BooleanType(), new FloatType(), new IntegerType(), new StringType()]))->yes()) { |
1011: | return TrinaryLogic::createNo(); |
1012: | } |
1013: | } |
1014: | |
1015: | return TrinaryLogic::createMaybe(); |
1016: | } |
1017: | |
1018: | public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType |
1019: | { |
1020: | return new BooleanType(); |
1021: | } |
1022: | |
1023: | public function tryRemove(Type $typeToRemove): ?Type |
1024: | { |
1025: | if ($this->isSuperTypeOf($typeToRemove)->yes()) { |
1026: | return $this->subtract($typeToRemove); |
1027: | } |
1028: | |
1029: | return null; |
1030: | } |
1031: | |
1032: | public function exponentiate(Type $exponent): Type |
1033: | { |
1034: | return new BenevolentUnionType([ |
1035: | new FloatType(), |
1036: | new IntegerType(), |
1037: | ]); |
1038: | } |
1039: | |
1040: | public function getFiniteTypes(): array |
1041: | { |
1042: | return []; |
1043: | } |
1044: | |
1045: | public function toPhpDocNode(): TypeNode |
1046: | { |
1047: | return new IdentifierTypeNode('mixed'); |
1048: | } |
1049: | |
1050: | } |
1051: | |