1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use PHPStan\TrinaryLogic;
6: use PHPStan\Type\Generic\GenericObjectType;
7: use PHPStan\Type\Generic\TemplateMixedType;
8: use PHPStan\Type\Generic\TemplateType;
9: use PHPStan\Type\Generic\TemplateTypeMap;
10: use PHPStan\Type\Generic\TemplateTypeVariance;
11: use PHPStan\Type\Traits\MaybeArrayTypeTrait;
12: use PHPStan\Type\Traits\MaybeCallableTypeTrait;
13: use PHPStan\Type\Traits\MaybeObjectTypeTrait;
14: use PHPStan\Type\Traits\MaybeOffsetAccessibleTypeTrait;
15: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
16: use PHPStan\Type\Traits\UndecidedBooleanTypeTrait;
17: use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
18: use Traversable;
19: use function array_merge;
20: use function sprintf;
21:
22: /** @api */
23: class IterableType implements CompoundType
24: {
25:
26: use MaybeArrayTypeTrait;
27: use MaybeCallableTypeTrait;
28: use MaybeObjectTypeTrait;
29: use MaybeOffsetAccessibleTypeTrait;
30: use UndecidedBooleanTypeTrait;
31: use UndecidedComparisonCompoundTypeTrait;
32: use NonGeneralizableTypeTrait;
33:
34: /** @api */
35: public function __construct(
36: private Type $keyType,
37: private Type $itemType,
38: )
39: {
40: }
41:
42: public function getKeyType(): Type
43: {
44: return $this->keyType;
45: }
46:
47: public function getItemType(): Type
48: {
49: return $this->itemType;
50: }
51:
52: /**
53: * @return string[]
54: */
55: public function getReferencedClasses(): array
56: {
57: return array_merge(
58: $this->keyType->getReferencedClasses(),
59: $this->getItemType()->getReferencedClasses(),
60: );
61: }
62:
63: public function getConstantStrings(): array
64: {
65: return [];
66: }
67:
68: public function accepts(Type $type, bool $strictTypes): TrinaryLogic
69: {
70: if ($type->isConstantArray()->yes() && $type->isIterableAtLeastOnce()->no()) {
71: return TrinaryLogic::createYes();
72: }
73: if ($type->isIterable()->yes()) {
74: return $this->getIterableValueType()->accepts($type->getIterableValueType(), $strictTypes)
75: ->and($this->getIterableKeyType()->accepts($type->getIterableKeyType(), $strictTypes));
76: }
77:
78: if ($type instanceof CompoundType) {
79: return $type->isAcceptedBy($this, $strictTypes);
80: }
81:
82: return TrinaryLogic::createNo();
83: }
84:
85: public function isSuperTypeOf(Type $type): TrinaryLogic
86: {
87: if ($type instanceof CompoundType) {
88: return $type->isSubTypeOf($this);
89: }
90:
91: return $type->isIterable()
92: ->and($this->getIterableValueType()->isSuperTypeOf($type->getIterableValueType()))
93: ->and($this->getIterableKeyType()->isSuperTypeOf($type->getIterableKeyType()));
94: }
95:
96: public function isSuperTypeOfMixed(Type $type): TrinaryLogic
97: {
98: return $type->isIterable()
99: ->and($this->isNestedTypeSuperTypeOf($this->getIterableValueType(), $type->getIterableValueType()))
100: ->and($this->isNestedTypeSuperTypeOf($this->getIterableKeyType(), $type->getIterableKeyType()));
101: }
102:
103: private function isNestedTypeSuperTypeOf(Type $a, Type $b): TrinaryLogic
104: {
105: if (!$a instanceof MixedType || !$b instanceof MixedType) {
106: return $a->isSuperTypeOf($b);
107: }
108:
109: if ($a instanceof TemplateMixedType || $b instanceof TemplateMixedType) {
110: return $a->isSuperTypeOf($b);
111: }
112:
113: if ($a->isExplicitMixed()) {
114: if ($b->isExplicitMixed()) {
115: return TrinaryLogic::createYes();
116: }
117:
118: return TrinaryLogic::createMaybe();
119: }
120:
121: return TrinaryLogic::createYes();
122: }
123:
124: public function isSubTypeOf(Type $otherType): TrinaryLogic
125: {
126: if ($otherType instanceof IntersectionType || $otherType instanceof UnionType) {
127: return $otherType->isSuperTypeOf(new UnionType([
128: new ArrayType($this->keyType, $this->itemType),
129: new IntersectionType([
130: new ObjectType(Traversable::class),
131: $this,
132: ]),
133: ]));
134: }
135:
136: if ($otherType instanceof self) {
137: $limit = TrinaryLogic::createYes();
138: } else {
139: $limit = TrinaryLogic::createMaybe();
140: }
141:
142: if ($otherType->isConstantArray()->yes() && $otherType->isIterableAtLeastOnce()->no()) {
143: return TrinaryLogic::createMaybe();
144: }
145:
146: return $limit->and(
147: $otherType->isIterable(),
148: $otherType->getIterableValueType()->isSuperTypeOf($this->itemType),
149: $otherType->getIterableKeyType()->isSuperTypeOf($this->keyType),
150: );
151: }
152:
153: public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic
154: {
155: return $this->isSubTypeOf($acceptingType);
156: }
157:
158: public function equals(Type $type): bool
159: {
160: if (!$type instanceof self) {
161: return false;
162: }
163:
164: return $this->keyType->equals($type->keyType)
165: && $this->itemType->equals($type->itemType);
166: }
167:
168: public function describe(VerbosityLevel $level): string
169: {
170: $isMixedKeyType = $this->keyType instanceof MixedType && !$this->keyType instanceof TemplateType;
171: $isMixedItemType = $this->itemType instanceof MixedType && !$this->itemType instanceof TemplateType;
172:
173: if ($isMixedKeyType) {
174: if ($isMixedItemType) {
175: return 'iterable';
176: }
177:
178: return sprintf('iterable<%s>', $this->itemType->describe($level));
179: }
180:
181: return sprintf('iterable<%s, %s>', $this->keyType->describe($level), $this->itemType->describe($level));
182: }
183:
184: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
185: {
186: if ($this->getIterableKeyType()->isSuperTypeOf($offsetType)->no()) {
187: return TrinaryLogic::createNo();
188: }
189:
190: return TrinaryLogic::createMaybe();
191: }
192:
193: public function toNumber(): Type
194: {
195: return new ErrorType();
196: }
197:
198: public function toString(): Type
199: {
200: return new ErrorType();
201: }
202:
203: public function toInteger(): Type
204: {
205: return new ErrorType();
206: }
207:
208: public function toFloat(): Type
209: {
210: return new ErrorType();
211: }
212:
213: public function toArray(): Type
214: {
215: return new ArrayType($this->keyType, $this->getItemType());
216: }
217:
218: public function toArrayKey(): Type
219: {
220: return new ErrorType();
221: }
222:
223: public function isIterable(): TrinaryLogic
224: {
225: return TrinaryLogic::createYes();
226: }
227:
228: public function isIterableAtLeastOnce(): TrinaryLogic
229: {
230: return TrinaryLogic::createMaybe();
231: }
232:
233: public function getArraySize(): Type
234: {
235: return IntegerRangeType::fromInterval(0, null);
236: }
237:
238: public function getIterableKeyType(): Type
239: {
240: return $this->keyType;
241: }
242:
243: public function getFirstIterableKeyType(): Type
244: {
245: return $this->keyType;
246: }
247:
248: public function getLastIterableKeyType(): Type
249: {
250: return $this->keyType;
251: }
252:
253: public function getIterableValueType(): Type
254: {
255: return $this->getItemType();
256: }
257:
258: public function getFirstIterableValueType(): Type
259: {
260: return $this->getItemType();
261: }
262:
263: public function getLastIterableValueType(): Type
264: {
265: return $this->getItemType();
266: }
267:
268: public function isNull(): TrinaryLogic
269: {
270: return TrinaryLogic::createNo();
271: }
272:
273: public function isTrue(): TrinaryLogic
274: {
275: return TrinaryLogic::createNo();
276: }
277:
278: public function isFalse(): TrinaryLogic
279: {
280: return TrinaryLogic::createNo();
281: }
282:
283: public function isBoolean(): TrinaryLogic
284: {
285: return TrinaryLogic::createNo();
286: }
287:
288: public function isFloat(): TrinaryLogic
289: {
290: return TrinaryLogic::createNo();
291: }
292:
293: public function isInteger(): TrinaryLogic
294: {
295: return TrinaryLogic::createNo();
296: }
297:
298: public function isString(): TrinaryLogic
299: {
300: return TrinaryLogic::createNo();
301: }
302:
303: public function isNumericString(): TrinaryLogic
304: {
305: return TrinaryLogic::createNo();
306: }
307:
308: public function isNonEmptyString(): TrinaryLogic
309: {
310: return TrinaryLogic::createNo();
311: }
312:
313: public function isNonFalsyString(): TrinaryLogic
314: {
315: return TrinaryLogic::createNo();
316: }
317:
318: public function isLiteralString(): TrinaryLogic
319: {
320: return TrinaryLogic::createNo();
321: }
322:
323: public function isClassStringType(): TrinaryLogic
324: {
325: return TrinaryLogic::createNo();
326: }
327:
328: public function isVoid(): TrinaryLogic
329: {
330: return TrinaryLogic::createNo();
331: }
332:
333: public function isScalar(): TrinaryLogic
334: {
335: return TrinaryLogic::createNo();
336: }
337:
338: public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
339: {
340: if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) {
341: return $receivedType->inferTemplateTypesOn($this);
342: }
343:
344: if (!$receivedType->isIterable()->yes()) {
345: return TemplateTypeMap::createEmpty();
346: }
347:
348: $keyTypeMap = $this->getIterableKeyType()->inferTemplateTypes($receivedType->getIterableKeyType());
349: $valueTypeMap = $this->getIterableValueType()->inferTemplateTypes($receivedType->getIterableValueType());
350:
351: return $keyTypeMap->union($valueTypeMap);
352: }
353:
354: public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array
355: {
356: return array_merge(
357: $this->getIterableKeyType()->getReferencedTemplateTypes(TemplateTypeVariance::createCovariant()),
358: $this->getIterableValueType()->getReferencedTemplateTypes(TemplateTypeVariance::createCovariant()),
359: );
360: }
361:
362: public function traverse(callable $cb): Type
363: {
364: $keyType = $cb($this->keyType);
365: $itemType = $cb($this->itemType);
366:
367: if ($keyType !== $this->keyType || $itemType !== $this->itemType) {
368: return new self($keyType, $itemType);
369: }
370:
371: return $this;
372: }
373:
374: public function tryRemove(Type $typeToRemove): ?Type
375: {
376: $arrayType = new ArrayType(new MixedType(), new MixedType());
377: if ($typeToRemove->isSuperTypeOf($arrayType)->yes()) {
378: return new GenericObjectType(Traversable::class, [
379: $this->getIterableKeyType(),
380: $this->getIterableValueType(),
381: ]);
382: }
383:
384: $traversableType = new ObjectType(Traversable::class);
385: if ($typeToRemove->isSuperTypeOf($traversableType)->yes()) {
386: return new ArrayType($this->getIterableKeyType(), $this->getIterableValueType());
387: }
388:
389: return null;
390: }
391:
392: /**
393: * @param mixed[] $properties
394: */
395: public static function __set_state(array $properties): Type
396: {
397: return new self($properties['keyType'], $properties['itemType']);
398: }
399:
400: }
401: