diff --git a/src/Type/Php/LtrimFunctionReturnTypeExtension.php b/src/Type/Php/LtrimFunctionReturnTypeExtension.php index 9cf4b23cf64..fccf0521c53 100644 --- a/src/Type/Php/LtrimFunctionReturnTypeExtension.php +++ b/src/Type/Php/LtrimFunctionReturnTypeExtension.php @@ -18,12 +18,17 @@ use PHPStan\Type\TypeCombinator; use function count; use function ltrim; -use function preg_match; #[AutowiredService] final class LtrimFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { + public function __construct( + private TrimCharactersHelper $trimCharactersHelper, + ) + { + } + public function isFunctionSupported(FunctionReflection $functionReflection): bool { return $functionReflection->getName() === 'ltrim'; @@ -72,7 +77,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } } elseif ($trimConstantString->getValue() === '\\' && $string->isClassString()->yes()) { $result[] = new ClassStringType(); - } elseif (preg_match('/\d/', $trimConstantString->getValue()) === 0 && $string->isNumericString()->yes()) { + } elseif (!$this->trimCharactersHelper->containsDigit($trimConstantString->getValue()) && $string->isNumericString()->yes()) { $result[] = new IntersectionType([new StringType(), new AccessoryNumericStringType()]); } else { return $defaultType; diff --git a/src/Type/Php/TrimCharactersHelper.php b/src/Type/Php/TrimCharactersHelper.php new file mode 100644 index 00000000000..cba5fd278fb --- /dev/null +++ b/src/Type/Php/TrimCharactersHelper.php @@ -0,0 +1,44 @@ += $byte + ) { + if ($byte <= ord('9') && ord($characters[$i + 3]) >= ord('0')) { + return true; + } + + $i += 3; + continue; + } + + if ($byte >= ord('0') && $byte <= ord('9')) { + return true; + } + } + + return false; + } + +} diff --git a/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php b/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php index 4a88a6616f0..e658b6df12d 100644 --- a/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php @@ -17,7 +17,6 @@ use PHPStan\Type\TypeCombinator; use function count; use function in_array; -use function preg_match; use function rtrim; use function trim; @@ -25,6 +24,12 @@ final class TrimFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { + public function __construct( + private TrimCharactersHelper $trimCharactersHelper, + ) + { + } + public function isFunctionSupported(FunctionReflection $functionReflection): bool { return in_array($functionReflection->getName(), ['trim', 'rtrim', 'chop'], true); @@ -77,7 +82,7 @@ public function getTypeFromFunctionCall( true, ); } - } elseif (preg_match('/\d/', $trimConstantString->getValue()) === 0 && $stringType->isNumericString()->yes()) { + } elseif (!$this->trimCharactersHelper->containsDigit($trimConstantString->getValue()) && $stringType->isNumericString()->yes()) { $result[] = new IntersectionType([new StringType(), new AccessoryNumericStringType()]); } else { return $defaultType; diff --git a/tests/PHPStan/Analyser/nsrt/numeric-string-trim.php b/tests/PHPStan/Analyser/nsrt/numeric-string-trim.php index 9a35224ab06..359f0f9f917 100644 --- a/tests/PHPStan/Analyser/nsrt/numeric-string-trim.php +++ b/tests/PHPStan/Analyser/nsrt/numeric-string-trim.php @@ -28,6 +28,16 @@ public function doTrim(string $numericString, string $string): void assertType('string', trim($numericString, '-0')); assertType('string', ltrim($numericString, '-0')); assertType('string', rtrim($numericString, '-0')); + + assertType('string', trim($numericString, '/..:')); + assertType('string', ltrim($numericString, '/..:')); + assertType('string', rtrim($numericString, '/..:')); + assertType('string', chop($numericString, '/..:')); + assertType('string', trim($numericString, '!..~')); + + assertType('numeric-string', trim($numericString, 'a..z')); + assertType('numeric-string', trim($numericString, '..:')); + assertType('numeric-string', trim($numericString, ':../')); } }