Skip to content

Commit 72281be

Browse files
authored
Simplify SprintfFunctionDynamicReturnTypeExtension
1 parent 466e677 commit 72281be

File tree

4 files changed

+68
-40
lines changed

4 files changed

+68
-40
lines changed

src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php

Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -49,50 +49,56 @@ public function getTypeFromFunctionCall(
4949
}
5050

5151
$formatType = $scope->getType($args[0]->value);
52+
if (count($args) === 1) {
53+
return $this->getConstantType($args, null, $functionReflection, $scope);
54+
}
5255

53-
if (count($formatType->getConstantStrings()) > 0) {
54-
$singlePlaceholderEarlyReturn = null;
55-
foreach ($formatType->getConstantStrings() as $constantString) {
56-
// The printf format is %[argnum$][flags][width][.precision]
57-
if (preg_match('/^%([0-9]*\$)?[0-9]*\.?[0-9]*([sbdeEfFgGhHouxX])$/', $constantString->getValue(), $matches) === 1) {
58-
if ($matches[1] !== '') {
59-
// invalid positional argument
60-
if ($matches[1] === '0$') {
61-
return null;
62-
}
63-
$checkArg = intval(substr($matches[1], 0, -1));
64-
} else {
65-
$checkArg = 1;
66-
}
56+
$formatStrings = $formatType->getConstantStrings();
57+
if (count($formatStrings) === 0) {
58+
return null;
59+
}
6760

68-
// constant string specifies a numbered argument that does not exist
69-
if (!array_key_exists($checkArg, $args)) {
61+
$singlePlaceholderEarlyReturn = null;
62+
foreach ($formatType->getConstantStrings() as $constantString) {
63+
// The printf format is %[argnum$][flags][width][.precision]
64+
if (preg_match('/^%([0-9]*\$)?[0-9]*\.?[0-9]*([sbdeEfFgGhHouxX])$/', $constantString->getValue(), $matches) === 1) {
65+
if ($matches[1] !== '') {
66+
// invalid positional argument
67+
if ($matches[1] === '0$') {
7068
return null;
7169
}
70+
$checkArg = intval(substr($matches[1], 0, -1));
71+
} else {
72+
$checkArg = 1;
73+
}
7274

73-
// if the format string is just a placeholder and specified an argument
74-
// of stringy type, then the return value will be of the same type
75-
$checkArgType = $scope->getType($args[$checkArg]->value);
76-
77-
if ($matches[2] === 's' && $checkArgType->isString()->yes()) {
78-
$singlePlaceholderEarlyReturn = $checkArgType;
79-
} elseif ($matches[2] !== 's') {
80-
$singlePlaceholderEarlyReturn = new IntersectionType([
81-
new StringType(),
82-
new AccessoryNumericStringType(),
83-
]);
84-
}
75+
// constant string specifies a numbered argument that does not exist
76+
if (!array_key_exists($checkArg, $args)) {
77+
return null;
78+
}
8579

86-
continue;
80+
// if the format string is just a placeholder and specified an argument
81+
// of stringy type, then the return value will be of the same type
82+
$checkArgType = $scope->getType($args[$checkArg]->value);
83+
84+
if ($matches[2] === 's' && $checkArgType->isString()->yes()) {
85+
$singlePlaceholderEarlyReturn = $checkArgType;
86+
} elseif ($matches[2] !== 's') {
87+
$singlePlaceholderEarlyReturn = new IntersectionType([
88+
new StringType(),
89+
new AccessoryNumericStringType(),
90+
]);
8791
}
8892

89-
$singlePlaceholderEarlyReturn = null;
90-
break;
93+
continue;
9194
}
9295

93-
if ($singlePlaceholderEarlyReturn !== null) {
94-
return $singlePlaceholderEarlyReturn;
95-
}
96+
$singlePlaceholderEarlyReturn = null;
97+
break;
98+
}
99+
100+
if ($singlePlaceholderEarlyReturn !== null) {
101+
return $singlePlaceholderEarlyReturn;
96102
}
97103

98104
if ($formatType->isNonFalsyString()->yes()) {
@@ -115,11 +121,15 @@ public function getTypeFromFunctionCall(
115121
/**
116122
* @param Arg[] $args
117123
*/
118-
private function getConstantType(array $args, Type $fallbackReturnType, FunctionReflection $functionReflection, Scope $scope): Type
124+
private function getConstantType(array $args, ?Type $fallbackReturnType, FunctionReflection $functionReflection, Scope $scope): ?Type
119125
{
120126
$values = [];
121127
$combinationsCount = 1;
122128
foreach ($args as $arg) {
129+
if ($arg->unpack) {
130+
return $fallbackReturnType;
131+
}
132+
123133
$argType = $scope->getType($arg->value);
124134
$constantScalarValues = $argType->getConstantScalarValues();
125135

tests/PHPStan/Analyser/nsrt/non-empty-string.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,9 +349,9 @@ public function doFoo(string $s, string $nonEmpty, int $i, bool $bool, $constUni
349349
assertType('non-empty-string', preg_quote($nonEmpty));
350350

351351
assertType('string', sprintf($s));
352-
assertType('non-empty-string', sprintf($nonEmpty));
352+
assertType('string', sprintf($nonEmpty));
353353
assertType('string', vsprintf($s, []));
354-
assertType('non-empty-string', vsprintf($nonEmpty, []));
354+
assertType('string', vsprintf($nonEmpty, []));
355355

356356
assertType('0', strlen(''));
357357
assertType('5', strlen('hallo'));

tests/PHPStan/Analyser/nsrt/non-falsy-string.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ function concat(string $s, string $nonFalsey, $numericS, $nonEmpty, $literalStri
7474
* @param non-empty-array<non-falsy-string> $arrayOfNonFalsey
7575
* @param non-empty-array $nonEmptyArray
7676
*/
77-
function stringFunctions(string $s, $nonFalsey, $arrayOfNonFalsey, $nonEmptyArray)
77+
function stringFunctions(string $s, $nonFalsey, $arrayOfNonFalsey, $nonEmptyArray, array $arr)
7878
{
7979
assertType('string', implode($nonFalsey, []));
8080
assertType('non-falsy-string', implode($nonFalsey, $nonEmptyArray));
@@ -104,8 +104,15 @@ function stringFunctions(string $s, $nonFalsey, $arrayOfNonFalsey, $nonEmptyArra
104104

105105
assertType('non-falsy-string', preg_quote($nonFalsey));
106106

107-
assertType('non-falsy-string', sprintf($nonFalsey));
108-
assertType('non-falsy-string', vsprintf($nonFalsey, []));
107+
assertType('string', sprintf($nonFalsey));
108+
assertType("'foo'", sprintf('foo'));
109+
assertType("string", sprintf(...$arr));
110+
assertType("non-falsy-string", sprintf('%s', ...$arr)); // should be 'string'
111+
assertType('string', vsprintf($nonFalsey, []));
112+
assertType('string', vsprintf($nonFalsey, []));
113+
assertType("non-falsy-string", vsprintf('foo', [])); // should be 'foo'
114+
assertType("non-falsy-string", vsprintf('%s', ...$arr)); // should be 'string'
115+
assertType("string", vsprintf(...$arr));
109116

110117
assertType('int<1, max>', strlen($nonFalsey));
111118

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php // lint >= 8.0
2+
3+
namespace PrintFErrorsPhp8;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
function doFoo()
8+
{
9+
assertType("string", sprintf('%s')); // error
10+
assertType("string", vsprintf('%s')); // error
11+
}

0 commit comments

Comments
 (0)