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\ArrayType;
11: use PHPStan\Type\BooleanType;
12: use PHPStan\Type\CompoundType;
13: use PHPStan\Type\Constant\ConstantBooleanType;
14: use PHPStan\Type\Constant\ConstantFloatType;
15: use PHPStan\Type\Constant\ConstantIntegerType;
16: use PHPStan\Type\ErrorType;
17: use PHPStan\Type\IntegerRangeType;
18: use PHPStan\Type\IntersectionType;
19: use PHPStan\Type\IsSuperTypeOfResult;
20: use PHPStan\Type\MixedType;
21: use PHPStan\Type\Traits\MaybeCallableTypeTrait;
22: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
23: use PHPStan\Type\Traits\NonGenericTypeTrait;
24: use PHPStan\Type\Traits\NonObjectTypeTrait;
25: use PHPStan\Type\Traits\NonRemoveableTypeTrait;
26: use PHPStan\Type\Traits\TruthyBooleanTypeTrait;
27: use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
28: use PHPStan\Type\Type;
29: use PHPStan\Type\UnionType;
30: use PHPStan\Type\VerbosityLevel;
31:
32: class NonEmptyArrayType implements CompoundType, AccessoryType
33: {
34:
35: use MaybeCallableTypeTrait;
36: use NonObjectTypeTrait;
37: use TruthyBooleanTypeTrait;
38: use NonGenericTypeTrait;
39: use UndecidedComparisonCompoundTypeTrait;
40: use NonRemoveableTypeTrait;
41: use NonGeneralizableTypeTrait;
42:
43: /** @api */
44: public function __construct()
45: {
46: }
47:
48: public function getReferencedClasses(): array
49: {
50: return [];
51: }
52:
53: public function getObjectClassNames(): array
54: {
55: return [];
56: }
57:
58: public function getObjectClassReflections(): array
59: {
60: return [];
61: }
62:
63: public function getArrays(): array
64: {
65: return [];
66: }
67:
68: public function getConstantArrays(): array
69: {
70: return [];
71: }
72:
73: public function getConstantStrings(): array
74: {
75: return [];
76: }
77:
78: public function accepts(Type $type, bool $strictTypes): AcceptsResult
79: {
80: $isArray = $type->isArray();
81: $isIterableAtLeastOnce = $type->isIterableAtLeastOnce();
82: $isNonEmptyArray = $isArray->and($isIterableAtLeastOnce);
83:
84: if ($isNonEmptyArray->yes()) {
85: return AcceptsResult::createYes();
86: }
87:
88: if ($type instanceof CompoundType) {
89: return $type->isAcceptedBy($this, $strictTypes);
90: }
91:
92: return new AcceptsResult($isNonEmptyArray, []);
93: }
94:
95: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
96: {
97: if ($this->equals($type)) {
98: return IsSuperTypeOfResult::createYes();
99: }
100:
101: if ($type instanceof CompoundType) {
102: return $type->isSubTypeOf($this);
103: }
104:
105: return new IsSuperTypeOfResult($type->isArray()->and($type->isIterableAtLeastOnce()), []);
106: }
107:
108: public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult
109: {
110: if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) {
111: return $otherType->isSuperTypeOf($this);
112: }
113:
114: return new IsSuperTypeOfResult(
115: $otherType->isArray()->and($otherType->isIterableAtLeastOnce())->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()),
116: [],
117: );
118: }
119:
120: public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult
121: {
122: return $this->isSubTypeOf($acceptingType)->toAcceptsResult();
123: }
124:
125: public function equals(Type $type): bool
126: {
127: return $type instanceof self;
128: }
129:
130: public function describe(VerbosityLevel $level): string
131: {
132: return 'non-empty-array';
133: }
134:
135: public function isOffsetAccessible(): TrinaryLogic
136: {
137: return TrinaryLogic::createYes();
138: }
139:
140: public function isOffsetAccessLegal(): TrinaryLogic
141: {
142: return TrinaryLogic::createYes();
143: }
144:
145: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
146: {
147: return TrinaryLogic::createMaybe();
148: }
149:
150: public function getOffsetValueType(Type $offsetType): Type
151: {
152: return new MixedType();
153: }
154:
155: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
156: {
157: return $this;
158: }
159:
160: public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
161: {
162: return $this;
163: }
164:
165: public function unsetOffset(Type $offsetType): Type
166: {
167: return new ErrorType();
168: }
169:
170: public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type
171: {
172: return new ErrorType();
173: }
174:
175: public function getKeysArray(): Type
176: {
177: return $this;
178: }
179:
180: public function getValuesArray(): Type
181: {
182: return $this;
183: }
184:
185: public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type
186: {
187: return $this;
188: }
189:
190: public function fillKeysArray(Type $valueType): Type
191: {
192: return $this;
193: }
194:
195: public function flipArray(): Type
196: {
197: return $this;
198: }
199:
200: public function intersectKeyArray(Type $otherArraysType): Type
201: {
202: return new MixedType();
203: }
204:
205: public function popArray(): Type
206: {
207: return new MixedType();
208: }
209:
210: public function reverseArray(TrinaryLogic $preserveKeys): Type
211: {
212: return $this;
213: }
214:
215: public function searchArray(Type $needleType, ?TrinaryLogic $strict = null): Type
216: {
217: return new MixedType();
218: }
219:
220: public function shiftArray(): Type
221: {
222: return new MixedType();
223: }
224:
225: public function shuffleArray(): Type
226: {
227: return $this;
228: }
229:
230: public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type
231: {
232: if ((new ConstantIntegerType(0))->isSuperTypeOf($offsetType)->yes() && $lengthType->isNull()->yes()) {
233: return $this;
234: }
235:
236: return new MixedType();
237: }
238:
239: public function spliceArray(Type $offsetType, Type $lengthType, Type $replacementType): Type
240: {
241: if (
242: (new ConstantIntegerType(0))->isSuperTypeOf($lengthType)->yes()
243: || $replacementType->toArray()->isIterableAtLeastOnce()->yes()
244: ) {
245: return $this;
246: }
247:
248: return new MixedType();
249: }
250:
251: public function truncateListToSize(Type $sizeType): Type
252: {
253: // The accessory only asserts "this array is non-empty" — truncating
254: // to a positive size leaves that property in place.
255: return $this;
256: }
257:
258: public function makeListMaybe(): Type
259: {
260: // Non-emptiness is independent of list-ness; weaken-list keeps it.
261: return $this;
262: }
263:
264: public function mapValueType(callable $cb): Type
265: {
266: // Mapping doesn't change the entry count; non-emptiness is preserved.
267: return $this;
268: }
269:
270: public function mapKeyType(callable $cb): Type
271: {
272: return $this;
273: }
274:
275: public function makeAllArrayKeysOptional(): Type
276: {
277: // Without `ConstantArrayType` keys to mark optional, this is a no-op.
278: // Non-emptiness is unrelated to per-key optionality and is preserved.
279: return $this;
280: }
281:
282: public function changeKeyCaseArray(?int $case): Type
283: {
284: // Case-folding keys doesn't change the entry count.
285: return $this;
286: }
287:
288: public function filterArrayRemovingFalsey(): Type
289: {
290: // Filtering may leave the array empty — drop the assertion.
291: return new MixedType();
292: }
293:
294: public function isIterable(): TrinaryLogic
295: {
296: return TrinaryLogic::createYes();
297: }
298:
299: public function isIterableAtLeastOnce(): TrinaryLogic
300: {
301: return TrinaryLogic::createYes();
302: }
303:
304: public function getArraySize(): Type
305: {
306: return IntegerRangeType::fromInterval(1, null);
307: }
308:
309: public function getIterableKeyType(): Type
310: {
311: return new MixedType();
312: }
313:
314: public function getFirstIterableKeyType(): Type
315: {
316: return new MixedType();
317: }
318:
319: public function getLastIterableKeyType(): Type
320: {
321: return new MixedType();
322: }
323:
324: public function getIterableValueType(): Type
325: {
326: return new MixedType();
327: }
328:
329: public function getFirstIterableValueType(): Type
330: {
331: return new MixedType();
332: }
333:
334: public function getLastIterableValueType(): Type
335: {
336: return new MixedType();
337: }
338:
339: public function isArray(): TrinaryLogic
340: {
341: return TrinaryLogic::createYes();
342: }
343:
344: public function isConstantArray(): TrinaryLogic
345: {
346: return TrinaryLogic::createMaybe();
347: }
348:
349: public function isOversizedArray(): TrinaryLogic
350: {
351: return TrinaryLogic::createMaybe();
352: }
353:
354: public function isList(): TrinaryLogic
355: {
356: return TrinaryLogic::createMaybe();
357: }
358:
359: public function isNull(): TrinaryLogic
360: {
361: return TrinaryLogic::createNo();
362: }
363:
364: public function isConstantValue(): TrinaryLogic
365: {
366: return TrinaryLogic::createMaybe();
367: }
368:
369: public function isConstantScalarValue(): TrinaryLogic
370: {
371: return TrinaryLogic::createNo();
372: }
373:
374: public function getConstantScalarTypes(): array
375: {
376: return [];
377: }
378:
379: public function getConstantScalarValues(): array
380: {
381: return [];
382: }
383:
384: public function isTrue(): TrinaryLogic
385: {
386: return TrinaryLogic::createNo();
387: }
388:
389: public function isFalse(): TrinaryLogic
390: {
391: return TrinaryLogic::createNo();
392: }
393:
394: public function isBoolean(): TrinaryLogic
395: {
396: return TrinaryLogic::createNo();
397: }
398:
399: public function isFloat(): TrinaryLogic
400: {
401: return TrinaryLogic::createNo();
402: }
403:
404: public function isInteger(): TrinaryLogic
405: {
406: return TrinaryLogic::createNo();
407: }
408:
409: public function isString(): TrinaryLogic
410: {
411: return TrinaryLogic::createNo();
412: }
413:
414: public function isNumericString(): TrinaryLogic
415: {
416: return TrinaryLogic::createNo();
417: }
418:
419: public function isDecimalIntegerString(): TrinaryLogic
420: {
421: return TrinaryLogic::createNo();
422: }
423:
424: public function isNonEmptyString(): TrinaryLogic
425: {
426: return TrinaryLogic::createNo();
427: }
428:
429: public function isNonFalsyString(): TrinaryLogic
430: {
431: return TrinaryLogic::createNo();
432: }
433:
434: public function isLiteralString(): TrinaryLogic
435: {
436: return TrinaryLogic::createNo();
437: }
438:
439: public function isLowercaseString(): TrinaryLogic
440: {
441: return TrinaryLogic::createNo();
442: }
443:
444: public function isClassString(): TrinaryLogic
445: {
446: return TrinaryLogic::createNo();
447: }
448:
449: public function isUppercaseString(): TrinaryLogic
450: {
451: return TrinaryLogic::createNo();
452: }
453:
454: public function getClassStringObjectType(): Type
455: {
456: return new ErrorType();
457: }
458:
459: public function getObjectTypeOrClassStringObjectType(): Type
460: {
461: return new ErrorType();
462: }
463:
464: public function isVoid(): TrinaryLogic
465: {
466: return TrinaryLogic::createNo();
467: }
468:
469: public function isScalar(): TrinaryLogic
470: {
471: return TrinaryLogic::createNo();
472: }
473:
474: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
475: {
476: if ($type->isArray()->yes() && $type->isIterableAtLeastOnce()->no()) {
477: return new ConstantBooleanType(false);
478: }
479:
480: return new BooleanType();
481: }
482:
483: public function toNumber(): Type
484: {
485: return new ErrorType();
486: }
487:
488: public function toBitwiseNotType(): Type
489: {
490: return new ErrorType();
491: }
492:
493: public function toAbsoluteNumber(): Type
494: {
495: return new ErrorType();
496: }
497:
498: public function toInteger(): Type
499: {
500: return new ConstantIntegerType(1);
501: }
502:
503: public function toFloat(): Type
504: {
505: return new ConstantFloatType(1.0);
506: }
507:
508: public function toString(): Type
509: {
510: return new ErrorType();
511: }
512:
513: public function toArray(): Type
514: {
515: return $this;
516: }
517:
518: public function toArrayKey(): Type
519: {
520: return new ErrorType();
521: }
522:
523: public function toCoercedArgumentType(bool $strictTypes): Type
524: {
525: return $this;
526: }
527:
528: public function traverse(callable $cb): Type
529: {
530: return $this;
531: }
532:
533: public function traverseSimultaneously(Type $right, callable $cb): Type
534: {
535: return $this;
536: }
537:
538: public function exponentiate(Type $exponent): Type
539: {
540: return new ErrorType();
541: }
542:
543: public function getFiniteTypes(): array
544: {
545: return [];
546: }
547:
548: public function getDefaultBaseType(): Type
549: {
550: return new ArrayType(new MixedType(), new MixedType());
551: }
552:
553: public function toPhpDocNode(): TypeNode
554: {
555: return new IdentifierTypeNode('non-empty-array');
556: }
557:
558: public function hasTemplateOrLateResolvableType(): bool
559: {
560: return false;
561: }
562:
563: }
564: