Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit f6ed272

Browse files
authored
Add optional strict check for printf parameter types
1 parent e4e21c7 commit f6ed272

File tree

7 files changed

+182
-18
lines changed

7 files changed

+182
-18
lines changed

‎conf/config.level5.neon‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ services:
2020
class: PHPStan\Rules\Functions\ParameterCastableToNumberRule
2121
-
2222
class: PHPStan\Rules\Functions\PrintfParameterTypeRule
23+
arguments:
24+
checkStrictPrintfPlaceholderTypes: %checkStrictPrintfPlaceholderTypes%

‎conf/config.neon‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ parameters:
6666
strictRulesInstalled: false
6767
deprecationRulesInstalled: false
6868
inferPrivatePropertyTypeFromConstructor: false
69+
checkStrictPrintfPlaceholderTypes: false
6970
reportMaybes: false
7071
reportMaybesInMethodSignatures: false
7172
reportMaybesInPropertyPhpDocTypes: false

‎conf/parametersSchema.neon‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ parametersSchema:
6969
strictRulesInstalled: bool()
7070
deprecationRulesInstalled: bool()
7171
inferPrivatePropertyTypeFromConstructor: bool()
72+
checkStrictPrintfPlaceholderTypes: bool()
7273

7374
tips: structure([
7475
discoveringSymbols: bool()

‎src/Rules/Functions/PrintfParameterTypeRule.php‎

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public function __construct(
4242
private PrintfHelper $printfHelper,
4343
private ReflectionProvider $reflectionProvider,
4444
private RuleLevelHelper $ruleLevelHelper,
45+
private bool $checkStrictPrintfPlaceholderTypes,
4546
)
4647
{
4748
}
@@ -100,15 +101,23 @@ public function processNode(Node $node, Scope $scope): array
100101
new NullType(),
101102
);
102103
// Type on the left can go to the type on the right, but not vice versa.
103-
$allowedTypeNameMap = [
104-
'strict-int' => 'int',
105-
'int' => 'castable to int',
106-
'float' => 'castable to float',
107-
// These are here just for completeness. They won't be used because, these types are already enforced by
108-
// CallToFunctionParametersRule.
109-
'string' => 'castable to string',
110-
'mixed' => 'castable to string',
111-
];
104+
$allowedTypeNameMap = $this->checkStrictPrintfPlaceholderTypes
105+
? [
106+
'strict-int' => 'int',
107+
'int' => 'int',
108+
'float' => 'float',
109+
'string' => '__stringandstringable',
110+
'mixed' => '__stringandstringable',
111+
]
112+
: [
113+
'strict-int' => 'int',
114+
'int' => 'castable to int',
115+
'float' => 'castable to float',
116+
// These are here just for completeness. They won't be used because, these types are already enforced by
117+
// CallToFunctionParametersRule.
118+
'string' => 'castable to string',
119+
'mixed' => 'castable to string',
120+
];
112121

113122
for ($i = $formatArgumentPosition + 1, $j = 0; $i < $argsCount; $i++, $j++) {
114123
// Some arguments may be skipped entirely.
@@ -117,10 +126,10 @@ public function processNode(Node $node, Scope $scope): array
117126
$scope,
118127
$args[$i]->value,
119128
'',
120-
staticfn (Type $t) => $placeholder->doesArgumentTypeMatchPlaceholder($t),
129+
fn (Type $t) => $placeholder->doesArgumentTypeMatchPlaceholder($t, $this->checkStrictPrintfPlaceholderTypes),
121130
)->getType();
122131

123-
if ($argType instanceof ErrorType || $placeholder->doesArgumentTypeMatchPlaceholder($argType)) {
132+
if ($argType instanceof ErrorType || $placeholder->doesArgumentTypeMatchPlaceholder($argType, $this->checkStrictPrintfPlaceholderTypes)) {
124133
continue;
125134
}
126135

‎src/Rules/Functions/PrintfPlaceholder.php‎

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44

55
use PHPStan\ShouldNotHappenException;
66
use PHPStan\Type\ErrorType;
7+
use PHPStan\Type\FloatType;
78
use PHPStan\Type\IntegerType;
9+
use PHPStan\Type\StringAlwaysAcceptingObjectWithToStringType;
810
use PHPStan\Type\Type;
11+
use PHPStan\Type\TypeCombinator;
912

1013
final class PrintfPlaceholder
1114
{
@@ -20,20 +23,30 @@ public function __construct(
2023
{
2124
}
2225

23-
public function doesArgumentTypeMatchPlaceholder(Type $argumentType): bool
26+
public function doesArgumentTypeMatchPlaceholder(Type $argumentType, bool$strictPlaceholderTypes): bool
2427
{
2528
switch ($this->acceptingType) {
2629
case 'strict-int':
2730
return (new IntegerType())->accepts($argumentType, true)->yes();
2831
case 'int':
29-
return ! $argumentType->toInteger() instanceof ErrorType;
32+
return $strictPlaceholderTypes
33+
? (new IntegerType())->accepts($argumentType, true)->yes()
34+
: ! $argumentType->toInteger() instanceof ErrorType;
3035
case 'float':
31-
return ! $argumentType->toFloat() instanceof ErrorType;
32-
// The function signature already limits the parameters to stringable types, so there's
33-
// no point in checking string again here.
36+
return $strictPlaceholderTypes
37+
? (newFloatType())->accepts($argumentType, true)->yes()
38+
: ! $argumentType->toFloat() instanceof ErrorType;
3439
case 'string':
3540
case 'mixed':
36-
return true;
41+
// The function signature already limits the parameters to stringable types, so there's
42+
// no point in checking string again here.
43+
return !$strictPlaceholderTypes
44+
// Don't accept null or bool. It's likely to be a mistake.
45+
|| TypeCombinator::union(
46+
new StringAlwaysAcceptingObjectWithToStringType(),
47+
// float also accepts int.
48+
new FloatType(),
49+
)->accepts($argumentType, true)->yes();
3750
// Without this PHPStan with PHP 7.4 reports "...should return bool but return statement is missing."
3851
// Presumably, because promoted properties are turned into regular properties and the phpdoc isn't applied to the property.
3952
default:

‎tests/PHPStan/Rules/Functions/PrintfParameterTypeRuleTest.php‎

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
class PrintfParameterTypeRuleTest extends RuleTestCase
1515
{
1616

17+
private bool $checkStrictPrintfPlaceholderTypes = false;
18+
1719
protected function getRule(): Rule
1820
{
1921
$reflectionProvider = $this->createReflectionProvider();
@@ -30,6 +32,7 @@ protected function getRule(): Rule
3032
true,
3133
false,
3234
),
35+
$this->checkStrictPrintfPlaceholderTypes,
3336
);
3437
}
3538

@@ -111,4 +114,139 @@ public function test(): void
111114
]);
112115
}
113116

117+
public function testStrict(): void
118+
{
119+
$this->checkStrictPrintfPlaceholderTypes = true;
120+
$this->analyse([__DIR__ . '/data/printf-param-types.php'], [
121+
[
122+
'Parameter #2 of function printf is expected to be int by placeholder #1 ("%d"), PrintfParamTypes\\FooStringable given.',
123+
15,
124+
],
125+
[
126+
'Parameter #2 of function printf is expected to be int by placeholder #1 ("%d"), int|PrintfParamTypes\\FooStringable given.',
127+
16,
128+
],
129+
[
130+
'Parameter #2 of function printf is expected to be float by placeholder #1 ("%f"), PrintfParamTypes\\FooStringable given.',
131+
17,
132+
],
133+
[
134+
'Parameter #2 of function sprintf is expected to be int by placeholder #1 ("%d"), PrintfParamTypes\\FooStringable given.',
135+
18,
136+
],
137+
[
138+
'Parameter #3 of function fprintf is expected to be float by placeholder #1 ("%f"), PrintfParamTypes\\FooStringable given.',
139+
19,
140+
],
141+
[
142+
'Parameter #2 of function printf is expected to be int by placeholder #1 ("%*s" (width)), string given.',
143+
20,
144+
],
145+
[
146+
'Parameter #2 of function printf is expected to be int by placeholder #1 ("%*s" (width)), float given.',
147+
21,
148+
],
149+
[
150+
'Parameter #2 of function printf is expected to be int by placeholder #1 ("%*s" (width)), SimpleXMLElement given.',
151+
22,
152+
],
153+
[
154+
'Parameter #2 of function printf is expected to be int by placeholder #1 ("%*s" (width)), null given.',
155+
23,
156+
],
157+
[
158+
'Parameter #2 of function printf is expected to be int by placeholder #1 ("%*s" (width)), true given.',
159+
24,
160+
],
161+
[
162+
'Parameter #2 of function printf is expected to be int by placeholder #1 ("%.*s" (precision)), string given.',
163+
25,
164+
],
165+
[
166+
'Parameter #2 of function printf is expected to be int by placeholder #2 ("%3$.*s" (precision)), string given.',
167+
26,
168+
],
169+
[
170+
'Parameter #2 of function printf is expected to be float by placeholder #1 ("%1$-\'X10.2f"), PrintfParamTypes\\FooStringable given.',
171+
27,
172+
],
173+
[
174+
'Parameter #2 of function printf is expected to be float by placeholder #2 ("%1$*.*f" (value)), PrintfParamTypes\\FooStringable given.',
175+
28,
176+
],
177+
[
178+
'Parameter #4 of function printf is expected to be float by placeholder #1 ("%3$f"), PrintfParamTypes\\FooStringable given.',
179+
29,
180+
],
181+
[
182+
'Parameter #2 of function printf is expected to be float by placeholder #1 ("%1$f"), PrintfParamTypes\\FooStringable given.',
183+
30,
184+
],
185+
[
186+
'Parameter #2 of function printf is expected to be int by placeholder #2 ("%1$d"), PrintfParamTypes\\FooStringable given.',
187+
30,
188+
],
189+
[
190+
'Parameter #2 of function printf is expected to be int by placeholder #1 ("%1$*d" (width)), float given.',
191+
31,
192+
],
193+
[
194+
'Parameter #2 of function printf is expected to be int by placeholder #1 ("%1$*d" (value)), float given.',
195+
31,
196+
],
197+
[
198+
'Parameter #2 of function printf is expected to be int by placeholder #1 ("%d"), float given.',
199+
34,
200+
],
201+
[
202+
'Parameter #2 of function printf is expected to be int by placeholder #1 ("%d"), float|int given.',
203+
35,
204+
],
205+
[
206+
'Parameter #2 of function printf is expected to be int by placeholder #1 ("%d"), string given.',
207+
36,
208+
],
209+
[
210+
'Parameter #2 of function printf is expected to be int by placeholder #1 ("%d"), string given.',
211+
37,
212+
],
213+
[
214+
'Parameter #2 of function printf is expected to be int by placeholder #1 ("%d"), null given.',
215+
38,
216+
],
217+
[
218+
'Parameter #2 of function printf is expected to be int by placeholder #1 ("%d"), true given.',
219+
39,
220+
],
221+
[
222+
'Parameter #2 of function printf is expected to be int by placeholder #1 ("%d"), SimpleXMLElement given.',
223+
40,
224+
],
225+
[
226+
'Parameter #2 of function printf is expected to be float by placeholder #1 ("%f"), string given.',
227+
42,
228+
],
229+
[
230+
'Parameter #2 of function printf is expected to be float by placeholder #1 ("%f"), null given.',
231+
43,
232+
],
233+
[
234+
'Parameter #2 of function printf is expected to be float by placeholder #1 ("%f"), true given.',
235+
44,
236+
],
237+
[
238+
'Parameter #2 of function printf is expected to be float by placeholder #1 ("%f"), SimpleXMLElement given.',
239+
45,
240+
],
241+
[
242+
'Parameter #2 of function printf is expected to be __stringandstringable by placeholder #1 ("%s"), null given.',
243+
47,
244+
],
245+
[
246+
'Parameter #2 of function printf is expected to be __stringandstringable by placeholder #1 ("%s"), true given.',
247+
48,
248+
],
249+
]);
250+
}
251+
114252
}

‎tests/PHPStan/Rules/Functions/data/printf-param-types.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public function __toString(): string
3939
printf('%d', true);
4040
printf('%d', new \SimpleXMLElement('<a>aaa</a>'));
4141

42-
printf('%f', 'a');
42+
printf('%f', '1.2345678901234567890123456789013245678901234567989');
4343
printf('%f', null);
4444
printf('%f', true);
4545
printf('%f', new \SimpleXMLElement('<a>aaa</a>'));

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /