From 1910be33df52e2c23fc4f0b0e714b07fe419901e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 28 Mar 2026 16:08:43 +0100 Subject: [PATCH 1/4] ctype_digit() narrows to decimal-int-string --- src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php | 5 +++-- tests/PHPStan/Analyser/nsrt/callsite-cast-narrowing.php | 7 ++++--- tests/PHPStan/Analyser/nsrt/ctype-digit.php | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php b/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php index 04daf2ed552..baa2e29d788 100644 --- a/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php @@ -12,6 +12,7 @@ use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\ShouldNotHappenException; +use PHPStan\Type\Accessory\AccessoryDecimalIntegerStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\FunctionTypeSpecifyingExtension; @@ -56,7 +57,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n if ($context->true()) { $types[] = new IntersectionType([ new StringType(), - new AccessoryNumericStringType(), + new AccessoryDecimalIntegerStringType(), ]); } @@ -68,7 +69,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n IntegerRangeType::fromInterval(0, null), new IntersectionType([ new StringType(), - new AccessoryNumericStringType(), + new AccessoryDecimalIntegerStringType(), ]), new ConstantBooleanType(true), ]); diff --git a/tests/PHPStan/Analyser/nsrt/callsite-cast-narrowing.php b/tests/PHPStan/Analyser/nsrt/callsite-cast-narrowing.php index 239e49d53a9..e1225bf3197 100644 --- a/tests/PHPStan/Analyser/nsrt/callsite-cast-narrowing.php +++ b/tests/PHPStan/Analyser/nsrt/callsite-cast-narrowing.php @@ -13,7 +13,7 @@ class HelloWorld public function sayHello($mixed, int $int, string $string, $numericString, $nonEmptyString, bool $bool): void { if (ctype_digit((string) $mixed)) { - assertType('int<0, max>|numeric-string|true', $mixed); + assertType('int<0, max>|decimal-int-string|true', $mixed); } else { assertType('mixed~(int<0, max>|numeric-string|true)', $mixed); } @@ -41,7 +41,7 @@ public function sayHello($mixed, int $int, string $string, $numericString, $nonE assertType('int', $int); if (ctype_digit((string) $string)) { - assertType('numeric-string', $string); + assertType('decimal-int-string', $string); } else { assertType('string', $string); } @@ -54,10 +54,11 @@ public function sayHello($mixed, int $int, string $string, $numericString, $nonE } assertType('string', $string); + // see https://3v4l.org/1Qrlg#veol if (ctype_digit((string) $numericString)) { assertType('numeric-string', $numericString); } else { - assertType('*NEVER*', $numericString); + assertType('numeric-string', $numericString); } assertType('numeric-string', $numericString); diff --git a/tests/PHPStan/Analyser/nsrt/ctype-digit.php b/tests/PHPStan/Analyser/nsrt/ctype-digit.php index ed4704daa71..ec496e6fbe1 100644 --- a/tests/PHPStan/Analyser/nsrt/ctype-digit.php +++ b/tests/PHPStan/Analyser/nsrt/ctype-digit.php @@ -14,7 +14,7 @@ public function foo(mixed $foo): void assertType('mixed', $foo); if (is_string($foo) && ctype_digit($foo)) { - assertType('numeric-string', $foo); + assertType('decimal-int-string', $foo); } else { assertType('mixed', $foo); } @@ -26,7 +26,7 @@ public function foo(mixed $foo): void } if (ctype_digit($foo)) { - assertType('int<48, 57>|int<256, max>|numeric-string', $foo); + assertType('int<48, 57>|int<256, max>|decimal-int-string', $foo); return; } From d1ae068666498060f433f5c37812ba4d2aa1c634 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 28 Mar 2026 16:11:09 +0100 Subject: [PATCH 2/4] Update CtypeDigitFunctionTypeSpecifyingExtension.php --- src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php b/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php index baa2e29d788..cab2d7d007d 100644 --- a/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php @@ -13,7 +13,6 @@ use PHPStan\Reflection\FunctionReflection; use PHPStan\ShouldNotHappenException; use PHPStan\Type\Accessory\AccessoryDecimalIntegerStringType; -use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\FunctionTypeSpecifyingExtension; use PHPStan\Type\IntegerRangeType; From 58118c9ad32bc3ae585c2fb346e6439be56e2e83 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 28 Mar 2026 16:20:26 +0100 Subject: [PATCH 3/4] Update TypeCombinatorTest.php --- tests/PHPStan/Type/TypeCombinatorTest.php | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 9ea18aac9b5..ccfe81ca989 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -5158,6 +5158,30 @@ public static function dataRemove(): array UnionType::class, 'array|ArrayObject', ], + [ + new IntersectionType([ + new StringType(), + new AccessoryNumericStringType(), + ]), + new IntersectionType([ + new StringType(), + new AccessoryDecimalIntegerStringType(), + ]), + IntersectionType::class, + 'numeric-string' + ], + [ + new IntersectionType([ + new StringType(), + new AccessoryDecimalIntegerStringType(), + ]), + new IntersectionType([ + new StringType(), + new AccessoryNumericStringType(), + ]), + NeverType::class, + '*NEVER*=implicit' + ], [ new ConstantBooleanType(true), new ConstantBooleanType(false), From 7ddd07f168abef9ab95ac19bd096d88347c17c92 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 28 Mar 2026 16:27:39 +0100 Subject: [PATCH 4/4] Update callsite-cast-narrowing.php --- tests/PHPStan/Analyser/nsrt/callsite-cast-narrowing.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/callsite-cast-narrowing.php b/tests/PHPStan/Analyser/nsrt/callsite-cast-narrowing.php index e1225bf3197..9dc8c720674 100644 --- a/tests/PHPStan/Analyser/nsrt/callsite-cast-narrowing.php +++ b/tests/PHPStan/Analyser/nsrt/callsite-cast-narrowing.php @@ -56,7 +56,7 @@ public function sayHello($mixed, int $int, string $string, $numericString, $nonE // see https://3v4l.org/1Qrlg#veol if (ctype_digit((string) $numericString)) { - assertType('numeric-string', $numericString); + assertType('decimal-int-string', $numericString); } else { assertType('numeric-string', $numericString); }