From cca1d2b21313b5154ca780e4298bc97658536972 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 27 May 2025 09:49:56 +0200 Subject: [PATCH 1/2] Improve TrimDynamicReturnType --- phpstan-baseline.neon | 6 --- .../Php/LtrimFunctionReturnTypeExtension.php | 24 ++++++++++-- ...TrimFunctionDynamicReturnTypeExtension.php | 39 ++++++++++++++++++- tests/PHPStan/Analyser/nsrt/trim.php | 36 +++++++++++++++++ 4 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/trim.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index a07ea29556..8a84b172f6 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1560,12 +1560,6 @@ parameters: count: 1 path: src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php - - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 2 - path: src/Type/Php/LtrimFunctionReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType diff --git a/src/Type/Php/LtrimFunctionReturnTypeExtension.php b/src/Type/Php/LtrimFunctionReturnTypeExtension.php index 701e474fe5..77cd8cbb7b 100644 --- a/src/Type/Php/LtrimFunctionReturnTypeExtension.php +++ b/src/Type/Php/LtrimFunctionReturnTypeExtension.php @@ -14,6 +14,7 @@ use PHPStan\Type\IntersectionType; use PHPStan\Type\StringType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use function count; use function ltrim; @@ -53,12 +54,27 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $trimChars = $scope->getType($functionCall->getArgs()[1]->value); - if ($trimChars instanceof ConstantStringType && $trimChars->getValue() === '\\' && $string->isClassString()->yes()) { - if ($string instanceof ConstantStringType) { - return new ConstantStringType(ltrim($string->getValue(), $trimChars->getValue()), true); + $trimConstantStrings = $trimChars->getConstantStrings(); + if (count($trimConstantStrings) > 0) { + $result = []; + $stringConstantStrings = $string->getConstantStrings(); + + foreach ($trimConstantStrings as $trimConstantString) { + if (count($stringConstantStrings) > 0) { + foreach ($stringConstantStrings as $stringConstantString) { + $result[] = new ConstantStringType( + ltrim($stringConstantString->getValue(), $trimConstantString->getValue()), + true, + ); + } + } elseif ($trimConstantString->getValue() === '\\' && $string->isClassString()->yes()) { + $result[] = new ClassStringType(); + } else { + return $defaultType; + } } - return new ClassStringType(); + return TypeCombinator::union(...$result); } return $defaultType; diff --git a/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php b/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php index 3b6c27320a..3cf845f523 100644 --- a/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php @@ -8,12 +8,16 @@ use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryUppercaseStringType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntersectionType; use PHPStan\Type\StringType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use function count; use function in_array; +use function rtrim; +use function trim; #[AutowiredService] final class TrimFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension @@ -37,6 +41,7 @@ public function getTypeFromFunctionCall( $stringType = $scope->getType($args[0]->value); $accessory = []; + $defaultType = new StringType(); if ($stringType->isLowercaseString()->yes()) { $accessory[] = new AccessoryLowercaseStringType(); } @@ -45,10 +50,40 @@ public function getTypeFromFunctionCall( } if (count($accessory) > 0) { $accessory[] = new StringType(); - return new IntersectionType($accessory); + $defaultType = new IntersectionType($accessory); } - return new StringType(); + if (count($functionCall->getArgs()) !== 2) { + return $defaultType; + } + + $trimChars = $scope->getType($functionCall->getArgs()[1]->value); + + $trimConstantStrings = $trimChars->getConstantStrings(); + if (count($trimConstantStrings) > 0) { + $result = []; + $stringConstantStrings = $stringType->getConstantStrings(); + $functionName = $functionReflection->getName(); + + foreach ($trimConstantStrings as $trimConstantString) { + if (count($stringConstantStrings) === 0) { + return $defaultType; + } + + foreach ($stringConstantStrings as $stringConstantString) { + $result[] = new ConstantStringType( + $functionName === 'rtrim' + ? rtrim($stringConstantString->getValue(), $trimConstantString->getValue()) + : trim($stringConstantString->getValue(), $trimConstantString->getValue()), + true, + ); + } + } + + return TypeCombinator::union(...$result); + } + + return $defaultType; } } diff --git a/tests/PHPStan/Analyser/nsrt/trim.php b/tests/PHPStan/Analyser/nsrt/trim.php new file mode 100644 index 0000000000..60fa0a9105 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/trim.php @@ -0,0 +1,36 @@ + Date: Tue, 27 May 2025 11:22:41 +0200 Subject: [PATCH 2/2] Add tests --- tests/PHPStan/Analyser/nsrt/trim.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/trim.php b/tests/PHPStan/Analyser/nsrt/trim.php index 60fa0a9105..51908b0995 100644 --- a/tests/PHPStan/Analyser/nsrt/trim.php +++ b/tests/PHPStan/Analyser/nsrt/trim.php @@ -10,9 +10,10 @@ class Foo /** * @param 'foo' $foo * @param 'foo'|'bar' $fooOrBar + * @param 'constant'|class-string $constantOrFooClass * @param string $string */ - public function doTrim($foo, $fooOrBar, $string): void + public function doTrim($foo, $fooOrBar, $constantOrFooClass, $string): void { assertType('string', trim($string, $foo)); assertType('string', ltrim($string, $foo)); @@ -31,6 +32,13 @@ public function doTrim($foo, $fooOrBar, $string): void assertType('\'\'|\'bar\'', trim($fooOrBar, $foo)); assertType('\'\'|\'bar\'', ltrim($fooOrBar, $foo)); assertType('\'\'|\'bar\'', rtrim($fooOrBar, $foo)); + + assertType('string', trim($constantOrFooClass, '\\')); + assertType('string', ltrim($constantOrFooClass, '\\')); + assertType('string', rtrim($constantOrFooClass, '\\')); + assertType('string', trim($constantOrFooClass, '\\')); + assertType('string', ltrim($constantOrFooClass, '\\')); + assertType('string', rtrim($constantOrFooClass, '\\')); } }