1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use ArrayAccess;
6: use PHPStan\Reflection\ClassMemberAccessAnswerer;
7: use PHPStan\Reflection\ConstantReflection;
8: use PHPStan\Reflection\Dummy\DummyConstantReflection;
9: use PHPStan\Reflection\Dummy\DummyMethodReflection;
10: use PHPStan\Reflection\Dummy\DummyPropertyReflection;
11: use PHPStan\Reflection\MethodReflection;
12: use PHPStan\Reflection\ParametersAcceptor;
13: use PHPStan\Reflection\PropertyReflection;
14: use PHPStan\Reflection\TrivialParametersAcceptor;
15: use PHPStan\Reflection\Type\CallbackUnresolvedMethodPrototypeReflection;
16: use PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection;
17: use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
18: use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
19: use PHPStan\TrinaryLogic;
20: use PHPStan\Type\Accessory\AccessoryLiteralStringType;
21: use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
22: use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
23: use PHPStan\Type\Accessory\AccessoryNumericStringType;
24: use PHPStan\Type\Accessory\OversizedArrayType;
25: use PHPStan\Type\Constant\ConstantArrayType;
26: use PHPStan\Type\Constant\ConstantBooleanType;
27: use PHPStan\Type\Generic\TemplateMixedType;
28: use PHPStan\Type\Generic\TemplateType;
29: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
30: use PHPStan\Type\Traits\NonGenericTypeTrait;
31: use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
32: use function sprintf;
33:
34: /** @api */
35: class MixedType implements CompoundType, SubtractableType
36: {
37:
38: use NonGenericTypeTrait;
39: use UndecidedComparisonCompoundTypeTrait;
40: use NonGeneralizableTypeTrait;
41:
42: private ?Type $subtractedType;
43:
44: /** @api */
45: public function __construct(
46: private bool $isExplicitMixed = false,
47: ?Type $subtractedType = null,
48: )
49: {
50: if ($subtractedType instanceof NeverType) {
51: $subtractedType = null;
52: }
53:
54: $this->subtractedType = $subtractedType;
55: }
56:
57: /**
58: * @return string[]
59: */
60: public function getReferencedClasses(): array
61: {
62: return [];
63: }
64:
65: public function accepts(Type $type, bool $strictTypes): TrinaryLogic
66: {
67: return TrinaryLogic::createYes();
68: }
69:
70: public function isSuperTypeOfMixed(MixedType $type): TrinaryLogic
71: {
72: if ($this->subtractedType === null) {
73: if ($this->isExplicitMixed) {
74: if ($type->isExplicitMixed) {
75: return TrinaryLogic::createYes();
76: }
77: return TrinaryLogic::createMaybe();
78: }
79:
80: return TrinaryLogic::createYes();
81: }
82:
83: if ($type->subtractedType === null) {
84: return TrinaryLogic::createMaybe();
85: }
86:
87: $isSuperType = $type->subtractedType->isSuperTypeOf($this->subtractedType);
88: if ($isSuperType->yes()) {
89: if ($this->isExplicitMixed) {
90: if ($type->isExplicitMixed) {
91: return TrinaryLogic::createYes();
92: }
93: return TrinaryLogic::createMaybe();
94: }
95:
96: return TrinaryLogic::createYes();
97: }
98:
99: return TrinaryLogic::createMaybe();
100: }
101:
102: public function isSuperTypeOf(Type $type): TrinaryLogic
103: {
104: if ($this->subtractedType === null || $type instanceof NeverType) {
105: return TrinaryLogic::createYes();
106: }
107:
108: if ($type instanceof self) {
109: if ($type->subtractedType === null) {
110: return TrinaryLogic::createMaybe();
111: }
112: $isSuperType = $type->subtractedType->isSuperTypeOf($this->subtractedType);
113: if ($isSuperType->yes()) {
114: return TrinaryLogic::createYes();
115: }
116:
117: return TrinaryLogic::createMaybe();
118: }
119:
120: return $this->subtractedType->isSuperTypeOf($type)->negate();
121: }
122:
123: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
124: {
125: return new self($this->isExplicitMixed);
126: }
127:
128: public function unsetOffset(Type $offsetType): Type
129: {
130: if ($this->subtractedType !== null) {
131: return new self($this->isExplicitMixed, TypeCombinator::remove($this->subtractedType, new ConstantArrayType([], [])));
132: }
133: return $this;
134: }
135:
136: public function isCallable(): TrinaryLogic
137: {
138: if ($this->subtractedType !== null) {
139: if ($this->subtractedType->isSuperTypeOf(new CallableType())->yes()) {
140: return TrinaryLogic::createNo();
141: }
142: }
143:
144: return TrinaryLogic::createMaybe();
145: }
146:
147: /**
148: * @return ParametersAcceptor[]
149: */
150: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
151: {
152: return [new TrivialParametersAcceptor()];
153: }
154:
155: public function equals(Type $type): bool
156: {
157: if (!$type instanceof self) {
158: return false;
159: }
160:
161: if ($this->subtractedType === null) {
162: if ($type->subtractedType === null) {
163: return true;
164: }
165:
166: return false;
167: }
168:
169: if ($type->subtractedType === null) {
170: return false;
171: }
172:
173: return $this->subtractedType->equals($type->subtractedType);
174: }
175:
176: public function isSubTypeOf(Type $otherType): TrinaryLogic
177: {
178: if ($otherType instanceof self && !$otherType instanceof TemplateMixedType) {
179: return TrinaryLogic::createYes();
180: }
181:
182: if ($this->subtractedType !== null) {
183: $isSuperType = $this->subtractedType->isSuperTypeOf($otherType);
184: if ($isSuperType->yes()) {
185: return TrinaryLogic::createNo();
186: }
187: }
188:
189: return TrinaryLogic::createMaybe();
190: }
191:
192: public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic
193: {
194: $isSuperType = $this->isSuperTypeOf($acceptingType);
195: if ($isSuperType->no()) {
196: return $isSuperType;
197: }
198: return TrinaryLogic::createYes();
199: }
200:
201: public function canAccessProperties(): TrinaryLogic
202: {
203: return TrinaryLogic::createYes();
204: }
205:
206: public function hasProperty(string $propertyName): TrinaryLogic
207: {
208: return TrinaryLogic::createYes();
209: }
210:
211: public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection
212: {
213: return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty();
214: }
215:
216: public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
217: {
218: $property = new DummyPropertyReflection();
219: return new CallbackUnresolvedPropertyPrototypeReflection(
220: $property,
221: $property->getDeclaringClass(),
222: false,
223: static fn (Type $type): Type => $type,
224: );
225: }
226:
227: public function canCallMethods(): TrinaryLogic
228: {
229: return TrinaryLogic::createYes();
230: }
231:
232: public function hasMethod(string $methodName): TrinaryLogic
233: {
234: return TrinaryLogic::createYes();
235: }
236:
237: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection
238: {
239: return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
240: }
241:
242: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
243: {
244: $method = new DummyMethodReflection($methodName);
245: return new CallbackUnresolvedMethodPrototypeReflection(
246: $method,
247: $method->getDeclaringClass(),
248: false,
249: static fn (Type $type): Type => $type,
250: );
251: }
252:
253: public function canAccessConstants(): TrinaryLogic
254: {
255: return TrinaryLogic::createYes();
256: }
257:
258: public function hasConstant(string $constantName): TrinaryLogic
259: {
260: return TrinaryLogic::createYes();
261: }
262:
263: public function getConstant(string $constantName): ConstantReflection
264: {
265: return new DummyConstantReflection($constantName);
266: }
267:
268: public function isCloneable(): TrinaryLogic
269: {
270: return TrinaryLogic::createYes();
271: }
272:
273: public function describe(VerbosityLevel $level): string
274: {
275: return $level->handle(
276: static fn (): string => 'mixed',
277: static fn (): string => 'mixed',
278: function () use ($level): string {
279: $description = 'mixed';
280: if ($this->subtractedType !== null) {
281: $description .= sprintf('~%s', $this->subtractedType->describe($level));
282: }
283:
284: return $description;
285: },
286: function () use ($level): string {
287: $description = 'mixed';
288: if ($this->subtractedType !== null) {
289: $description .= sprintf('~%s', $this->subtractedType->describe($level));
290: }
291:
292: if ($this->isExplicitMixed) {
293: $description .= '=explicit';
294: } else {
295: $description .= '=implicit';
296: }
297:
298: return $description;
299: },
300: );
301: }
302:
303: public function toBoolean(): BooleanType
304: {
305: if ($this->subtractedType !== null && StaticTypeFactory::falsey()->equals($this->subtractedType)) {
306: return new ConstantBooleanType(true);
307: }
308:
309: return new BooleanType();
310: }
311:
312: public function toNumber(): Type
313: {
314: return new UnionType([
315: $this->toInteger(),
316: $this->toFloat(),
317: ]);
318: }
319:
320: public function toInteger(): Type
321: {
322: return new IntegerType();
323: }
324:
325: public function toFloat(): Type
326: {
327: return new FloatType();
328: }
329:
330: public function toString(): Type
331: {
332: return new StringType();
333: }
334:
335: public function toArray(): Type
336: {
337: $mixed = new self($this->isExplicitMixed);
338:
339: return new ArrayType($mixed, $mixed);
340: }
341:
342: public function isIterable(): TrinaryLogic
343: {
344: if ($this->subtractedType !== null) {
345: if ($this->subtractedType->isSuperTypeOf(new IterableType(new MixedType(), new MixedType()))->yes()) {
346: return TrinaryLogic::createNo();
347: }
348: }
349:
350: return TrinaryLogic::createMaybe();
351: }
352:
353: public function isIterableAtLeastOnce(): TrinaryLogic
354: {
355: return $this->isIterable();
356: }
357:
358: public function getIterableKeyType(): Type
359: {
360: return new self($this->isExplicitMixed);
361: }
362:
363: public function getIterableValueType(): Type
364: {
365: return new self($this->isExplicitMixed);
366: }
367:
368: public function isOffsetAccessible(): TrinaryLogic
369: {
370: if ($this->subtractedType !== null) {
371: $offsetAccessibles = new UnionType([
372: new StringType(),
373: new ArrayType(new MixedType(), new MixedType()),
374: new ObjectType(ArrayAccess::class),
375: ]);
376:
377: if ($this->subtractedType->isSuperTypeOf($offsetAccessibles)->yes()) {
378: return TrinaryLogic::createNo();
379: }
380: }
381: return TrinaryLogic::createMaybe();
382: }
383:
384: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
385: {
386: return TrinaryLogic::createMaybe();
387: }
388:
389: public function getOffsetValueType(Type $offsetType): Type
390: {
391: return new self($this->isExplicitMixed);
392: }
393:
394: public function isExplicitMixed(): bool
395: {
396: return $this->isExplicitMixed;
397: }
398:
399: public function subtract(Type $type): Type
400: {
401: if ($type instanceof self && !$type instanceof TemplateType) {
402: return new NeverType();
403: }
404: if ($this->subtractedType !== null) {
405: $type = TypeCombinator::union($this->subtractedType, $type);
406: }
407:
408: return new self($this->isExplicitMixed, $type);
409: }
410:
411: public function getTypeWithoutSubtractedType(): Type
412: {
413: return new self($this->isExplicitMixed);
414: }
415:
416: public function changeSubtractedType(?Type $subtractedType): Type
417: {
418: return new self($this->isExplicitMixed, $subtractedType);
419: }
420:
421: public function getSubtractedType(): ?Type
422: {
423: return $this->subtractedType;
424: }
425:
426: public function traverse(callable $cb): Type
427: {
428: return $this;
429: }
430:
431: public function isArray(): TrinaryLogic
432: {
433: if ($this->subtractedType !== null) {
434: if ($this->subtractedType->isSuperTypeOf(new ArrayType(new MixedType(), new MixedType()))->yes()) {
435: return TrinaryLogic::createNo();
436: }
437: }
438:
439: return TrinaryLogic::createMaybe();
440: }
441:
442: public function isOversizedArray(): TrinaryLogic
443: {
444: if ($this->subtractedType !== null) {
445: $oversizedArray = TypeCombinator::intersect(
446: new ArrayType(new MixedType(), new MixedType()),
447: new OversizedArrayType(),
448: );
449:
450: if ($this->subtractedType->isSuperTypeOf($oversizedArray)->yes()) {
451: return TrinaryLogic::createNo();
452: }
453: }
454:
455: return TrinaryLogic::createMaybe();
456: }
457:
458: public function isString(): TrinaryLogic
459: {
460: if ($this->subtractedType !== null) {
461: if ($this->subtractedType->isSuperTypeOf(new StringType())->yes()) {
462: return TrinaryLogic::createNo();
463: }
464: }
465: return TrinaryLogic::createMaybe();
466: }
467:
468: public function isNumericString(): TrinaryLogic
469: {
470: if ($this->subtractedType !== null) {
471: $numericString = TypeCombinator::intersect(
472: new StringType(),
473: new AccessoryNumericStringType(),
474: );
475:
476: if ($this->subtractedType->isSuperTypeOf($numericString)->yes()) {
477: return TrinaryLogic::createNo();
478: }
479: }
480:
481: return TrinaryLogic::createMaybe();
482: }
483:
484: public function isNonEmptyString(): TrinaryLogic
485: {
486: if ($this->subtractedType !== null) {
487: $nonEmptyString = TypeCombinator::intersect(
488: new StringType(),
489: new AccessoryNonEmptyStringType(),
490: );
491:
492: if ($this->subtractedType->isSuperTypeOf($nonEmptyString)->yes()) {
493: return TrinaryLogic::createNo();
494: }
495: }
496:
497: return TrinaryLogic::createMaybe();
498: }
499:
500: public function isNonFalsyString(): TrinaryLogic
501: {
502: if ($this->subtractedType !== null) {
503: $nonFalsyString = TypeCombinator::intersect(
504: new StringType(),
505: new AccessoryNonFalsyStringType(),
506: );
507:
508: if ($this->subtractedType->isSuperTypeOf($nonFalsyString)->yes()) {
509: return TrinaryLogic::createNo();
510: }
511: }
512:
513: return TrinaryLogic::createMaybe();
514: }
515:
516: public function isLiteralString(): TrinaryLogic
517: {
518: if ($this->subtractedType !== null) {
519: $literalString = TypeCombinator::intersect(
520: new StringType(),
521: new AccessoryLiteralStringType(),
522: );
523:
524: if ($this->subtractedType->isSuperTypeOf($literalString)->yes()) {
525: return TrinaryLogic::createNo();
526: }
527: }
528:
529: return TrinaryLogic::createMaybe();
530: }
531:
532: public function tryRemove(Type $typeToRemove): ?Type
533: {
534: if ($this->isSuperTypeOf($typeToRemove)->yes()) {
535: return $this->subtract($typeToRemove);
536: }
537:
538: return null;
539: }
540:
541: /**
542: * @param mixed[] $properties
543: */
544: public static function __set_state(array $properties): Type
545: {
546: return new self(
547: $properties['isExplicitMixed'],
548: $properties['subtractedType'] ?? null,
549: );
550: }
551:
552: }
553: