Conversation
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>
zonuexe
force-pushed
the
int-range-float-2-pow-63
branch
from
September 22, 2026 20:26
960a598 to
76aae52
Compare
The previous commit made createAllSmallerThan() and createAllGreaterThanOrEqualTo() treat a float bound `>= PHP_INT_MAX` as past the int range. That holds on 64-bit builds, where comparing with PHP_INT_MAX converts it to float and rounds it up to 2**63, but on 32-bit builds PHP_INT_MAX is exact as a float: createAllGreaterThanOrEqualTo( 2147483647.0) would return never although the int PHP_INT_MAX satisfies it, and createAllSmallerThan(2147483647.0) would return int instead of int<min, 2147483646>. Decide on the ceil() that is about to be cast instead, compared against PHP_INT_MAX + 1.0, the first float above PHP_INT_MAX, which is exact on both widths (2**63 and 2**31). On 64-bit builds every float at or above 2**52 is integral, so the result there is the same as with `>=`. This is the same code as proposed for 2.3.x in phpstan#6546. testCreateFromFloat's literals are 64-bit int bounds, so it is skipped on 32-bit hosts; testFloatBounds covers both widths.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
IntegerRangeType::createAllSmallerThan()andcreateAllGreaterThanOrEqualTo()mishandle a float argument of exactly2**63(9.2233720368547758E18, i.e.PHP_INT_MAX + 1).The guard
$value > PHP_INT_MAXcompares againstPHP_INT_MAXconverted to float, which is2**63itself, so the guard is false for exactly2**63. The code then reaches(int) ceil($value), which emits "The float 9.223372036854776E+18 is not representable as an int, cast occurred" and gives the wrong type:createAllSmallerThan(2**63)*NEVER*+ warningintcreateAllGreaterThanOrEqualTo(2**63)int<min, max>+ warning*NEVER*As a result,
if ($i < 9.2233720368547758E18)on anintnarrowed$itoneverin the truthy branch. On thePHP_INT_MINside, a float-2**63is exactlyPHP_INT_MINand can be cast safely, and all four factories already return the right types there.The first commit changed these two guards to
>=, like the other two factories and like #6541 on 2.3.x. That is right on 64-bit builds, but on 32-bit buildsPHP_INT_MAX(2147483647) is exact as a float, so>=would makecreateAllGreaterThanOrEqualTo(2147483647.0)return*NEVER*although the intPHP_INT_MAXsatisfies it. The second commit instead decides on theceil()that is about to be cast, and compares it withPHP_INT_MAX + 1.0, the first float abovePHP_INT_MAXon both widths. On 64-bit the result is the same as with>=. The code is the same as #6546 proposes for 2.3.x, so the two branches agree when 2.2.x is merged up.This surfaced as warnings during self-analysis of code comparing an
int|floatwith9.2233720368547758E18, which #6539 worked around by comparing only a float.Tests:
IntegerRangeTypeTest::testCreateFromFloatcovers all four factories at2**63, the largest float below it,-2**63, and the largest float below-2**63. These literals are 64-bit int bounds, so it is skipped on 32-bit hosts.IntegerRangeTypeTest::testFloatBoundsis the same as in Compare float bounds against PHP_INT_MAX + 1.0 in IntegerRangeType #6546: the bounds aroundPHP_INT_MAXandPHP_INT_MIN, with expectations that depend onPHP_INT_SIZE. On linux/386 (Dockerphp:8.5-cli, PHP 8.5.10), two of its rows fail with the first commit alone, and all pass with the second.<,<=,>and>=between anintand9.2233720368547758E18. It passes on linux/386 too.