diff --git a/build/ignore-by-php-version.neon.php b/build/ignore-by-php-version.neon.php index 8aeff82810..d9bb8452e3 100644 --- a/build/ignore-by-php-version.neon.php +++ b/build/ignore-by-php-version.neon.php @@ -38,6 +38,11 @@ } else { $includes[] = __DIR__ . '/new-phpunit.neon'; } + +if (PHP_VERSION_ID < 80300) { + $includes[] = __DIR__ . '/str-increment.neon'; +} + $config = []; $config['includes'] = $includes; diff --git a/build/str-increment.neon b/build/str-increment.neon new file mode 100644 index 0000000000..327eab4361 --- /dev/null +++ b/build/str-increment.neon @@ -0,0 +1,4 @@ +parameters: + ignoreErrors: + - + message: '#^Used function str_increment not found\.#' diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 57d047fbf8..534722b2ad 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -150,6 +150,7 @@ use function array_values; use function count; use function explode; +use function function_exists; use function get_class; use function implode; use function in_array; @@ -160,6 +161,7 @@ use function ltrim; use function md5; use function sprintf; +use function str_increment; use function str_starts_with; use function strlen; use function strtolower; @@ -1731,12 +1733,26 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu $newTypes = []; foreach ($varScalars as $varValue) { + // until PHP 8.5 it was valid to increment/decrement an empty string. + // see https://github.com/php/php-src/issues/19597 if ($node instanceof Expr\PreInc) { - if (!is_bool($varValue)) { + if ($varValue === '') { + $varValue = '1'; + } elseif ( + is_string($varValue) + && !is_numeric($varValue) + && function_exists('str_increment') + ) { + $varValue = str_increment($varValue); + } elseif (!is_bool($varValue)) { ++$varValue; } - } elseif (is_numeric($varValue)) { - --$varValue; + } else { + if ($varValue === '') { + $varValue = -1; + } elseif (is_numeric($varValue)) { + --$varValue; + } } $newTypes[] = $this->getTypeFromValue($varValue); diff --git a/src/Type/Php/StrIncrementDecrementFunctionReturnTypeExtension.php b/src/Type/Php/StrIncrementDecrementFunctionReturnTypeExtension.php index 5c936e5f72..208d5b0b9e 100644 --- a/src/Type/Php/StrIncrementDecrementFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrIncrementDecrementFunctionReturnTypeExtension.php @@ -6,6 +6,7 @@ use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; +use PHPStan\ShouldNotHappenException; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\ErrorType; @@ -13,6 +14,7 @@ use PHPStan\Type\TypeCombinator; use function chr; use function count; +use function function_exists; use function implode; use function in_array; use function is_float; @@ -21,6 +23,7 @@ use function is_string; use function ord; use function preg_match; +use function str_increment; use function str_split; use function stripos; @@ -103,6 +106,14 @@ private function increment(string $s): string } } + if ($s === '') { + throw new ShouldNotHappenException(); + } + + if (function_exists('str_increment')) { + return str_increment($s); + } + return (string) ++$s; } diff --git a/tests/PHPStan/Analyser/nsrt/pre-dec.php b/tests/PHPStan/Analyser/nsrt/pre-dec.php new file mode 100644 index 0000000000..7e4bed4d1d --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/pre-dec.php @@ -0,0 +1,23 @@ +