1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type\Accessory;
4:
5: use PHPStan\Php\PhpVersion;
6: use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
7: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
8: use PHPStan\Reflection\ClassMemberAccessAnswerer;
9: use PHPStan\Reflection\TrivialParametersAcceptor;
10: use PHPStan\ShouldNotHappenException;
11: use PHPStan\TrinaryLogic;
12: use PHPStan\Type\AcceptsResult;
13: use PHPStan\Type\BenevolentUnionType;
14: use PHPStan\Type\BooleanType;
15: use PHPStan\Type\CompoundType;
16: use PHPStan\Type\Constant\ConstantArrayType;
17: use PHPStan\Type\Constant\ConstantBooleanType;
18: use PHPStan\Type\Constant\ConstantIntegerType;
19: use PHPStan\Type\ErrorType;
20: use PHPStan\Type\FloatType;
21: use PHPStan\Type\GeneralizePrecision;
22: use PHPStan\Type\IntegerType;
23: use PHPStan\Type\IntersectionType;
24: use PHPStan\Type\IsSuperTypeOfResult;
25: use PHPStan\Type\StringType;
26: use PHPStan\Type\Traits\NonArrayTypeTrait;
27: use PHPStan\Type\Traits\NonGenericTypeTrait;
28: use PHPStan\Type\Traits\NonIterableTypeTrait;
29: use PHPStan\Type\Traits\NonObjectTypeTrait;
30: use PHPStan\Type\Traits\NonRemoveableTypeTrait;
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: /**
38: * This accessory type is coupled with `Type::isDecimalIntegerString()` method.
39: *
40: * When inverse=false, this represents strings containing decimal integers.
41: * These are guaranteed to be cast to an integer in an array key.
42: * Examples of constant values covered by this type: "0", "1", "1234", "-1"
43: *
44: * When inverse=true, this represents strings containing non-decimal integers and other text.
45: * These are guaranteed to stay as string in an array key.
46: * Examples of constant values covered by this type: "+1", "00", "18E+3", "1.2", "1,3", "foo"
47: *
48: * @api
49: */
50: class AccessoryDecimalIntegerStringType implements CompoundType, AccessoryType
51: {
52:
53: use NonArrayTypeTrait;
54: use NonObjectTypeTrait;
55: use NonIterableTypeTrait;
56: use UndecidedComparisonCompoundTypeTrait;
57: use NonGenericTypeTrait;
58: use NonRemoveableTypeTrait;
59:
60: /** @api */
61: public function __construct(private bool $inverse = false)
62: {
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 getConstantStrings(): array
81: {
82: return [];
83: }
84:
85: public function accepts(Type $type, bool $strictTypes): AcceptsResult
86: {
87: $isDecimalIntegerString = $type->isDecimalIntegerString();
88:
89: if (
90: $type->isString()->yes()
91: && ($this->inverse ? $isDecimalIntegerString->no() : $isDecimalIntegerString->yes())
92: ) {
93: return AcceptsResult::createYes();
94: }
95:
96: if ($type instanceof CompoundType) {
97: return $type->isAcceptedBy($this, $strictTypes);
98: }
99:
100: $result = $type->isString()->and($this->inverse ? $isDecimalIntegerString->negate() : $isDecimalIntegerString);
101:
102: return new AcceptsResult($result, []);
103: }
104:
105: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
106: {
107: if ($type instanceof CompoundType) {
108: return $type->isSubTypeOf($this);
109: }
110:
111: if ($this->equals($type)) {
112: return IsSuperTypeOfResult::createYes();
113: }
114:
115: $isDecimalIntegerString = $type->isDecimalIntegerString();
116: $result = $type->isString()->and($this->inverse ? $isDecimalIntegerString->negate() : $isDecimalIntegerString);
117:
118: return new IsSuperTypeOfResult($result, []);
119: }
120:
121: public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult
122: {
123: if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) {
124: return $otherType->isSuperTypeOf($this);
125: }
126:
127: if (
128: (
129: $otherType instanceof AccessoryNumericStringType
130: || $otherType instanceof AccessoryLowercaseStringType
131: || $otherType instanceof AccessoryUppercaseStringType
132: )
133: && !$this->inverse
134: ) {
135: return IsSuperTypeOfResult::createYes();
136: }
137:
138: $otherTypeResult = $otherType->isString()->and($this->inverse ? $otherType->isDecimalIntegerString()->negate() : $otherType->isDecimalIntegerString());
139:
140: return new IsSuperTypeOfResult(
141: $otherTypeResult->and($otherType->equals($this) ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()),
142: [],
143: );
144: }
145:
146: public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult
147: {
148: return $this->isSubTypeOf($acceptingType)->toAcceptsResult();
149: }
150:
151: public function equals(Type $type): bool
152: {
153: return $type instanceof self && $this->inverse === $type->inverse;
154: }
155:
156: public function describe(VerbosityLevel $level): string
157: {
158: return $this->inverse ? 'non-decimal-int-string' : 'decimal-int-string';
159: }
160:
161: public function isOffsetAccessible(): TrinaryLogic
162: {
163: return TrinaryLogic::createYes();
164: }
165:
166: public function isOffsetAccessLegal(): TrinaryLogic
167: {
168: return TrinaryLogic::createYes();
169: }
170:
171: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
172: {
173: return $offsetType->isInteger()->and(TrinaryLogic::createMaybe());
174: }
175:
176: public function getOffsetValueType(Type $offsetType): Type
177: {
178: if ($this->hasOffsetValueType($offsetType)->no()) {
179: return new ErrorType();
180: }
181:
182: return new StringType();
183: }
184:
185: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
186: {
187: $stringOffset = (new StringType())->setOffsetValueType($offsetType, $valueType, $unionValues);
188:
189: if ($stringOffset instanceof ErrorType) {
190: return $stringOffset;
191: }
192:
193: return $this;
194: }
195:
196: public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
197: {
198: return $this;
199: }
200:
201: public function unsetOffset(Type $offsetType): Type
202: {
203: return new ErrorType();
204: }
205:
206: public function toNumber(): Type
207: {
208: if ($this->inverse) {
209: return new UnionType([
210: $this->toInteger(),
211: $this->toFloat(),
212: ]);
213: }
214:
215: return $this->toInteger();
216: }
217:
218: public function toAbsoluteNumber(): Type
219: {
220: return $this->toNumber()->toAbsoluteNumber();
221: }
222:
223: public function toBoolean(): BooleanType
224: {
225: return $this->isNonFalsyString()->negate()->toBooleanType();
226: }
227:
228: public function toInteger(): Type
229: {
230: return new IntegerType();
231: }
232:
233: public function toFloat(): Type
234: {
235: return new FloatType();
236: }
237:
238: public function toString(): Type
239: {
240: return $this;
241: }
242:
243: public function toArray(): Type
244: {
245: return new ConstantArrayType(
246: [new ConstantIntegerType(0)],
247: [$this],
248: [1],
249: isList: TrinaryLogic::createYes(),
250: );
251: }
252:
253: public function toArrayKey(): Type
254: {
255: if ($this->inverse) {
256: return $this;
257: }
258:
259: return new IntegerType();
260: }
261:
262: public function toCoercedArgumentType(bool $strictTypes): Type
263: {
264: if (!$strictTypes) {
265: return TypeCombinator::union($this->toInteger(), $this->toFloat(), $this, $this->toBoolean());
266: }
267:
268: return $this;
269: }
270:
271: public function isNull(): TrinaryLogic
272: {
273: return TrinaryLogic::createNo();
274: }
275:
276: public function isConstantValue(): TrinaryLogic
277: {
278: return TrinaryLogic::createMaybe();
279: }
280:
281: public function isConstantScalarValue(): TrinaryLogic
282: {
283: return TrinaryLogic::createMaybe();
284: }
285:
286: public function getConstantScalarTypes(): array
287: {
288: return [];
289: }
290:
291: public function getConstantScalarValues(): array
292: {
293: return [];
294: }
295:
296: public function isCallable(): TrinaryLogic
297: {
298: return $this->inverse ? TrinaryLogic::createMaybe() : TrinaryLogic::createNo();
299: }
300:
301: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
302: {
303: if ($this->inverse) {
304: return [new TrivialParametersAcceptor()];
305: }
306:
307: throw new ShouldNotHappenException();
308: }
309:
310: public function isTrue(): TrinaryLogic
311: {
312: return TrinaryLogic::createNo();
313: }
314:
315: public function isFalse(): TrinaryLogic
316: {
317: return TrinaryLogic::createNo();
318: }
319:
320: public function isBoolean(): TrinaryLogic
321: {
322: return TrinaryLogic::createNo();
323: }
324:
325: public function isFloat(): TrinaryLogic
326: {
327: return TrinaryLogic::createNo();
328: }
329:
330: public function isInteger(): TrinaryLogic
331: {
332: return TrinaryLogic::createNo();
333: }
334:
335: public function isString(): TrinaryLogic
336: {
337: return TrinaryLogic::createYes();
338: }
339:
340: public function isNumericString(): TrinaryLogic
341: {
342: return $this->inverse ? TrinaryLogic::createMaybe() : TrinaryLogic::createYes();
343: }
344:
345: public function isDecimalIntegerString(): TrinaryLogic
346: {
347: return TrinaryLogic::createFromBoolean(!$this->inverse);
348: }
349:
350: public function isNonEmptyString(): TrinaryLogic
351: {
352: return $this->inverse ? TrinaryLogic::createMaybe() : TrinaryLogic::createYes();
353: }
354:
355: public function isNonFalsyString(): TrinaryLogic
356: {
357: return TrinaryLogic::createMaybe();
358: }
359:
360: public function isLiteralString(): TrinaryLogic
361: {
362: return TrinaryLogic::createMaybe();
363: }
364:
365: public function isLowercaseString(): TrinaryLogic
366: {
367: return $this->inverse ? TrinaryLogic::createMaybe() : TrinaryLogic::createYes();
368: }
369:
370: public function isUppercaseString(): TrinaryLogic
371: {
372: return $this->inverse ? TrinaryLogic::createMaybe() : TrinaryLogic::createYes();
373: }
374:
375: public function isClassString(): TrinaryLogic
376: {
377: return TrinaryLogic::createNo();
378: }
379:
380: public function getClassStringObjectType(): Type
381: {
382: return new ErrorType();
383: }
384:
385: public function getObjectTypeOrClassStringObjectType(): Type
386: {
387: return new ErrorType();
388: }
389:
390: public function isVoid(): TrinaryLogic
391: {
392: return TrinaryLogic::createNo();
393: }
394:
395: public function isScalar(): TrinaryLogic
396: {
397: return TrinaryLogic::createYes();
398: }
399:
400: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
401: {
402: if ($type->isNull()->yes()) {
403: return new ConstantBooleanType(false);
404: }
405:
406: if ($type->isString()->yes()) {
407: if ($this->inverse) {
408: if ($type->isDecimalIntegerString()->yes()) {
409: return new ConstantBooleanType(false);
410: }
411: } elseif ($type->isDecimalIntegerString()->no()) {
412: return new ConstantBooleanType(false);
413: }
414: }
415:
416: return new BooleanType();
417: }
418:
419: public function traverse(callable $cb): Type
420: {
421: return $this;
422: }
423:
424: public function traverseSimultaneously(Type $right, callable $cb): Type
425: {
426: return $this;
427: }
428:
429: public function generalize(GeneralizePrecision $precision): Type
430: {
431: return new StringType();
432: }
433:
434: public function exponentiate(Type $exponent): Type
435: {
436: return new BenevolentUnionType([
437: new FloatType(),
438: new IntegerType(),
439: ]);
440: }
441:
442: public function getFiniteTypes(): array
443: {
444: return [];
445: }
446:
447: public function toPhpDocNode(): TypeNode
448: {
449: return new IdentifierTypeNode($this->inverse ? 'non-decimal-int-string' : 'decimal-int-string');
450: }
451:
452: public function hasTemplateOrLateResolvableType(): bool
453: {
454: return false;
455: }
456:
457: }
458: