Skip to content

Commit a712784

Browse files
committed
Merge remote-tracking branch 'origin/1.10.x' into 1.11.x
2 parents f6b3cbc + 7a2af63 commit a712784

File tree

7 files changed

+147
-59
lines changed

7 files changed

+147
-59
lines changed

src/Analyser/ArgumentsNormalizer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ private static function reorderArgs(ParametersAcceptor $parametersAcceptor, Call
209209
);
210210
} else {
211211
if (!$hasVariadic) {
212-
return null;
212+
continue;
213213
}
214214

215215
$attributes = $arg->getAttributes();

src/Rules/Functions/ArrayFilterRule.php

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@
44

55
use PhpParser\Node;
66
use PhpParser\Node\Expr\FuncCall;
7+
use PHPStan\Analyser\ArgumentsNormalizer;
78
use PHPStan\Analyser\Scope;
9+
use PHPStan\Reflection\ParametersAcceptorSelector;
810
use PHPStan\Reflection\ReflectionProvider;
911
use PHPStan\Rules\Rule;
1012
use PHPStan\Rules\RuleErrorBuilder;
1113
use PHPStan\Type\StaticTypeFactory;
1214
use PHPStan\Type\VerbosityLevel;
1315
use function count;
1416
use function sprintf;
15-
use function strtolower;
1617

1718
/**
1819
* @implements Rule<Node\Expr\FuncCall>
@@ -38,13 +39,28 @@ public function processNode(Node $node, Scope $scope): array
3839
return [];
3940
}
4041

41-
$functionName = $this->reflectionProvider->resolveFunctionName($node->name, $scope);
42+
if (!$this->reflectionProvider->hasFunction($node->name, $scope)) {
43+
return [];
44+
}
45+
46+
$functionReflection = $this->reflectionProvider->getFunction($node->name, $scope);
47+
if ($functionReflection->getName() !== 'array_filter') {
48+
return [];
49+
}
50+
51+
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
52+
$scope,
53+
$node->getArgs(),
54+
$functionReflection->getVariants(),
55+
$functionReflection->getNamedArgumentsVariants(),
56+
);
4257

43-
if ($functionName === null || strtolower($functionName) !== 'array_filter') {
58+
$normalizedFuncCall = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $node);
59+
if ($normalizedFuncCall === null) {
4460
return [];
4561
}
4662

47-
$args = $node->getArgs();
63+
$args = $normalizedFuncCall->getArgs();
4864
if (count($args) !== 1) {
4965
return [];
5066
}
@@ -57,11 +73,18 @@ public function processNode(Node $node, Scope $scope): array
5773

5874
if ($arrayType->isIterableAtLeastOnce()->no()) {
5975
$message = 'Parameter #1 $array (%s) to function array_filter is empty, call has no effect.';
76+
$errorBuilder = RuleErrorBuilder::message(sprintf(
77+
$message,
78+
$arrayType->describe(VerbosityLevel::value()),
79+
))->identifier('arrayFilter.empty');
80+
if ($this->treatPhpDocTypesAsCertain) {
81+
$nativeArrayType = $scope->getNativeType($args[0]->value);
82+
if (!$nativeArrayType->isIterableAtLeastOnce()->no()) {
83+
$errorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.');
84+
}
85+
}
6086
return [
61-
RuleErrorBuilder::message(sprintf(
62-
$message,
63-
$arrayType->describe(VerbosityLevel::value()),
64-
))->identifier('arrayFilter.empty')->build(),
87+
$errorBuilder->build(),
6588
];
6689
}
6790

@@ -70,21 +93,41 @@ public function processNode(Node $node, Scope $scope): array
7093

7194
if ($isSuperType->no()) {
7295
$message = 'Parameter #1 $array (%s) to function array_filter does not contain falsy values, the array will always stay the same.';
96+
$errorBuilder = RuleErrorBuilder::message(sprintf(
97+
$message,
98+
$arrayType->describe(VerbosityLevel::value()),
99+
))->identifier('arrayFilter.same');
100+
101+
if ($this->treatPhpDocTypesAsCertain) {
102+
$nativeArrayType = $scope->getNativeType($args[0]->value);
103+
$isNativeSuperType = $falsyType->isSuperTypeOf($nativeArrayType->getIterableValueType());
104+
if (!$isNativeSuperType->no()) {
105+
$errorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.');
106+
}
107+
}
108+
73109
return [
74-
RuleErrorBuilder::message(sprintf(
75-
$message,
76-
$arrayType->describe(VerbosityLevel::value()),
77-
))->identifier('arrayFilter.same')->build(),
110+
$errorBuilder->build(),
78111
];
79112
}
80113

81114
if ($isSuperType->yes()) {
82115
$message = 'Parameter #1 $array (%s) to function array_filter contains falsy values only, the result will always be an empty array.';
116+
$errorBuilder = RuleErrorBuilder::message(sprintf(
117+
$message,
118+
$arrayType->describe(VerbosityLevel::value()),
119+
))->identifier('arrayFilter.alwaysEmpty');
120+
121+
if ($this->treatPhpDocTypesAsCertain) {
122+
$nativeArrayType = $scope->getNativeType($args[0]->value);
123+
$isNativeSuperType = $falsyType->isSuperTypeOf($nativeArrayType->getIterableValueType());
124+
if (!$isNativeSuperType->yes()) {
125+
$errorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.');
126+
}
127+
}
128+
83129
return [
84-
RuleErrorBuilder::message(sprintf(
85-
$message,
86-
$arrayType->describe(VerbosityLevel::value()),
87-
))->identifier('arrayFilter.alwaysEmpty')->build(),
130+
$errorBuilder->build(),
88131
];
89132
}
90133

src/Rules/Functions/ArrayValuesRule.php

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use PHPStan\Type\VerbosityLevel;
1515
use function count;
1616
use function sprintf;
17-
use function strtolower;
1817

1918
/**
2019
* @implements Rule<Node\Expr\FuncCall>
@@ -44,19 +43,20 @@ public function processNode(Node $node, Scope $scope): array
4443
return [];
4544
}
4645

47-
$functionName = $this->reflectionProvider->resolveFunctionName($node->name, $scope);
48-
49-
if ($functionName === null || strtolower($functionName) !== 'array_values') {
46+
if (!$this->reflectionProvider->hasFunction($node->name, $scope)) {
5047
return [];
5148
}
5249

5350
$functionReflection = $this->reflectionProvider->getFunction($node->name, $scope);
51+
if ($functionReflection->getName() !== 'array_values') {
52+
return [];
53+
}
5454

5555
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
5656
$scope,
5757
$node->getArgs(),
5858
$functionReflection->getVariants(),
59-
null,
59+
$functionReflection->getNamedArgumentsVariants(),
6060
);
6161

6262
$normalizedFuncCall = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $node);
@@ -66,36 +66,49 @@ public function processNode(Node $node, Scope $scope): array
6666
}
6767

6868
$args = $normalizedFuncCall->getArgs();
69-
70-
if (count($args) !== 1) {
69+
if (count($args) === 0) {
7170
return [];
7271
}
7372

74-
if ($this->treatPhpDocTypesAsCertain === true) {
73+
if ($this->treatPhpDocTypesAsCertain) {
7574
$arrayType = $scope->getType($args[0]->value);
7675
} else {
7776
$arrayType = $scope->getNativeType($args[0]->value);
7877
}
7978

8079
if ($arrayType->isIterableAtLeastOnce()->no()) {
8180
$message = 'Parameter #1 $array (%s) to function array_values is empty, call has no effect.';
81+
$errorBuilder = RuleErrorBuilder::message(sprintf(
82+
$message,
83+
$arrayType->describe(VerbosityLevel::value()),
84+
));
85+
if ($this->treatPhpDocTypesAsCertain) {
86+
$nativeArrayType = $scope->getNativeType($args[0]->value);
87+
if (!$nativeArrayType->isIterableAtLeastOnce()->no()) {
88+
$errorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.');
89+
}
90+
}
8291

8392
return [
84-
RuleErrorBuilder::message(sprintf(
85-
$message,
86-
$arrayType->describe(VerbosityLevel::value()),
87-
))->build(),
93+
$errorBuilder->build(),
8894
];
8995
}
9096

9197
if ($arrayType->isList()->yes()) {
9298
$message = 'Parameter #1 $array (%s) of array_values is already a list, call has no effect.';
99+
$errorBuilder = RuleErrorBuilder::message(sprintf(
100+
$message,
101+
$arrayType->describe(VerbosityLevel::value()),
102+
));
103+
if ($this->treatPhpDocTypesAsCertain) {
104+
$nativeArrayType = $scope->getNativeType($args[0]->value);
105+
if (!$nativeArrayType->isList()->yes()) {
106+
$errorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.');
107+
}
108+
}
93109

94110
return [
95-
RuleErrorBuilder::message(sprintf(
96-
$message,
97-
$arrayType->describe(VerbosityLevel::value()),
98-
))->build(),
111+
$errorBuilder->build(),
99112
];
100113
}
101114

tests/PHPStan/Analyser/ArgumentsNormalizerTest.php

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,43 @@ public function dataReorderValid(): iterable
202202
new StringType(),
203203
],
204204
];
205+
206+
yield [
207+
[
208+
['one', true, false, new IntegerType()],
209+
['two', true, false, new StringType()],
210+
['three', true, false, new FloatType()],
211+
],
212+
[],
213+
[],
214+
];
215+
216+
yield [
217+
[
218+
['one', true, false, new IntegerType()],
219+
['two', true, false, new StringType()],
220+
['three', true, false, new FloatType()],
221+
],
222+
[
223+
[new StringType(), 'onee'],
224+
],
225+
[],
226+
];
227+
228+
yield [
229+
[
230+
['one', true, false, new IntegerType()],
231+
['two', true, false, new StringType()],
232+
['three', true, false, new FloatType()],
233+
],
234+
[
235+
[new IntegerType(), null],
236+
[new StringType(), 'onee'],
237+
],
238+
[
239+
new IntegerType(),
240+
],
241+
];
205242
}
206243

207244
/**
@@ -282,29 +319,6 @@ public function dataReorderInvalid(): iterable
282319
[new StringType(), 'three'],
283320
],
284321
];
285-
286-
yield [
287-
[
288-
['one', true, false, new IntegerType()],
289-
['two', true, false, new StringType()],
290-
['three', true, false, new FloatType()],
291-
],
292-
[
293-
[new StringType(), 'onee'],
294-
],
295-
];
296-
297-
yield [
298-
[
299-
['one', true, false, new IntegerType()],
300-
['two', true, false, new StringType()],
301-
['three', true, false, new FloatType()],
302-
],
303-
[
304-
[new IntegerType(), null],
305-
[new StringType(), 'onee'],
306-
],
307-
];
308322
}
309323

310324
/**

tests/PHPStan/Rules/Functions/ArrayFilterRuleTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ protected function getRule(): Rule
2020

2121
public function testFile(): void
2222
{
23+
$tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.';
2324
$expectedErrors = [
2425
[
2526
'Parameter #1 $array (array{1, 3}) to function array_filter does not contain falsy values, the array will always stay the same.',
@@ -40,6 +41,7 @@ public function testFile(): void
4041
[
4142
'Parameter #1 $array (array<stdClass>) to function array_filter does not contain falsy values, the array will always stay the same.',
4243
20,
44+
$tipText,
4345
],
4446
[
4547
'Parameter #1 $array (array{0}) to function array_filter contains falsy values only, the result will always be an empty array.',
@@ -60,6 +62,7 @@ public function testFile(): void
6062
[
6163
'Parameter #1 $array (array<false|null>) to function array_filter contains falsy values only, the result will always be an empty array.',
6264
27,
65+
$tipText,
6366
],
6467
[
6568
'Parameter #1 $array (array{}) to function array_filter is empty, call has no effect.',
@@ -72,10 +75,13 @@ public function testFile(): void
7275

7376
public function testBug2065WithPhpDocTypesAsCertain(): void
7477
{
78+
$tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.';
79+
7580
$expectedErrors = [
7681
[
7782
'Parameter #1 $array (array<class-string>) to function array_filter does not contain falsy values, the array will always stay the same.',
7883
12,
84+
$tipText,
7985
],
8086
];
8187

tests/PHPStan/Rules/Functions/ArrayValuesRuleTest.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ protected function getRule(): Rule
2121

2222
public function testFile(): void
2323
{
24+
$tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.';
2425
$expectedErrors = [
2526
[
2627
'Parameter #1 $array (array{0, 1, 3}) of array_values is already a list, call has no effect.',
@@ -41,6 +42,7 @@ public function testFile(): void
4142
[
4243
'Parameter #1 $array (list<int>) of array_values is already a list, call has no effect.',
4344
14,
45+
$tipText,
4446
],
4547
[
4648
'Parameter #1 $array (array{0}) of array_values is already a list, call has no effect.',
@@ -58,12 +60,18 @@ public function testFile(): void
5860
'Parameter #1 $array (array{}) to function array_values is empty, call has no effect.',
5961
21,
6062
],
63+
[
64+
'Parameter #1 $array (array{}) to function array_values is empty, call has no effect.',
65+
25,
66+
$tipText,
67+
],
6168
];
6269

6370
if (PHP_VERSION_ID >= 80000) {
6471
$expectedErrors[] = [
6572
'Parameter #1 $array (list<int>) of array_values is already a list, call has no effect.',
66-
24,
73+
28,
74+
$tipText,
6775
];
6876
}
6977

tests/PHPStan/Rules/Functions/data/array_values_list.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,9 @@
2020
array_values([null, 0]);
2121
array_values([]);
2222

23-
array_values(array: $array);
24-
array_values(array: $list);
23+
/** @var array{} $empty */
24+
$empty = doFoo();
25+
array_values($empty);
26+
27+
array_values(unused: true, array: $array);
28+
array_values(unused: true, array: $list);

0 commit comments

Comments
 (0)