diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php index 05efc3c4e..12a985462 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php @@ -130,10 +130,18 @@ class ArrayMergeReturnTypeProvider implements FunctionReturnTypeProviderInterfac $class_strings[$key] = true; } - if (!isset($generic_properties[$key]) || !$type->possibly_undefined) { + if (!isset($generic_properties[$key]) || ( + !$type->possibly_undefined + && !$unpacking_possibly_empty + && $is_replace + )) { + if ($unpacking_possibly_empty) { + $type = $type->setPossiblyUndefined(true); + } $generic_properties[$key] = $type; } else { - $was_possibly_undefined = $generic_properties[$key]->possibly_undefined; + $was_possibly_undefined = $generic_properties[$key]->possibly_undefined + || $unpacking_possibly_empty; $generic_properties[$key] = Type::combineUnionTypes( $generic_properties[$key], @@ -147,7 +155,7 @@ class ArrayMergeReturnTypeProvider implements FunctionReturnTypeProviderInterfac } } - if (!$unpacked_type_part->is_list && !$unpacking_possibly_empty) { + if (!$unpacked_type_part->is_list) { $all_nonempty_lists = false; } diff --git a/tests/ArrayFunctionCallTest.php b/tests/ArrayFunctionCallTest.php index 369b6aae5..3189266a0 100644 --- a/tests/ArrayFunctionCallTest.php +++ b/tests/ArrayFunctionCallTest.php @@ -211,6 +211,24 @@ class ArrayFunctionCallTest extends TestCase 'ignored_issues' => [], 'php_version' => '8.0', ], + 'arrayMergeListOfShapes' => [ + 'code' => ' */ + $a = []; + + $b = array_merge(...$a); + + /** @var non-empty-list */ + $c = []; + + $d = array_merge(...$c); + ', + 'assertions' => [ + '$b' => 'array{a?: int}', + '$d' => 'array{a: int}', + ] + ], 'arrayMergeIntArrays' => [ 'code' => '