From 077aae4d92f22a46c3c8db5effb67908ed4f0a8e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 17 Jun 2022 18:36:26 +0200 Subject: [PATCH 1/2] support positional arguments in sprintf() constant format inference --- ...intfFunctionDynamicReturnTypeExtension.php | 10 ++++++-- tests/PHPStan/Analyser/data/bug-7387.php | 23 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php index ad7b58c227..6d0051d213 100644 --- a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php @@ -33,7 +33,7 @@ public function getTypeFromFunctionCall( FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope, - ): Type + ): ?Type { $args = $functionCall->getArgs(); if (count($args) === 0) { @@ -43,7 +43,13 @@ public function getTypeFromFunctionCall( $formatType = $scope->getType($args[0]->value); if ($formatType instanceof ConstantStringType) { - if (preg_match('/^%[0-9]*\.?[0-9]+[bdeEfFgGhHouxX]$/', $formatType->getValue()) === 1) { + // The printf format is %[argnum$][flags][width][.precision] + if (preg_match('/^%([0-9]*\$)?[0-9]*\.?[0-9]+[bdeEfFgGhHouxX]$/', $formatType->getValue(), $matches) === 1) { + // invalid positional argument + if (array_key_exists(1, $matches) && $matches[1] === '0$') { + return null; + } + return new IntersectionType([ new StringType(), new AccessoryNumericStringType(), diff --git a/tests/PHPStan/Analyser/data/bug-7387.php b/tests/PHPStan/Analyser/data/bug-7387.php index ef8ab3d44d..f13038a1ee 100644 --- a/tests/PHPStan/Analyser/data/bug-7387.php +++ b/tests/PHPStan/Analyser/data/bug-7387.php @@ -42,4 +42,27 @@ public function specifiers(int $i) { assertType('numeric-string', sprintf('%14X', $i)); } + + public function positionalArgs($mixed, int $i, float $f, string $s) { + // https://3v4l.org/vVL0c + assertType('non-empty-string', sprintf('%2$14s', $mixed, $i)); + + assertType('numeric-string', sprintf('%2$.14F', $mixed, $i)); + assertType('numeric-string', sprintf('%2$.14F', $mixed, $f)); + assertType('numeric-string', sprintf('%2$.14F', $mixed, $s)); + + assertType('numeric-string', sprintf('%2$1.14F', $mixed, $i)); + assertType('numeric-string', sprintf('%2$2.14F', $mixed, $f)); + assertType('numeric-string', sprintf('%2$3.14F', $mixed, $s)); + + assertType('numeric-string', sprintf('%2$14F', $mixed, $i)); + assertType('numeric-string', sprintf('%2$14F', $mixed, $f)); + assertType('numeric-string', sprintf('%2$14F', $mixed, $s)); + + assertType('numeric-string', sprintf('%10$14F', $mixed, $s)); + } + + public function invalidPositionalArgFormat($mixed, string $s) { + assertType('string', sprintf('%0$14F', $mixed, $s)); + } } From dcff4f8de08f7c2aa66ad42a05e3b797feeb0e19 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 18 Jun 2022 07:42:04 +0200 Subject: [PATCH 2/2] Cs --- src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php index 6d0051d213..ea068e19ae 100644 --- a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php @@ -15,6 +15,7 @@ use PHPStan\Type\StringType; use PHPStan\Type\Type; use Throwable; +use function array_key_exists; use function array_shift; use function count; use function is_string;