diff --git a/src/Rules/Functions/ArrayFilterStrictRule.php b/src/Rules/Functions/ArrayFilterStrictRule.php index 918f9de..f78f943 100644 --- a/src/Rules/Functions/ArrayFilterStrictRule.php +++ b/src/Rules/Functions/ArrayFilterStrictRule.php @@ -12,6 +12,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\Type; +use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; use function count; use function sprintf; @@ -82,6 +83,41 @@ public function processNode(Node $node, Scope $scope): array } if (count($args) === 1) { + if ($this->treatPhpDocTypesAsCertain) { + $arrayType = $scope->getType($args[0]->value); + } else { + $arrayType = $scope->getNativeType($args[0]->value); + } + + $itemType = $arrayType->getIterableValueType(); + if ($itemType instanceof UnionType) { + $hasTruthy = false; + $hasFalsey = false; + foreach ($itemType->getTypes() as $innerType) { + $booleanType = $innerType->toBoolean(); + if ($booleanType->isTrue()->yes()) { + $hasTruthy = true; + continue; + } + if ($booleanType->isFalse()->yes()) { + $hasFalsey = true; + continue; + } + + $hasTruthy = false; + $hasFalsey = false; + break; + } + + if ($hasTruthy && $hasFalsey) { + return []; + } + } elseif ($itemType->isBoolean()->yes()) { + return []; + } elseif ($itemType->isArray()->yes()) { + return []; + } + return [ RuleErrorBuilder::message('Call to function array_filter() requires parameter #2 to be passed to avoid loose comparison semantics.') ->identifier('arrayFilter.strict') diff --git a/tests/Rules/Functions/ArrayFilterStrictRuleTest.php b/tests/Rules/Functions/ArrayFilterStrictRuleTest.php index e7bcb8a..45ac87a 100644 --- a/tests/Rules/Functions/ArrayFilterStrictRuleTest.php +++ b/tests/Rules/Functions/ArrayFilterStrictRuleTest.php @@ -55,4 +55,24 @@ public function testRule(): void ]); } + public function testRuleAllowMissingCallbackInSomeCases(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->checkNullables = true; + $this->analyse([__DIR__ . '/data/array-filter-allow.php'], [ + [ + 'Call to function array_filter() requires parameter #2 to be passed to avoid loose comparison semantics.', + 27, + ], + [ + 'Call to function array_filter() requires parameter #2 to be passed to avoid loose comparison semantics.', + 37, + ], + [ + 'Call to function array_filter() requires parameter #2 to be passed to avoid loose comparison semantics.', + 49, + ], + ]); + } + } diff --git a/tests/Rules/Functions/data/array-filter-allow.php b/tests/Rules/Functions/data/array-filter-allow.php new file mode 100644 index 0000000..28bec88 --- /dev/null +++ b/tests/Rules/Functions/data/array-filter-allow.php @@ -0,0 +1,58 @@ + $a + */ + public function doFoo( + array $a + ): array + { + return array_filter($a); + } + + /** + * @param array $a + */ + public function doFoo2( + array $a + ): array + { + return array_filter($a); + } + + /** + * @param array $a + */ + public function doFoo3( + array $a + ): array + { + return array_filter($a); + } + + /** @param array $a */ + public function doFoo4(array $a): array + { + return array_filter($a); + } + + /** @param array $a */ + public function doFoo5(array $a): array + { + return array_filter($a); + } + + /** @param array> $a */ + public function doFoo6(array $a): array + { + return array_filter($a); + } + +}