|
23 | 23 | use function array_fill; |
24 | 24 | use function array_key_exists; |
25 | 25 | use function array_shift; |
| 26 | +use function array_values; |
26 | 27 | use function count; |
27 | 28 | use function in_array; |
28 | 29 | use function intval; |
@@ -95,14 +96,13 @@ public function getTypeFromFunctionCall( |
95 | 96 | $checkArg = 1; |
96 | 97 | } |
97 | 98 |
|
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) { |
100 | 101 | return null; |
101 | 102 | } |
102 | 103 |
|
103 | 104 | // if the format string is just a placeholder and specified an argument |
104 | 105 | // of stringy type, then the return value will be of the same type |
105 | | - $checkArgType = $scope->getType($args[$checkArg]->value); |
106 | 106 | if ( |
107 | 107 | $matches['specifier'] === 's' |
108 | 108 | && ($checkArgType->isString()->yes() || $checkArgType->isInteger()->yes()) |
@@ -201,6 +201,48 @@ private function allValuesSatisfies(FunctionReflection $functionReflection, Scop |
201 | 201 | return false; |
202 | 202 | } |
203 | 203 |
|
| 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 | + |
204 | 246 | /** |
205 | 247 | * Detect constant strings in the format which neither depend on placeholders nor on given value arguments. |
206 | 248 | */ |
|
0 commit comments