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