Skip to content

Commit 960a598

Browse files
zonuexeclaude
andcommitted
Treat the float 2**63 as above every int in IntegerRangeType factories
A float compared with PHP_INT_MAX compares against the float 2**63, so `$value > PHP_INT_MAX` is false for exactly 2**63. createAllSmallerThan() and createAllGreaterThanOrEqualTo() then cast 2**63 to int, which emits "not representable as an int" and yields NeverType / int<min, max> instead of int / NeverType. Use `>=` like the other two factories do. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 31470ab commit 960a598

3 files changed

Lines changed: 93 additions & 2 deletions

File tree

‎src/Type/IntegerRangeType.php‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public static function createAllSmallerThan($value): Type
9090
return self::fromInterval(null, $value, -1);
9191
}
9292

93-
if ($value > PHP_INT_MAX) {
93+
if ($value >= PHP_INT_MAX) {
9494
return new IntegerType();
9595
}
9696

@@ -160,7 +160,7 @@ public static function createAllGreaterThanOrEqualTo($value): Type
160160
return new IntegerType();
161161
}
162162

163-
if ($value > PHP_INT_MAX) {
163+
if ($value >= PHP_INT_MAX) {
164164
return new NeverType();
165165
}
166166

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace IntegerRangeFloatTwoToThe63;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
function smaller(int $i): void
8+
{
9+
if ($i < 9.2233720368547758E18) {
10+
assertType('int', $i);
11+
} else {
12+
assertType('*NEVER*', $i);
13+
}
14+
assertType('int', $i);
15+
}
16+
17+
function greaterOrEqual(int $i): void
18+
{
19+
if ($i >= 9.2233720368547758E18) {
20+
assertType('*NEVER*', $i);
21+
} else {
22+
assertType('int', $i);
23+
}
24+
assertType('int', $i);
25+
}
26+
27+
function smallerOrEqual(int $i): void
28+
{
29+
if ($i <= 9.2233720368547758E18) {
30+
assertType('int', $i);
31+
} else {
32+
assertType('*NEVER*', $i);
33+
}
34+
}
35+
36+
function greater(int $i): void
37+
{
38+
if ($i > 9.2233720368547758E18) {
39+
assertType('*NEVER*', $i);
40+
} else {
41+
assertType('int', $i);
42+
}
43+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type;
4+
5+
use Closure;
6+
use PHPStan\Testing\PHPStanTestCase;
7+
use PHPUnit\Framework\Attributes\DataProvider;
8+
9+
class IntegerRangeTypeTest extends PHPStanTestCase
10+
{
11+
12+
public static function dataCreateFromFloat(): iterable
13+
{
14+
// 2**63 is PHP_INT_MAX + 1, the smallest float greater than every int
15+
yield '< 2**63' => [IntegerRangeType::createAllSmallerThan(...), 9.2233720368547758E18, 'int'];
16+
yield '<= 2**63' => [IntegerRangeType::createAllSmallerThanOrEqualTo(...), 9.2233720368547758E18, 'int'];
17+
yield '> 2**63' => [IntegerRangeType::createAllGreaterThan(...), 9.2233720368547758E18, '*NEVER*'];
18+
yield '>= 2**63' => [IntegerRangeType::createAllGreaterThanOrEqualTo(...), 9.2233720368547758E18, '*NEVER*'];
19+
20+
// the largest float smaller than 2**63
21+
yield '< 2**63 - 1024' => [IntegerRangeType::createAllSmallerThan(...), 9.2233720368547748E18, 'int<min, 9223372036854774783>'];
22+
yield '<= 2**63 - 1024' => [IntegerRangeType::createAllSmallerThanOrEqualTo(...), 9.2233720368547748E18, 'int<min, 9223372036854774784>'];
23+
yield '> 2**63 - 1024' => [IntegerRangeType::createAllGreaterThan(...), 9.2233720368547748E18, 'int<9223372036854774785, max>'];
24+
yield '>= 2**63 - 1024' => [IntegerRangeType::createAllGreaterThanOrEqualTo(...), 9.2233720368547748E18, 'int<9223372036854774784, max>'];
25+
26+
// -2**63 is exactly PHP_INT_MIN
27+
yield '< -2**63' => [IntegerRangeType::createAllSmallerThan(...), -9.2233720368547758E18, '*NEVER*'];
28+
yield '<= -2**63' => [IntegerRangeType::createAllSmallerThanOrEqualTo(...), -9.2233720368547758E18, '-9223372036854775808'];
29+
yield '> -2**63' => [IntegerRangeType::createAllGreaterThan(...), -9.2233720368547758E18, 'int<-9223372036854775807, max>'];
30+
yield '>= -2**63' => [IntegerRangeType::createAllGreaterThanOrEqualTo(...), -9.2233720368547758E18, 'int'];
31+
32+
// the largest float smaller than -2**63
33+
yield '< -2**63 - 2048' => [IntegerRangeType::createAllSmallerThan(...), -9.2233720368547779E18, '*NEVER*'];
34+
yield '<= -2**63 - 2048' => [IntegerRangeType::createAllSmallerThanOrEqualTo(...), -9.2233720368547779E18, '*NEVER*'];
35+
yield '> -2**63 - 2048' => [IntegerRangeType::createAllGreaterThan(...), -9.2233720368547779E18, 'int'];
36+
yield '>= -2**63 - 2048' => [IntegerRangeType::createAllGreaterThanOrEqualTo(...), -9.2233720368547779E18, 'int'];
37+
}
38+
39+
/**
40+
* @param Closure(float): Type $factory
41+
*/
42+
#[DataProvider('dataCreateFromFloat')]
43+
public function testCreateFromFloat(Closure $factory, float $value, string $expected): void
44+
{
45+
$this->assertSame($expected, $factory($value)->describe(VerbosityLevel::precise()));
46+
}
47+
48+
}

0 commit comments

Comments
 (0)