Skip to content

Commit

Permalink
Fix array_filter with callback that always evaluates to false
Browse files Browse the repository at this point in the history
  • Loading branch information
herndlm authored and ondrejmirtes committed Jan 24, 2022
1 parent 156700b commit d8dc6d0
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 0 deletions.
Expand Up @@ -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([], []);
}
}
}

Expand Down
7 changes: 7 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -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');
Expand Down
39 changes: 39 additions & 0 deletions tests/PHPStan/Analyser/data/array-filter-arrow-functions.php
@@ -0,0 +1,39 @@
<?php // lint >= 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<int>', $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<int>', $filtered3); // not supported yet
}

/**
* @param array<int|string, int|string> $map1
* @param array<int|string, int|string> $map2
* @param array<int|string, int|string> $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<int|string, string>', $filtered1);

$filtered2 = array_filter($map2, static fn($item): bool => is_string($item), ARRAY_FILTER_USE_KEY);
assertType('array<int|string, int|string>', $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<int|string, int|string>', $filtered3); // not supported yet
}
39 changes: 39 additions & 0 deletions tests/PHPStan/Analyser/data/array-filter-callables.php
@@ -0,0 +1,39 @@
<?php

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 function ($item): bool { return is_string($item); });
assertType('array{}', $filtered1);

$filtered2 = array_filter($list2, static function ($item): bool { return is_string($item); }, ARRAY_FILTER_USE_KEY);
assertType('array<int>', $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<int>', $filtered3); // not supported yet
}

/**
* @param array<int|string, int|string> $map1
* @param array<int|string, int|string> $map2
* @param array<int|string, int|string> $map3
*/
function filtersString(array $map1, array $map2, array $map3): void
{
$filtered1 = array_filter($map1, static function ($item): bool { return is_string($item); });
assertType('array<int|string, string>', $filtered1);

$filtered2 = array_filter($map2, static function ($item): bool { return is_string($item); }, ARRAY_FILTER_USE_KEY);
assertType('array<int|string, int|string>', $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<int|string, int|string>', $filtered3); // not supported yet
}
37 changes: 37 additions & 0 deletions tests/PHPStan/Analyser/data/array-filter.php
@@ -0,0 +1,37 @@
<?php

namespace ArrayFilter;

use function PHPStan\Testing\assertType;

function withoutAnyArgs(): void
{
$filtered1 = array_filter();
assertType('array', $filtered1);
}

/**
* @param $var1 $mixed
*/
function withMixedInsteadOfArray($var1): void
{
$filtered1 = array_filter($var1);
assertType('(array|null)', $filtered1);
}

/**
* @param array<string, bool|float|int|string> $map1
* @param array<string, bool|float|int|string> $map2
* @param array<string, bool|float|int|string> $map3
*/
function withoutCallback(array $map1, array $map2, array $map3): void
{
$filtered1 = array_filter($map1);
assertType('array<string, float|int<min, -1>|int<1, max>|non-empty-string|true>', $filtered1);

$filtered2 = array_filter($map2, null, ARRAY_FILTER_USE_KEY);
assertType('array<string, bool|float|int|string>', $filtered2); // not supported yet

$filtered3 = array_filter($map3, null, ARRAY_FILTER_USE_BOTH);
assertType('array<string, bool|float|int|string>', $filtered3); // not supported yet
}

0 comments on commit d8dc6d0

Please sign in to comment.