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\TrinaryLogic;
9: use PHPStan\Type\AcceptsResult;
10: use PHPStan\Type\BooleanType;
11: use PHPStan\Type\CompoundType;
12: use PHPStan\Type\Constant\ConstantIntegerType;
13: use PHPStan\Type\Constant\ConstantStringType;
14: use PHPStan\Type\Enum\EnumCaseObjectType;
15: use PHPStan\Type\ErrorType;
16: use PHPStan\Type\IntegerRangeType;
17: use PHPStan\Type\IntersectionType;
18: use PHPStan\Type\IsSuperTypeOfResult;
19: use PHPStan\Type\MixedType;
20: use PHPStan\Type\ObjectWithoutClassType;
21: use PHPStan\Type\Traits\MaybeArrayTypeTrait;
22: use PHPStan\Type\Traits\MaybeCallableTypeTrait;
23: use PHPStan\Type\Traits\MaybeIterableTypeTrait;
24: use PHPStan\Type\Traits\MaybeObjectTypeTrait;
25: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
26: use PHPStan\Type\Traits\NonGenericTypeTrait;
27: use PHPStan\Type\Traits\NonRemoveableTypeTrait;
28: use PHPStan\Type\Traits\TruthyBooleanTypeTrait;
29: use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
30: use PHPStan\Type\Type;
31: use PHPStan\Type\TypeCombinator;
32: use PHPStan\Type\UnionType;
33: use PHPStan\Type\VerbosityLevel;
34: use function sprintf;
35:
36: class HasOffsetType implements CompoundType, AccessoryType
37: {
38:
39: use MaybeArrayTypeTrait;
40: use MaybeCallableTypeTrait;
41: use MaybeIterableTypeTrait;
42: use MaybeObjectTypeTrait;
43: use TruthyBooleanTypeTrait;
44: use NonGenericTypeTrait;
45: use UndecidedComparisonCompoundTypeTrait;
46: use NonRemoveableTypeTrait;
47: use NonGeneralizableTypeTrait;
48:
49: /**
50: * @api
51: */
52: public function __construct(private ConstantStringType|ConstantIntegerType $offsetType)
53: {
54: }
55:
56: /**
57: * @return ConstantStringType|ConstantIntegerType
58: */
59: public function getOffsetType(): Type
60: {
61: return $this->offsetType;
62: }
63:
64: public function getReferencedClasses(): array
65: {
66: return [];
67: }
68:
69: public function getObjectClassNames(): array
70: {
71: return [];
72: }
73:
74: public function getObjectClassReflections(): array
75: {
76: return [];
77: }
78:
79: public function getConstantStrings(): array
80: {
81: return [];
82: }
83:
84: public function accepts(Type $type, bool $strictTypes): AcceptsResult
85: {
86: if ($type instanceof CompoundType) {
87: return $type->isAcceptedBy($this, $strictTypes);
88: }
89:
90: return new AcceptsResult($type->isOffsetAccessible()->and($type->hasOffsetValueType($this->offsetType)), []);
91: }
92:
93: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
94: {
95: if ($this->equals($type)) {
96: return IsSuperTypeOfResult::createYes();
97: }
98: return new IsSuperTypeOfResult($type->isOffsetAccessible()->and($type->hasOffsetValueType($this->offsetType)), []);
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: $result = new IsSuperTypeOfResult($otherType->isOffsetAccessible()->and($otherType->hasOffsetValueType($this->offsetType)), []);
108:
109: return $result
110: ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe());
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: && $this->offsetType->equals($type->offsetType);
122: }
123:
124: public function describe(VerbosityLevel $level): string
125: {
126: return sprintf('hasOffset(%s)', $this->offsetType->describe($level));
127: }
128:
129: public function isOffsetAccessible(): TrinaryLogic
130: {
131: return TrinaryLogic::createYes();
132: }
133:
134: public function isOffsetAccessLegal(): TrinaryLogic
135: {
136: return TrinaryLogic::createYes();
137: }
138:
139: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
140: {
141: if ($offsetType->isConstantScalarValue()->yes() && $offsetType->equals($this->offsetType)) {
142: return TrinaryLogic::createYes();
143: }
144:
145: return TrinaryLogic::createMaybe();
146: }
147:
148: public function getOffsetValueType(Type $offsetType): Type
149: {
150: return new MixedType();
151: }
152:
153: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
154: {
155: return $this;
156: }
157:
158: public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
159: {
160: return $this;
161: }
162:
163: public function unsetOffset(Type $offsetType): Type
164: {
165: if ($this->offsetType->isSuperTypeOf($offsetType)->yes()) {
166: return new ErrorType();
167: }
168: return $this;
169: }
170:
171: public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type
172: {
173: return new NonEmptyArrayType();
174: }
175:
176: public function fillKeysArray(Type $valueType): Type
177: {
178: return new NonEmptyArrayType();
179: }
180:
181: public function intersectKeyArray(Type $otherArraysType): Type
182: {
183: if ($otherArraysType->hasOffsetValueType($this->offsetType)->yes()) {
184: return $this;
185: }
186:
187: return new MixedType();
188: }
189:
190: public function reverseArray(TrinaryLogic $preserveKeys): Type
191: {
192: if ($preserveKeys->yes()) {
193: return $this;
194: }
195:
196: return new NonEmptyArrayType();
197: }
198:
199: public function shuffleArray(): Type
200: {
201: return new NonEmptyArrayType();
202: }
203:
204: public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type
205: {
206: if (
207: $this->offsetType->isSuperTypeOf($offsetType)->yes()
208: && ($lengthType->isNull()->yes() || IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($lengthType)->yes())
209: ) {
210: return $preserveKeys->yes()
211: ? TypeCombinator::intersect($this, new NonEmptyArrayType())
212: : new NonEmptyArrayType();
213: }
214:
215: return new MixedType();
216: }
217:
218: public function spliceArray(Type $offsetType, Type $lengthType, Type $replacementType): Type
219: {
220: if ((new ConstantIntegerType(0))->isSuperTypeOf($lengthType)->yes()) {
221: return $this;
222: }
223:
224: return new MixedType();
225: }
226:
227: public function isIterableAtLeastOnce(): TrinaryLogic
228: {
229: return TrinaryLogic::createYes();
230: }
231:
232: public function isList(): TrinaryLogic
233: {
234: if ($this->offsetType->isString()->yes()) {
235: return TrinaryLogic::createNo();
236: }
237:
238: return TrinaryLogic::createMaybe();
239: }
240:
241: public function isNull(): TrinaryLogic
242: {
243: return TrinaryLogic::createNo();
244: }
245:
246: public function isConstantValue(): TrinaryLogic
247: {
248: return TrinaryLogic::createNo();
249: }
250:
251: public function isConstantScalarValue(): TrinaryLogic
252: {
253: return TrinaryLogic::createNo();
254: }
255:
256: public function getConstantScalarTypes(): array
257: {
258: return [];
259: }
260:
261: public function getConstantScalarValues(): array
262: {
263: return [];
264: }
265:
266: public function isTrue(): TrinaryLogic
267: {
268: return TrinaryLogic::createNo();
269: }
270:
271: public function isFalse(): TrinaryLogic
272: {
273: return TrinaryLogic::createNo();
274: }
275:
276: public function isBoolean(): TrinaryLogic
277: {
278: return TrinaryLogic::createNo();
279: }
280:
281: public function isFloat(): TrinaryLogic
282: {
283: return TrinaryLogic::createNo();
284: }
285:
286: public function isInteger(): TrinaryLogic
287: {
288: return TrinaryLogic::createNo();
289: }
290:
291: public function isString(): TrinaryLogic
292: {
293: return TrinaryLogic::createMaybe();
294: }
295:
296: public function isNumericString(): TrinaryLogic
297: {
298: return TrinaryLogic::createMaybe();
299: }
300:
301: public function isNonEmptyString(): TrinaryLogic
302: {
303: return TrinaryLogic::createMaybe();
304: }
305:
306: public function isNonFalsyString(): TrinaryLogic
307: {
308: return TrinaryLogic::createMaybe();
309: }
310:
311: public function isLiteralString(): TrinaryLogic
312: {
313: return TrinaryLogic::createMaybe();
314: }
315:
316: public function isLowercaseString(): TrinaryLogic
317: {
318: return TrinaryLogic::createMaybe();
319: }
320:
321: public function isClassString(): TrinaryLogic
322: {
323: return TrinaryLogic::createMaybe();
324: }
325:
326: public function isUppercaseString(): TrinaryLogic
327: {
328: return TrinaryLogic::createMaybe();
329: }
330:
331: public function getClassStringObjectType(): Type
332: {
333: return new ObjectWithoutClassType();
334: }
335:
336: public function getObjectTypeOrClassStringObjectType(): Type
337: {
338: return new ObjectWithoutClassType();
339: }
340:
341: public function isVoid(): TrinaryLogic
342: {
343: return TrinaryLogic::createNo();
344: }
345:
346: public function isScalar(): TrinaryLogic
347: {
348: return TrinaryLogic::createMaybe();
349: }
350:
351: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
352: {
353: return new BooleanType();
354: }
355:
356: public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
357: {
358: return $this->getKeysArray();
359: }
360:
361: public function getKeysArray(): Type
362: {
363: return new NonEmptyArrayType();
364: }
365:
366: public function getValuesArray(): Type
367: {
368: return new NonEmptyArrayType();
369: }
370:
371: public function toNumber(): Type
372: {
373: return new ErrorType();
374: }
375:
376: public function toAbsoluteNumber(): Type
377: {
378: return new ErrorType();
379: }
380:
381: public function toInteger(): Type
382: {
383: return new ErrorType();
384: }
385:
386: public function toFloat(): Type
387: {
388: return new ErrorType();
389: }
390:
391: public function toString(): Type
392: {
393: return new ErrorType();
394: }
395:
396: public function toArray(): Type
397: {
398: return new MixedType();
399: }
400:
401: public function toArrayKey(): Type
402: {
403: return new ErrorType();
404: }
405:
406: public function toCoercedArgumentType(bool $strictTypes): Type
407: {
408: return $this;
409: }
410:
411: public function getEnumCases(): array
412: {
413: return [];
414: }
415:
416: public function getEnumCaseObject(): ?EnumCaseObjectType
417: {
418: return null;
419: }
420:
421: public function traverse(callable $cb): Type
422: {
423: return $this;
424: }
425:
426: public function traverseSimultaneously(Type $right, callable $cb): Type
427: {
428: return $this;
429: }
430:
431: public function exponentiate(Type $exponent): Type
432: {
433: return new ErrorType();
434: }
435:
436: public function getFiniteTypes(): array
437: {
438: return [];
439: }
440:
441: public function toPhpDocNode(): TypeNode
442: {
443: return new IdentifierTypeNode(''); // no PHPDoc representation
444: }
445:
446: public function hasTemplateOrLateResolvableType(): bool
447: {
448: return $this->offsetType->hasTemplateOrLateResolvableType();
449: }
450:
451: }
452: