diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php index 130569a32..600d04444 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php @@ -48,7 +48,7 @@ class ArrayMergeReturnTypeProvider implements FunctionReturnTypeProviderInterfac return Type::getMixed(); } - $is_replace = mb_strcut($event->getFunctionId(), 6, 7) === 'replace'; + $is_replace = substr($event->getFunctionId(), 6, 7) === 'replace'; $inner_value_types = []; $inner_key_types = []; @@ -70,135 +70,156 @@ class ArrayMergeReturnTypeProvider implements FunctionReturnTypeProviderInterfac } foreach ($call_arg_type->getAtomicTypes() as $type_part) { + $unpacking_indefinite_number_of_args = false; + $unpacking_possibly_empty = false; if ($call_arg->unpack) { - if (!$type_part instanceof TArray) { - if ($type_part instanceof TKeyedArray) { - $type_part_value_type = $type_part->getGenericValueType(); - } elseif ($type_part instanceof TList) { - $type_part_value_type = $type_part->type_param; - } else { - return Type::getArray(); + if ($type_part instanceof TKeyedArray) { + $unpacked_type_parts = $type_part->getGenericValueType(); + $unpacking_indefinite_number_of_args = $type_part->fallback_params !== null; + $unpacking_possibly_empty = true; + foreach ($type_part->properties as $property) { + if (!$property->possibly_undefined) { + $unpacking_possibly_empty = false; + break; + } } + } elseif ($type_part instanceof TArray) { + $unpacked_type_parts = $type_part->type_params[1]; + $unpacking_indefinite_number_of_args = true; + $unpacking_possibly_empty = !$type_part instanceof TNonEmptyArray; + } elseif ($type_part instanceof TList) { + $unpacked_type_parts = $type_part->type_param; + $unpacking_indefinite_number_of_args = true; + $unpacking_possibly_empty = !$type_part instanceof TNonEmptyList; } else { - $type_part_value_type = $type_part->type_params[1]; - } - - $unpacked_type_parts = []; - - foreach ($type_part_value_type->getAtomicTypes() as $value_type_part) { - $unpacked_type_parts[] = $value_type_part; + return Type::getArray(); } + $unpacked_type_parts = $unpacked_type_parts->getAtomicTypes(); } else { $unpacked_type_parts = [$type_part]; } foreach ($unpacked_type_parts as $unpacked_type_part) { - if (!$unpacked_type_part instanceof TArray) { - if (($unpacked_type_part instanceof TFalse - && $call_arg_type->ignore_falsable_issues) - || ($unpacked_type_part instanceof TNull - && $call_arg_type->ignore_nullable_issues) - ) { - continue; - } + if (($unpacked_type_part instanceof TFalse + && $call_arg_type->ignore_falsable_issues) + || ($unpacked_type_part instanceof TNull + && $call_arg_type->ignore_nullable_issues) + ) { + continue; + } - if ($unpacked_type_part instanceof TKeyedArray) { - $max_keyed_array_size = max( - $max_keyed_array_size, - count($unpacked_type_part->properties) - ); + if ($unpacked_type_part instanceof TKeyedArray) { + $max_keyed_array_size = max( + $max_keyed_array_size, + count($unpacked_type_part->properties) + ); - foreach ($unpacked_type_part->properties as $key => $type) { - if (!is_string($key)) { - if ($is_replace) { - $generic_properties[$key] = $type; - } else { - $generic_properties[] = $type; - } - continue; - } else { - $all_int_offsets = false; - } - - if (isset($unpacked_type_part->class_strings[$key])) { - $class_strings[$key] = true; - } - - if (!isset($generic_properties[$key]) || !$type->possibly_undefined) { - $generic_properties[$key] = $type; - } else { - $was_possibly_undefined = $generic_properties[$key]->possibly_undefined; - - $generic_properties[$key] = Type::combineUnionTypes( - $generic_properties[$key], - $type, - $codebase, - false, - true, - 500, - $was_possibly_undefined - ); - } - } - - if (!$unpacked_type_part->is_list) { - $all_nonempty_lists = false; - } - - if ($unpacked_type_part->fallback_params === null) { - $any_nonempty = true; - } - - continue; - } - - if ($unpacked_type_part instanceof TList) { - $all_keyed_arrays = false; - - if (!$unpacked_type_part instanceof TNonEmptyList) { - $all_nonempty_lists = false; + $added_inner_values = false; + foreach ($unpacked_type_part->properties as $key => $type) { + if (is_string($key)) { + $all_int_offsets = false; } else { + if ($is_replace) { + $generic_properties[$key] = $type; + } elseif ($unpacking_indefinite_number_of_args) { + $added_inner_values = true; + $inner_value_types[] = $type; + } else { + $generic_properties[] = $type; + } + continue; + } + + if (isset($unpacked_type_part->class_strings[$key])) { + $class_strings[$key] = true; + } + + if (!isset($generic_properties[$key]) || !$type->possibly_undefined) { + $generic_properties[$key] = $type; + } else { + $was_possibly_undefined = $generic_properties[$key]->possibly_undefined; + + $generic_properties[$key] = Type::combineUnionTypes( + $generic_properties[$key], + $type, + $codebase, + false, + true, + 500, + $was_possibly_undefined + ); + } + } + + if (!$unpacked_type_part->is_list && !$unpacking_possibly_empty) { + $all_nonempty_lists = false; + } + + if ($added_inner_values) { + $all_keyed_arrays = false; + $inner_key_types []= new TInt; + } + + if ($unpacked_type_part->fallback_params === null) { + if (!$unpacking_possibly_empty) { $any_nonempty = true; } } else { - if ($unpacked_type_part instanceof TMixed - && $unpacked_type_part->from_loop_isset - ) { - $unpacked_type_part = new TArray([ - Type::getArrayKey(), - Type::getMixed(true), - ]); - } else { - return Type::getArray(); - } - } - } else { - if (!$unpacked_type_part->isEmptyArray()) { - foreach ($generic_properties as $key => $keyed_type) { - $generic_properties[$key] = Type::combineUnionTypes( - $keyed_type, - $unpacked_type_part->type_params[1], - $codebase - ); - } - $all_keyed_arrays = false; - $all_nonempty_lists = false; + $inner_value_types = array_merge( + $inner_value_types, + array_values($unpacked_type_part->fallback_params[1]->getAtomicTypes()) + ); + $inner_key_types = array_merge( + $inner_key_types, + array_values($unpacked_type_part->fallback_params[0]->getAtomicTypes()) + ); } + + continue; } - if ($unpacked_type_part instanceof TArray) { + + if ($unpacked_type_part instanceof TList) { + $all_keyed_arrays = false; + + if ($unpacked_type_part instanceof TNonEmptyList && !$unpacking_possibly_empty) { + $any_nonempty = true; + } else { + $all_nonempty_lists = false; + } + } elseif ($unpacked_type_part instanceof TArray) { if ($unpacked_type_part->isEmptyArray()) { continue; } + foreach ($generic_properties as $key => $keyed_type) { + $generic_properties[$key] = Type::combineUnionTypes( + $keyed_type, + $unpacked_type_part->type_params[1], + $codebase + ); + } + + $all_keyed_arrays = false; + $all_nonempty_lists = false; + if (!$unpacked_type_part->type_params[0]->isInt()) { $all_int_offsets = false; } - if ($unpacked_type_part instanceof TNonEmptyArray) { + if ($unpacked_type_part instanceof TNonEmptyArray && !$unpacking_possibly_empty) { $any_nonempty = true; } + } elseif ($unpacked_type_part instanceof TMixed + && $unpacked_type_part->from_loop_isset + ) { + $unpacked_type_part = new TArray([ + Type::getArrayKey(), + Type::getMixed(true), + ]); + } else { + return Type::getArray(); } $inner_key_types = array_merge(