diff --git a/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php b/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php index e863f3a832..c0ba83e062 100644 --- a/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php +++ b/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php @@ -83,6 +83,9 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $scope = $scope->assignVariable($itemVariableName, $itemType); $scope = $scope->filterByTruthyValue($expr); $itemType = $scope->getVariableType($itemVariableName); + if ($itemType instanceof NeverType) { + return new ConstantArrayType([], []); + } } } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 70aac8b22f..9b971eca83 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -275,6 +275,13 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/infer-array-key.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/offset-value-after-assign.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2112.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/array-filter.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/array-filter-callables.php'); + if (PHP_VERSION_ID >= 70400) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/array-filter-arrow-functions.php'); + } + yield from $this->gatherAssertTypes(__DIR__ . '/data/array-flip.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/array-map.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/array-map-closure.php'); diff --git a/tests/PHPStan/Analyser/data/array-filter-arrow-functions.php b/tests/PHPStan/Analyser/data/array-filter-arrow-functions.php new file mode 100644 index 0000000000..f49e1fe3a0 --- /dev/null +++ b/tests/PHPStan/Analyser/data/array-filter-arrow-functions.php @@ -0,0 +1,39 @@ += 7.4 + +namespace ArrayFilter; + +use function PHPStan\Testing\assertType; + +/** + * @param int[] $list1 + * @param int[] $list2 + * @param int[] $list3 + */ +function alwaysEvaluatesToFalse(array $list1, array $list2, array $list3): void +{ + $filtered1 = array_filter($list1, static fn($item): bool => is_string($item)); + assertType('array{}', $filtered1); + + $filtered2 = array_filter($list2, static fn($item): bool => is_string($item), ARRAY_FILTER_USE_KEY); + assertType('array', $filtered2); // not supported yet + + $filtered3 = array_filter($list3, static fn($item, $key): bool => is_string($item) && is_string($key), ARRAY_FILTER_USE_BOTH); + assertType('array', $filtered3); // not supported yet +} + +/** + * @param array $map1 + * @param array $map2 + * @param array $map3 + */ +function filtersString(array $map1, array $map2, array $map3, array $map4): void +{ + $filtered1 = array_filter($map1, static fn($item): bool => is_string($item)); + assertType('array', $filtered1); + + $filtered2 = array_filter($map2, static fn($item): bool => is_string($item), ARRAY_FILTER_USE_KEY); + assertType('array', $filtered2); // not supported yet + + $filtered3 = array_filter($map3, static fn($item, $key): bool => is_string($item) && is_string($key), ARRAY_FILTER_USE_BOTH); + assertType('array', $filtered3); // not supported yet +} diff --git a/tests/PHPStan/Analyser/data/array-filter-callables.php b/tests/PHPStan/Analyser/data/array-filter-callables.php new file mode 100644 index 0000000000..2f32fbc528 --- /dev/null +++ b/tests/PHPStan/Analyser/data/array-filter-callables.php @@ -0,0 +1,39 @@ +', $filtered2); // not supported yet + + $filtered3 = array_filter($list3, static function ($item, $key): bool { return is_string($item) && is_string($key); }, ARRAY_FILTER_USE_BOTH); + assertType('array', $filtered3); // not supported yet +} + +/** + * @param array $map1 + * @param array $map2 + * @param array $map3 + */ +function filtersString(array $map1, array $map2, array $map3): void +{ + $filtered1 = array_filter($map1, static function ($item): bool { return is_string($item); }); + assertType('array', $filtered1); + + $filtered2 = array_filter($map2, static function ($item): bool { return is_string($item); }, ARRAY_FILTER_USE_KEY); + assertType('array', $filtered2); // not supported yet + + $filtered3 = array_filter($map3, static function ($item, $key): bool { return is_string($item) && is_string($key); }, ARRAY_FILTER_USE_BOTH); + assertType('array', $filtered3); // not supported yet +} diff --git a/tests/PHPStan/Analyser/data/array-filter.php b/tests/PHPStan/Analyser/data/array-filter.php new file mode 100644 index 0000000000..06ea76f861 --- /dev/null +++ b/tests/PHPStan/Analyser/data/array-filter.php @@ -0,0 +1,37 @@ + $map1 + * @param array $map2 + * @param array $map3 + */ +function withoutCallback(array $map1, array $map2, array $map3): void +{ + $filtered1 = array_filter($map1); + assertType('array|int<1, max>|non-empty-string|true>', $filtered1); + + $filtered2 = array_filter($map2, null, ARRAY_FILTER_USE_KEY); + assertType('array', $filtered2); // not supported yet + + $filtered3 = array_filter($map3, null, ARRAY_FILTER_USE_BOTH); + assertType('array', $filtered3); // not supported yet +}