diff --git a/src/Rules/Functions/PrintfHelper.php b/src/Rules/Functions/PrintfHelper.php index 65334d578f2..411972885d3 100644 --- a/src/Rules/Functions/PrintfHelper.php +++ b/src/Rules/Functions/PrintfHelper.php @@ -26,24 +26,24 @@ public function __construct(private PhpVersion $phpVersion) public function getPrintfPlaceholdersCount(string $format): ?int { - return $this->getPlaceholdersCount(self::PRINTF_SPECIFIER_PATTERN, $format); + return $this->getPlaceholdersCount(self::PRINTF_SPECIFIER_PATTERN, $format, false); } /** @phpstan-return array> parameter index => placeholders */ public function getPrintfPlaceholders(string $format): ?array { - return $this->parsePlaceholders(self::PRINTF_SPECIFIER_PATTERN, $format); + return $this->parsePlaceholders(self::PRINTF_SPECIFIER_PATTERN, $format, false); } public function getScanfPlaceholdersCount(string $format): ?int { - return $this->getPlaceholdersCount('(?[cdDeEfinosuxX%s]|\[[^\]]+\])', $format); + return $this->getPlaceholdersCount('(?[cdDeEfinosuxX%s]|\[[^\]]+\])', $format, true); } /** * @phpstan-return array>|null parameter index => placeholders */ - private function parsePlaceholders(string $specifiersPattern, string $format): ?array + private function parsePlaceholders(string $specifiersPattern, string $format, bool $isScanf): ?array { $addSpecifier = ''; if ($this->phpVersion->supportsHhPrintfSpecifier()) { @@ -72,6 +72,10 @@ private function parsePlaceholders(string $specifiersPattern, string $format): ? $showValueSuffix = false; if (isset($placeholder['width']) && $placeholder['width'] !== '') { + if ($isScanf) { + // In scanf, * means assignment suppression - skip this placeholder entirely + continue; + } $parsedPlaceholders[] = new PrintfPlaceholder( sprintf('"%s" (width)', $placeholder[0]), $parameterIdx++, @@ -132,9 +136,9 @@ private function getAcceptingTypeBySpecifier(string $specifier): string return 'mixed'; } - private function getPlaceholdersCount(string $specifiersPattern, string $format): ?int + private function getPlaceholdersCount(string $specifiersPattern, string $format, bool $isScanf): ?int { - $placeholdersMap = $this->parsePlaceholders($specifiersPattern, $format); + $placeholdersMap = $this->parsePlaceholders($specifiersPattern, $format, $isScanf); if ($placeholdersMap === null) { return null; } diff --git a/tests/PHPStan/Analyser/nsrt/sscanf.php b/tests/PHPStan/Analyser/nsrt/sscanf.php index 6e880506011..484febdf9b4 100644 --- a/tests/PHPStan/Analyser/nsrt/sscanf.php +++ b/tests/PHPStan/Analyser/nsrt/sscanf.php @@ -48,3 +48,10 @@ function fooo(string $s) { assertType('array{int|null, int|null, int|null}|null', sscanf('00ccff', '%2x%2x%2x')); } + +function sscanfSuppression(string $s) { + // %* means assignment suppression - these should not appear in return array + assertType('array{int|null}|null', sscanf($s, '%*s %d')); + assertType('array{string|null}|null', sscanf($s, '%*d %s')); + assertType('array{int|null}|null', sscanf($s, '%*[a-z]%d')); +} diff --git a/tests/PHPStan/Rules/Functions/PrintfParametersRuleTest.php b/tests/PHPStan/Rules/Functions/PrintfParametersRuleTest.php index c4e5f101a30..1e3ab9ddd78 100644 --- a/tests/PHPStan/Rules/Functions/PrintfParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/PrintfParametersRuleTest.php @@ -142,4 +142,9 @@ public function testBug8774(): void $this->analyse([__DIR__ . '/data/bug-8774.php'], []); } + public function testBug10260(): void + { + $this->analyse([__DIR__ . '/data/bug-10260.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-10260.php b/tests/PHPStan/Rules/Functions/data/bug-10260.php new file mode 100644 index 00000000000..9f3032cbfd4 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-10260.php @@ -0,0 +1,27 @@ +