Skip to content

Commit 14fd384

Browse files
VincentLangletondrejmirtes
authored andcommitted
Improve sprintf support
1 parent 0132833 commit 14fd384

File tree

4 files changed

+86
-15
lines changed

4 files changed

+86
-15
lines changed

src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -125,32 +125,45 @@ public function getTypeFromFunctionCall(
125125
return $singlePlaceholderEarlyReturn;
126126
}
127127

128+
if ($allPatternsNonFalsy) {
129+
return new IntersectionType([
130+
new StringType(),
131+
new AccessoryNonFalsyStringType(),
132+
]);
133+
}
134+
128135
$isNonEmpty = $allPatternsNonEmpty;
129136
if (
130-
count($formatStrings) === 0
137+
!$isNonEmpty
131138
&& $functionReflection->getName() === 'sprintf'
132-
&& count($args) === 2
139+
&& count($args) >= 2
133140
&& $formatType->isNonEmptyString()->yes()
134-
&& $scope->getType($args[1]->value)->isNonEmptyString()->yes()
135141
) {
136-
$isNonEmpty = true;
142+
$allArgsNonEmpty = true;
143+
foreach ($args as $key => $arg) {
144+
if ($key === 0) {
145+
continue;
146+
}
147+
148+
if (!$scope->getType($arg->value)->toString()->isNonEmptyString()->yes()) {
149+
$allArgsNonEmpty = false;
150+
break;
151+
}
152+
}
153+
154+
if ($allArgsNonEmpty) {
155+
$isNonEmpty = true;
156+
}
137157
}
138158

139-
if ($allPatternsNonFalsy) {
140-
$returnType = new IntersectionType([
141-
new StringType(),
142-
new AccessoryNonFalsyStringType(),
143-
]);
144-
} elseif ($isNonEmpty) {
145-
$returnType = new IntersectionType([
159+
if ($isNonEmpty) {
160+
return new IntersectionType([
146161
new StringType(),
147162
new AccessoryNonEmptyStringType(),
148163
]);
149-
} else {
150-
$returnType = new StringType();
151164
}
152165

153-
return $returnType;
166+
return new StringType();
154167
}
155168

156169
/**

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,8 +356,12 @@ public function doFoo(string $s, string $nonEmpty, string $nonFalsy, int $i, boo
356356
assertType('string', sprintf($s, $nonFalsy));
357357
assertType('string', sprintf($nonFalsy, $s));
358358
assertType('non-empty-string', sprintf($nonEmpty, $nonEmpty));
359-
assertType('non-empty-string', sprintf($nonEmpty, $nonFalsy));
359+
assertType('non-empty-string', sprintf($nonEmpty, $nonEmpty, $nonEmpty));
360+
assertType('non-empty-string', sprintf($nonEmpty, $nonFalsy, $nonFalsy));
360361
assertType('non-empty-string', sprintf($nonFalsy, $nonEmpty));
362+
assertType('non-empty-string', sprintf($nonFalsy, $nonEmpty, $nonEmpty));
363+
assertType('non-empty-string', sprintf($nonFalsy, $nonFalsy, $nonEmpty));
364+
assertType('non-empty-string', sprintf($nonFalsy, $nonFalsy, $nonFalsy));
361365
assertType('string', vsprintf($s, []));
362366
assertType('string', vsprintf($nonEmpty, []));
363367

tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,4 +1034,9 @@ public function testBug10721(): void
10341034
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-10721.php'], []);
10351035
}
10361036

1037+
public function testBug11491(): void
1038+
{
1039+
$this->analyse([__DIR__ . '/data/bug-11491.php'], []);
1040+
}
1041+
10371042
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace PHPStan\Rules\Methods\data;
4+
5+
use function sprintf;
6+
7+
final class NamedFacet
8+
{
9+
private const string SEPARATOR = '+++';
10+
11+
private function __construct(
12+
/** @var positive-int */
13+
private int $id,
14+
/** @var non-empty-string */
15+
private string $name,
16+
) {}
17+
18+
/**
19+
* @param positive-int $id
20+
* @param non-empty-string $name
21+
*/
22+
public static function fromIdAndName(int $id, string $name): self
23+
{
24+
return new self($id, $name);
25+
}
26+
27+
/** @return positive-int */
28+
public function id(): int
29+
{
30+
return $this->id;
31+
}
32+
33+
/** @return non-empty-string */
34+
public function name(): string
35+
{
36+
return $this->name;
37+
}
38+
39+
/** @return non-empty-string */
40+
public function toFacetValue(): string
41+
{
42+
return sprintf(
43+
'%s%s%s',
44+
$this->name,
45+
self::SEPARATOR,
46+
$this->id,
47+
);
48+
}
49+
}

0 commit comments

Comments
 (0)