diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php index 7e2fc83f8..e31a6b544 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php @@ -31,6 +31,7 @@ class ArrayColumnReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturn } $row_shape = null; + $input_array_not_empty = false; // calculate row shape if (($first_arg_type = $statements_source->node_data->getType($call_args[0]->value)) @@ -54,6 +55,10 @@ class ArrayColumnReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturn $row_shape = $row_type->getAtomicTypes()['array']; } } + + $input_array_not_empty = $input_array instanceof Type\Atomic\TNonEmptyList || + $input_array instanceof Type\Atomic\TNonEmptyArray || + $input_array instanceof Type\Atomic\ObjectLike; } $value_column_name = null; @@ -83,9 +88,13 @@ class ArrayColumnReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturn $result_key_type = Type::getArrayKey(); $result_element_type = null; + $have_at_least_one_res = false; // calculate results if ($row_shape instanceof Type\Atomic\ObjectLike) { if ((null !== $value_column_name) && isset($row_shape->properties[$value_column_name])) { + if ($input_array_not_empty) { + $have_at_least_one_res = true; + } $result_element_type = $row_shape->properties[$value_column_name]; } else { $result_element_type = Type::getMixed(); @@ -96,10 +105,16 @@ class ArrayColumnReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturn } } - return new Type\Union([ - isset($call_args[2]) && (string) $third_arg_type !== 'null' - ? new Type\Atomic\TArray([$result_key_type, $result_element_type ?? Type::getMixed()]) - : new Type\Atomic\TList($result_element_type ?? Type::getMixed()) - ]); + if (isset($call_args[2]) && (string)$third_arg_type !== 'null') { + $type = $have_at_least_one_res ? + new Type\Atomic\TNonEmptyArray([$result_key_type, $result_element_type ?? Type::getMixed()]) + : new Type\Atomic\TArray([$result_key_type, $result_element_type ?? Type::getMixed()]); + } else { + $type = $have_at_least_one_res ? + new Type\Atomic\TNonEmptyList($result_element_type ?? Type::getMixed()) + : new Type\Atomic\TList($result_element_type ?? Type::getMixed()); + } + + return new Type\Union([$type]); } } diff --git a/tests/ArrayFunctionCallTest.php b/tests/ArrayFunctionCallTest.php index f8a0ccc5d..cc12f1d51 100644 --- a/tests/ArrayFunctionCallTest.php +++ b/tests/ArrayFunctionCallTest.php @@ -811,11 +811,12 @@ class ArrayFunctionCallTest extends TestCase $h = array_column(makeGenericArray(), 0); $i = array_column(makeShapeArray(), 0); $j = array_column(makeUnionArray(), 0); + $k = array_column([[0 => "test"]], 0); ', 'assertions' => [ - '$a' => 'list', - '$b' => 'list', - '$c' => 'array', + '$a' => 'non-empty-list', + '$b' => 'non-empty-list', + '$c' => 'non-empty-array', '$d' => 'list', '$e' => 'list', '$f' => 'array', @@ -823,6 +824,7 @@ class ArrayFunctionCallTest extends TestCase '$h' => 'list', '$i' => 'list', '$j' => 'list', + '$k' => 'non-empty-list', ], ], 'splatArrayIntersect' => [