1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use PHPStan\Php\PhpVersion;
6: use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
7: use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
8: use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
9: use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
10: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
11: use PHPStan\Reflection\InitializerExprTypeResolver;
12: use PHPStan\TrinaryLogic;
13: use PHPStan\Turbo\ShadowedByTurboExtension;
14: use PHPStan\Type\Accessory\AccessoryDecimalIntegerStringType;
15: use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
16: use PHPStan\Type\Constant\ConstantBooleanType;
17: use PHPStan\Type\Constant\ConstantIntegerType;
18: use function array_filter;
19: use function array_map;
20: use function assert;
21: use function ceil;
22: use function count;
23: use function floor;
24: use function get_class;
25: use function is_float;
26: use function is_int;
27: use function max;
28: use function min;
29: use function sprintf;
30: use const PHP_INT_MAX;
31: use const PHP_INT_MIN;
32:
33: /** @api */
34: #[ShadowedByTurboExtension(implementation: __DIR__ . '/../../turbo-ext/src/IntegerRangeType.cpp')]
35: class IntegerRangeType extends IntegerType implements CompoundType
36: {
37:
38: private function __construct(private ?int $min, private ?int $max)
39: {
40: parent::__construct();
41: assert($min === null || $max === null || $min <= $max);
42: assert($min !== null || $max !== null);
43: }
44:
45: public static function fromInterval(?int $min, ?int $max, int $shift = 0): Type
46: {
47: if ($min !== null && $max !== null) {
48: if ($min > $max) {
49: return new NeverType();
50: }
51: if ($min === $max) {
52: return new ConstantIntegerType($min + $shift);
53: }
54: }
55:
56: if ($min === null && $max === null) {
57: return new IntegerType();
58: }
59:
60: $range = (new self($min, $max))->shift($shift);
61: if (!$range instanceof self) {
62: return $range;
63: }
64:
65: // Nothing is smaller than the smallest integer, and nothing is bigger than the biggest one,
66: // so an unbounded side that reaches either one holds a single value.
67: if ($range->min === null && $range->max === PHP_INT_MIN) {
68: return new ConstantIntegerType(PHP_INT_MIN);
69: }
70: if ($range->min === PHP_INT_MAX && $range->max === null) {
71: return new ConstantIntegerType(PHP_INT_MAX);
72: }
73:
74: return $range;
75: }
76:
77: protected static function isDisjoint(?int $minA, ?int $maxA, ?int $minB, ?int $maxB, bool $touchingIsDisjoint = true): bool
78: {
79: $offset = $touchingIsDisjoint ? 0 : 1;
80: return $minA !== null && $maxB !== null && $minA > $maxB + $offset
81: || $maxA !== null && $minB !== null && $maxA + $offset < $minB;
82: }
83:
84: /**
85: * Return the range of integers smaller than the given value
86: *
87: * @param int|float $value
88: */
89: public static function createAllSmallerThan($value): Type
90: {
91: if (is_int($value)) {
92: return self::fromInterval(null, $value, -1);
93: }
94:
95: // a float never holds PHP_INT_MAX itself, so the first float past the
96: // int range is (float) PHP_INT_MAX: every int is smaller than that
97: if ($value >= PHP_INT_MAX) {
98: return new IntegerType();
99: }
100:
101: if ($value <= PHP_INT_MIN) {
102: return new NeverType();
103: }
104:
105: return self::fromInterval(null, (int) ceil($value), -1);
106: }
107:
108: /**
109: * Return the range of integers smaller than or equal to the given value
110: *
111: * @param int|float $value
112: */
113: public static function createAllSmallerThanOrEqualTo($value): Type
114: {
115: if (is_int($value)) {
116: return self::fromInterval(null, $value);
117: }
118:
119: if ($value >= PHP_INT_MAX) {
120: return new IntegerType();
121: }
122:
123: if ($value < PHP_INT_MIN) {
124: return new NeverType();
125: }
126:
127: return self::fromInterval(null, (int) floor($value));
128: }
129:
130: /**
131: * Return the range of integers greater than the given value
132: *
133: * @param int|float $value
134: */
135: public static function createAllGreaterThan($value): Type
136: {
137: if (is_int($value)) {
138: return self::fromInterval($value, null, 1);
139: }
140:
141: if ($value < PHP_INT_MIN) {
142: return new IntegerType();
143: }
144:
145: if ($value >= PHP_INT_MAX) {
146: return new NeverType();
147: }
148:
149: return self::fromInterval((int) floor($value), null, 1);
150: }
151:
152: /**
153: * Return the range of integers greater than or equal to the given value
154: *
155: * @param int|float $value
156: */
157: public static function createAllGreaterThanOrEqualTo($value): Type
158: {
159: if (is_int($value)) {
160: return self::fromInterval($value, null);
161: }
162:
163: if ($value <= PHP_INT_MIN) {
164: return new IntegerType();
165: }
166:
167: // (float) PHP_INT_MAX is already past the int range, so no int
168: // reaches it
169: if ($value >= PHP_INT_MAX) {
170: return new NeverType();
171: }
172:
173: return self::fromInterval((int) ceil($value), null);
174: }
175:
176: public function getMin(): ?int
177: {
178: return $this->min;
179: }
180:
181: public function getMax(): ?int
182: {
183: return $this->max;
184: }
185:
186: public function describe(VerbosityLevel $level): string
187: {
188: return sprintf('int<%s, %s>', $this->min ?? 'min', $this->max ?? 'max');
189: }
190:
191: public function shift(int $amount): Type
192: {
193: if ($amount === 0) {
194: return $this;
195: }
196:
197: $min = $this->min;
198: $max = $this->max;
199:
200: if ($amount < 0) {
201: if ($max !== null) {
202: if ($max < PHP_INT_MIN - $amount) {
203: return new NeverType();
204: }
205: $max += $amount;
206: }
207: if ($min !== null) {
208: $min = $min < PHP_INT_MIN - $amount ? null : $min + $amount;
209: }
210: } else {
211: if ($min !== null) {
212: if ($min > PHP_INT_MAX - $amount) {
213: return new NeverType();
214: }
215: $min += $amount;
216: }
217: if ($max !== null) {
218: $max = $max > PHP_INT_MAX - $amount ? null : $max + $amount;
219: }
220: }
221:
222: return self::fromInterval($min, $max);
223: }
224:
225: public function accepts(Type $type, bool $strictTypes): AcceptsResult
226: {
227: if ($type instanceof parent) {
228: return $this->isSuperTypeOf($type)->toAcceptsResult();
229: }
230:
231: if ($type instanceof CompoundType) {
232: return $type->isAcceptedBy($this, $strictTypes);
233: }
234:
235: return AcceptsResult::createNo();
236: }
237:
238: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
239: {
240: if ($type instanceof self || $type instanceof ConstantIntegerType) {
241: if ($type instanceof self) {
242: $typeMin = $type->min;
243: $typeMax = $type->max;
244: } else {
245: $typeMin = $type->getValue();
246: $typeMax = $type->getValue();
247: }
248:
249: if (self::isDisjoint($this->min, $this->max, $typeMin, $typeMax)) {
250: return IsSuperTypeOfResult::createNo();
251: }
252:
253: if (
254: ($this->min === null || $typeMin !== null && $this->min <= $typeMin)
255: && ($this->max === null || $typeMax !== null && $this->max >= $typeMax)
256: ) {
257: return IsSuperTypeOfResult::createYes();
258: }
259:
260: return IsSuperTypeOfResult::createMaybe();
261: }
262:
263: if ($type instanceof parent) {
264: return IsSuperTypeOfResult::createMaybe();
265: }
266:
267: if ($type instanceof CompoundType) {
268: return $type->isSubTypeOf($this);
269: }
270:
271: return IsSuperTypeOfResult::createNo();
272: }
273:
274: public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult
275: {
276: if ($otherType instanceof parent) {
277: return $otherType->isSuperTypeOf($this);
278: }
279:
280: if ($otherType instanceof UnionType) {
281: return $this->isSubTypeOfUnionWithReason($otherType);
282: }
283:
284: if ($otherType instanceof IntersectionType) {
285: return $otherType->isSuperTypeOf($this);
286: }
287:
288: return IsSuperTypeOfResult::createNo();
289: }
290:
291: private function isSubTypeOfUnionWithReason(UnionType $otherType): IsSuperTypeOfResult
292: {
293: if ($this->min !== null && $this->max !== null) {
294: $matchingConstantIntegers = array_filter(
295: $otherType->getTypes(),
296: fn (Type $type): bool => $type instanceof ConstantIntegerType && $type->getValue() >= $this->min && $type->getValue() <= $this->max,
297: );
298:
299: if (count($matchingConstantIntegers) === ($this->max - $this->min + 1)) {
300: return IsSuperTypeOfResult::createYes();
301: }
302: }
303:
304: return IsSuperTypeOfResult::createNo()->or(...array_map(fn (Type $innerType) => $this->isSubTypeOf($innerType), $otherType->getTypes()));
305: }
306:
307: public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult
308: {
309: return $this->isSubTypeOf($acceptingType)->toAcceptsResult();
310: }
311:
312: public function equals(Type $type): bool
313: {
314: return $type instanceof self && $this->min === $type->min && $this->max === $type->max;
315: }
316:
317: public function generalize(GeneralizePrecision $precision): Type
318: {
319: return new IntegerType();
320: }
321:
322: public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic
323: {
324: if ($this->min === null) {
325: $minIsSmaller = TrinaryLogic::createYes();
326: } else {
327: $minIsSmaller = (new ConstantIntegerType($this->min))->isSmallerThan($otherType, $phpVersion);
328: }
329:
330: if ($this->max === null) {
331: $maxIsSmaller = TrinaryLogic::createNo();
332: } else {
333: $maxIsSmaller = (new ConstantIntegerType($this->max))->isSmallerThan($otherType, $phpVersion);
334: }
335:
336: // 0 can have different results in contrast to the interval edges, see https://3v4l.org/iGoti
337: $zeroInt = new ConstantIntegerType(0);
338: if (!$zeroInt->isSuperTypeOf($this)->no()) {
339: return TrinaryLogic::extremeIdentity(
340: $zeroInt->isSmallerThan($otherType, $phpVersion),
341: $minIsSmaller,
342: $maxIsSmaller,
343: );
344: }
345:
346: return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller);
347: }
348:
349: public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic
350: {
351: if ($this->min === null) {
352: $minIsSmaller = TrinaryLogic::createYes();
353: } else {
354: $minIsSmaller = (new ConstantIntegerType($this->min))->isSmallerThanOrEqual($otherType, $phpVersion);
355: }
356:
357: if ($this->max === null) {
358: $maxIsSmaller = TrinaryLogic::createNo();
359: } else {
360: $maxIsSmaller = (new ConstantIntegerType($this->max))->isSmallerThanOrEqual($otherType, $phpVersion);
361: }
362:
363: // 0 can have different results in contrast to the interval edges, see https://3v4l.org/iGoti
364: $zeroInt = new ConstantIntegerType(0);
365: if (!$zeroInt->isSuperTypeOf($this)->no()) {
366: return TrinaryLogic::extremeIdentity(
367: $zeroInt->isSmallerThanOrEqual($otherType, $phpVersion),
368: $minIsSmaller,
369: $maxIsSmaller,
370: );
371: }
372:
373: return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller);
374: }
375:
376: public function isGreaterThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic
377: {
378: if ($this->min === null) {
379: $minIsSmaller = TrinaryLogic::createNo();
380: } else {
381: $minIsSmaller = $otherType->isSmallerThan(new ConstantIntegerType($this->min), $phpVersion);
382: }
383:
384: if ($this->max === null) {
385: $maxIsSmaller = TrinaryLogic::createYes();
386: } else {
387: $maxIsSmaller = $otherType->isSmallerThan(new ConstantIntegerType($this->max), $phpVersion);
388: }
389:
390: // 0 can have different results in contrast to the interval edges, see https://3v4l.org/iGoti
391: $zeroInt = new ConstantIntegerType(0);
392: if (!$zeroInt->isSuperTypeOf($this)->no()) {
393: return TrinaryLogic::extremeIdentity(
394: $otherType->isSmallerThan($zeroInt, $phpVersion),
395: $minIsSmaller,
396: $maxIsSmaller,
397: );
398: }
399:
400: return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller);
401: }
402:
403: public function isGreaterThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic
404: {
405: if ($this->min === null) {
406: $minIsSmaller = TrinaryLogic::createNo();
407: } else {
408: $minIsSmaller = $otherType->isSmallerThanOrEqual(new ConstantIntegerType($this->min), $phpVersion);
409: }
410:
411: if ($this->max === null) {
412: $maxIsSmaller = TrinaryLogic::createYes();
413: } else {
414: $maxIsSmaller = $otherType->isSmallerThanOrEqual(new ConstantIntegerType($this->max), $phpVersion);
415: }
416:
417: // 0 can have different results in contrast to the interval edges, see https://3v4l.org/iGoti
418: $zeroInt = new ConstantIntegerType(0);
419: if (!$zeroInt->isSuperTypeOf($this)->no()) {
420: return TrinaryLogic::extremeIdentity(
421: $otherType->isSmallerThanOrEqual($zeroInt, $phpVersion),
422: $minIsSmaller,
423: $maxIsSmaller,
424: );
425: }
426:
427: return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller);
428: }
429:
430: public function getSmallerType(PhpVersion $phpVersion): Type
431: {
432: $subtractedTypes = [
433: new ConstantBooleanType(true),
434: ];
435:
436: if ($this->max !== null) {
437: $subtractedTypes[] = self::createAllGreaterThanOrEqualTo($this->max);
438: }
439:
440: return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes));
441: }
442:
443: public function getSmallerOrEqualType(PhpVersion $phpVersion): Type
444: {
445: $subtractedTypes = [];
446:
447: if ($this->max !== null) {
448: $subtractedTypes[] = self::createAllGreaterThan($this->max);
449: }
450:
451: return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes));
452: }
453:
454: public function getGreaterType(PhpVersion $phpVersion): Type
455: {
456: $subtractedTypes = [
457: new NullType(),
458: new ConstantBooleanType(false),
459: ];
460:
461: if ($this->min !== null) {
462: $subtractedTypes[] = self::createAllSmallerThanOrEqualTo($this->min);
463: }
464:
465: if ($this->min !== null && $this->min > 0 || $this->max !== null && $this->max < 0) {
466: $subtractedTypes[] = new ConstantBooleanType(true);
467: }
468:
469: return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes));
470: }
471:
472: public function getGreaterOrEqualType(PhpVersion $phpVersion): Type
473: {
474: $subtractedTypes = [];
475:
476: if ($this->min !== null) {
477: $subtractedTypes[] = self::createAllSmallerThan($this->min);
478: }
479:
480: if ($this->min !== null && $this->min > 0 || $this->max !== null && $this->max < 0) {
481: $subtractedTypes[] = new NullType();
482: $subtractedTypes[] = new ConstantBooleanType(false);
483: }
484:
485: return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes));
486: }
487:
488: public function toBoolean(): BooleanType
489: {
490: $isZero = (new ConstantIntegerType(0))->isSuperTypeOf($this);
491: if ($isZero->no()) {
492: return new ConstantBooleanType(true);
493: }
494:
495: if ($isZero->maybe()) {
496: return new BooleanType();
497: }
498:
499: return new ConstantBooleanType(false);
500: }
501:
502: public function toAbsoluteNumber(): Type
503: {
504: if ($this->min !== null && $this->min >= 0) {
505: return $this;
506: }
507:
508: // Negating the smallest integer overflows, so its absolute value is treated as unbounded,
509: // the same way an unbounded lower bound is. This keeps abs(int<min, 0>) and
510: // abs(int<-9223372036854775808, 0>) in agreement.
511: $inversedMin = $this->min !== null && $this->min !== PHP_INT_MIN ? -$this->min : null;
512:
513: if ($this->max === null || $this->max >= 0) {
514: return self::fromInterval(0, $inversedMin !== null && $this->max !== null ? max($inversedMin, $this->max) : null);
515: }
516:
517: return self::fromInterval(-$this->max, $inversedMin);
518: }
519:
520: public function toBitwiseNotType(): Type
521: {
522: // ~int<a, b> = int<~b, ~a> (bitwise NOT reverses the order)
523: return self::fromInterval(
524: $this->max !== null ? ~$this->max : null,
525: $this->min !== null ? ~$this->min : null,
526: );
527: }
528:
529: public function toString(): Type
530: {
531: $finiteTypes = $this->getFiniteTypes();
532: if ($finiteTypes !== []) {
533: return TypeCombinator::union(...$finiteTypes)->toString();
534: }
535:
536: $isZero = (new ConstantIntegerType(0))->isSuperTypeOf($this);
537: if ($isZero->no()) {
538: return new IntersectionType([
539: new StringType(),
540: new AccessoryDecimalIntegerStringType(),
541: new AccessoryNonFalsyStringType(),
542: ]);
543: }
544:
545: return new IntersectionType([
546: new StringType(),
547: new AccessoryDecimalIntegerStringType(),
548: ]);
549: }
550:
551: /**
552: * Return the union with another type, but only if it can be expressed in a simpler way than using UnionType
553: *
554: */
555: public function tryUnion(Type $otherType): ?Type
556: {
557: if ($otherType instanceof self || $otherType instanceof ConstantIntegerType) {
558: if ($otherType instanceof self) {
559: $otherMin = $otherType->min;
560: $otherMax = $otherType->max;
561: } else {
562: $otherMin = $otherType->getValue();
563: $otherMax = $otherType->getValue();
564: }
565:
566: if (self::isDisjoint($this->min, $this->max, $otherMin, $otherMax, false)) {
567: return null;
568: }
569:
570: return self::fromInterval(
571: $this->min !== null && $otherMin !== null ? min($this->min, $otherMin) : null,
572: $this->max !== null && $otherMax !== null ? max($this->max, $otherMax) : null,
573: );
574: }
575:
576: if (get_class($otherType) === parent::class) {
577: return $otherType;
578: }
579:
580: return null;
581: }
582:
583: /**
584: * Return the intersection with another type, but only if it can be expressed in a simpler way than using
585: * IntersectionType
586: *
587: */
588: public function tryIntersect(Type $otherType): ?Type
589: {
590: if ($otherType instanceof self || $otherType instanceof ConstantIntegerType) {
591: if ($otherType instanceof self) {
592: $otherMin = $otherType->min;
593: $otherMax = $otherType->max;
594: } else {
595: $otherMin = $otherType->getValue();
596: $otherMax = $otherType->getValue();
597: }
598:
599: if (self::isDisjoint($this->min, $this->max, $otherMin, $otherMax, false)) {
600: return new NeverType();
601: }
602:
603: if ($this->min === null) {
604: $newMin = $otherMin;
605: } elseif ($otherMin === null) {
606: $newMin = $this->min;
607: } else {
608: $newMin = max($this->min, $otherMin);
609: }
610:
611: if ($this->max === null) {
612: $newMax = $otherMax;
613: } elseif ($otherMax === null) {
614: $newMax = $this->max;
615: } else {
616: $newMax = min($this->max, $otherMax);
617: }
618:
619: return self::fromInterval($newMin, $newMax);
620: }
621:
622: if (get_class($otherType) === parent::class) {
623: return $this;
624: }
625:
626: return null;
627: }
628:
629: /**
630: * Return the different with another type, or null if it cannot be represented.
631: *
632: */
633: public function tryRemove(Type $typeToRemove): ?Type
634: {
635: if (get_class($typeToRemove) === parent::class) {
636: return new NeverType();
637: }
638:
639: if ($typeToRemove instanceof self || $typeToRemove instanceof ConstantIntegerType) {
640: if ($typeToRemove instanceof self) {
641: $removeMin = $typeToRemove->min;
642: $removeMax = $typeToRemove->max;
643: } else {
644: $removeMin = $typeToRemove->getValue();
645: $removeMax = $typeToRemove->getValue();
646: }
647:
648: if (
649: $this->min !== null && $removeMax !== null && $removeMax < $this->min
650: || $this->max !== null && $removeMin !== null && $this->max < $removeMin
651: ) {
652: return $this;
653: }
654:
655: if ($removeMin !== null && $removeMin !== PHP_INT_MIN) {
656: $lowerPart = self::fromInterval($this->min, $removeMin - 1);
657: } else {
658: $lowerPart = null;
659: }
660: if ($removeMax !== null && $removeMax !== PHP_INT_MAX) {
661: $upperPart = self::fromInterval($removeMax + 1, $this->max);
662: } else {
663: $upperPart = null;
664: }
665:
666: if ($lowerPart !== null && $upperPart !== null) {
667: return TypeCombinator::union($lowerPart, $upperPart);
668: }
669:
670: return $lowerPart ?? $upperPart;
671: }
672:
673: return null;
674: }
675:
676: public function exponentiate(Type $exponent): Type
677: {
678: if ($exponent instanceof UnionType) {
679: $results = [];
680: foreach ($exponent->getTypes() as $unionType) {
681: $results[] = $this->exponentiate($unionType);
682: }
683: return TypeCombinator::union(...$results);
684: }
685:
686: if ($exponent instanceof IntegerRangeType) {
687: $min = null;
688: $max = null;
689: if ($this->getMin() !== null && $exponent->getMin() !== null) {
690: $min = $this->getMin() ** $exponent->getMin();
691: }
692: if ($this->getMax() !== null && $exponent->getMax() !== null) {
693: $max = $this->getMax() ** $exponent->getMax();
694: }
695:
696: if (($min !== null || $max !== null) && !is_float($min) && !is_float($max)) {
697: return self::fromInterval($min, $max);
698: }
699: }
700:
701: if ($exponent instanceof ConstantScalarType) {
702: $exponentValue = $exponent->getValue();
703: if (is_int($exponentValue)) {
704: $min = null;
705: $max = null;
706: if ($this->getMin() !== null) {
707: $min = $this->getMin() ** $exponentValue;
708: }
709: if ($this->getMax() !== null) {
710: $max = $this->getMax() ** $exponentValue;
711: }
712:
713: if (!is_float($min) && !is_float($max)) {
714: return self::fromInterval($min, $max);
715: }
716: }
717: }
718:
719: return parent::exponentiate($exponent);
720: }
721:
722: /**
723: * @return list<ConstantIntegerType>
724: */
725: public function getFiniteTypes(): array
726: {
727: if ($this->min === null || $this->max === null) {
728: return [];
729: }
730:
731: $size = $this->max - $this->min;
732: if ($size > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT) {
733: return [];
734: }
735:
736: $types = [];
737: for ($i = 0; $i <= $size; $i++) {
738: // $this->min + $size is $this->max, so nothing here overflows —
739: // unlike $i++ past PHP_INT_MAX, which would turn $i into a float
740: $types[] = new ConstantIntegerType($this->min + $i);
741: }
742:
743: return $types;
744: }
745:
746: public function toPhpDocNode(): TypeNode
747: {
748: if ($this->min === null) {
749: $min = new IdentifierTypeNode('min');
750: } else {
751: $min = new ConstTypeNode(new ConstExprIntegerNode((string) $this->min));
752: }
753:
754: if ($this->max === null) {
755: $max = new IdentifierTypeNode('max');
756: } else {
757: $max = new ConstTypeNode(new ConstExprIntegerNode((string) $this->max));
758: }
759:
760: return new GenericTypeNode(new IdentifierTypeNode('int'), [$min, $max]);
761: }
762:
763: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
764: {
765: $zeroInt = new ConstantIntegerType(0);
766: if ($zeroInt->isSuperTypeOf($this)->no()) {
767: if ($type->isTrue()->yes()) {
768: return new ConstantBooleanType(true);
769: }
770: if ($type->isFalse()->yes()) {
771: return new ConstantBooleanType(false);
772: }
773: }
774:
775: if (
776: $this->isSmallerThan($type, $phpVersion)->yes()
777: || $this->isGreaterThan($type, $phpVersion)->yes()
778: ) {
779: return new ConstantBooleanType(false);
780: }
781:
782: return parent::looseCompare($type, $phpVersion);
783: }
784:
785: }
786: