55use Nette \Utils \Strings ;
66use PHPStan \DependencyInjection \AutowiredService ;
77use PHPStan \Php \PhpVersion ;
8+ use ValueError ;
89use function array_filter ;
910use function array_keys ;
1011use function count ;
1112use function in_array ;
1213use function max ;
14+ use function restore_error_handler ;
15+ use function set_error_handler ;
1316use function sprintf ;
17+ use function sscanf ;
1418use function strlen ;
1519use const PREG_SET_ORDER ;
1620
@@ -26,24 +30,44 @@ public function __construct(private PhpVersion $phpVersion)
2630
2731 public function getPrintfPlaceholdersCount (string $ format ): ?int
2832 {
29- return $ this ->getPlaceholdersCount (self ::PRINTF_SPECIFIER_PATTERN , $ format, false );
33+ return $ this ->getPlaceholdersCount (self ::PRINTF_SPECIFIER_PATTERN , $ format );
3034 }
3135
3236 /** @phpstan-return array<int, non-empty-list<PrintfPlaceholder>> parameter index => placeholders */
3337 public function getPrintfPlaceholders (string $ format ): ?array
3438 {
35- return $ this ->parsePlaceholders (self ::PRINTF_SPECIFIER_PATTERN , $ format, false );
39+ return $ this ->parsePlaceholders (self ::PRINTF_SPECIFIER_PATTERN , $ format );
3640 }
3741
3842 public function getScanfPlaceholdersCount (string $ format ): ?int
3943 {
40- return $ this ->getPlaceholdersCount ('(?<specifier>[cdDeEfinosuxX%s]|\[[^\]]+\]) ' , $ format , true );
44+ $ throws = $ this ->phpVersion ->throwsValueErrorForInternalFunctions ();
45+ try {
46+ if ($ throws === false ) {
47+ set_error_handler (
48+ static function ($ s , $ m ) {
49+ throw new ValueError ($ m , 0 );
50+ },
51+ );
52+ }
53+ $ result = sscanf ('' , '%*n ' . $ format );
54+ if ($ result === null ) {
55+ return null ;
56+ }
57+ return count ($ result );
58+ } catch (ValueError ) {
59+ return null ;
60+ } finally {
61+ if ($ throws === false ) {
62+ restore_error_handler ();
63+ }
64+ }
4165 }
4266
4367 /**
4468 * @phpstan-return array<int, non-empty-list<PrintfPlaceholder>>|null parameter index => placeholders
4569 */
46- private function parsePlaceholders (string $ specifiersPattern , string $ format, bool $ isScanf ): ?array
70+ private function parsePlaceholders (string $ specifiersPattern , string $ format ): ?array
4771 {
4872 $ addSpecifier = '' ;
4973 if ($ this ->phpVersion ->supportsHhPrintfSpecifier ()) {
@@ -72,10 +96,6 @@ private function parsePlaceholders(string $specifiersPattern, string $format, bo
7296 $ showValueSuffix = false ;
7397
7498 if (isset ($ placeholder ['width ' ]) && $ placeholder ['width ' ] !== '' ) {
75- if ($ isScanf ) {
76- // In scanf, * means assignment suppression - skip this placeholder entirely
77- continue ;
78- }
7999 $ parsedPlaceholders [] = new PrintfPlaceholder (
80100 sprintf ('"%s" (width) ' , $ placeholder [0 ]),
81101 $ parameterIdx ++,
@@ -136,9 +156,9 @@ private function getAcceptingTypeBySpecifier(string $specifier): string
136156 return 'mixed ' ;
137157 }
138158
139- private function getPlaceholdersCount (string $ specifiersPattern , string $ format, bool $ isScanf ): ?int
159+ private function getPlaceholdersCount (string $ specifiersPattern , string $ format ): ?int
140160 {
141- $ placeholdersMap = $ this ->parsePlaceholders ($ specifiersPattern , $ format, $ isScanf );
161+ $ placeholdersMap = $ this ->parsePlaceholders ($ specifiersPattern , $ format );
142162 if ($ placeholdersMap === null ) {
143163 return null ;
144164 }
0 commit comments