Return int|float from AccessoryDecimalIntegerStringType::toNumber() to account for overflow#5815
Return int|float from AccessoryDecimalIntegerStringType::toNumber() to account for overflow#5815phpstan-bot wants to merge 2 commits into
int|float from AccessoryDecimalIntegerStringType::toNumber() to account for overflow#5815Conversation
…)` to account for overflow - A non-inverse `decimal-int-string` previously coerced to a pure `int` via `toNumber()`, but decimal integer strings larger than PHP_INT_MAX (or smaller than PHP_INT_MIN) overflow to `float` when cast to a number (e.g. `+"9999999999999999999"`). - `toNumber()` now returns `int|float` for both the inverse and non-inverse cases, matching `AccessoryNumericStringType::toNumber()` and the constant-string path (`ConstantStringType::toNumber()` already folds overflow to float via `+$value`). - This fixes `+$s` / arithmetic on `decimal-int-string` being inferred as `int`, which caused false "is_int() will always evaluate to true" errors. - Updated the existing `decimal-int-string` inference test (`$s + $s` is now `float|int`, consistent with the `non-decimal-int-string` sibling already asserting `float|int`). - Probed siblings: `AccessoryNumericStringType::toNumber()` and the inverse `decimal-int-string` already returned `int|float`; `toArrayKey()` is left as-is since the type's contract is explicitly about array-key casting.
staabm
left a comment
There was a problem hiding this comment.
add a test showing this pr no longer emits
Call to function is_int() with int will always evaluate to true.
on https://phpstan.org/r/b711705f-d5b4-423c-99be-4a60c7fa92a7
…-string Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Done. I addressed staabm's review by adding a rule-level regression test demonstrating the What I added
Verification
I committed this separately from the existing fix (commit |
Summary
Casting a
decimal-int-stringto a number with the unary+operator (or via arithmetic) was inferred asint. Large decimal integer strings overflowPHP_INT_MAX/PHP_INT_MINand becomefloatat runtime, so the result is actuallyint|float. The wrongintinference also produced false positives like "Call to function is_int() with int will always evaluate to true."This change makes the numeric coercion of a
decimal-int-stringproduceint|float.Changes
src/Type/Accessory/AccessoryDecimalIntegerStringType.php:toNumber()now returnsint|floatfor the non-inverse case too (it previously returned justint). Both the inverse and non-inverse cases now return the sameint|floatunion.tests/PHPStan/Analyser/nsrt/decimal-int-string.php: updated the now-corrected assertion —$s + $sfor adecimal-int-stringisfloat|int(matching thenon-decimal-int-stringsibling, which already assertedfloat|int).tests/PHPStan/Analyser/nsrt/bug-14786.php: new regression test.Root cause
The
decimal-int-stringaccessory type was designed primarily around array-key casting, where it models "string that becomes an integer key". ItstoNumber()reused that integer assumption and returned a pureIntegerType. But numeric coercion (+$s,$s + 1, etc.) of a decimal integer string overflows tofloatonce the value exceeds the platform int range, so the coerced type must beint|float.The same numeric-overflow handling already existed in the parallel constructs:
AccessoryNumericStringType::toNumber()returnsint|float.non-decimal-int-stringbranch ofAccessoryDecimalIntegerStringType::toNumber()already returnedint|float.ConstantStringType::toNumber()folds overflow toConstantFloatTypevia+$value.Only the non-inverse
decimal-int-stringbranch was inconsistent. I also probedtoArrayKey(), which has the same overflow edge case at runtime, but left it unchanged: that method encodes the type's documented contract (adecimal-int-stringis treated as an integer array key) and changing it would be a separate, much larger semantic change affecting the type's purpose.Test
tests/PHPStan/Analyser/nsrt/bug-14786.phpasserts+$sisfloat|intfordecimal-int-string, and also covers the already-correct siblings (non-decimal-int-string,numeric-string), the(int) $scast (staysint, no overflow to float), and arithmetic ($s + 1,$s * 2). The test fails before the fix (actualint) and passes after.decimal-int-string.phpassertion verified to be the corrected (not buggy) behavior.Fixes phpstan/phpstan#14786