Skip to content

Commit 4273fbb

Browse files
Improve vsprintf return type
1 parent eca394c commit 4273fbb

File tree

2 files changed

+46
-4
lines changed

2 files changed

+46
-4
lines changed

Diff for: src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php

+45-3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use function array_fill;
2424
use function array_key_exists;
2525
use function array_shift;
26+
use function array_values;
2627
use function count;
2728
use function in_array;
2829
use function intval;
@@ -95,14 +96,13 @@ public function getTypeFromFunctionCall(
9596
$checkArg = 1;
9697
}
9798

98-
// constant string specifies a numbered argument that does not exist
99-
if (!array_key_exists($checkArg, $args)) {
99+
$checkArgType = $this->getValueType($functionReflection, $scope, $args, $checkArg);
100+
if ($checkArgType === null) {
100101
return null;
101102
}
102103

103104
// if the format string is just a placeholder and specified an argument
104105
// of stringy type, then the return value will be of the same type
105-
$checkArgType = $scope->getType($args[$checkArg]->value);
106106
if (
107107
$matches['specifier'] === 's'
108108
&& ($checkArgType->isString()->yes() || $checkArgType->isInteger()->yes())
@@ -201,6 +201,48 @@ private function allValuesSatisfies(FunctionReflection $functionReflection, Scop
201201
return false;
202202
}
203203

204+
/**
205+
* @param Arg[] $args
206+
*/
207+
private function getValueType(FunctionReflection $functionReflection, Scope $scope, array $args, int $argNumber): ?Type
208+
{
209+
if ($functionReflection->getName() === 'sprintf') {
210+
// constant string specifies a numbered argument that does not exist
211+
if (!array_key_exists($argNumber, $args)) {
212+
return null;
213+
}
214+
215+
return $scope->getType($args[$argNumber]->value);
216+
}
217+
218+
if ($functionReflection->getName() === 'vsprintf') {
219+
if (!array_key_exists(1, $args)) {
220+
return null;
221+
}
222+
223+
$valuesType = $scope->getType($args[1]->value);
224+
$resultTypes = [];
225+
226+
$valuesConstantArrays = $valuesType->getConstantArrays();
227+
foreach ($valuesConstantArrays as $valuesConstantArray) {
228+
// vsprintf does not care about the keys of the array, only the order
229+
$types = array_values($valuesConstantArray->getValueTypes());
230+
if (!array_key_exists($argNumber - 1, $types)) {
231+
return null;
232+
}
233+
234+
$resultTypes[] = $types[$argNumber - 1];
235+
}
236+
if (count($resultTypes) === 0) {
237+
return $valuesType->getIterableValueType();
238+
}
239+
240+
return TypeCombinator::union(...$resultTypes);
241+
}
242+
243+
return null;
244+
}
245+
204246
/**
205247
* Detect constant strings in the format which neither depend on placeholders nor on given value arguments.
206248
*/

Diff for: tests/PHPStan/Analyser/nsrt/bug-7387.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public function vsprintf(array $array)
110110
assertType('numeric-string', vsprintf("%4d", explode('-', '1988-8-1')));
111111
assertType('numeric-string', vsprintf("%4d", $array));
112112
assertType('numeric-string', vsprintf("%4d", ['123']));
113-
assertType('non-empty-string', vsprintf("%s", ['123'])); // could be '123'
113+
assertType('\'123\'', vsprintf("%s", ['123']));
114114
// too many arguments.. php silently allows it
115115
assertType('numeric-string', vsprintf("%4d", ['123', '456']));
116116
}

0 commit comments

Comments
 (0)