Repository navigation
Do not cast floats past the int range, or NAN, where PHP does not - #6541
Merged
Merged
Conversation
PHP never casts NAN to a string to compare it with one: NAN is equal to nothing, not even to 'NAN'. `LooseComparisonHelper` cast it anyway, so `NAN == 'NAN'` was inferred as true (and the cast warns since PHP 8.5). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBzALCg92yf3LXFxxNBzoY
PHP_INT_MAX is not representable as a float, so the nearest float above the int range is (float) PHP_INT_MAX itself. `createAllSmallerThan()` and `createAllGreaterThanOrEqualTo()` compared with `>`, letting that float through to a cast that wrapped it around to PHP_INT_MIN (and warns since PHP 8.5): `createAllSmallerThan((float) PHP_INT_MAX)` returned *NEVER* instead of int. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBzALCg92yf3LXFxxNBzoY
`integerRangeMath()` cast the float bounds of the result to int. A bound that overflowed - PHP_INT_MIN / -1 is the float 9.2233720368547758E+18 - wrapped around (and warns since PHP 8.5), which left the range empty: `int<PHP_INT_MIN, -1> / -1` came out as float, losing every value the division does produce as an int. The values past the int range are floats, so the integer part of the result reaches no further than the int range does, and the bound stays there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBzALCg92yf3LXFxxNBzoY
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBzALCg92yf3LXFxxNBzoY
ondrejmirtes
force-pushed
the
php85-float-cast-warnings
branch
from
September 22, 2026 19:35
6a9eee1 to
148ff5f
Compare
This was referenced Sep 23, 2026
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.
Three places cast a float to int or to string where PHP does not do that. Each gave a wrong result, and PHP 8.5 now also warns about the cast. They share a root cause:
PHP_INT_MAXis not representable as a float, so(float) PHP_INT_MAX(2^63) is already past the int range.NAN == 'NAN'was inferred astrue(and reported as "always true").LooseComparisonHelpercompared a number with a non-numeric string through the number's string cast, but PHP never stringifies NAN: NAN is equal to nothing. It isfalsenow.int<PHP_INT_MIN, -1> / -1was inferred asfloat, losing every value the division produces as an int.integerRangeMath()cast the overflowing bound (PHP_INT_MIN / -1is the float 2^63) to int, which wrapped it around to PHP_INT_MIN and left the range empty. The bound now stays at the edge of the int range, since the values past it are floats: the result isint<1, 9223372036854775807>.IntegerRangeType::createAllSmallerThan((float) PHP_INT_MAX)returned*NEVER*instead ofint, andcreateAllGreaterThanOrEqualTo()had the same boundary. Their guards compared with>, which lets 2^63 through to a cast that wraps it. PHPStan's analysis of its own code reached this once the division above stopped producing an empty range.InitializerExprTypeResolverandIntegerRangeTypeare turbo-shadowed, so their C++ mirrors get the same changes, followed by the expected-version bump.Found through the PHP 8.5 warnings the turbo smoke test's
type-family.phpchild prints to stderr (the warnings that deadlocked the Windows 8.5/8.6 legs before fa79f34). The remaining "Undefined array key 1" fromTemplateArgumentResolveris that test deliberately passing a gapped array, not a bug.🤖 Generated with Claude Code
https://claude.ai/code/session_01NBzALCg92yf3LXFxxNBzoY