Skip to content

Commit cb9be91

Browse files
Fix sprintf dynamic return type
1 parent b049d8d commit cb9be91

File tree

2 files changed

+55
-11
lines changed

2 files changed

+55
-11
lines changed

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

+12-11
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use function count;
3030
use function in_array;
3131
use function intval;
32+
use function is_array;
3233
use function is_string;
3334
use function preg_match;
3435
use function sprintf;
@@ -69,7 +70,7 @@ public function getTypeFromFunctionCall(
6970
static fn (Type $type): bool => $type->toString()->isLowercaseString()->yes()
7071
);
7172

72-
$singlePlaceholderEarlyReturn = null;
73+
$singlePlaceholderEarlyReturn = [];
7374
$allPatternsNonEmpty = count($formatStrings) !== 0;
7475
$allPatternsNonFalsy = count($formatStrings) !== 0;
7576
foreach ($formatStrings as $constantString) {
@@ -93,8 +94,11 @@ public function getTypeFromFunctionCall(
9394
$allPatternsNonFalsy = false;
9495
}
9596

96-
// The printf format is %[argnum$][flags][width][.precision]specifier.
97-
if (preg_match('/^%(?P<argnum>[0-9]*\$)?(?P<width>[0-9]*)\.?[0-9]*(?P<specifier>[sbdeEfFgGhHouxX])$/', $constantString->getValue(), $matches) === 1) {
97+
if (
98+
is_array($singlePlaceholderEarlyReturn)
99+
// The printf format is %[argnum$][flags][width][.precision]specifier.
100+
&& preg_match('/^%(?P<argnum>[0-9]*\$)?(?P<width>[0-9]*)\.?[0-9]*(?P<specifier>[sbdeEfFgGhHouxX])$/', $constantString->getValue(), $matches) === 1
101+
) {
98102
if ($matches['argnum'] !== '') {
99103
// invalid positional argument
100104
if ($matches['argnum'] === '0$') {
@@ -122,24 +126,22 @@ public function getTypeFromFunctionCall(
122126
$constArgTypes = $checkArgType->getConstantScalarTypes();
123127
}
124128
if ($constArgTypes !== []) {
125-
$result = [];
126129
$printfArgs = array_fill(0, count($args) - 1, '');
127130
foreach ($constArgTypes as $constArgType) {
128131
$printfArgs[$checkArg - 1] = $constArgType->getValue();
129132
try {
130-
$result[] = new ConstantStringType(@sprintf($constantString->getValue(), ...$printfArgs));
133+
$singlePlaceholderEarlyReturn[] = new ConstantStringType(@sprintf($constantString->getValue(), ...$printfArgs));
131134
} catch (Throwable) {
132135
continue 2;
133136
}
134137
}
135-
$singlePlaceholderEarlyReturn = TypeCombinator::union(...$result);
136138

137139
continue;
138140
}
139141

140-
$singlePlaceholderEarlyReturn = $checkArgType->toString();
142+
$singlePlaceholderEarlyReturn[] = $checkArgType->toString();
141143
} elseif ($matches['specifier'] !== 's') {
142-
$singlePlaceholderEarlyReturn = $this->getStringReturnType(
144+
$singlePlaceholderEarlyReturn[] = $this->getStringReturnType(
143145
new AccessoryNumericStringType(),
144146
$isLowercase,
145147
);
@@ -149,11 +151,10 @@ public function getTypeFromFunctionCall(
149151
}
150152

151153
$singlePlaceholderEarlyReturn = null;
152-
break;
153154
}
154155

155-
if ($singlePlaceholderEarlyReturn !== null) {
156-
return $singlePlaceholderEarlyReturn;
156+
if (is_array($singlePlaceholderEarlyReturn) && count($singlePlaceholderEarlyReturn) > 0) {
157+
return TypeCombinator::union(...$singlePlaceholderEarlyReturn);
157158
}
158159

159160
if ($allPatternsNonFalsy) {

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

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Bug12065;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
final class Foo
8+
{
9+
public function bar(
10+
string $key,
11+
bool $preserveKeys,
12+
bool $flatten = true,
13+
): void {
14+
$format = $preserveKeys ? '[%s]' : '';
15+
16+
if ($flatten) {
17+
$_key = sprintf($format, $key);
18+
} else {
19+
$_key = sprintf($format, $key);
20+
assertType('string', $_key);
21+
// @phpstan-ignore identical.alwaysFalse
22+
if ($_key === '') {
23+
// ...
24+
}
25+
}
26+
}
27+
28+
/**
29+
* @param int<1,2> $key
30+
* @param bool $preserveKeys
31+
*
32+
* @return void
33+
*/
34+
public function bar2(
35+
string $key,
36+
bool $preserveKeys,
37+
): void {
38+
$format = $preserveKeys ? '%s' : '%d';
39+
40+
$_key = sprintf($format, $key);
41+
assertType("string", $_key);
42+
}
43+
}

0 commit comments

Comments
 (0)