From c7712ddb28bffe5b2c48e8a858564ddb22095237 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 16:42:22 +0300 Subject: [PATCH] checker: fix array of variant not accepted for array of sumtype (fixes #24140) --- vlib/v/ast/table.v | 33 +++++++++++ vlib/v/checker/check_types.v | 10 ++++ vlib/v/gen/c/cgen.v | 61 ++++++++++++++++++++ vlib/v/tests/array_variant_arg_upcast_test.v | 59 +++++++++++++++++++ 4 files changed, 163 insertions(+) create mode 100644 vlib/v/tests/array_variant_arg_upcast_test.v diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 90967f4a7..c62ce1713 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -1880,6 +1880,39 @@ pub fn (t &Table) is_sumtype_or_in_variant(parent Type, typ Type) bool { return t.sumtype_has_variant(parent, typ, false) } +// can_implicit_array_cast reports whether `got` can be converted to `expected` +// by boxing each array element into the expected interface or sum type. +pub fn (t &Table) can_implicit_array_cast(got Type, expected Type) bool { + got_unaliased := t.unaliased_type(got) + expected_unaliased := t.unaliased_type(expected) + got_sym := t.final_sym(got_unaliased) + expected_sym := t.final_sym(expected_unaliased) + if got_sym.kind != .array || expected_sym.kind != .array { + return false + } + got_info := got_sym.info as Array + expected_info := expected_sym.info as Array + if got_info.nr_dims != expected_info.nr_dims { + return false + } + got_elem_type := t.unaliased_type(got_info.elem_type) + expected_elem_type := t.unaliased_type(expected_info.elem_type) + if got_elem_type == expected_elem_type { + return false + } + match t.final_sym(expected_elem_type).kind { + .sum_type { + return t.is_sumtype_or_in_variant(expected_elem_type, mktyp(got_elem_type)) + } + .interface { + return t.does_type_implement_interface(got_elem_type, expected_elem_type) + } + else { + return false + } + } +} + @[inline] pub fn (t &Table) is_interface_var(var ScopeObject) bool { return var is Var && var.orig_type != 0 && t.sym(var.orig_type).kind == .interface diff --git a/vlib/v/checker/check_types.v b/vlib/v/checker/check_types.v index 444600cf0..f9d812cf9 100644 --- a/vlib/v/checker/check_types.v +++ b/vlib/v/checker/check_types.v @@ -362,6 +362,16 @@ fn (mut c Checker) check_expected_call_arg(got_ ast.Type, expected_ ast.Type, la if expected_typ_sym.kind == .interface && c.type_implements(got, expected, token.Pos{}) { return } + if !arg.is_mut && c.table.can_implicit_array_cast(got, expected) { + expected_info := c.table.final_sym(expected).info as ast.Array + expected_elem_type := c.table.unaliased_type(expected_info.elem_type) + if c.table.final_sym(expected_elem_type).kind == .interface { + got_info := c.table.final_sym(got).info as ast.Array + got_elem_type := c.table.unaliased_type(got_info.elem_type) + c.type_implements(got_elem_type, expected_elem_type, arg.expr.pos()) + } + return + } // Check on Generics types, there are some case where we have the following case // `&Type[int] == &Type[]`. This is a common case we are implementing a function diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 939720ea8..18013e4a4 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -3761,6 +3761,7 @@ fn (mut g Gen) expr_with_fixed_array(expr ast.Expr, got_type_raw ast.Type, expec return tmp_var } +<<<<<<< HEAD fn (mut g Gen) gen_interface_to_interface_conversion(expr ast.Expr, expr_type ast.Type, to_type ast.Type) { mut expr_type_sym := g.table.sym(expr_type) to_sym := g.table.sym(to_type) @@ -3782,6 +3783,60 @@ fn (mut g Gen) gen_interface_to_interface_conversion(expr ast.Expr, expr_type as expr_type_sym.info = info } +// expr_with_array_element_upcast materializes an array whose elements are boxed into +// the expected interface or sum type element type. +fn (mut g Gen) expr_with_array_element_upcast(expr ast.Expr, got_type ast.Type, expected_type ast.Type) { + got_sym := g.table.final_sym(got_type) + expected_sym := g.table.final_sym(expected_type) + got_info := got_sym.info as ast.Array + expected_info := expected_sym.info as ast.Array + got_elem_type := got_info.elem_type + expected_elem_type := expected_info.elem_type + got_styp := g.styp(got_type) + expected_styp := g.styp(expected_type) + got_elem_styp := g.styp(got_elem_type) + expected_elem_styp := g.styp(expected_elem_type) + expected_elem_sym := g.table.final_sym(expected_elem_type) + noscan := g.check_noscan(expected_elem_type) + stmt_str := g.go_before_last_stmt().trim_space() + g.empty_line = true + source_tmp := g.new_tmp_var() + result_tmp := g.new_tmp_var() + index_tmp := g.new_tmp_var() + item_tmp := g.new_tmp_var() + converted_tmp := g.new_tmp_var() + g.write('${got_styp} ${source_tmp}_orig = ') + g.expr(expr) + g.writeln(';') + g.writeln('${expected_styp} ${result_tmp} = builtin____new_array${noscan}(0, ${source_tmp}_orig.len, sizeof(${expected_elem_styp}));') + g.writeln('for (${ast.int_type_name} ${index_tmp} = 0; ${index_tmp} < ${source_tmp}_orig.len; ++${index_tmp}) {') + g.indent++ + if expected_elem_sym.kind == .interface { + expected_interface_info := expected_elem_sym.info as ast.Interface + mut fname := 'I_${g.cc_type(got_elem_type, true)}_to_Interface_${expected_elem_sym.cname}' + lock g.referenced_fns { + g.referenced_fns[fname] = true + } + if expected_interface_info.is_generic { + fname = g.generic_fn_name(expected_interface_info.concrete_types, fname) + } + g.writeln('${expected_elem_styp} ${converted_tmp} = ${fname}(&(((${got_elem_styp}*)${source_tmp}_orig.data)[${index_tmp}]));') + } else { + g.write_prepared_var(item_tmp, got_elem_type, got_elem_styp, source_tmp, index_tmp, + true, false) + g.write('${expected_elem_styp} ${converted_tmp} = ') + g.expr_with_cast(ast.Ident{ + name: item_tmp + }, got_elem_type, expected_elem_type) + g.writeln(';') + } + g.writeln('builtin__array_push${noscan}((array*)&${result_tmp}, &${converted_tmp});') + g.indent-- + g.writeln('}') + g.write(stmt_str) + g.write(result_tmp) +} + // use instead of expr() when you need to cast to a different type fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_type ast.Type) { got_type := ast.mktyp(got_type_raw) @@ -3815,6 +3870,12 @@ fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_typ g.expr(expr) return } + if !got_is_ptr && !expected_is_ptr && !got_type_raw.has_flag(.shared_f) + && !expected_type.has_flag(.shared_f) + && g.table.can_implicit_array_cast(got_type, expected_type) { + g.expr_with_array_element_upcast(expr, got_type, expected_type) + return + } if got_sym.info !is ast.Interface && exp_sym.info is ast.Interface && got_type.idx() != expected_type.idx() && !expected_type.has_flag(.result) { if expr is ast.StructInit && !got_type.is_ptr() { diff --git a/vlib/v/tests/array_variant_arg_upcast_test.v b/vlib/v/tests/array_variant_arg_upcast_test.v new file mode 100644 index 000000000..b1d6fa6df --- /dev/null +++ b/vlib/v/tests/array_variant_arg_upcast_test.v @@ -0,0 +1,59 @@ +// vtest flance: -Wfatal-errors + +type AnimalArg = CatArg | DogArg + +struct CatArg { + name string +} + +struct DogArg { + name string +} + +fn animal_arg_names(animals []AnimalArg) []string { + mut names := []string{cap: animals.len} + for animal in animals { + names << animal.name + } + return names +} + +fn test_array_of_sumtype_variant_is_accepted_as_argument() { + cats := [ + CatArg{ + name: 'Kitty' + }, + CatArg{ + name: 'Misty' + }, + ] + assert animal_arg_names(cats) == ['Kitty', 'Misty'] +} + +interface NamedArg { + name() string +} + +struct BirdArg { + name_value string +} + +fn (b BirdArg) name() string { + return b.name_value +} + +fn named_arg_names(items []NamedArg) []string { + return items.map(it.name()) +} + +fn test_array_of_interface_implementor_is_accepted_as_argument() { + birds := [ + BirdArg{ + name_value: 'Kiwi' + }, + BirdArg{ + name_value: 'Mango' + }, + ] + assert named_arg_names(birds) == ['Kiwi', 'Mango'] +} -- 2.39.5