From c52ffc6f2d0b0d5c07783b470b6851451dd42074 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Mon, 10 Aug 2026 20:47:04 +0000 Subject: [PATCH 1/2] Include the `PHP_INT_MIN` float overflow in `abs()` and unary minus of unbounded integer types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * `IntegerType::toAbsoluteNumber()` now returns `int<0, max>|9.223372036854776E+18` instead of just `int<0, max>`, because `abs(PHP_INT_MIN)` is not representable as an int. * `IntegerRangeType::toAbsoluteNumber()` unions the same constant float whenever the range can hold `PHP_INT_MIN` (unbounded lower bound or `min === PHP_INT_MIN`), and returns the float alone for the range that holds nothing but `PHP_INT_MIN`. Ranges starting at `PHP_INT_MIN + 1` or higher stay pure integer ranges. * `InitializerExprTypeResolver::getUnaryMinusType()` applies the same overflow to `-$x` for non-constant integer types; constants were already handled. `int<0, max>`, `int<-5, 5>` and other ranges that exclude `PHP_INT_MIN` are unaffected, and unary plus is untouched. * `ConstantIntegerType::toAbsoluteNumber()` now checks `is_int(abs($this->value))` directly – that used to be dead code to PHPStan itself and is exactly what this change fixes. * Dropped the now-superfluous `@var int|float` in `getUnaryMinusTypeFromType()` plus its baseline entry, since PHPStan now infers `-$scalarValue` as `float|int` natively. * Probed adjacent code paths: `UnionType`/`IntersectionType` delegate correctly, `FloatType`/`ConstantFloatType`/`StringType`/array types cannot overflow, and `int`-typed binary operators (`+`, `*`, `-`) intentionally keep ignoring overflow, so they were left alone. --- phpstan-baseline.neon | 6 ---- .../InitializerExprTypeResolver.php | 19 ++++++++--- src/Type/Constant/ConstantIntegerType.php | 11 +++---- src/Type/IntegerRangeType.php | 24 ++++++++++---- src/Type/IntegerType.php | 8 ++++- tests/PHPStan/Analyser/Fiber/data/fnsr.php | 4 +-- tests/PHPStan/Analyser/data/abs-64bit.php | 19 +++++++++-- .../Analyser/data/unary-minus-64bit.php | 30 +++++++++++++++-- tests/PHPStan/Analyser/nsrt/abs.php | 20 +++++------ tests/PHPStan/Analyser/nsrt/binary.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-9224b.php | 4 +-- .../Analyser/nsrt/integer-range-types.php | 2 +- .../BaselineNeonErrorFormatterTest.php | 3 +- .../Rules/Functions/ReturnTypeRuleTest.php | 16 +++++++++ .../Rules/Functions/data/bug-15069.php | 33 +++++++++++++++++++ 15 files changed, 155 insertions(+), 46 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-15069.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 76efc63f397..28bfc7ab049 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -489,12 +489,6 @@ parameters: count: 6 path: src/Reflection/InitializerExprTypeResolver.php - - - rawMessage: PHPDoc tag @var with type float|int is not subtype of native type int. - identifier: varTag.nativeType - count: 1 - path: src/Reflection/InitializerExprTypeResolver.php - - rawMessage: PHPDoc tag @var with type float|int is not subtype of type int. identifier: varTag.type diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 7332aab8f36..d9e73843383 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -122,6 +122,7 @@ use function str_starts_with; use function strtolower; use const INF; +use const PHP_INT_MIN; #[AutowiredService] final class InitializerExprTypeResolver @@ -2607,12 +2608,21 @@ public function getUnaryMinusType(Expr $expr, callable $getTypeCallback): Type return $specifiedTypes; } - $type = $this->getUnaryMinusTypeFromType($expr, $type); - if ($type instanceof IntegerRangeType) { - return $getTypeCallback(new Expr\BinaryOp\Mul($expr, new Int_(-1))); + $negatedType = $this->getUnaryMinusTypeFromType($expr, $type); + if ($negatedType instanceof IntegerRangeType) { + $negatedType = $getTypeCallback(new Expr\BinaryOp\Mul($expr, new Int_(-1))); } - return $type; + $numberType = $type->toNumber(); + if ( + $numberType->isInteger()->yes() + && !(new ConstantIntegerType(PHP_INT_MIN))->isSuperTypeOf($numberType)->no() + ) { + // Negating the smallest integer overflows into a float. + return TypeCombinator::union($negatedType, new ConstantFloatType(-(float) PHP_INT_MIN)); + } + + return $negatedType; } public function getUnaryMinusTypeFromType(Expr $expr, Type $type): Type @@ -2624,7 +2634,6 @@ public function getUnaryMinusTypeFromType(Expr $expr, Type $type): Type $newTypes = []; foreach ($scalarValues as $scalarValue) { if (is_int($scalarValue)) { - /** @var int|float $newValue */ $newValue = -$scalarValue; if (!is_int($newValue)) { // Negating the smallest integer overflows into a float. diff --git a/src/Type/Constant/ConstantIntegerType.php b/src/Type/Constant/ConstantIntegerType.php index 37965c001e9..c5fa20607a6 100644 --- a/src/Type/Constant/ConstantIntegerType.php +++ b/src/Type/Constant/ConstantIntegerType.php @@ -17,8 +17,8 @@ use PHPStan\Type\TypeCombinator; use PHPStan\Type\VerbosityLevel; use function abs; +use function is_int; use function sprintf; -use const PHP_INT_MIN; /** @api */ class ConstantIntegerType extends IntegerType implements ConstantScalarType @@ -86,14 +86,13 @@ public function toBitwiseNotType(): Type public function toAbsoluteNumber(): Type { - if ($this->value === PHP_INT_MIN) { + $absoluteValue = abs($this->value); + if (!is_int($absoluteValue)) { // The absolute value of the smallest integer is not representable as an int. - // Checking is_int(abs($this->value)) instead is dead code to PHPStan itself, - // which infers abs(int) as int<0, max>. - return new ConstantFloatType(-(float) $this->value); + return new ConstantFloatType($absoluteValue); } - return new self(abs($this->value)); + return new self($absoluteValue); } public function toString(): Type diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index 605507008b3..a20324a9ca5 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -13,6 +13,7 @@ use PHPStan\Type\Accessory\AccessoryDecimalIntegerStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; use function array_filter; use function array_map; @@ -499,16 +500,27 @@ public function toAbsoluteNumber(): Type return $this; } - // Negating the smallest integer overflows, so its absolute value is treated as unbounded, - // the same way an unbounded lower bound is. This keeps abs(int) and - // abs(int<-9223372036854775808, 0>) in agreement. - $inversedMin = $this->min !== null && $this->min !== PHP_INT_MIN ? -$this->min : null; + // The absolute value of the smallest integer overflows into a float. + $overflowType = new ConstantFloatType(-(float) PHP_INT_MIN); + if ($this->max !== null && $this->max <= PHP_INT_MIN) { + return $overflowType; + } + + // Without the smallest integer the absolute values only reach PHP_INT_MAX, + // which is what an unbounded upper bound stands for. + $inversedMin = $this->min !== null && $this->min > PHP_INT_MIN ? -$this->min : null; if ($this->max === null || $this->max >= 0) { - return self::fromInterval(0, $inversedMin !== null && $this->max !== null ? max($inversedMin, $this->max) : null); + $absoluteRange = self::fromInterval(0, $inversedMin !== null && $this->max !== null ? max($inversedMin, $this->max) : null); + } else { + $absoluteRange = self::fromInterval(-$this->max, $inversedMin); + } + + if ($inversedMin !== null) { + return $absoluteRange; } - return self::fromInterval(-$this->max, $inversedMin); + return TypeCombinator::union($absoluteRange, $overflowType); } public function toString(): Type diff --git a/src/Type/IntegerType.php b/src/Type/IntegerType.php index 2ba3ad1a080..deddeef61fb 100644 --- a/src/Type/IntegerType.php +++ b/src/Type/IntegerType.php @@ -9,6 +9,7 @@ use PHPStan\Type\Accessory\AccessoryDecimalIntegerStringType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Traits\NonArrayTypeTrait; use PHPStan\Type\Traits\NonCallableTypeTrait; @@ -19,6 +20,7 @@ use PHPStan\Type\Traits\NonOffsetAccessibleTypeTrait; use PHPStan\Type\Traits\UndecidedBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; +use const PHP_INT_MIN; /** @api */ #[InstanceofDeprecated(insteadUse: 'Type::isInteger()')] @@ -63,7 +65,11 @@ public function toBitwiseNotType(): Type public function toAbsoluteNumber(): Type { - return IntegerRangeType::createAllGreaterThanOrEqualTo(0); + return TypeCombinator::union( + IntegerRangeType::createAllGreaterThanOrEqualTo(0), + // The absolute value of the smallest integer overflows into a float. + new ConstantFloatType(-(float) PHP_INT_MIN), + ); } public function toFloat(): Type diff --git a/tests/PHPStan/Analyser/Fiber/data/fnsr.php b/tests/PHPStan/Analyser/Fiber/data/fnsr.php index ccfeaa3e85a..9145276025b 100644 --- a/tests/PHPStan/Analyser/Fiber/data/fnsr.php +++ b/tests/PHPStan/Analyser/Fiber/data/fnsr.php @@ -212,8 +212,8 @@ function doUnaryMinus(int $i) { assertType('-1', -$a); assertNativeType('-1', -$a); - assertType('int', -$i); - assertNativeType('int', -$i); + assertType('9.223372036854776E+18|int', -$i); + assertNativeType('9.223372036854776E+18|int', -$i); } /** diff --git a/tests/PHPStan/Analyser/data/abs-64bit.php b/tests/PHPStan/Analyser/data/abs-64bit.php index c1f34ae9015..127d1b86ad6 100644 --- a/tests/PHPStan/Analyser/data/abs-64bit.php +++ b/tests/PHPStan/Analyser/data/abs-64bit.php @@ -15,13 +15,20 @@ function integerRanges(int $int): void { /** @var int<-9223372036854775808, 0> $int */ - assertType('int<0, max>', abs($int)); + assertType('9.223372036854776E+18|int<0, max>', abs($int)); /** @var int<-9223372036854775808, -1> $int */ - assertType('int<1, max>', abs($int)); + assertType('9.223372036854776E+18|int<1, max>', abs($int)); /** @var int<-9223372036854775808, 9223372036854775807> $int */ - assertType('int<0, max>', abs($int)); + assertType('9.223372036854776E+18|int<0, max>', abs($int)); + + // One step away from the overflow, so the result stays an integer. + /** @var int<-9223372036854775807, 0> $int */ + assertType('int<0, 9223372036854775807>', abs($int)); + + /** @var int<-9223372036854775807, 9223372036854775807> $int */ + assertType('int<0, 9223372036854775807>', abs($int)); // IntegerRangeType::fromInterval() collapses these to a single value. /** @var int $int */ @@ -32,3 +39,9 @@ function integerRanges(int $int): void assertType('9223372036854775807', $int); assertType('9223372036854775807', abs($int)); } + +// https://github.com/phpstan/phpstan/issues/15069 +function absint($maybeint): void +{ + assertType('9.223372036854776E+18|int<0, max>', abs((int) $maybeint)); +} diff --git a/tests/PHPStan/Analyser/data/unary-minus-64bit.php b/tests/PHPStan/Analyser/data/unary-minus-64bit.php index 23105acd860..3880450145b 100644 --- a/tests/PHPStan/Analyser/data/unary-minus-64bit.php +++ b/tests/PHPStan/Analyser/data/unary-minus-64bit.php @@ -17,13 +17,39 @@ assertType('9223372036854775807', -(-9223372036854775807)); assertType('-9223372036854775807', -9223372036854775807); +function unboundedIntegers(int $int, string $numericString): void +{ + // https://github.com/phpstan/phpstan/issues/15069 + assertType('9.223372036854776E+18|int', -$int); + assertType('9.223372036854776E+18|int', -((int) $numericString)); + + // Unary plus never overflows. + assertType('int', +$int); + + /** @var numeric-string $numericString */ + assertType('float|int', -$numericString); +} + function integerRanges(int $int): void { /** @var int $int */ - assertType('int<1, max>', -$int); + assertType('9.223372036854776E+18|int<1, max>', -$int); /** @var int<-9223372036854775808, -1> $int */ - assertType('int<1, max>', -$int); + assertType('9.223372036854776E+18|int<1, max>', -$int); + + /** @var int<-9223372036854775807, -1> $int */ + assertType('int<1, 9223372036854775807>', -$int); + + /** @var int $int */ + assertType('9.223372036854776E+18|int<-5, max>', -$int); + + // The lower bound is known, so the overflow cannot happen. + /** @var int<0, max> $int */ + assertType('int', -$int); + + /** @var int<-5, 5> $int */ + assertType('int<-5, 5>', -$int); } function constantUnion(int $int): void diff --git a/tests/PHPStan/Analyser/nsrt/abs.php b/tests/PHPStan/Analyser/nsrt/abs.php index 506f436c02e..3da21a3431f 100644 --- a/tests/PHPStan/Analyser/nsrt/abs.php +++ b/tests/PHPStan/Analyser/nsrt/abs.php @@ -12,19 +12,19 @@ class Foo public function singleIntegerRange(int $int): void { /** @var int $int */ - assertType('int<0, max>', abs($int)); + assertType('9.223372036854776E+18|int<0, max>', abs($int)); /** @var positive-int $int */ assertType('int<1, max>', abs($int)); /** @var negative-int $int */ - assertType('int<1, max>', abs($int)); + assertType('9.223372036854776E+18|int<1, max>', abs($int)); /** @var non-negative-int $int */ assertType('int<0, max>', abs($int)); /** @var non-positive-int $int */ - assertType('int<0, max>', abs($int)); + assertType('9.223372036854776E+18|int<0, max>', abs($int)); /** @var int<0, max> $int */ assertType('int<0, max>', abs($int)); @@ -45,13 +45,13 @@ public function singleIntegerRange(int $int): void assertType('int<123, 456>', abs($int)); /** @var int $int */ - assertType('int<0, max>', abs($int)); + assertType('9.223372036854776E+18|int<0, max>', abs($int)); /** @var int $int */ - assertType('int<1, max>', abs($int)); + assertType('9.223372036854776E+18|int<1, max>', abs($int)); /** @var int $int */ - assertType('int<123, max>', abs($int)); + assertType('9.223372036854776E+18|int<123, max>', abs($int)); /** @var int<-456, -123> $int */ assertType('int<123, 456>', abs($int)); @@ -60,16 +60,16 @@ public function singleIntegerRange(int $int): void assertType('int<0, 123>', abs($int)); /** @var int $int */ - assertType('int<0, max>', abs($int)); + assertType('9.223372036854776E+18|int<0, max>', abs($int)); } public function multipleIntegerRanges(int $int): void { /** @var non-zero-int $int */ - assertType('int<1, max>', abs($int)); + assertType('9.223372036854776E+18|int<1, max>', abs($int)); /** @var int|int<1, max> $int */ - assertType('int<1, max>', abs($int)); + assertType('9.223372036854776E+18|int<1, max>', abs($int)); /** @var int<-20, -10>|int<5, 25> $int */ assertType('int<5, 25>', abs($int)); @@ -106,7 +106,7 @@ public function mixedIntegerUnion(int $int): void assertType('123|int<456, max>', abs($int)); /** @var int|-123 $int */ - assertType('123|int<456, max>', abs($int)); + assertType('123|9.223372036854776E+18|int<456, max>', abs($int)); /** @var -123|int<124, 125> $int */ assertType('int<123, 125>', abs($int)); diff --git a/tests/PHPStan/Analyser/nsrt/binary.php b/tests/PHPStan/Analyser/nsrt/binary.php index ce3a13e6cc6..d29b831c880 100644 --- a/tests/PHPStan/Analyser/nsrt/binary.php +++ b/tests/PHPStan/Analyser/nsrt/binary.php @@ -273,7 +273,7 @@ public function doFoo(array $generalArray) assertType('*ERROR*', -"blabla"); assertType('-5', -5); assertType('5', -(-5)); - assertType('int', -$integer); + assertType('9.223372036854776E+18|int', -$integer); assertType('-2|-1', -$conditionalInt); assertType('*ERROR*', -$string); assertType('2', 1 + 1); diff --git a/tests/PHPStan/Analyser/nsrt/bug-9224b.php b/tests/PHPStan/Analyser/nsrt/bug-9224b.php index 863d17cb85e..a7a637d312c 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-9224b.php +++ b/tests/PHPStan/Analyser/nsrt/bug-9224b.php @@ -10,8 +10,8 @@ class Foo /** @param array $arr */ public function sayHello(array $arr): void { - assertType('array>', array_map('abs', $arr)); - assertType('array>', array_map(abs(...), $arr)); + assertType('array<9.223372036854776E+18|int<0, max>>', array_map('abs', $arr)); + assertType('array<9.223372036854776E+18|int<0, max>>', array_map(abs(...), $arr)); } } diff --git a/tests/PHPStan/Analyser/nsrt/integer-range-types.php b/tests/PHPStan/Analyser/nsrt/integer-range-types.php index 8adf56f1c10..032d35dafdf 100644 --- a/tests/PHPStan/Analyser/nsrt/integer-range-types.php +++ b/tests/PHPStan/Analyser/nsrt/integer-range-types.php @@ -329,7 +329,7 @@ public function unaryMinus($r1, $r2, $rMin, $rMax, $rZero) { assertType('int<-10, -1>', -$r1); assertType('int<-10, 5>', -$r2); - assertType('int<-5, max>', -$rMin); + assertType('9.223372036854776E+18|int<-5, max>', -$rMin); assertType('int', -$rMax); assertType('int<-50, 0>', -$rZero); } diff --git a/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php index c9463f9675a..fdaacd9e139 100644 --- a/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php @@ -383,7 +383,7 @@ public function testOutputOrdering(array $errors): void } /** - * @return Generator, existingBaselineContent: string, expectedNewlinesCount: int}> + * @return Generator, existingBaselineContent: string, expectedNewlinesCount: int<0, max>}> */ public static function endOfFileNewlinesProvider(): Generator { @@ -467,6 +467,7 @@ public static function endOfFileNewlinesProvider(): Generator /** * @param list $errors + * @param int<0, max> $expectedNewlinesCount */ #[DataProvider('endOfFileNewlinesProvider')] public function testEndOfFileNewlines( diff --git a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php index 88a4b5562cf..7a567832efe 100644 --- a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php @@ -481,6 +481,22 @@ public function testBug13190(): void ]); } + public function testBug15069(): void + { + $this->checkNullables = true; + $this->checkExplicitMixed = false; + $this->analyse([__DIR__ . '/data/bug-15069.php'], [ + [ + 'Function Bug15069\absint() should return int but returns float|int<0, max>.', + 10, + ], + [ + 'Function Bug15069\negate() should return int but returns float|int.', + 24, + ], + ]); + } + public function testBug13114(): void { $this->checkNullables = true; diff --git a/tests/PHPStan/Rules/Functions/data/bug-15069.php b/tests/PHPStan/Rules/Functions/data/bug-15069.php new file mode 100644 index 00000000000..45fb29cbca1 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-15069.php @@ -0,0 +1,33 @@ + $int + */ +function negateBoundedRange(int $int): int +{ + return -$int; +} From 39ba9688263feb3533d64047316a924fcb4ec7c3 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Tue, 11 Aug 2026 13:34:07 +0000 Subject: [PATCH 2/2] Treat a float at `(float) PHP_INT_MAX` as out of the integer range `(float) PHP_INT_MAX` rounds up to `2 ** 63`, so `$value > PHP_INT_MAX` is false for it and `createAllSmallerThan()` and `createAllGreaterThanOrEqualTo()` fell through to `(int) ceil($value)`, which is out of range and wrapped around to `PHP_INT_MIN`. Both returned the exact opposite of the correct answer: IntegerRangeType::createAllSmallerThan(9.223372036854776E+18); // *NEVER* instead of int IntegerRangeType::createAllGreaterThanOrEqualTo(9.223372036854776E+18); // int instead of *NEVER* So `if ($i < 9223372036854775808)` narrowed `int` to `*NEVER*` in the truthy branch. That float is what `abs()` and unary minus of `PHP_INT_MIN` overflow into, which is how it started showing up in comparisons. Co-Authored-By: Claude Opus 5 --- src/Type/IntegerRangeType.php | 8 ++- .../Analyser/NodeScopeResolverTest.php | 1 + .../data/integer-range-float-bounds-64bit.php | 67 +++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/integer-range-float-bounds-64bit.php diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index a20324a9ca5..e1ec1ef6b85 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -91,7 +91,9 @@ public static function createAllSmallerThan($value): Type return self::fromInterval(null, $value, -1); } - if ($value > PHP_INT_MAX) { + // (float) PHP_INT_MAX rounds up to 2 ** 63, so a float that equals it + // is already bigger than every integer. + if ($value >= (float) PHP_INT_MAX) { return new IntegerType(); } @@ -161,7 +163,9 @@ public static function createAllGreaterThanOrEqualTo($value): Type return new IntegerType(); } - if ($value > PHP_INT_MAX) { + // (float) PHP_INT_MAX rounds up to 2 ** 63, so a float that equals it + // is already bigger than every integer. + if ($value >= (float) PHP_INT_MAX) { return new NeverType(); } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 24f55aad643..ff870ef81a6 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -114,6 +114,7 @@ private static function findTestFiles(): iterable yield __DIR__ . '/data/predefined-constants-64bit.php'; yield __DIR__ . '/data/abs-64bit.php'; yield __DIR__ . '/data/unary-minus-64bit.php'; + yield __DIR__ . '/data/integer-range-float-bounds-64bit.php'; } else { yield __DIR__ . '/data/predefined-constants-32bit.php'; } diff --git a/tests/PHPStan/Analyser/data/integer-range-float-bounds-64bit.php b/tests/PHPStan/Analyser/data/integer-range-float-bounds-64bit.php new file mode 100644 index 00000000000..951e6fd0326 --- /dev/null +++ b/tests/PHPStan/Analyser/data/integer-range-float-bounds-64bit.php @@ -0,0 +1,67 @@ + 9223372036854775808) { + assertType('*NEVER*', $i); + } else { + assertType('int', $i); + } + + if ($i >= 9223372036854775808) { + assertType('*NEVER*', $i); + } else { + assertType('int', $i); + } +} + +// (float) PHP_INT_MIN is exactly PHP_INT_MIN, so nothing is smaller than it. +function belowIntegerRange(int $i): void +{ + assertType('-9.223372036854776E+18', -9223372036854775808); + + if ($i < -9223372036854775808) { + assertType('*NEVER*', $i); + } else { + assertType('int', $i); + } + + if ($i >= -9223372036854775808) { + assertType('int', $i); + } else { + assertType('*NEVER*', $i); + } +} + +// The biggest float below 2 ** 63 still fits into the integer range. +function insideIntegerRange(int $i): void +{ + if ($i < 9223372036854774784.0) { + assertType('int', $i); + } + + if ($i >= 9223372036854774784.0) { + assertType('int<9223372036854774784, max>', $i); + } +}