Skip to content

Commit eb78fe3

Browse files
VincentLangletondrejmirtes
authored andcommitted
Improve Vsprintf inference
1 parent c539491 commit eb78fe3

File tree

3 files changed

+40
-20
lines changed

3 files changed

+40
-20
lines changed

src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -155,37 +155,50 @@ public function getTypeFromFunctionCall(
155155
}
156156

157157
$isNonEmpty = $allPatternsNonEmpty;
158-
if (
159-
!$isNonEmpty
160-
&& $functionReflection->getName() === 'sprintf'
161-
&& count($args) >= 2
162-
&& $formatType->isNonEmptyString()->yes()
163-
) {
164-
$allArgsNonEmpty = true;
158+
if (!$isNonEmpty && $formatType->isNonEmptyString()->yes()) {
159+
$isNonEmpty = $this->allValuesSatisfies(
160+
$functionReflection,
161+
$scope,
162+
$args,
163+
static fn (Type $type): bool => $type->toString()->isNonEmptyString()->yes()
164+
);
165+
}
166+
167+
if ($isNonEmpty) {
168+
return new IntersectionType([
169+
new StringType(),
170+
new AccessoryNonEmptyStringType(),
171+
]);
172+
}
173+
174+
return new StringType();
175+
}
176+
177+
/**
178+
* @param array<Arg> $args
179+
* @param callable(Type): bool $cb
180+
*/
181+
private function allValuesSatisfies(FunctionReflection $functionReflection, Scope $scope, array $args, callable $cb): bool
182+
{
183+
if ($functionReflection->getName() === 'sprintf' && count($args) >= 2) {
165184
foreach ($args as $key => $arg) {
166185
if ($key === 0) {
167186
continue;
168187
}
169188

170-
if (!$scope->getType($arg->value)->toString()->isNonEmptyString()->yes()) {
171-
$allArgsNonEmpty = false;
172-
break;
189+
if (!$cb($scope->getType($arg->value))) {
190+
return false;
173191
}
174192
}
175193

176-
if ($allArgsNonEmpty) {
177-
$isNonEmpty = true;
178-
}
194+
return true;
179195
}
180196

181-
if ($isNonEmpty) {
182-
return new IntersectionType([
183-
new StringType(),
184-
new AccessoryNonEmptyStringType(),
185-
]);
197+
if ($functionReflection->getName() === 'vsprintf' && count($args) >= 2) {
198+
return $cb($scope->getType($args[1]->value)->getIterableValueType());
186199
}
187200

188-
return new StringType();
201+
return false;
189202
}
190203

191204
/**

tests/PHPStan/Analyser/nsrt/bug-7387.php

Lines changed: 1 addition & 1 deletion
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('string', vsprintf("%s", ['123'])); // could be '123'
113+
assertType('non-empty-string', vsprintf("%s", ['123'])); // could be '123'
114114
// too many arguments.. php silently allows it
115115
assertType('numeric-string', vsprintf("%4d", ['123', '456']));
116116
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,13 @@ public function doFoo(string $s, string $nonEmpty, string $nonFalsy, int $i, boo
364364
assertType('non-empty-string', sprintf($nonFalsy, $nonFalsy, $nonFalsy));
365365
assertType('string', vsprintf($s, []));
366366
assertType('string', vsprintf($nonEmpty, []));
367+
assertType('non-empty-string', vsprintf($nonEmpty, [$nonEmpty]));
368+
assertType('non-empty-string', vsprintf($nonEmpty, [$nonEmpty, $nonEmpty]));
369+
assertType('non-empty-string', vsprintf($nonEmpty, [$nonFalsy, $nonFalsy]));
370+
assertType('non-empty-string', vsprintf($nonFalsy, [$nonEmpty]));
371+
assertType('non-empty-string', vsprintf($nonFalsy, [$nonEmpty, $nonEmpty]));
372+
assertType('non-empty-string', vsprintf($nonFalsy, [$nonFalsy, $nonEmpty]));
373+
assertType('non-empty-string', vsprintf($nonFalsy, [$nonFalsy, $nonFalsy]));
367374

368375
assertType('non-empty-string', sprintf("%s0%s", $s, $s));
369376
assertType('non-empty-string', sprintf("%s0%s%s%s%s", $s, $s, $s, $s, $s));

0 commit comments

Comments
 (0)