1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use PHPStan\DependencyInjection\ReportUnsafeArrayStringKeyCastingToggle;
6: use PHPStan\Php\PhpVersion;
7: use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
8: use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
9: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
10: use PHPStan\Reflection\ClassMemberAccessAnswerer;
11: use PHPStan\Reflection\TrivialParametersAcceptor;
12: use PHPStan\Rules\Arrays\AllowedArrayKeysTypes;
13: use PHPStan\ShouldNotHappenException;
14: use PHPStan\TrinaryLogic;
15: use PHPStan\Type\Accessory\AccessoryArrayListType;
16: use PHPStan\Type\Accessory\AccessoryDecimalIntegerStringType;
17: use PHPStan\Type\Accessory\HasOffsetValueType;
18: use PHPStan\Type\Accessory\NonEmptyArrayType;
19: use PHPStan\Type\Constant\ConstantArrayType;
20: use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
21: use PHPStan\Type\Constant\ConstantBooleanType;
22: use PHPStan\Type\Constant\ConstantFloatType;
23: use PHPStan\Type\Constant\ConstantIntegerType;
24: use PHPStan\Type\Constant\ConstantStringType;
25: use PHPStan\Type\Generic\TemplateMixedType;
26: use PHPStan\Type\Generic\TemplateStrictMixedType;
27: use PHPStan\Type\Generic\TemplateTypeMap;
28: use PHPStan\Type\Generic\TemplateTypeVariance;
29: use PHPStan\Type\Traits\ArrayTypeTrait;
30: use PHPStan\Type\Traits\MaybeCallableTypeTrait;
31: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
32: use PHPStan\Type\Traits\NonObjectTypeTrait;
33: use PHPStan\Type\Traits\UndecidedBooleanTypeTrait;
34: use PHPStan\Type\Traits\UndecidedComparisonTypeTrait;
35: use function array_merge;
36: use function count;
37: use function in_array;
38: use function sprintf;
39:
40: /** @api */
41: class ArrayType implements Type
42: {
43:
44: use ArrayTypeTrait;
45: use MaybeCallableTypeTrait;
46: use NonObjectTypeTrait;
47: use UndecidedBooleanTypeTrait;
48: use UndecidedComparisonTypeTrait;
49: use NonGeneralizableTypeTrait;
50:
51: private Type $keyType;
52:
53: private ?Type $cachedIterableKeyType = null;
54:
55: /** @api */
56: public function __construct(Type $keyType, private Type $itemType)
57: {
58: if (in_array($keyType->describe(VerbosityLevel::value()), ['(int|string)', '(int|non-decimal-int-string)'], true)) {
59: $keyType = new MixedType();
60: }
61: if ($keyType instanceof StrictMixedType && !$keyType instanceof TemplateStrictMixedType) {
62: $keyType = (new UnionType([new StringType(), new IntegerType()]))->toArrayKey();
63: }
64:
65: $this->keyType = $keyType;
66: }
67:
68: public function getKeyType(): Type
69: {
70: return $this->keyType;
71: }
72:
73: public function getItemType(): Type
74: {
75: return $this->itemType;
76: }
77:
78: public function getReferencedClasses(): array
79: {
80: return array_merge(
81: $this->keyType->getReferencedClasses(),
82: $this->getItemType()->getReferencedClasses(),
83: );
84: }
85:
86: public function getConstantArrays(): array
87: {
88: return [];
89: }
90:
91: public function accepts(Type $type, bool $strictTypes): AcceptsResult
92: {
93: if ($type instanceof CompoundType) {
94: return $type->isAcceptedBy($this, $strictTypes);
95: }
96:
97: if ($type instanceof ConstantArrayType) {
98: $result = AcceptsResult::createYes();
99: $thisKeyType = $this->keyType;
100: $itemType = $this->getItemType();
101: foreach ($type->getKeyTypes() as $i => $keyType) {
102: $valueType = $type->getValueTypes()[$i];
103: $acceptsKey = $thisKeyType->accepts($keyType, $strictTypes);
104: $acceptsValue = $itemType->accepts($valueType, $strictTypes);
105: $result = $result->and($acceptsKey)->and($acceptsValue);
106: }
107:
108: return $result;
109: }
110:
111: if ($type instanceof ArrayType) {
112: return $this->getItemType()->accepts($type->getItemType(), $strictTypes)
113: ->and($this->keyType->accepts($type->keyType, $strictTypes));
114: }
115:
116: return AcceptsResult::createNo();
117: }
118:
119: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
120: {
121: if ($type instanceof self || $type instanceof ConstantArrayType) {
122: return $this->getItemType()->isSuperTypeOf($type->getItemType())
123: ->and($this->getIterableKeyType()->isSuperTypeOf($type->getIterableKeyType()));
124: }
125:
126: if ($type instanceof CompoundType) {
127: return $type->isSubTypeOf($this);
128: }
129:
130: return IsSuperTypeOfResult::createNo();
131: }
132:
133: public function equals(Type $type): bool
134: {
135: return $type instanceof self
136: && $this->getItemType()->equals($type->getIterableValueType())
137: && $this->keyType->equals($type->keyType);
138: }
139:
140: public function describe(VerbosityLevel $level): string
141: {
142: $isMixedKeyType = $this->keyType instanceof MixedType && $this->keyType->describe(VerbosityLevel::precise()) === 'mixed' && !$this->keyType->isExplicitMixed();
143: $isMixedItemType = $this->itemType instanceof MixedType && $this->itemType->describe(VerbosityLevel::precise()) === 'mixed' && !$this->itemType->isExplicitMixed();
144:
145: $valueHandler = function () use ($level, $isMixedKeyType, $isMixedItemType): string {
146: if ($isMixedKeyType || $this->keyType instanceof NeverType) {
147: if ($isMixedItemType || $this->itemType instanceof NeverType) {
148: return 'array';
149: }
150:
151: return sprintf('array<%s>', $this->itemType->describe($level));
152: }
153:
154: return sprintf('array<%s, %s>', $this->keyType->describe($level), $this->itemType->describe($level));
155: };
156:
157: return $level->handle(
158: $valueHandler,
159: $valueHandler,
160: function () use ($level, $isMixedKeyType, $isMixedItemType): string {
161: if ($isMixedKeyType) {
162: if ($isMixedItemType) {
163: return 'array';
164: }
165:
166: return sprintf('array<%s>', $this->itemType->describe($level));
167: }
168:
169: return sprintf('array<%s, %s>', $this->keyType->describe($level), $this->itemType->describe($level));
170: },
171: );
172: }
173:
174: public function generalizeValues(): self
175: {
176: return new self($this->keyType, $this->itemType->generalize(GeneralizePrecision::lessSpecific()));
177: }
178:
179: public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
180: {
181: return $this->getKeysArray();
182: }
183:
184: public function getKeysArray(): Type
185: {
186: return TypeCombinator::intersect(new self(new IntegerType(), $this->getIterableKeyType()), new AccessoryArrayListType());
187: }
188:
189: public function getValuesArray(): Type
190: {
191: return TypeCombinator::intersect(new self(new IntegerType(), $this->itemType), new AccessoryArrayListType());
192: }
193:
194: public function isIterableAtLeastOnce(): TrinaryLogic
195: {
196: return TrinaryLogic::createMaybe();
197: }
198:
199: public function getArraySize(): Type
200: {
201: return IntegerRangeType::fromInterval(0, null);
202: }
203:
204: public function getIterableKeyType(): Type
205: {
206: if ($this->cachedIterableKeyType !== null) {
207: return $this->cachedIterableKeyType;
208: }
209: $keyType = $this->keyType;
210: if ($keyType instanceof MixedType && !$keyType instanceof TemplateMixedType) {
211: $keyType = (new BenevolentUnionType([new IntegerType(), new StringType()]))->toArrayKey();
212: }
213: if ($keyType instanceof StrictMixedType) {
214: $keyType = (new BenevolentUnionType([new IntegerType(), new StringType()]))->toArrayKey();
215: }
216:
217: $level = ReportUnsafeArrayStringKeyCastingToggle::getLevel();
218: if ($level === null) {
219: return $this->cachedIterableKeyType = $keyType;
220: }
221:
222: if ($level === ReportUnsafeArrayStringKeyCastingToggle::PREVENT) {
223: return $this->cachedIterableKeyType = $keyType;
224: }
225:
226: if ($level !== ReportUnsafeArrayStringKeyCastingToggle::DETECT) { // @phpstan-ignore notIdentical.alwaysFalse
227: throw new ShouldNotHappenException();
228: }
229:
230: return $this->cachedIterableKeyType = TypeTraverser::map($keyType, static function (Type $type, callable $traverse): Type {
231: if ($type instanceof UnionType) {
232: return $traverse($type);
233: }
234:
235: if ($type->isString()->yes() && !$type->isDecimalIntegerString()->no()) {
236: return TypeCombinator::union(
237: new IntegerType(),
238: TypeCombinator::intersect($type, new AccessoryDecimalIntegerStringType(inverse: true)),
239: );
240: }
241:
242: return $type;
243: });
244: }
245:
246: public function getFirstIterableKeyType(): Type
247: {
248: return $this->getIterableKeyType();
249: }
250:
251: public function getLastIterableKeyType(): Type
252: {
253: return $this->getIterableKeyType();
254: }
255:
256: public function getIterableValueType(): Type
257: {
258: return $this->getItemType();
259: }
260:
261: public function getFirstIterableValueType(): Type
262: {
263: return $this->getItemType();
264: }
265:
266: public function getLastIterableValueType(): Type
267: {
268: return $this->getItemType();
269: }
270:
271: public function isConstantArray(): TrinaryLogic
272: {
273: return TrinaryLogic::createNo();
274: }
275:
276: public function isList(): TrinaryLogic
277: {
278: if (IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($this->getKeyType())->no()) {
279: return TrinaryLogic::createNo();
280: }
281:
282: if ($this->getKeyType()->isSuperTypeOf(new ConstantIntegerType(0))->no()) {
283: return TrinaryLogic::createNo();
284: }
285:
286: return TrinaryLogic::createMaybe();
287: }
288:
289: public function isConstantValue(): TrinaryLogic
290: {
291: return TrinaryLogic::createNo();
292: }
293:
294: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
295: {
296: if ($type->isInteger()->yes()) {
297: return new ConstantBooleanType(false);
298: }
299:
300: return new BooleanType();
301: }
302:
303: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
304: {
305: $offsetArrayKeyType = $offsetType->toArrayKey();
306: if ($offsetArrayKeyType instanceof ErrorType) {
307: $allowedArrayKeys = AllowedArrayKeysTypes::getType();
308: $offsetArrayKeyType = TypeCombinator::intersect($allowedArrayKeys, $offsetType)->toArrayKey();
309: if ($offsetArrayKeyType instanceof NeverType) {
310: return TrinaryLogic::createNo();
311: }
312: }
313: $offsetType = $offsetArrayKeyType;
314:
315: if ($this->getKeyType()->isSuperTypeOf($offsetType)->no()
316: && ($offsetType->isString()->no() || !$offsetType->isConstantScalarValue()->no())
317: ) {
318: return TrinaryLogic::createNo();
319: }
320:
321: return TrinaryLogic::createMaybe();
322: }
323:
324: public function getOffsetValueType(Type $offsetType): Type
325: {
326: $offsetType = $offsetType->toArrayKey();
327: if ($this->getKeyType()->isSuperTypeOf($offsetType)->no()
328: && ($offsetType->isString()->no() || !$offsetType->isConstantScalarValue()->no())
329: ) {
330: return new ErrorType();
331: }
332:
333: $type = $this->getItemType();
334: if ($type instanceof ErrorType) {
335: return new MixedType();
336: }
337:
338: return $type;
339: }
340:
341: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
342: {
343: if ($offsetType === null) {
344: $isKeyTypeInteger = $this->keyType->isInteger();
345: if ($isKeyTypeInteger->no()) {
346: $offsetType = new IntegerType();
347: } elseif ($isKeyTypeInteger->yes()) {
348: /** @var list<ConstantIntegerType> $constantScalars */
349: $constantScalars = $this->keyType->getConstantScalarTypes();
350: if (count($constantScalars) > 0) {
351: foreach ($constantScalars as $constantScalar) {
352: $constantScalars[] = ConstantTypeHelper::getTypeFromValue($constantScalar->getValue() + 1);
353: }
354:
355: $offsetType = TypeCombinator::union(...$constantScalars);
356: } else {
357: $offsetType = $this->keyType;
358: }
359: } else {
360: $integerTypes = [];
361: TypeTraverser::map($this->keyType, static function (Type $type, callable $traverse) use (&$integerTypes): Type {
362: if ($type instanceof UnionType) {
363: return $traverse($type);
364: }
365:
366: $isInteger = $type->isInteger();
367: if ($isInteger->yes()) {
368: $integerTypes[] = $type;
369: }
370:
371: return $type;
372: });
373: if (count($integerTypes) === 0) {
374: $offsetType = $this->keyType;
375: } else {
376: $offsetType = TypeCombinator::union(...$integerTypes);
377: }
378: }
379: } else {
380: $offsetType = $offsetType->toArrayKey();
381: }
382:
383: if ($offsetType instanceof ConstantStringType || $offsetType instanceof ConstantIntegerType) {
384: if ($offsetType->isSuperTypeOf($this->keyType)->yes()) {
385: $builder = ConstantArrayTypeBuilder::createEmpty();
386: $builder->setOffsetValueType($offsetType, $valueType);
387: return $builder->getArray();
388: }
389:
390: return new IntersectionType([
391: new self(
392: TypeCombinator::union($this->keyType, $offsetType),
393: TypeCombinator::union($this->itemType, $valueType),
394: ),
395: new HasOffsetValueType($offsetType, $valueType),
396: new NonEmptyArrayType(),
397: ]);
398: }
399:
400: return new IntersectionType([
401: new self(
402: TypeCombinator::union($this->keyType, $offsetType),
403: $unionValues ? TypeCombinator::union($this->itemType, $valueType) : $valueType,
404: ),
405: new NonEmptyArrayType(),
406: ]);
407: }
408:
409: public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
410: {
411: if ($this->itemType->isConstantArray()->yes() && $valueType->isConstantArray()->yes()) {
412: $newItemTypes = [];
413:
414: foreach ($valueType->getConstantArrays() as $constArray) {
415: $newItemType = $this->itemType;
416: $optionalKeyTypes = [];
417: foreach ($constArray->getKeyTypes() as $i => $keyType) {
418: $newItemType = $newItemType->setExistingOffsetValueType($keyType, $constArray->getOffsetValueType($keyType));
419:
420: if (!$constArray->isOptionalKey($i)) {
421: continue;
422: }
423:
424: $optionalKeyTypes[] = $keyType;
425: }
426: $newItemTypes[] = $newItemType;
427:
428: if ($optionalKeyTypes === []) {
429: continue;
430: }
431:
432: foreach ($optionalKeyTypes as $keyType) {
433: $newItemType = $newItemType->unsetOffset($keyType);
434: }
435: $newItemTypes[] = $newItemType;
436: }
437:
438: $newItemType = TypeCombinator::union(...$newItemTypes);
439: if ($newItemType !== $this->itemType) {
440: return new self(
441: $this->keyType,
442: $newItemType,
443: );
444: }
445: }
446:
447: return new self(
448: $this->keyType,
449: TypeCombinator::union($this->itemType, $valueType),
450: );
451: }
452:
453: public function unsetOffset(Type $offsetType): Type
454: {
455: $offsetType = $offsetType->toArrayKey();
456:
457: if (
458: ($offsetType instanceof ConstantIntegerType || $offsetType instanceof ConstantStringType)
459: && !$this->keyType->isSuperTypeOf($offsetType)->no()
460: ) {
461: $keyType = TypeCombinator::remove($this->keyType, $offsetType);
462: if ($keyType instanceof NeverType) {
463: return new ConstantArrayType([], []);
464: }
465:
466: return new self($keyType, $this->itemType);
467: }
468:
469: return $this;
470: }
471:
472: public function fillKeysArray(Type $valueType): Type
473: {
474: $itemType = $this->getItemType();
475: if ($itemType->isInteger()->no()) {
476: $stringKeyType = $itemType->toString();
477: if ($stringKeyType instanceof ErrorType) {
478: return $stringKeyType;
479: }
480:
481: return new ArrayType($stringKeyType, $valueType);
482: }
483:
484: return new ArrayType($itemType, $valueType);
485: }
486:
487: public function flipArray(): Type
488: {
489: return new self($this->getIterableValueType()->toArrayKey(), $this->getIterableKeyType());
490: }
491:
492: public function intersectKeyArray(Type $otherArraysType): Type
493: {
494: $isKeySuperType = $otherArraysType->getIterableKeyType()->isSuperTypeOf($this->getIterableKeyType());
495: if ($isKeySuperType->no()) {
496: return ConstantArrayTypeBuilder::createEmpty()->getArray();
497: }
498:
499: if ($isKeySuperType->yes()) {
500: return $this;
501: }
502:
503: return new self($otherArraysType->getIterableKeyType(), $this->getIterableValueType());
504: }
505:
506: public function popArray(): Type
507: {
508: return $this;
509: }
510:
511: public function reverseArray(TrinaryLogic $preserveKeys): Type
512: {
513: return $this;
514: }
515:
516: public function searchArray(Type $needleType, ?TrinaryLogic $strict = null): Type
517: {
518: $strict ??= TrinaryLogic::createMaybe();
519: if ($strict->yes() && $this->getIterableValueType()->isSuperTypeOf($needleType)->no()) {
520: return new ConstantBooleanType(false);
521: }
522:
523: return TypeCombinator::union($this->getIterableKeyType(), new ConstantBooleanType(false));
524: }
525:
526: public function shiftArray(): Type
527: {
528: return $this;
529: }
530:
531: public function shuffleArray(): Type
532: {
533: return new IntersectionType([new self(IntegerRangeType::createAllGreaterThanOrEqualTo(0), $this->itemType), new AccessoryArrayListType()]);
534: }
535:
536: public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type
537: {
538: if ((new ConstantIntegerType(0))->isSuperTypeOf($lengthType)->yes()) {
539: return new ConstantArrayType([], []);
540: }
541:
542: if ($preserveKeys->no() && $this->keyType->isInteger()->yes()) {
543: return new IntersectionType([new self(IntegerRangeType::createAllGreaterThanOrEqualTo(0), $this->itemType), new AccessoryArrayListType()]);
544: }
545:
546: return $this;
547: }
548:
549: public function spliceArray(Type $offsetType, Type $lengthType, Type $replacementType): Type
550: {
551: $replacementArrayType = $replacementType->toArray();
552: $replacementArrayTypeIsIterableAtLeastOnce = $replacementArrayType->isIterableAtLeastOnce();
553:
554: if ((new ConstantIntegerType(0))->isSuperTypeOf($offsetType)->yes() && $lengthType->isNull()->yes() && $replacementArrayTypeIsIterableAtLeastOnce->no()) {
555: return new ConstantArrayType([], []);
556: }
557:
558: $existingArrayKeyType = $this->getIterableKeyType();
559: $keyType = TypeTraverser::map($existingArrayKeyType, static function (Type $type, callable $traverse): Type {
560: if ($type instanceof UnionType) {
561: return $traverse($type);
562: }
563:
564: if ($type->isInteger()->yes()) {
565: return IntegerRangeType::createAllGreaterThanOrEqualTo(0);
566: }
567:
568: return $type;
569: });
570:
571: $arrayType = new self(
572: TypeCombinator::union($keyType, $replacementArrayType->getKeysArray()->getIterableKeyType()),
573: TypeCombinator::union($this->getIterableValueType(), $replacementArrayType->getIterableValueType()),
574: );
575:
576: $accessories = [];
577: if ($replacementArrayTypeIsIterableAtLeastOnce->yes()) {
578: $accessories[] = new NonEmptyArrayType();
579: }
580: if ($existingArrayKeyType->isInteger()->yes()) {
581: $accessories[] = new AccessoryArrayListType();
582: }
583: if (count($accessories) > 0) {
584: $accessories[] = $arrayType;
585:
586: return new IntersectionType($accessories);
587: }
588:
589: return $arrayType;
590: }
591:
592: public function isCallable(): TrinaryLogic
593: {
594: return TrinaryLogic::createMaybe()->and($this->itemType->isString());
595: }
596:
597: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
598: {
599: if ($this->isCallable()->no()) {
600: throw new ShouldNotHappenException();
601: }
602:
603: return [new TrivialParametersAcceptor()];
604: }
605:
606: public function toInteger(): Type
607: {
608: return new UnionType([
609: new ConstantIntegerType(0),
610: new ConstantIntegerType(1),
611: ]);
612: }
613:
614: public function toFloat(): Type
615: {
616: return new UnionType([
617: new ConstantFloatType(0.0),
618: new ConstantFloatType(1.0),
619: ]);
620: }
621:
622: public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
623: {
624: if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) {
625: return $receivedType->inferTemplateTypesOn($this);
626: }
627:
628: if ($receivedType->isArray()->yes()) {
629: $keyTypeMap = $this->getIterableKeyType()->inferTemplateTypes($receivedType->getIterableKeyType());
630: $itemTypeMap = $this->getItemType()->inferTemplateTypes($receivedType->getIterableValueType());
631:
632: return $keyTypeMap->union($itemTypeMap);
633: }
634:
635: return TemplateTypeMap::createEmpty();
636: }
637:
638: public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array
639: {
640: $variance = $positionVariance->compose(TemplateTypeVariance::createCovariant());
641:
642: return array_merge(
643: $this->getIterableKeyType()->getReferencedTemplateTypes($variance),
644: $this->getItemType()->getReferencedTemplateTypes($variance),
645: );
646: }
647:
648: public function traverse(callable $cb): Type
649: {
650: $keyType = $cb($this->keyType);
651: $itemType = $cb($this->itemType);
652:
653: if ($keyType !== $this->keyType || $itemType !== $this->itemType) {
654: if ($keyType instanceof NeverType && $itemType instanceof NeverType) {
655: return new ConstantArrayType([], []);
656: }
657:
658: return new self($keyType, $itemType);
659: }
660:
661: return $this;
662: }
663:
664: public function toPhpDocNode(): TypeNode
665: {
666: $isMixedKeyType = $this->keyType instanceof MixedType && $this->keyType->describe(VerbosityLevel::precise()) === 'mixed' && !$this->keyType->isExplicitMixed();
667: $isMixedItemType = $this->itemType instanceof MixedType && $this->itemType->describe(VerbosityLevel::precise()) === 'mixed' && !$this->itemType->isExplicitMixed();
668:
669: if ($isMixedKeyType) {
670: if ($isMixedItemType) {
671: return new IdentifierTypeNode('array');
672: }
673:
674: return new GenericTypeNode(
675: new IdentifierTypeNode('array'),
676: [
677: $this->itemType->toPhpDocNode(),
678: ],
679: );
680: }
681:
682: return new GenericTypeNode(
683: new IdentifierTypeNode('array'),
684: [
685: $this->keyType->toPhpDocNode(),
686: $this->itemType->toPhpDocNode(),
687: ],
688: );
689: }
690:
691: public function traverseSimultaneously(Type $right, callable $cb): Type
692: {
693: $keyType = $cb($this->keyType, $right->getIterableKeyType());
694: $itemType = $cb($this->itemType, $right->getIterableValueType());
695:
696: if ($keyType !== $this->keyType || $itemType !== $this->itemType) {
697: if ($keyType instanceof NeverType && $itemType instanceof NeverType) {
698: return new ConstantArrayType([], []);
699: }
700:
701: return new self($keyType, $itemType);
702: }
703:
704: return $this;
705: }
706:
707: public function tryRemove(Type $typeToRemove): ?Type
708: {
709: if ($typeToRemove->isConstantArray()->yes() && $typeToRemove->isIterableAtLeastOnce()->no()) {
710: return TypeCombinator::intersect($this, new NonEmptyArrayType());
711: }
712:
713: if ($typeToRemove->isSuperTypeOf(new ConstantArrayType([], []))->yes()) {
714: return TypeCombinator::intersect($this, new NonEmptyArrayType());
715: }
716:
717: if ($typeToRemove instanceof NonEmptyArrayType) {
718: return new ConstantArrayType([], []);
719: }
720:
721: return null;
722: }
723:
724: public function getFiniteTypes(): array
725: {
726: return [];
727: }
728:
729: public function hasTemplateOrLateResolvableType(): bool
730: {
731: return $this->keyType->hasTemplateOrLateResolvableType() || $this->itemType->hasTemplateOrLateResolvableType();
732: }
733:
734: }
735: