Skip to content

Do not cast floats past the int range, or NAN, where PHP does not - #6541

Merged
ondrejmirtes merged 4 commits into
2.3.xfrom
php85-float-cast-warnings
Sep 22, 2026
Merged

ondrejmirtes merged 4 commits into
2.3.xfrom
php85-float-cast-warnings

Conversation

@ondrejmirtes

Copy link
Copy Markdown
Member

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_MAX is not representable as a float, so (float) PHP_INT_MAX (2^63) is already past the int range.

  • NAN == 'NAN' was inferred as true (and reported as "always true"). LooseComparisonHelper compared a number with a non-numeric string through the number's string cast, but PHP never stringifies NAN: NAN is equal to nothing. It is false now.
  • int<PHP_INT_MIN, -1> / -1 was inferred as float, losing every value the division produces as an int. integerRangeMath() cast the overflowing bound (PHP_INT_MIN / -1 is 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 is int<1, 9223372036854775807>.
  • IntegerRangeType::createAllSmallerThan((float) PHP_INT_MAX) returned *NEVER* instead of int, and createAllGreaterThanOrEqualTo() 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.

InitializerExprTypeResolver and IntegerRangeType are 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.php child prints to stderr (the warnings that deadlocked the Windows 8.5/8.6 legs before fa79f34). The remaining "Undefined array key 1" from TemplateArgumentResolver is that test deliberately passing a gapped array, not a bug.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NBzALCg92yf3LXFxxNBzoY

ondrejmirtes and others added 4 commits September 22, 2026 21:27
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
ondrejmirtes force-pushed the php85-float-cast-warnings branch from 6a9eee1 to 148ff5f Compare September 22, 2026 19:35
@ondrejmirtes
ondrejmirtes merged commit 148ff5f into 2.3.x Sep 22, 2026
1 check passed
@ondrejmirtes
ondrejmirtes deleted the php85-float-cast-warnings branch September 22, 2026 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant