From 749aafee71771584566b6f84e1f38e0144183075 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 21 Feb 2026 18:44:09 +0100 Subject: [PATCH 1/3] Fix cast object inferences --- resources/functionMap.php | 8 ++--- src/Type/ObjectType.php | 16 +++++++-- .../Rules/Cast/InvalidCastRuleTest.php | 14 ++++++++ tests/PHPStan/Rules/Cast/data/bug-14136.php | 36 +++++++++++++++++++ .../CallToFunctionParametersRuleTest.php | 14 ++++++++ .../Rules/Functions/data/bug-14136.php | 36 +++++++++++++++++++ 6 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 tests/PHPStan/Rules/Cast/data/bug-14136.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-14136.php diff --git a/resources/functionMap.php b/resources/functionMap.php index 88f32cc389..c17fa57c88 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -1650,7 +1650,7 @@ 'DomXsltStylesheet::result_dump_mem' => ['string', 'xmldoc'=>'DOMDocument'], 'DOTNET::__construct' => ['void', 'assembly_name'=>'string', 'class_name'=>'string', 'codepage='=>'int'], 'dotnet_load' => ['int', 'assembly_name'=>'string', 'datatype_name='=>'string', 'codepage='=>'int'], -'doubleval' => ['float', 'var'=>'int|float|__stringNotStringable|bool|array|resource|null'], +'doubleval' => ['float', 'var'=>'int|float|__stringNotStringable|bool|array|resource|GMP|SimpleXMLElement|null'], 'Ds\Deque::__construct' => ['void', 'values='=>'mixed'], 'Ds\Deque::count' => ['0|positive-int'], 'Ds\Deque::jsonSerialize' => ['array'], @@ -2448,7 +2448,7 @@ 'finfo_file' => ['string|false', 'finfo'=>'resource', 'file_name'=>'string', 'options='=>'int', 'context='=>'resource'], 'finfo_open' => ['resource|false', 'options='=>'int', 'arg='=>'string'], 'finfo_set_flags' => ['bool', 'finfo'=>'resource', 'options'=>'int'], -'floatval' => ['float', 'var'=>'int|float|__stringNotStringable|bool|array|resource|null'], +'floatval' => ['float', 'var'=>'int|float|__stringNotStringable|bool|array|resource|GMP|SimpleXMLElement|null'], 'flock' => ['bool', 'fp'=>'resource', 'operation'=>'int-mask', '&w_wouldblock='=>'0|1'], 'floor' => ['__benevolent', 'number'=>'float'], 'flush' => ['void'], @@ -5075,7 +5075,7 @@ 'intltz_to_date_time_zone' => ['DateTimeZone|false', 'obj'=>''], 'intltz_use_daylight_time' => ['bool', 'obj'=>''], 'intlz_create_default' => ['IntlTimeZone'], -'intval' => ['int', 'var'=>'int|float|__stringNotStringable|bool|array|resource|null', 'base='=>'int'], +'intval' => ['int', 'var'=>'int|float|__stringNotStringable|bool|array|resource|GMP|SimpleXMLElement|null', 'base='=>'int'], 'InvalidArgumentException::__clone' => ['void'], 'InvalidArgumentException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'(?Throwable)|(?InvalidArgumentException)'], 'InvalidArgumentException::__toString' => ['string'], @@ -10438,7 +10438,7 @@ 'strtoupper' => ['uppercase-string', 'str'=>'string'], 'strtr' => ['string', 'str'=>'string', 'from'=>'string', 'to'=>'string'], 'strtr\'1' => ['string', 'str'=>'string', 'replace_pairs'=>'array'], -'strval' => ['string', 'var'=>'__stringAndStringable|int|float|bool|resource|null'], +'strval' => ['string', 'var'=>'__stringAndStringable|int|float|bool|resource|GMP|null'], 'substr' => ['__benevolent', 'string'=>'string', 'start'=>'int', 'length='=>'int'], 'substr_compare' => ['int<-1, 1>|false', 'main_str'=>'string', 'str'=>'string', 'offset'=>'int', 'length='=>'int', 'case_sensitivity='=>'bool'], 'substr_count' => ['0|positive-int', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'length='=>'int'], diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 8e295b9fc4..09a138e34f 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -772,7 +772,10 @@ public function toAbsoluteNumber(): Type public function toInteger(): Type { - if ($this->isInstanceOf('SimpleXMLElement')->yes()) { + if ( + $this->isInstanceOf('SimpleXMLElement')->yes() + || $this->isInstanceOf('GMP')->yes() + ) { return new IntegerType(); } @@ -785,7 +788,10 @@ public function toInteger(): Type public function toFloat(): Type { - if ($this->isInstanceOf('SimpleXMLElement')->yes()) { + if ( + $this->isInstanceOf('SimpleXMLElement')->yes() + || $this->isInstanceOf('GMP')->yes() + ) { return new FloatType(); } return new ErrorType(); @@ -793,7 +799,10 @@ public function toFloat(): Type public function toString(): Type { - if ($this->isInstanceOf('BcMath\Number')->yes()) { + if ( + $this->isInstanceOf('BcMath\Number')->yes() + || $this->isInstanceOf('GMP')->yes() + ) { return new IntersectionType([ new StringType(), new AccessoryNumericStringType(), @@ -910,6 +919,7 @@ public function toBoolean(): BooleanType if ( $this->isInstanceOf('SimpleXMLElement')->yes() || $this->isInstanceOf('BcMath\Number')->yes() + || $this->isInstanceOf('GMP')->yes() ) { return new BooleanType(); } diff --git a/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php b/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php index 3bbc8521a5..949d4eeb7c 100644 --- a/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php +++ b/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php @@ -72,6 +72,20 @@ public function testRuleWithNullsafeVariant(): void ]); } + public function testBug14136(): void + { + $this->analyse([__DIR__ . '/data/bug-14136.php'], [ + [ + 'Cannot cast BcMath\Number to int.', + 15, + ], + [ + 'Cannot cast BcMath\Number to float.', + 22, + ], + ]); + } + public function testCastObjectToString(): void { $this->analyse([__DIR__ . '/data/cast-object-to-string.php'], [ diff --git a/tests/PHPStan/Rules/Cast/data/bug-14136.php b/tests/PHPStan/Rules/Cast/data/bug-14136.php new file mode 100644 index 0000000000..f897abca71 --- /dev/null +++ b/tests/PHPStan/Rules/Cast/data/bug-14136.php @@ -0,0 +1,36 @@ +3 +EOT; + +$xml = simplexml_load_string($xml); +var_dump(intval($xml)); +var_dump(intval(gmp_init(42))); +var_dump(intval (new \BCMath\Number(99))); // Invalid +var_dump((int) ($xml)); +var_dump((int)(gmp_init(42))); +var_dump((int) new \BCMath\Number(99)); // Invalid + +var_dump(floatval ($xml)); +var_dump(floatval(gmp_init(42))); +var_dump(floatval(new \BCMath\Number(99))); // Invalid +var_dump((float) ($xml)); +var_dump((float)(gmp_init(42))); +var_dump((float) new \BCMath\Number(99)); // Invalid + +var_dump(strval($xml)); +var_dump(strval(gmp_init(42))); +var_dump(strval(new \BCMath\Number(99))); +var_dump((string) ($xml)); +var_dump((string)(gmp_init(42))); +var_dump((string) new \BCMath\Number(99)); + +var_dump(boolval($xml)); +var_dump(boolval(gmp_init(42))); +var_dump(boolval (new \BCMath\Number(99))); +var_dump((bool) ($xml)); +var_dump((bool) (gmp_init(42))); +var_dump((bool) (new \BCMath\Number(42))); diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 9fa51e42e6..dfcdb199fc 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -2678,6 +2678,20 @@ public function testBug9652(): void $this->analyse([__DIR__ . '/data/bug-9652.php'], []); } + public function testBug14136(): void + { + $this->analyse([__DIR__ . '/data/bug-14136.php'], [ + [ + 'Parameter #1 $value of function intval expects array|bool|float|GMP|int|resource|SimpleXMLElement|string|null, BcMath\Number given.', + 12, + ], + [ + 'Parameter #1 $value of function floatval expects array|bool|float|GMP|int|resource|SimpleXMLElement|string|null, BcMath\Number given.', + 19, + ], + ]); + } + #[RequiresPhp('>= 8.1')] public function testBug8936(): void { diff --git a/tests/PHPStan/Rules/Functions/data/bug-14136.php b/tests/PHPStan/Rules/Functions/data/bug-14136.php new file mode 100644 index 0000000000..88d6fbd539 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-14136.php @@ -0,0 +1,36 @@ +3 +EOT; + +$xml = simplexml_load_string($xml); +var_dump(intval($xml)); +var_dump(intval(gmp_init(42))); +var_dump(intval (new \BCMath\Number(99))); // Invalid +var_dump((int) ($xml)); +var_dump((int)(gmp_init(42))); +var_dump((int) new \BCMath\Number(99)); // Invalid + +var_dump(floatval ($xml)); +var_dump(floatval(gmp_init(42))); +var_dump(floatval(new \BCMath\Number(99))); // Invalid +var_dump((float) ($xml)); +var_dump((float)(gmp_init(42))); +var_dump((float) new \BCMath\Number(99)); // Invalid + +var_dump(strval($xml)); +var_dump(strval(gmp_init(42))); +var_dump(strval(new \BCMath\Number(99))); +var_dump((string) ($xml)); +var_dump((string)(gmp_init(42))); +var_dump((string) new \BCMath\Number(99)); + +var_dump(boolval($xml)); +var_dump(boolval(gmp_init(42))); +var_dump(boolval (new \BCMath\Number(99))); +var_dump((bool) ($xml)); +var_dump((bool) (gmp_init(42))); +var_dump((bool) (new \BCMath\Number(42))); From 518cb27d7997df7b6bd6e85d1f874f75d36374c6 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 21 Feb 2026 18:48:40 +0100 Subject: [PATCH 2/3] Nsrt --- tests/PHPStan/Analyser/nsrt/bug-14136.php | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-14136.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-14136.php b/tests/PHPStan/Analyser/nsrt/bug-14136.php new file mode 100644 index 0000000000..2a226aeeee --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-14136.php @@ -0,0 +1,34 @@ +3 +EOT; + +$xml = simplexml_load_string($xml); +assertType('int', intval($xml)); +assertType('int', intval(gmp_init(42))); +assertType('int', (int) ($xml)); +assertType('int', (int) (gmp_init(42))); + +assertType('float', floatval($xml)); +assertType('float', floatval(gmp_init(42))); +assertType('float', (float) ($xml)); +assertType('float', (float) (gmp_init(42))); + +assertType('string', strval($xml)); +assertType('non-empty-string&numeric-string', strval(gmp_init(42))); +assertType('non-empty-string&numeric-string', strval(new \BCMath\Number(99))); +assertType('string', (string) ($xml)); +assertType('non-empty-string&numeric-string', (string) (gmp_init(42))); +assertType('non-empty-string&numeric-string', (string) new \BCMath\Number(99)); + +assertType('bool', boolval($xml)); +assertType('bool', boolval(gmp_init(0))); +assertType('bool', boolval(new \BCMath\Number(0))); +assertType('bool', (bool) ($xml)); +assertType('bool', (bool) (gmp_init(0))); +assertType('bool', (bool) (new \BCMath\Number(0))); From 4ecb98621d020c52bcc3285c2d43401b5c75e348 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 21 Feb 2026 19:07:34 +0100 Subject: [PATCH 3/3] Remove BCMath\Number --- tests/PHPStan/Analyser/nsrt/bug-14136.php | 4 --- .../Rules/Cast/InvalidCastRuleTest.php | 11 +------ tests/PHPStan/Rules/Cast/data/bug-14136.php | 8 ----- .../CallToFunctionParametersRuleTest.php | 29 +++++++------------ .../Rules/Functions/data/bug-14136.php | 8 ----- 5 files changed, 11 insertions(+), 49 deletions(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-14136.php b/tests/PHPStan/Analyser/nsrt/bug-14136.php index 2a226aeeee..53ba75be8e 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-14136.php +++ b/tests/PHPStan/Analyser/nsrt/bug-14136.php @@ -21,14 +21,10 @@ assertType('string', strval($xml)); assertType('non-empty-string&numeric-string', strval(gmp_init(42))); -assertType('non-empty-string&numeric-string', strval(new \BCMath\Number(99))); assertType('string', (string) ($xml)); assertType('non-empty-string&numeric-string', (string) (gmp_init(42))); -assertType('non-empty-string&numeric-string', (string) new \BCMath\Number(99)); assertType('bool', boolval($xml)); assertType('bool', boolval(gmp_init(0))); -assertType('bool', boolval(new \BCMath\Number(0))); assertType('bool', (bool) ($xml)); assertType('bool', (bool) (gmp_init(0))); -assertType('bool', (bool) (new \BCMath\Number(0))); diff --git a/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php b/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php index 949d4eeb7c..17f581ba24 100644 --- a/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php +++ b/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php @@ -74,16 +74,7 @@ public function testRuleWithNullsafeVariant(): void public function testBug14136(): void { - $this->analyse([__DIR__ . '/data/bug-14136.php'], [ - [ - 'Cannot cast BcMath\Number to int.', - 15, - ], - [ - 'Cannot cast BcMath\Number to float.', - 22, - ], - ]); + $this->analyse([__DIR__ . '/data/bug-14136.php'], []); } public function testCastObjectToString(): void diff --git a/tests/PHPStan/Rules/Cast/data/bug-14136.php b/tests/PHPStan/Rules/Cast/data/bug-14136.php index f897abca71..b8f3af0ae6 100644 --- a/tests/PHPStan/Rules/Cast/data/bug-14136.php +++ b/tests/PHPStan/Rules/Cast/data/bug-14136.php @@ -9,28 +9,20 @@ $xml = simplexml_load_string($xml); var_dump(intval($xml)); var_dump(intval(gmp_init(42))); -var_dump(intval (new \BCMath\Number(99))); // Invalid var_dump((int) ($xml)); var_dump((int)(gmp_init(42))); -var_dump((int) new \BCMath\Number(99)); // Invalid var_dump(floatval ($xml)); var_dump(floatval(gmp_init(42))); -var_dump(floatval(new \BCMath\Number(99))); // Invalid var_dump((float) ($xml)); var_dump((float)(gmp_init(42))); -var_dump((float) new \BCMath\Number(99)); // Invalid var_dump(strval($xml)); var_dump(strval(gmp_init(42))); -var_dump(strval(new \BCMath\Number(99))); var_dump((string) ($xml)); var_dump((string)(gmp_init(42))); -var_dump((string) new \BCMath\Number(99)); var_dump(boolval($xml)); var_dump(boolval(gmp_init(42))); -var_dump(boolval (new \BCMath\Number(99))); var_dump((bool) ($xml)); var_dump((bool) (gmp_init(42))); -var_dump((bool) (new \BCMath\Number(42))); diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index dfcdb199fc..b1130f54df 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -2481,39 +2481,39 @@ public function testBug6560(string $fileName): void $this->analyse([__DIR__ . '/data/' . $fileName], [ [ - sprintf('Parameter #1 %s of function strval expects bool|float|int|resource|string|null, array given.', $varName), + sprintf('Parameter #1 %s of function strval expects bool|float|GMP|int|resource|string|null, array given.', $varName), 23, ], [ - sprintf('Parameter #1 %s of function strval expects bool|float|int|resource|string|null, stdClass given.', $varName), + sprintf('Parameter #1 %s of function strval expects bool|float|GMP|int|resource|string|null, stdClass given.', $varName), 77, ], [ - sprintf('Parameter #1 %s of function intval expects array|bool|float|int|resource|string|null, stdClass given.', $varName), + sprintf('Parameter #1 %s of function intval expects array|bool|float|GMP|int|resource|SimpleXMLElement|string|null, stdClass given.', $varName), 80, ], [ - sprintf('Parameter #1 %s of function floatval expects array|bool|float|int|resource|string|null, stdClass given.', $varName), + sprintf('Parameter #1 %s of function floatval expects array|bool|float|GMP|int|resource|SimpleXMLElement|string|null, stdClass given.', $varName), 83, ], [ - sprintf('Parameter #1 %s of function intval expects array|bool|float|int|resource|string|null, %s@anonymous/tests/PHPStan/Rules/Functions/data/' . $fileName . ':13 given.', $varName, $stringableName), + sprintf('Parameter #1 %s of function intval expects array|bool|float|GMP|int|resource|SimpleXMLElement|string|null, %s@anonymous/tests/PHPStan/Rules/Functions/data/' . $fileName . ':13 given.', $varName, $stringableName), 89, ], [ - sprintf('Parameter #1 %s of function floatval expects array|bool|float|int|resource|string|null, %s@anonymous/tests/PHPStan/Rules/Functions/data/' . $fileName . ':13 given.', $varName, $stringableName), + sprintf('Parameter #1 %s of function floatval expects array|bool|float|GMP|int|resource|SimpleXMLElement|string|null, %s@anonymous/tests/PHPStan/Rules/Functions/data/' . $fileName . ':13 given.', $varName, $stringableName), 92, ], [ - sprintf('Parameter #1 %s of function strval expects bool|float|int|resource|string|null, mixed given.', $varName), + sprintf('Parameter #1 %s of function strval expects bool|float|GMP|int|resource|string|null, mixed given.', $varName), 95, ], [ - sprintf('Parameter #1 %s of function intval expects array|bool|float|int|resource|string|null, mixed given.', $varName), + sprintf('Parameter #1 %s of function intval expects array|bool|float|GMP|int|resource|SimpleXMLElement|string|null, mixed given.', $varName), 98, ], [ - sprintf('Parameter #1 %s of function floatval expects array|bool|float|int|resource|string|null, mixed given.', $varName), + sprintf('Parameter #1 %s of function floatval expects array|bool|float|GMP|int|resource|SimpleXMLElement|string|null, mixed given.', $varName), 101, ], ]); @@ -2680,16 +2680,7 @@ public function testBug9652(): void public function testBug14136(): void { - $this->analyse([__DIR__ . '/data/bug-14136.php'], [ - [ - 'Parameter #1 $value of function intval expects array|bool|float|GMP|int|resource|SimpleXMLElement|string|null, BcMath\Number given.', - 12, - ], - [ - 'Parameter #1 $value of function floatval expects array|bool|float|GMP|int|resource|SimpleXMLElement|string|null, BcMath\Number given.', - 19, - ], - ]); + $this->analyse([__DIR__ . '/data/bug-14136.php'], []); } #[RequiresPhp('>= 8.1')] diff --git a/tests/PHPStan/Rules/Functions/data/bug-14136.php b/tests/PHPStan/Rules/Functions/data/bug-14136.php index 88d6fbd539..bd5c8e46ce 100644 --- a/tests/PHPStan/Rules/Functions/data/bug-14136.php +++ b/tests/PHPStan/Rules/Functions/data/bug-14136.php @@ -9,28 +9,20 @@ $xml = simplexml_load_string($xml); var_dump(intval($xml)); var_dump(intval(gmp_init(42))); -var_dump(intval (new \BCMath\Number(99))); // Invalid var_dump((int) ($xml)); var_dump((int)(gmp_init(42))); -var_dump((int) new \BCMath\Number(99)); // Invalid var_dump(floatval ($xml)); var_dump(floatval(gmp_init(42))); -var_dump(floatval(new \BCMath\Number(99))); // Invalid var_dump((float) ($xml)); var_dump((float)(gmp_init(42))); -var_dump((float) new \BCMath\Number(99)); // Invalid var_dump(strval($xml)); var_dump(strval(gmp_init(42))); -var_dump(strval(new \BCMath\Number(99))); var_dump((string) ($xml)); var_dump((string)(gmp_init(42))); -var_dump((string) new \BCMath\Number(99)); var_dump(boolval($xml)); var_dump(boolval(gmp_init(42))); -var_dump(boolval (new \BCMath\Number(99))); var_dump((bool) ($xml)); var_dump((bool) (gmp_init(42))); -var_dump((bool) (new \BCMath\Number(42)));