Skip to content

Commit a247bd6

Browse files
authored
Merge pull request #11729 from eyupcanakman/fix/sprintf-precision-11721
Fix false InvalidArgument for sprintf precision placeholders
2 parents b115494 + 6e38dc1 commit a247bd6

File tree

2 files changed

+159
-7
lines changed

2 files changed

+159
-7
lines changed

src/Psalm/Internal/Provider/ReturnTypeProvider/SprintfReturnTypeProvider.php

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -161,17 +161,20 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev
161161
return Type::getString('');
162162
}
163163

164-
// these placeholders are too complex to handle for now
165-
if (preg_match(
166-
'/%(?:\d+\$)?[-+]?(?:\d+|\*)(?:\.(?:\d+|\*))?[bcdouxXeEfFgGhHs]/',
164+
// these placeholders still fall back to a generic return type,
165+
// but we validate their format and argument count first
166+
$has_complex_placeholder = preg_match(
167+
'/%(?:\d+\$)?[-+]?(?:'
168+
. '(?:\d+|\*(?:\d+\$)?)(?:\.(?:\d+|\*(?:\d+\$)?))?'
169+
. '|\.\*(?:\d+\$)?'
170+
. ')[bcdouxXeEfFgGhHs]/',
167171
$type->getSingleStringLiteral()->value,
168-
) === 1) {
169-
return null;
170-
}
172+
) === 1;
171173

172174
// assume a random, high number for tests
173175
$provided_placeholders_count = $has_splat_args === true ? 100 : count($call_args) - 1;
174-
$dummy = array_fill(0, $provided_placeholders_count, '');
176+
// Use integer dummies for * width/precision so PHP validates arity without raising a false ValueError.
177+
$dummy = array_fill(0, $provided_placeholders_count, $has_complex_placeholder ? 0 : '');
175178

176179
// check if we have enough/too many arguments and a valid format
177180
$initial_result = null;
@@ -287,6 +290,10 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev
287290
}
288291
}
289292

293+
if ($has_complex_placeholder) {
294+
return null;
295+
}
296+
290297
if ($event->getFunctionId() === 'printf') {
291298
// printf only has the format validated above
292299
// don't change the return type

tests/ReturnTypeProvider/SprintfTest.php

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,85 @@ public function providerValidCodeParse(): iterable
195195
'php_version' => '8.0',
196196
];
197197

198+
yield 'sprintfComplexPlaceholderNotYetSupported4' => [
199+
'code' => '<?php
200+
$precision = 1;
201+
$flt = 1.234;
202+
$val = sprintf("%.*f", $precision, $flt);
203+
',
204+
'assertions' => [
205+
'$val===' => 'string',
206+
],
207+
'ignored_issues' => [],
208+
'php_version' => '8.0',
209+
];
210+
211+
yield 'printfComplexPlaceholderNotYetSupported4' => [
212+
'code' => '<?php
213+
$precision = 1;
214+
$flt = 1.234;
215+
$val = printf("%.*f", $precision, $flt);
216+
',
217+
'assertions' => [
218+
'$val===' => 'int<0, max>',
219+
],
220+
'ignored_issues' => [],
221+
'php_version' => '8.0',
222+
];
223+
224+
yield 'sprintfComplexPlaceholderNotYetSupported5' => [
225+
'code' => '<?php
226+
$flt = 1.234;
227+
$precision = 1;
228+
$val = sprintf("%1\$.*2\$f", $flt, $precision);
229+
',
230+
'assertions' => [
231+
'$val===' => 'string',
232+
],
233+
'ignored_issues' => [],
234+
'php_version' => '8.0',
235+
];
236+
237+
yield 'sprintfComplexPlaceholderNotYetSupported6' => [
238+
'code' => '<?php
239+
$precision = 1;
240+
$flt = 1.234;
241+
$val = sprintf("%2\$.*1\$f", $precision, $flt);
242+
',
243+
'assertions' => [
244+
'$val===' => 'string',
245+
],
246+
'ignored_issues' => [],
247+
'php_version' => '8.0',
248+
];
249+
250+
yield 'sprintfComplexPlaceholderNotYetSupported7' => [
251+
'code' => '<?php
252+
$flt = 1.234;
253+
$precision = 1;
254+
$val = sprintf("%10.*2\$f", $flt, $precision);
255+
',
256+
'assertions' => [
257+
'$val===' => 'string',
258+
],
259+
'ignored_issues' => [],
260+
'php_version' => '8.0',
261+
];
262+
263+
yield 'sprintfComplexPlaceholderNotYetSupported8' => [
264+
'code' => '<?php
265+
$precision = 1;
266+
$width = 10;
267+
$flt = 1.234;
268+
$val = sprintf("%3\$*2\$.*1\$f", $precision, $width, $flt);
269+
',
270+
'assertions' => [
271+
'$val===' => 'string',
272+
],
273+
'ignored_issues' => [],
274+
'php_version' => '8.0',
275+
];
276+
198277
yield 'sprintfSplatUnpackingArray' => [
199278
'code' => '<?php
200279
$a = ["a", "b", "c"];
@@ -327,6 +406,72 @@ public function providerInvalidCodeParse(): iterable
327406
',
328407
'error_message' => 'InvalidArgument',
329408
],
409+
'sprintfEscapedPercentLiteralStillReportsTooManyArguments' => [
410+
'code' => '<?php
411+
$x = sprintf("%%.*f", "a", "b");
412+
',
413+
'error_message' => 'TooManyArguments',
414+
],
415+
'sprintfEscapedPercentThenPrecisionStarStillReportsTooFewArguments' => [
416+
'code' => '<?php
417+
$x = sprintf("%%%.*f", 1);
418+
',
419+
'error_message' => 'TooFewArguments',
420+
],
421+
'sprintfPositionalEscapedPercentThenPrecisionStarStillReportsTooFewArguments' => [
422+
'code' => '<?php
423+
$x = sprintf("%1$%%.*f", 1);
424+
',
425+
'error_message' => 'TooFewArguments',
426+
],
427+
'sprintfPrecisionStarTooFewArguments' => [
428+
'code' => '<?php
429+
$x = sprintf("%.*f", 1);
430+
',
431+
'error_message' => 'TooFewArguments',
432+
],
433+
'sprintfPrecisionStarTooManyArguments' => [
434+
'code' => '<?php
435+
$x = sprintf("%.*f", 1, 1.23, 99);
436+
',
437+
'error_message' => 'TooManyArguments',
438+
],
439+
'sprintfPositionalPrecisionStarTooFewArguments' => [
440+
'code' => '<?php
441+
$x = sprintf("%1\$.*2\$f", 1.23);
442+
',
443+
'error_message' => 'TooFewArguments',
444+
],
445+
'sprintfPositionalPrecisionStarTooManyArguments' => [
446+
'code' => '<?php
447+
$x = sprintf("%1\$.*2\$f", 1.23, 1, 99);
448+
',
449+
'error_message' => 'TooManyArguments',
450+
],
451+
'sprintfWidthAndPositionalPrecisionStarTooFewArguments' => [
452+
'code' => '<?php
453+
$x = sprintf("%10.*2\$f", 1.23);
454+
',
455+
'error_message' => 'TooFewArguments',
456+
],
457+
'sprintfWidthAndPositionalPrecisionStarTooManyArguments' => [
458+
'code' => '<?php
459+
$x = sprintf("%10.*2\$f", 1.23, 1, 99);
460+
',
461+
'error_message' => 'TooManyArguments',
462+
],
463+
'sprintfPositionalWidthAndPrecisionStarTooFewArguments' => [
464+
'code' => '<?php
465+
$x = sprintf("%3\$*2\$.*1\$f", 1, 10);
466+
',
467+
'error_message' => 'TooFewArguments',
468+
],
469+
'sprintfPositionalWidthAndPrecisionStarTooManyArguments' => [
470+
'code' => '<?php
471+
$x = sprintf("%3\$*2\$.*1\$f", 1, 10, 1.23, 99);
472+
',
473+
'error_message' => 'TooManyArguments',
474+
],
330475
'printfVariableFormat' => [
331476
'code' => '<?php
332477
/** @var string $bar */

0 commit comments

Comments
 (0)