From 5ef1309b3c3fbeb44385a400769262765911fbfb Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Sat, 6 Jun 2026 14:26:05 +0000 Subject: [PATCH 1/2] Return `int|float` from `AccessoryDecimalIntegerStringType::toNumber()` 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. --- .../AccessoryDecimalIntegerStringType.php | 15 ++++---- tests/PHPStan/Analyser/nsrt/bug-14786.php | 38 +++++++++++++++++++ .../Analyser/nsrt/decimal-int-string.php | 2 +- 3 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-14786.php diff --git a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php index d965eb0ebc9..ebe0d3ce67d 100644 --- a/src/Type/Accessory/AccessoryDecimalIntegerStringType.php +++ b/src/Type/Accessory/AccessoryDecimalIntegerStringType.php @@ -205,14 +205,13 @@ public function unsetOffset(Type $offsetType): Type public function toNumber(): Type { - if ($this->inverse) { - return new UnionType([ - $this->toInteger(), - $this->toFloat(), - ]); - } - - return $this->toInteger(); + // Decimal integer strings larger than PHP_INT_MAX (or smaller than + // PHP_INT_MIN) overflow to float when coerced to a number, so the + // numeric coercion is int|float even for non-inverse values. + return new UnionType([ + $this->toInteger(), + $this->toFloat(), + ]); } public function toAbsoluteNumber(): Type diff --git a/tests/PHPStan/Analyser/nsrt/bug-14786.php b/tests/PHPStan/Analyser/nsrt/bug-14786.php new file mode 100644 index 00000000000..b82afeb4029 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-14786.php @@ -0,0 +1,38 @@ + Date: Sun, 7 Jun 2026 09:13:22 +0000 Subject: [PATCH 2/2] Add rule test showing is_int() false positive is gone for decimal-int-string Co-Authored-By: Claude Opus 4.8 --- .../ImpossibleCheckTypeFunctionCallRuleTest.php | 6 ++++++ .../PHPStan/Rules/Comparison/data/bug-14786.php | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-14786.php diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 8cf7ea1ba1b..aa87e10e10c 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -1367,4 +1367,10 @@ public function testBug6211(): void ]); } + public function testBug14786(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-14786.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-14786.php b/tests/PHPStan/Rules/Comparison/data/bug-14786.php new file mode 100644 index 00000000000..db5b4d12bbb --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-14786.php @@ -0,0 +1,17 @@ +