Skip to content

Commit 8afd4af

Browse files
committed
Loosen up ArrayFilterStrictRule for unions with clearly truthy/falsey types
1 parent 568210b commit 8afd4af

File tree

3 files changed

+114
-0
lines changed

3 files changed

+114
-0
lines changed

src/Rules/Functions/ArrayFilterStrictRule.php

+36
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use PHPStan\Rules\Rule;
1313
use PHPStan\Rules\RuleErrorBuilder;
1414
use PHPStan\Type\Type;
15+
use PHPStan\Type\UnionType;
1516
use PHPStan\Type\VerbosityLevel;
1617
use function count;
1718
use function sprintf;
@@ -82,6 +83,41 @@ public function processNode(Node $node, Scope $scope): array
8283
}
8384

8485
if (count($args) === 1) {
86+
if ($this->treatPhpDocTypesAsCertain) {
87+
$arrayType = $scope->getType($args[0]->value);
88+
} else {
89+
$arrayType = $scope->getNativeType($args[0]->value);
90+
}
91+
92+
$itemType = $arrayType->getIterableValueType();
93+
if ($itemType instanceof UnionType) {
94+
$hasTruthy = false;
95+
$hasFalsey = false;
96+
foreach ($itemType->getTypes() as $innerType) {
97+
$booleanType = $innerType->toBoolean();
98+
if ($booleanType->isTrue()->yes()) {
99+
$hasTruthy = true;
100+
continue;
101+
}
102+
if ($booleanType->isFalse()->yes()) {
103+
$hasFalsey = true;
104+
continue;
105+
}
106+
107+
$hasTruthy = false;
108+
$hasFalsey = false;
109+
break;
110+
}
111+
112+
if ($hasTruthy && $hasFalsey) {
113+
return [];
114+
}
115+
} elseif ($itemType->isBoolean()->yes()) {
116+
return [];
117+
} elseif ($itemType->isArray()->yes()) {
118+
return [];
119+
}
120+
85121
return [RuleErrorBuilder::message('Call to function array_filter() requires parameter #2 to be passed to avoid loose comparison semantics.')->build()];
86122
}
87123

tests/Rules/Functions/ArrayFilterStrictRuleTest.php

+20
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,24 @@ public function testRule(): void
5555
]);
5656
}
5757

58+
public function testRuleAllowMissingCallbackInSomeCases(): void
59+
{
60+
$this->treatPhpDocTypesAsCertain = true;
61+
$this->checkNullables = true;
62+
$this->analyse([__DIR__ . '/data/array-filter-allow.php'], [
63+
[
64+
'Call to function array_filter() requires parameter #2 to be passed to avoid loose comparison semantics.',
65+
27,
66+
],
67+
[
68+
'Call to function array_filter() requires parameter #2 to be passed to avoid loose comparison semantics.',
69+
37,
70+
],
71+
[
72+
'Call to function array_filter() requires parameter #2 to be passed to avoid loose comparison semantics.',
73+
49,
74+
],
75+
]);
76+
}
77+
5878
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
namespace ArrayFilterAllow;
4+
5+
use function array_filter;
6+
7+
class Foo
8+
{
9+
10+
/**
11+
* @param array<self|null> $a
12+
*/
13+
public function doFoo(
14+
array $a
15+
): array
16+
{
17+
return array_filter($a);
18+
}
19+
20+
/**
21+
* @param array<self> $a
22+
*/
23+
public function doFoo2(
24+
array $a
25+
): array
26+
{
27+
return array_filter($a);
28+
}
29+
30+
/**
31+
* @param array<int|null> $a
32+
*/
33+
public function doFoo3(
34+
array $a
35+
): array
36+
{
37+
return array_filter($a);
38+
}
39+
40+
/** @param array<bool> $a */
41+
public function doFoo4(array $a): array
42+
{
43+
return array_filter($a);
44+
}
45+
46+
/** @param array<int> $a */
47+
public function doFoo5(array $a): array
48+
{
49+
return array_filter($a);
50+
}
51+
52+
/** @param array<array<int>> $a */
53+
public function doFoo6(array $a): array
54+
{
55+
return array_filter($a);
56+
}
57+
58+
}

0 commit comments

Comments
 (0)