1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type\Accessory;
4:
5: use PHPStan\DependencyInjection\ReportUnsafeArrayStringKeyCastingToggle;
6: use PHPStan\Php\PhpVersion;
7: use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
8: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
9: use PHPStan\TrinaryLogic;
10: use PHPStan\Type\AcceptsResult;
11: use PHPStan\Type\BenevolentUnionType;
12: use PHPStan\Type\BooleanType;
13: use PHPStan\Type\CompoundType;
14: use PHPStan\Type\Constant\ConstantArrayType;
15: use PHPStan\Type\Constant\ConstantBooleanType;
16: use PHPStan\Type\Constant\ConstantIntegerType;
17: use PHPStan\Type\Constant\ConstantStringType;
18: use PHPStan\Type\ErrorType;
19: use PHPStan\Type\FloatType;
20: use PHPStan\Type\GeneralizePrecision;
21: use PHPStan\Type\IntegerType;
22: use PHPStan\Type\IntersectionType;
23: use PHPStan\Type\IsSuperTypeOfResult;
24: use PHPStan\Type\StringType;
25: use PHPStan\Type\Traits\NonArrayTypeTrait;
26: use PHPStan\Type\Traits\NonCallableTypeTrait;
27: use PHPStan\Type\Traits\NonGenericTypeTrait;
28: use PHPStan\Type\Traits\NonIterableTypeTrait;
29: use PHPStan\Type\Traits\NonObjectTypeTrait;
30: use PHPStan\Type\Traits\UndecidedBooleanTypeTrait;
31: use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
32: use PHPStan\Type\Type;
33: use PHPStan\Type\TypeCombinator;
34: use PHPStan\Type\UnionType;
35: use PHPStan\Type\VerbosityLevel;
36:
37: class AccessoryNumericStringType implements CompoundType, AccessoryType
38: {
39:
40: use NonArrayTypeTrait;
41: use NonCallableTypeTrait;
42: use NonObjectTypeTrait;
43: use NonIterableTypeTrait;
44: use UndecidedBooleanTypeTrait;
45: use UndecidedComparisonCompoundTypeTrait;
46: use NonGenericTypeTrait;
47:
48: /** @api */
49: public function __construct()
50: {
51: }
52:
53: public function getReferencedClasses(): array
54: {
55: return [];
56: }
57:
58: public function getObjectClassNames(): array
59: {
60: return [];
61: }
62:
63: public function getObjectClassReflections(): array
64: {
65: return [];
66: }
67:
68: public function getConstantStrings(): array
69: {
70: return [];
71: }
72:
73: public function accepts(Type $type, bool $strictTypes): AcceptsResult
74: {
75: $isNumericString = $type->isNumericString();
76:
77: if ($isNumericString->yes()) {
78: return AcceptsResult::createYes();
79: }
80:
81: if ($type instanceof CompoundType) {
82: return $type->isAcceptedBy($this, $strictTypes);
83: }
84:
85: return new AcceptsResult($isNumericString, []);
86: }
87:
88: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
89: {
90: if ($type instanceof CompoundType) {
91: return $type->isSubTypeOf($this);
92: }
93:
94: if ($this->equals($type)) {
95: return IsSuperTypeOfResult::createYes();
96: }
97:
98: return new IsSuperTypeOfResult($type->isNumericString(), []);
99: }
100:
101: public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult
102: {
103: if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) {
104: return $otherType->isSuperTypeOf($this);
105: }
106:
107: return new IsSuperTypeOfResult(
108: $otherType->isNumericString()->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()),
109: [],
110: );
111: }
112:
113: public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult
114: {
115: return $this->isSubTypeOf($acceptingType)->toAcceptsResult();
116: }
117:
118: public function equals(Type $type): bool
119: {
120: return $type instanceof self;
121: }
122:
123: public function describe(VerbosityLevel $level): string
124: {
125: return 'numeric-string';
126: }
127:
128: public function isOffsetAccessible(): TrinaryLogic
129: {
130: return TrinaryLogic::createYes();
131: }
132:
133: public function isOffsetAccessLegal(): TrinaryLogic
134: {
135: return TrinaryLogic::createYes();
136: }
137:
138: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
139: {
140: return $offsetType->isInteger()->and(TrinaryLogic::createMaybe());
141: }
142:
143: public function getOffsetValueType(Type $offsetType): Type
144: {
145: if ($this->hasOffsetValueType($offsetType)->no()) {
146: return new ErrorType();
147: }
148:
149: return new StringType();
150: }
151:
152: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
153: {
154: $stringOffset = (new StringType())->setOffsetValueType($offsetType, $valueType, $unionValues);
155:
156: if ($stringOffset instanceof ErrorType) {
157: return $stringOffset;
158: }
159:
160: return $this;
161: }
162:
163: public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
164: {
165: return $this;
166: }
167:
168: public function unsetOffset(Type $offsetType): Type
169: {
170: return new ErrorType();
171: }
172:
173: public function toNumber(): Type
174: {
175: return new UnionType([
176: $this->toInteger(),
177: $this->toFloat(),
178: ]);
179: }
180:
181: public function toAbsoluteNumber(): Type
182: {
183: return $this->toNumber()->toAbsoluteNumber();
184: }
185:
186: public function toInteger(): Type
187: {
188: return new IntegerType();
189: }
190:
191: public function toFloat(): Type
192: {
193: return new FloatType();
194: }
195:
196: public function toString(): Type
197: {
198: return $this;
199: }
200:
201: public function toArray(): Type
202: {
203: return new ConstantArrayType(
204: [new ConstantIntegerType(0)],
205: [$this],
206: [1],
207: isList: TrinaryLogic::createYes(),
208: );
209: }
210:
211: public function toArrayKey(): Type
212: {
213: $level = ReportUnsafeArrayStringKeyCastingToggle::getLevel();
214: if ($level !== ReportUnsafeArrayStringKeyCastingToggle::PREVENT) {
215: new UnionType([
216: new IntegerType(),
217: new IntersectionType([
218: new StringType(),
219: new AccessoryNumericStringType(),
220: ]),
221: ]);
222: }
223:
224: return new UnionType([
225: new IntegerType(),
226: new IntersectionType([new StringType(), new AccessoryDecimalIntegerStringType(inverse: true)]),
227: ]);
228: }
229:
230: public function toCoercedArgumentType(bool $strictTypes): Type
231: {
232: if (!$strictTypes) {
233: return TypeCombinator::union($this->toInteger(), $this->toFloat(), $this, $this->toBoolean());
234: }
235:
236: return $this;
237: }
238:
239: public function isNull(): TrinaryLogic
240: {
241: return TrinaryLogic::createNo();
242: }
243:
244: public function isConstantValue(): TrinaryLogic
245: {
246: return TrinaryLogic::createMaybe();
247: }
248:
249: public function isConstantScalarValue(): TrinaryLogic
250: {
251: return TrinaryLogic::createMaybe();
252: }
253:
254: public function getConstantScalarTypes(): array
255: {
256: return [];
257: }
258:
259: public function getConstantScalarValues(): array
260: {
261: return [];
262: }
263:
264: public function isTrue(): TrinaryLogic
265: {
266: return TrinaryLogic::createNo();
267: }
268:
269: public function isFalse(): TrinaryLogic
270: {
271: return TrinaryLogic::createNo();
272: }
273:
274: public function isBoolean(): TrinaryLogic
275: {
276: return TrinaryLogic::createNo();
277: }
278:
279: public function isFloat(): TrinaryLogic
280: {
281: return TrinaryLogic::createNo();
282: }
283:
284: public function isInteger(): TrinaryLogic
285: {
286: return TrinaryLogic::createNo();
287: }
288:
289: public function isString(): TrinaryLogic
290: {
291: return TrinaryLogic::createYes();
292: }
293:
294: public function isNumericString(): TrinaryLogic
295: {
296: return TrinaryLogic::createYes();
297: }
298:
299: public function isDecimalIntegerString(): TrinaryLogic
300: {
301: return TrinaryLogic::createMaybe();
302: }
303:
304: public function isNonEmptyString(): TrinaryLogic
305: {
306: return TrinaryLogic::createYes();
307: }
308:
309: public function isNonFalsyString(): TrinaryLogic
310: {
311: return TrinaryLogic::createMaybe();
312: }
313:
314: public function isLiteralString(): TrinaryLogic
315: {
316: return TrinaryLogic::createMaybe();
317: }
318:
319: public function isLowercaseString(): TrinaryLogic
320: {
321: return TrinaryLogic::createMaybe();
322: }
323:
324: public function isUppercaseString(): TrinaryLogic
325: {
326: return TrinaryLogic::createMaybe();
327: }
328:
329: public function isClassString(): TrinaryLogic
330: {
331: return TrinaryLogic::createNo();
332: }
333:
334: public function getClassStringObjectType(): Type
335: {
336: return new ErrorType();
337: }
338:
339: public function getObjectTypeOrClassStringObjectType(): Type
340: {
341: return new ErrorType();
342: }
343:
344: public function isVoid(): TrinaryLogic
345: {
346: return TrinaryLogic::createNo();
347: }
348:
349: public function isScalar(): TrinaryLogic
350: {
351: return TrinaryLogic::createYes();
352: }
353:
354: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
355: {
356: if ($type->isNull()->yes()) {
357: return new ConstantBooleanType(false);
358: }
359:
360: if ($type->isString()->yes() && $type->isNumericString()->no()) {
361: return new ConstantBooleanType(false);
362: }
363:
364: return new BooleanType();
365: }
366:
367: public function traverse(callable $cb): Type
368: {
369: return $this;
370: }
371:
372: public function traverseSimultaneously(Type $right, callable $cb): Type
373: {
374: return $this;
375: }
376:
377: public function generalize(GeneralizePrecision $precision): Type
378: {
379: return new StringType();
380: }
381:
382: public function tryRemove(Type $typeToRemove): ?Type
383: {
384: if ($typeToRemove instanceof ConstantStringType && $typeToRemove->getValue() === '0') {
385: return new IntersectionType([$this, new AccessoryNonFalsyStringType()]);
386: }
387:
388: return null;
389: }
390:
391: public function exponentiate(Type $exponent): Type
392: {
393: return new BenevolentUnionType([
394: new FloatType(),
395: new IntegerType(),
396: ]);
397: }
398:
399: public function getFiniteTypes(): array
400: {
401: return [];
402: }
403:
404: public function toPhpDocNode(): TypeNode
405: {
406: return new IdentifierTypeNode('numeric-string');
407: }
408:
409: public function hasTemplateOrLateResolvableType(): bool
410: {
411: return false;
412: }
413:
414: }
415: