Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -161,17 +161,20 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev
return Type::getString('');
}

// these placeholders are too complex to handle for now
if (preg_match(
'/%(?:\d+\$)?[-+]?(?:\d+|\*)(?:\.(?:\d+|\*))?[bcdouxXeEfFgGhHs]/',
// these placeholders still fall back to a generic return type,
// but we validate their format and argument count first
$has_complex_placeholder = preg_match(
'/%(?:\d+\$)?[-+]?(?:'
. '(?:\d+|\*(?:\d+\$)?)(?:\.(?:\d+|\*(?:\d+\$)?))?'
. '|\.\*(?:\d+\$)?'
. ')[bcdouxXeEfFgGhHs]/',
$type->getSingleStringLiteral()->value,
) === 1) {
return null;
}
) === 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, '');
// Use integer dummies for * width/precision so PHP validates arity without raising a false ValueError.
$dummy = array_fill(0, $provided_placeholders_count, $has_complex_placeholder ? 0 : '');

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

if ($has_complex_placeholder) {
return null;
}

if ($event->getFunctionId() === 'printf') {
// printf only has the format validated above
// don't change the return type
Expand Down
145 changes: 145 additions & 0 deletions tests/ReturnTypeProvider/SprintfTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,85 @@ public function providerValidCodeParse(): iterable
'php_version' => '8.0',
];

yield 'sprintfComplexPlaceholderNotYetSupported4' => [
'code' => '<?php
$precision = 1;
$flt = 1.234;
$val = sprintf("%.*f", $precision, $flt);
',
'assertions' => [
'$val===' => 'string',
],
'ignored_issues' => [],
'php_version' => '8.0',
];

yield 'printfComplexPlaceholderNotYetSupported4' => [
'code' => '<?php
$precision = 1;
$flt = 1.234;
$val = printf("%.*f", $precision, $flt);
',
'assertions' => [
'$val===' => 'int<0, max>',
],
'ignored_issues' => [],
'php_version' => '8.0',
];

yield 'sprintfComplexPlaceholderNotYetSupported5' => [
'code' => '<?php
$flt = 1.234;
$precision = 1;
$val = sprintf("%1\$.*2\$f", $flt, $precision);
',
'assertions' => [
'$val===' => 'string',
],
'ignored_issues' => [],
'php_version' => '8.0',
];

yield 'sprintfComplexPlaceholderNotYetSupported6' => [
'code' => '<?php
$precision = 1;
$flt = 1.234;
$val = sprintf("%2\$.*1\$f", $precision, $flt);
',
'assertions' => [
'$val===' => 'string',
],
'ignored_issues' => [],
'php_version' => '8.0',
];

yield 'sprintfComplexPlaceholderNotYetSupported7' => [
'code' => '<?php
$flt = 1.234;
$precision = 1;
$val = sprintf("%10.*2\$f", $flt, $precision);
',
'assertions' => [
'$val===' => 'string',
],
'ignored_issues' => [],
'php_version' => '8.0',
];

yield 'sprintfComplexPlaceholderNotYetSupported8' => [
'code' => '<?php
$precision = 1;
$width = 10;
$flt = 1.234;
$val = sprintf("%3\$*2\$.*1\$f", $precision, $width, $flt);
',
'assertions' => [
'$val===' => 'string',
],
'ignored_issues' => [],
'php_version' => '8.0',
];

yield 'sprintfSplatUnpackingArray' => [
'code' => '<?php
$a = ["a", "b", "c"];
Expand Down Expand Up @@ -327,6 +406,72 @@ public function providerInvalidCodeParse(): iterable
',
'error_message' => 'InvalidArgument',
],
'sprintfEscapedPercentLiteralStillReportsTooManyArguments' => [
'code' => '<?php
$x = sprintf("%%.*f", "a", "b");
',
'error_message' => 'TooManyArguments',
],
'sprintfEscapedPercentThenPrecisionStarStillReportsTooFewArguments' => [
'code' => '<?php
$x = sprintf("%%%.*f", 1);
',
'error_message' => 'TooFewArguments',
],
'sprintfPositionalEscapedPercentThenPrecisionStarStillReportsTooFewArguments' => [
'code' => '<?php
$x = sprintf("%1$%%.*f", 1);
',
'error_message' => 'TooFewArguments',
],
'sprintfPrecisionStarTooFewArguments' => [
'code' => '<?php
$x = sprintf("%.*f", 1);
',
'error_message' => 'TooFewArguments',
],
'sprintfPrecisionStarTooManyArguments' => [
'code' => '<?php
$x = sprintf("%.*f", 1, 1.23, 99);
',
'error_message' => 'TooManyArguments',
],
'sprintfPositionalPrecisionStarTooFewArguments' => [
'code' => '<?php
$x = sprintf("%1\$.*2\$f", 1.23);
',
'error_message' => 'TooFewArguments',
],
'sprintfPositionalPrecisionStarTooManyArguments' => [
'code' => '<?php
$x = sprintf("%1\$.*2\$f", 1.23, 1, 99);
',
'error_message' => 'TooManyArguments',
],
'sprintfWidthAndPositionalPrecisionStarTooFewArguments' => [
'code' => '<?php
$x = sprintf("%10.*2\$f", 1.23);
',
'error_message' => 'TooFewArguments',
],
'sprintfWidthAndPositionalPrecisionStarTooManyArguments' => [
'code' => '<?php
$x = sprintf("%10.*2\$f", 1.23, 1, 99);
',
'error_message' => 'TooManyArguments',
],
'sprintfPositionalWidthAndPrecisionStarTooFewArguments' => [
'code' => '<?php
$x = sprintf("%3\$*2\$.*1\$f", 1, 10);
',
'error_message' => 'TooFewArguments',
],
'sprintfPositionalWidthAndPrecisionStarTooManyArguments' => [
'code' => '<?php
$x = sprintf("%3\$*2\$.*1\$f", 1, 10, 1.23, 99);
',
'error_message' => 'TooManyArguments',
],
'printfVariableFormat' => [
'code' => '<?php
/** @var string $bar */
Expand Down
Loading