|
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