From f4927dbe653460799b830370b8323644bf48e74a Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 16 Nov 2024 22:50:34 +0100 Subject: [PATCH 1/2] Fix sprintf dynamic return type --- ...intfFunctionDynamicReturnTypeExtension.php | 21 +++++---- tests/PHPStan/Analyser/nsrt/bug-12065.php | 43 +++++++++++++++++++ 2 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12065.php diff --git a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php index dc30f2afdd..af57302c58 100644 --- a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php @@ -29,6 +29,7 @@ use function count; use function in_array; use function intval; +use function is_array; use function is_string; use function preg_match; use function sprintf; @@ -69,7 +70,7 @@ public function getTypeFromFunctionCall( static fn (Type $type): bool => $type->toString()->isLowercaseString()->yes() ); - $singlePlaceholderEarlyReturn = null; + $singlePlaceholderEarlyReturn = []; $allPatternsNonEmpty = count($formatStrings) !== 0; $allPatternsNonFalsy = count($formatStrings) !== 0; foreach ($formatStrings as $constantString) { @@ -93,8 +94,11 @@ public function getTypeFromFunctionCall( $allPatternsNonFalsy = false; } - // The printf format is %[argnum$][flags][width][.precision]specifier. - if (preg_match('/^%(?P[0-9]*\$)?(?P[0-9]*)\.?[0-9]*(?P[sbdeEfFgGhHouxX])$/', $constantString->getValue(), $matches) === 1) { + if ( + is_array($singlePlaceholderEarlyReturn) + // The printf format is %[argnum$][flags][width][.precision]specifier. + && preg_match('/^%(?P[0-9]*\$)?(?P[0-9]*)\.?[0-9]*(?P[sbdeEfFgGhHouxX])$/', $constantString->getValue(), $matches) === 1 + ) { if ($matches['argnum'] !== '') { // invalid positional argument if ($matches['argnum'] === '0$') { @@ -132,14 +136,14 @@ public function getTypeFromFunctionCall( continue 2; } } - $singlePlaceholderEarlyReturn = TypeCombinator::union(...$result); + $singlePlaceholderEarlyReturn[] = TypeCombinator::union(...$result); continue; } - $singlePlaceholderEarlyReturn = $checkArgType->toString(); + $singlePlaceholderEarlyReturn[] = $checkArgType->toString(); } elseif ($matches['specifier'] !== 's') { - $singlePlaceholderEarlyReturn = $this->getStringReturnType( + $singlePlaceholderEarlyReturn[] = $this->getStringReturnType( new AccessoryNumericStringType(), $isLowercase, ); @@ -149,11 +153,10 @@ public function getTypeFromFunctionCall( } $singlePlaceholderEarlyReturn = null; - break; } - if ($singlePlaceholderEarlyReturn !== null) { - return $singlePlaceholderEarlyReturn; + if (is_array($singlePlaceholderEarlyReturn) && count($singlePlaceholderEarlyReturn) > 0) { + return TypeCombinator::union(...$singlePlaceholderEarlyReturn); } if ($allPatternsNonFalsy) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-12065.php b/tests/PHPStan/Analyser/nsrt/bug-12065.php new file mode 100644 index 0000000000..e0f9353eec --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12065.php @@ -0,0 +1,43 @@ + $key + * @param bool $preserveKeys + * + * @return void + */ + public function bar2( + string $key, + bool $preserveKeys, + ): void { + $format = $preserveKeys ? '%s' : '%d'; + + $_key = sprintf($format, $key); + assertType("string", $_key); + } +} From 66b674d823d119f7b10e3a06740985dcb6e75a60 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 17 Nov 2024 19:36:43 +0100 Subject: [PATCH 2/2] Simplified --- src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php index af57302c58..e379c4cc3c 100644 --- a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php @@ -126,17 +126,15 @@ public function getTypeFromFunctionCall( $constArgTypes = $checkArgType->getConstantScalarTypes(); } if ($constArgTypes !== []) { - $result = []; $printfArgs = array_fill(0, count($args) - 1, ''); foreach ($constArgTypes as $constArgType) { $printfArgs[$checkArg - 1] = $constArgType->getValue(); try { - $result[] = new ConstantStringType(@sprintf($constantString->getValue(), ...$printfArgs)); + $singlePlaceholderEarlyReturn[] = new ConstantStringType(@sprintf($constantString->getValue(), ...$printfArgs)); } catch (Throwable) { continue 2; } } - $singlePlaceholderEarlyReturn[] = TypeCombinator::union(...$result); continue; }