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