Skip to content

Commit

Permalink
fix return type of sprintf with single %s format
Browse files Browse the repository at this point in the history
  • Loading branch information
pilif committed Jun 19, 2024
1 parent 1bce0b4 commit f513931
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 11 deletions.
43 changes: 33 additions & 10 deletions src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
use function array_shift;
use function count;
use function in_array;
use function intval;
use function is_string;
use function preg_match;
use function sprintf;
use function substr;
use function vsprintf;

class SprintfFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
Expand All @@ -46,28 +48,49 @@ public function getTypeFromFunctionCall(
}

$formatType = $scope->getType($args[0]->value);

if (count($formatType->getConstantStrings()) > 0) {
$skip = false;
$singlePlaceholderEarlyReturn = null;
foreach ($formatType->getConstantStrings() as $constantString) {
// The printf format is %[argnum$][flags][width][.precision]
if (preg_match('/^%([0-9]*\$)?[0-9]*\.?[0-9]*[bdeEfFgGhHouxX]$/', $constantString->getValue(), $matches) === 1) {
// invalid positional argument
if (array_key_exists(1, $matches) && $matches[1] === '0$') {
if (preg_match('/^%([0-9]*\$)?[0-9]*\.?[0-9]*([sbdeEfFgGhHouxX])$/', $constantString->getValue(), $matches) === 1) {
if (array_key_exists(1, $matches) && ($matches[1] !== '')) {
// invalid positional argument
if ($matches[1] === '0$') {
return null;
}
$checkArg = intval(substr($matches[1], 0, -1));
} else {
$checkArg = 1;
}

// constant string specifies a numbered argument that does not exist
if (!array_key_exists($checkArg, $args)) {
return null;
}

// if the format string is just a placeholder and specified an argument
// of stringy type, then the return value will be of the same type
$checkArgType = $scope->getType($args[$checkArg]->value);

if ($matches[2] === 's' && $checkArgType->isString()->yes()) {
$singlePlaceholderEarlyReturn = $checkArgType;
} elseif ($matches[2] !== 's') {
$singlePlaceholderEarlyReturn = new IntersectionType([
new StringType(),
new AccessoryNumericStringType(),
]);
}

continue;
}

$skip = true;
$singlePlaceholderEarlyReturn = null;
break;
}

if (!$skip) {
return new IntersectionType([
new StringType(),
new AccessoryNumericStringType(),
]);
if ($singlePlaceholderEarlyReturn !== null) {
return $singlePlaceholderEarlyReturn;
}
}

Expand Down
43 changes: 43 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-11201.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php declare(strict_types = 1);

namespace Bug11201;

use function PHPStan\Testing\assertType;

/** @return array<string> */
function returnsArray(){
return [];
}

/** @return non-empty-string */
function returnsNonEmptyString(): string
{
return 'a';
}

/** @return non-falsy-string */
function returnsNonFalsyString(): string
{
return '1';
}

/** @return string */
function returnsJustString(): string
{
return rand(0,1) === 1 ? 'foo' : '';
}

$s = sprintf("%s", returnsNonEmptyString());
assertType('non-empty-string', $s);

$s = sprintf("%s", returnsNonFalsyString());
assertType('non-falsy-string', $s);

$s = sprintf("%s", returnsJustString());
assertType('string', $s);

$s = sprintf("%s", implode(', ', array_map('intval', returnsArray())));
assertType('string', $s);

$s = sprintf('%2$s', 1234, returnsNonFalsyString());
assertType('non-falsy-string', $s);
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/bug-7387.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public function positionalArgs($mixed, int $i, float $f, string $s) {
assertType('numeric-string', sprintf('%2$14F', $mixed, $f));
assertType('numeric-string', sprintf('%2$14F', $mixed, $s));

assertType('numeric-string', sprintf('%10$14F', $mixed, $s));
assertType('string', sprintf('%10$14F', $mixed, $s));
}

public function invalidPositionalArgFormat($mixed, string $s) {
Expand Down

0 comments on commit f513931

Please sign in to comment.