diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php index fac958d1d..14be1a400 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php @@ -64,6 +64,52 @@ class ArrayFilterReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturn } else { $inner_type = $first_arg_array->getGenericValueType(); $key_type = $first_arg_array->getGenericKeyType(); + + if (!isset($call_args[1]) && !$first_arg_array->previous_value_type) { + $had_one = count($first_arg_array->properties) === 1; + + $first_arg_array = clone $first_arg_array; + + $new_properties = \array_filter( + array_map( + function ($keyed_type) use ($statements_source, $context) { + $prev_keyed_type = $keyed_type; + + $keyed_type = \Psalm\Internal\Type\AssertionReconciler::reconcile( + '!falsy', + clone $keyed_type, + '', + $statements_source, + $context->inside_loop, + [], + null, + $statements_source->getSuppressedIssues() + ); + + $keyed_type->possibly_undefined = ($prev_keyed_type->hasInt() + && !$prev_keyed_type->hasLiteralInt()) + || $prev_keyed_type->hasFloat() + || $prev_keyed_type->getId() !== $keyed_type->getId(); + + return $keyed_type; + }, + $first_arg_array->properties + ), + function ($keyed_type) { + return !$keyed_type->isEmpty(); + } + ); + + if (!$new_properties) { + return Type::getEmptyArray(); + } + + $first_arg_array->properties = $new_properties; + + $first_arg_array->is_list = $first_arg_array->is_list && $had_one; + + return new Type\Union([$first_arg_array]); + } } if (!isset($call_args[1])) { @@ -97,7 +143,20 @@ class ArrayFilterReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturn if ($key_type->getLiteralInts()) { $key_type->addType(new Type\Atomic\TInt); } - } elseif (!isset($call_args[2])) { + + if (!$inner_type->getAtomicTypes()) { + return Type::getEmptyArray(); + } + + return new Type\Union([ + new Type\Atomic\TArray([ + $key_type, + $inner_type, + ]), + ]); + } + + if (!isset($call_args[2])) { $function_call_arg = $call_args[1]; if ($function_call_arg->value instanceof PhpParser\Node\Scalar\String_ diff --git a/tests/ArrayFunctionCallTest.php b/tests/ArrayFunctionCallTest.php index bd97a4e70..11f8f4818 100644 --- a/tests/ArrayFunctionCallTest.php +++ b/tests/ArrayFunctionCallTest.php @@ -16,15 +16,15 @@ class ArrayFunctionCallTest extends TestCase return [ 'arrayFilter' => [ ' 5, "b" => 12, "c" => null]); + $d = array_filter(["a" => rand(0, 10), "b" => rand(0, 10), "c" => null]); $e = array_filter( - ["a" => 5, "b" => 12, "c" => null], + ["a" => rand(0, 10), "b" => rand(0, 10), "c" => null], function(?int $i): bool { return true; } );', 'assertions' => [ - '$d' => 'array', + '$d' => 'array{a?: int, b?: int}', '$e' => 'array', ], ], @@ -2010,7 +2010,7 @@ class ArrayFunctionCallTest extends TestCase function ints(array $ints) : void {} $brr = array_filter([2,3,0,4,5]); ints($brr);', - 'error_message' => 'ArgumentTypeCoercion - src' . DIRECTORY_SEPARATOR . 'somefile.php:5:26 - Argument 1 of ints expects list, parent type array provided', + 'error_message' => 'InvalidArgument', ], 'usortOneParamInvalid' => [ '