From 151f101f7c36aeb7a6285fb8bff520d86dd8a6a7 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:12:12 +0200 Subject: [PATCH 01/10] fix PHP 7 sprintf too many arguments false positive Fix https://github.com/vimeo/psalm/issues/9941 --- .../ReturnTypeProvider/SprintfReturnTypeProvider.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php index 4b770d9e8..f38074759 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php @@ -123,8 +123,8 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface return Type::getString(); } - $args_count = count($call_args) - 1; - $dummy = array_fill(0, $args_count, ''); + $provided_placeholders_count = count($call_args) - 1; + $dummy = array_fill(0, $provided_placeholders_count, ''); // check if we have enough/too many arguments and a valid format $initial_result = null; @@ -166,7 +166,7 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface break 2; } catch (ArgumentCountError $error) { // PHP 8 - if (count($dummy) >= $args_count) { + if (count($dummy) === $provided_placeholders_count) { IssueBuffer::maybeAdd( new TooFewArguments( 'Too few arguments for ' . $event->getFunctionId(), @@ -181,7 +181,7 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface // we are in the next iteration, so we have 1 placeholder less here // otherwise we would have reported an error above already - if (count($dummy) + 1 === $args_count) { + if (count($dummy) + 1 === $provided_placeholders_count) { break; } @@ -202,7 +202,7 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface * * @psalm-suppress DocblockTypeContradiction */ - if ($result === false && count($dummy) >= $args_count) { + if ($result === false && count($dummy) === $provided_placeholders_count) { IssueBuffer::maybeAdd( new TooFewArguments( 'Too few arguments for ' . $event->getFunctionId(), @@ -220,7 +220,7 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface * * @psalm-suppress DocblockTypeContradiction */ - if ($result === false && count($dummy) + 1 !== $args_count) { + if ($result === false && count($dummy) + 1 <= $provided_placeholders_count) { IssueBuffer::maybeAdd( new TooManyArguments( 'Too many arguments for the number of placeholders in ' . $event->getFunctionId(), From f2a9e0913688fe210b7eac4cd8e4f36178e67aae Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Wed, 21 Jun 2023 16:51:12 +0200 Subject: [PATCH 02/10] reorganize code for PHP 7 to also check for invalid argument and reduce duplicate --- .../SprintfReturnTypeProvider.php | 61 +++++++++---------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php index f38074759..28b966ea0 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php @@ -178,23 +178,6 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface break 2; } - - // we are in the next iteration, so we have 1 placeholder less here - // otherwise we would have reported an error above already - if (count($dummy) + 1 === $provided_placeholders_count) { - break; - } - - IssueBuffer::maybeAdd( - new TooManyArguments( - 'Too many arguments for the number of placeholders in ' . $event->getFunctionId(), - $event->getCodeLocation(), - $event->getFunctionId(), - ), - $statements_source->getSuppressedIssues(), - ); - - break; } /** @@ -203,24 +186,34 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface * @psalm-suppress DocblockTypeContradiction */ if ($result === false && count($dummy) === $provided_placeholders_count) { - IssueBuffer::maybeAdd( - new TooFewArguments( - 'Too few arguments for ' . $event->getFunctionId(), - $event->getCodeLocation(), - $event->getFunctionId(), - ), - $statements_source->getSuppressedIssues(), - ); + // could be invalid format or too few arguments - we cannot distinguish this in PHP 7 without additional checks + $max_dummy = array_fill(0, 100, ''); + $result = @sprintf($type->getSingleStringLiteral()->value, ...$max_dummy); + if ($result === false) { + // the format is invalid + IssueBuffer::maybeAdd( + new InvalidArgument( + 'Argument 1 of ' . $event->getFunctionId() . ' is invalid', + $event->getCodeLocation(), + $event->getFunctionId(), + ), + $statements_source->getSuppressedIssues(), + ); + } else { + IssueBuffer::maybeAdd( + new TooFewArguments( + 'Too few arguments for ' . $event->getFunctionId(), + $event->getCodeLocation(), + $event->getFunctionId(), + ), + $statements_source->getSuppressedIssues(), + ); + } return Type::getFalse(); } - /** - * PHP 7 - * - * @psalm-suppress DocblockTypeContradiction - */ - if ($result === false && count($dummy) + 1 <= $provided_placeholders_count) { + if (is_string($result) && count($dummy) + 1 <= $provided_placeholders_count) { IssueBuffer::maybeAdd( new TooManyArguments( 'Too many arguments for the number of placeholders in ' . $event->getFunctionId(), @@ -233,7 +226,10 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface break; } - // for PHP 7, since it doesn't throw above + if (!is_string($result)) { + break; + } + // abort if it's empty, since we checked everything if (array_pop($dummy) === null) { break; @@ -274,7 +270,6 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface // if the function has more arguments than the pattern has placeholders, this could be a false positive // if the param is not used in the pattern - // however this is already reported above and returned, so this cannot happen if ($type->isNonEmptyString() || $type->isInt() || $type->isFloat()) { return Type::getNonEmptyString(); } From fad17686bd3fecbb122f90a5b41e656d19db4c11 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Wed, 21 Jun 2023 16:51:32 +0200 Subject: [PATCH 03/10] Fix false positive for array unpacking Fix https://github.com/vimeo/psalm/issues/9873 --- .../ReturnTypeProvider/SprintfReturnTypeProvider.php | 12 ++++++++++++ tests/ReturnTypeProvider/SprintfTest.php | 10 ++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php index 28b966ea0..3a7670c8e 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php @@ -62,6 +62,18 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface } $node_type_provider = $statements_source->getNodeTypeProvider(); + foreach ($call_args as $index => $call_arg) { + $type = $node_type_provider->getType($call_arg->value); + if ($type === null) { + continue; + } + + // if it's an array, used with splat operator, we cannot validate it reliably below and report false positive errors + if ($type->isArray()) { + return null; + } + } + foreach ($call_args as $index => $call_arg) { $type = $node_type_provider->getType($call_arg->value); if ($type === null && $index === 0 && $event->getFunctionId() === 'printf') { diff --git a/tests/ReturnTypeProvider/SprintfTest.php b/tests/ReturnTypeProvider/SprintfTest.php index 60e112f3e..b25ed7f90 100644 --- a/tests/ReturnTypeProvider/SprintfTest.php +++ b/tests/ReturnTypeProvider/SprintfTest.php @@ -182,6 +182,16 @@ class SprintfTest extends TestCase '$val===' => 'string', ], ]; + + yield 'sprintfSplatUnpackingArray' => [ + 'code' => ' [ + '$val===' => 'string', + ], + ]; } public function providerInvalidCodeParse(): iterable From 52ca871057625a2c43bef8fda89e142929ce1b89 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Wed, 21 Jun 2023 16:59:31 +0200 Subject: [PATCH 04/10] fix try/catch variable must be initialized --- .../Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php index 3a7670c8e..bf28b73ae 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php @@ -141,6 +141,7 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface // check if we have enough/too many arguments and a valid format $initial_result = null; while (count($dummy) > -1) { + $result = null; try { // before PHP 8, an uncatchable Warning is thrown if too few arguments are passed // which is ignored and handled below instead From 9599c240b3292279c13a371cb4c979a42fb2f123 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Wed, 21 Jun 2023 17:00:35 +0200 Subject: [PATCH 05/10] code style --- .../ReturnTypeProvider/SprintfReturnTypeProvider.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php index bf28b73ae..4750654ba 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php @@ -22,6 +22,7 @@ use ValueError; use function array_fill; use function array_pop; use function count; +use function is_string; use function preg_match; use function sprintf; @@ -68,7 +69,8 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface continue; } - // if it's an array, used with splat operator, we cannot validate it reliably below and report false positive errors + // if it's an array, used with splat operator + // we cannot validate it reliably below and report false positive errors if ($type->isArray()) { return null; } @@ -199,7 +201,8 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface * @psalm-suppress DocblockTypeContradiction */ if ($result === false && count($dummy) === $provided_placeholders_count) { - // could be invalid format or too few arguments - we cannot distinguish this in PHP 7 without additional checks + // could be invalid format or too few arguments + // we cannot distinguish this in PHP 7 without additional checks $max_dummy = array_fill(0, 100, ''); $result = @sprintf($type->getSingleStringLiteral()->value, ...$max_dummy); if ($result === false) { From 28b9e8dd950401ff44b70df9b7783a4ae3c28d6b Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Wed, 21 Jun 2023 17:18:03 +0200 Subject: [PATCH 06/10] run format checks for splat too --- .../SprintfReturnTypeProvider.php | 14 +++++++++++--- tests/ReturnTypeProvider/SprintfTest.php | 12 +++++++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php index 4750654ba..833ce29c4 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php @@ -62,8 +62,9 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface return null; } + $has_splat_args = false; $node_type_provider = $statements_source->getNodeTypeProvider(); - foreach ($call_args as $index => $call_arg) { + foreach ($call_args as $call_arg) { $type = $node_type_provider->getType($call_arg->value); if ($type === null) { continue; @@ -72,7 +73,8 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface // if it's an array, used with splat operator // we cannot validate it reliably below and report false positive errors if ($type->isArray()) { - return null; + $has_splat_args = true; + break; } } @@ -137,7 +139,8 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface return Type::getString(); } - $provided_placeholders_count = count($call_args) - 1; + // assume a random, high number for tests + $provided_placeholders_count = $has_splat_args === true ? 100 : count($call_args) - 1; $dummy = array_fill(0, $provided_placeholders_count, ''); // check if we have enough/too many arguments and a valid format @@ -229,6 +232,11 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface return Type::getFalse(); } + // we can only validate the format and arg 1 when using splat + if ($has_splat_args === true) { + break; + } + if (is_string($result) && count($dummy) + 1 <= $provided_placeholders_count) { IssueBuffer::maybeAdd( new TooManyArguments( diff --git a/tests/ReturnTypeProvider/SprintfTest.php b/tests/ReturnTypeProvider/SprintfTest.php index b25ed7f90..9bb7fa489 100644 --- a/tests/ReturnTypeProvider/SprintfTest.php +++ b/tests/ReturnTypeProvider/SprintfTest.php @@ -186,12 +186,22 @@ class SprintfTest extends TestCase yield 'sprintfSplatUnpackingArray' => [ 'code' => ' [ '$val===' => 'string', ], ]; + + yield 'sprintfSplatUnpackingArrayNonEmpty' => [ + 'code' => ' [ + '$val===' => 'non-empty-string', + ], + ]; } public function providerInvalidCodeParse(): iterable From 7f0217d1e318ae99f8d5bc371f86aa9d1163e447 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Sat, 24 Jun 2023 17:31:44 +0200 Subject: [PATCH 07/10] fix incorrect default stubs --- .../ReturnTypeProvider/SprintfReturnTypeProvider.php | 8 +------- stubs/CoreGenericFunctions.phpstub | 10 ++++------ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php index 833ce29c4..98820d41f 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php @@ -130,13 +130,7 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface '/%(?:\d+\$)?[-+]?(?:\d+|\*)(?:\.(?:\d+|\*))?[bcdouxXeEfFgGhHs]/', $type->getSingleStringLiteral()->value, ) === 1) { - if ($event->getFunctionId() === 'printf') { - return null; - } - - // the core stubs are wrong for these too, since these might be empty strings - // e.g. sprintf(\'%0.*s\', 0, "abc") - return Type::getString(); + return null; } // assume a random, high number for tests diff --git a/stubs/CoreGenericFunctions.phpstub b/stubs/CoreGenericFunctions.phpstub index 9277cb304..d87b97f95 100644 --- a/stubs/CoreGenericFunctions.phpstub +++ b/stubs/CoreGenericFunctions.phpstub @@ -1279,9 +1279,7 @@ function preg_quote(string $str, ?string $delimiter = null) : string {} * @psalm-pure * * @param string|int|float $values - * @return ($format is non-empty-string - * ? ($values is non-empty-string|int|float ? non-empty-string : string) - * : string) + * @return (PHP_MAJOR_VERSION is 8 ? string : string|false) * * @psalm-flow ($format, $values) -> return */ @@ -1290,7 +1288,7 @@ function sprintf(string $format, ...$values) {} /** * @psalm-pure * @param array $values - * @return string|false + * @return (PHP_MAJOR_VERSION is 8 ? string : string|false) * @psalm-ignore-falsable-return * * @psalm-flow ($format, $values) -> return @@ -1309,7 +1307,7 @@ function wordwrap(string $string, int $width = 75, string $break = "\n", bool $c * @psalm-pure * * @param string|int|float $values - * @return int<0, max> + * @return (PHP_MAJOR_VERSION is 8 ? int<0, max> : int<0, max>|false) * * @psalm-taint-specialize * @psalm-flow ($format, $values) -> return @@ -1320,7 +1318,7 @@ function printf(string $format, ...$values) {} /** * @param array $values - * @return int<0, max> + * @return (PHP_MAJOR_VERSION is 8 ? int<0, max> : int<0, max>|false) * * @psalm-pure * @psalm-taint-specialize From c5fee532f334c79053cd831384fb39af3e036f52 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Sat, 24 Jun 2023 17:46:54 +0200 Subject: [PATCH 08/10] consistently ignore falsable return, remove unused suppress with new types --- .../ReturnTypeProvider/SprintfReturnTypeProvider.php | 10 ---------- stubs/CoreGenericFunctions.phpstub | 3 +++ 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php index 98820d41f..1f154eab2 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php @@ -192,11 +192,6 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface } } - /** - * PHP 7 - * - * @psalm-suppress DocblockTypeContradiction - */ if ($result === false && count($dummy) === $provided_placeholders_count) { // could be invalid format or too few arguments // we cannot distinguish this in PHP 7 without additional checks @@ -260,11 +255,6 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface return null; } - /** - * PHP 7 can have false here - * - * @psalm-suppress RedundantConditionGivenDocblockType - */ if ($initial_result !== null && $initial_result !== false && $initial_result !== '') { return Type::getNonEmptyString(); } diff --git a/stubs/CoreGenericFunctions.phpstub b/stubs/CoreGenericFunctions.phpstub index d87b97f95..38cdb31b3 100644 --- a/stubs/CoreGenericFunctions.phpstub +++ b/stubs/CoreGenericFunctions.phpstub @@ -1280,6 +1280,7 @@ function preg_quote(string $str, ?string $delimiter = null) : string {} * * @param string|int|float $values * @return (PHP_MAJOR_VERSION is 8 ? string : string|false) + * @psalm-ignore-falsable-return * * @psalm-flow ($format, $values) -> return */ @@ -1308,6 +1309,7 @@ function wordwrap(string $string, int $width = 75, string $break = "\n", bool $c * * @param string|int|float $values * @return (PHP_MAJOR_VERSION is 8 ? int<0, max> : int<0, max>|false) + * @psalm-ignore-falsable-return * * @psalm-taint-specialize * @psalm-flow ($format, $values) -> return @@ -1319,6 +1321,7 @@ function printf(string $format, ...$values) {} /** * @param array $values * @return (PHP_MAJOR_VERSION is 8 ? int<0, max> : int<0, max>|false) + * @psalm-ignore-falsable-return * * @psalm-pure * @psalm-taint-specialize From 0535c6b87764e211cb571a60b69335b29bd847f3 Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Sat, 24 Jun 2023 18:02:54 +0200 Subject: [PATCH 09/10] PHP 7 format with only placeholders isn't falsable if valid limit tests to PHP 8 to avoid having to create them twice and add specific test for Issue 9941 --- .../SprintfReturnTypeProvider.php | 12 ++++++- tests/ReturnTypeProvider/SprintfTest.php | 32 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php index 1f154eab2..45ab32fca 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php @@ -78,6 +78,8 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface } } + // PHP 7 handling for formats that do not contain anything but placeholders + $is_falsable = true; foreach ($call_args as $index => $call_arg) { $type = $node_type_provider->getType($call_arg->value); if ($type === null && $index === 0 && $event->getFunctionId() === 'printf') { @@ -259,11 +261,15 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface return Type::getNonEmptyString(); } - // if we didn't have a valid result + // if we didn't have any valid result // the pattern is invalid or not yet supported by the return type provider if ($initial_result === null || $initial_result === false) { return null; } + + // the initial result is an empty string + // which means the format is valid and it depends on the args, whether it is non-empty-string or not + $is_falsable = false; } if ($index === 0 && $event->getFunctionId() === 'printf') { @@ -307,6 +313,10 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface return Type::getNonEmptyString(); } + if ( $is_falsable === false ) { + return Type::getString(); + } + return null; } } diff --git a/tests/ReturnTypeProvider/SprintfTest.php b/tests/ReturnTypeProvider/SprintfTest.php index 9bb7fa489..c98fba675 100644 --- a/tests/ReturnTypeProvider/SprintfTest.php +++ b/tests/ReturnTypeProvider/SprintfTest.php @@ -130,6 +130,8 @@ class SprintfTest extends TestCase 'assertions' => [ '$val===' => 'int<0, max>', ], + 'ignored_issues' => [], + 'php_version' => '8.0', ]; yield 'sprintfEmptyStringFormat' => [ @@ -163,6 +165,8 @@ class SprintfTest extends TestCase 'assertions' => [ '$val===' => 'string', ], + 'ignored_issues' => [], + 'php_version' => '8.0', ]; yield 'sprintfComplexPlaceholderNotYetSupported2' => [ @@ -172,6 +176,8 @@ class SprintfTest extends TestCase 'assertions' => [ '$val===' => 'string', ], + 'ignored_issues' => [], + 'php_version' => '8.0', ]; yield 'sprintfComplexPlaceholderNotYetSupported3' => [ @@ -181,6 +187,8 @@ class SprintfTest extends TestCase 'assertions' => [ '$val===' => 'string', ], + 'ignored_issues' => [], + 'php_version' => '8.0', ]; yield 'sprintfSplatUnpackingArray' => [ @@ -191,6 +199,8 @@ class SprintfTest extends TestCase 'assertions' => [ '$val===' => 'string', ], + 'ignored_issues' => [], + 'php_version' => '8.0', ]; yield 'sprintfSplatUnpackingArrayNonEmpty' => [ @@ -202,6 +212,28 @@ class SprintfTest extends TestCase '$val===' => 'non-empty-string', ], ]; + + yield 'sprintfMultiplePlaceholdersNoErrorsIssue9941PHP7' => [ + 'code' => ' %d (%d)", 123, 456, 789); + ', + 'assertions' => [ + '$val===' => 'non-empty-string', + ], + 'ignored_issues' => [], + 'php_version' => '7.4', + ]; + + yield 'sprintfMultiplePlaceholdersNoErrorsIssue9941PHP8' => [ + 'code' => ' %d (%d)", 123, 456, 789); + ', + 'assertions' => [ + '$val===' => 'non-empty-string', + ], + 'ignored_issues' => [], + 'php_version' => '8.0', + ]; } public function providerInvalidCodeParse(): iterable From d4dcee3203712d3326dada9f51d72012c1a78c3b Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Sat, 24 Jun 2023 18:10:37 +0200 Subject: [PATCH 10/10] code style --- .../Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php index 45ab32fca..f691685c0 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php @@ -313,7 +313,7 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface return Type::getNonEmptyString(); } - if ( $is_falsable === false ) { + if ($is_falsable === false) { return Type::getString(); }