From d5074ec579da71e86751be8cbc0c1fd49e1b7834 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 16:42:17 +0300 Subject: [PATCH] checker: fix cannot implement empty interface with different interface (fixes #24193) --- vlib/v/ast/table.v | 12 +++++-- vlib/v/checker/checker.v | 35 ++++++++++++++++++- vlib/v/gen/c/cgen.v | 26 ++++++++++---- vlib/v/gen/c/infix.v | 5 ++- .../empty_interface_ierror_or_block_test.v | 18 ++++++++++ 5 files changed, 82 insertions(+), 14 deletions(-) create mode 100644 vlib/v/tests/interfaces/empty_interface_ierror_or_block_test.v diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 8669738b0..9a26685a4 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -1996,9 +1996,15 @@ pub fn (t &Table) does_type_implement_interface(typ Type, inter_typ Type) bool { } } mut inter_sym := t.sym(inter_typ) - is_interface_upcast := sym.kind == .interface && inter_sym.kind == .interface - if is_interface_upcast && !t.interface_inherits_interface(typ, inter_typ) { - return false + if sym.kind == .interface && inter_sym.kind == .interface { + inter_info := inter_sym.info as Interface + if inter_info.methods.len == 0 && inter_info.fields.len == 0 + && inter_info.embeds.len == 0 { + return true + } + if !t.interface_inherits_interface(typ, inter_typ) { + return false + } } if mut inter_sym.info is Interface { attrs := unsafe { t.interfaces[inter_typ].attrs } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index e194cbb9d..4b617d5d7 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1816,7 +1816,40 @@ fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos to } is_interface_upcast := typ_sym.kind == .interface && inter_sym.kind == .interface && !styp.starts_with('JS.') && !inter_sym.name.starts_with('JS.') - if is_interface_upcast && !c.table.interface_inherits_interface(utyp, resolved_interface_type) { + if is_interface_upcast { + if mut inter_sym.info is ast.Interface { + if inter_sym.info.methods.len == 0 && inter_sym.info.fields.len == 0 + && inter_sym.info.embeds.len == 0 { + // Preserve the source interface's known variants so the empty interface keeps + // the original dynamic type for later `is`/`as` checks and conversions. + mut source_types := []ast.Type{} + match typ_sym.info { + ast.Interface { + source_types = typ_sym.info.types.clone() + } + else {} + } + mut target_variants := c.table.iface_types[inter_sym.name] + for variant in source_types { + variant_sym := c.table.sym(ast.mktyp(variant)) + if variant in [ast.nil_type, ast.none_type] || variant_sym.kind == .interface { + continue + } + if !inter_sym.info.types.contains(variant) { + inter_sym.info.types << variant + } + if variant !in target_variants { + target_variants << variant + } + } + c.table.iface_types[inter_sym.name] = target_variants + if !inter_sym.info.types.contains(ast.voidptr_type) { + inter_sym.info.types << ast.voidptr_type + } + return true + } + } + if !c.table.interface_inherits_interface(utyp, resolved_interface_type) { c.error('cannot implement interface `${inter_sym.name}` with a different interface `${styp}`', pos) return false diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 25a6f029e..4cd7ef9d1 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -3777,17 +3777,15 @@ fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_typ } g.expr(expr) g.write(')') - mut info := got_sym.info as ast.Interface + exp_info := exp_sym.info as ast.Interface lock info.conversions { if expected_type !in info.conversions { - left_variants := g.table.iface_types[got_sym.name] - right_variants := g.table.iface_types[exp_sym.name] - info.conversions[expected_type] = left_variants.filter(it in right_variants) + info.conversions[expected_type] = info.types.filter(it in exp_info.types) } } - mut muttable := unsafe { &ast.Table(g.table) } - muttable.type_symbols[got_type.idx()].info = info + mut got_interface_sym := g.table.sym(got_type) + got_interface_sym.info = info return } if got_sym.info !is ast.Interface && exp_sym.info is ast.Interface @@ -9861,7 +9859,21 @@ fn (mut g Gen) as_cast(node ast.AsCast) { g.as_cast_type_names[idx] = variant_sym.name } } else if expr_type_sym.kind == .interface && sym.kind == .interface { - g.gen_interface_to_interface_conversion(node.expr, node.expr_type, node.typ) + g.write('I_${expr_type_sym.cname}_as_I_${sym.cname}(') + if node.expr_type.is_ptr() { + g.write('*') + } + g.expr(node.expr) + g.write(')') + + mut info := expr_type_sym.info as ast.Interface + sym_info := sym.info as ast.Interface + lock info.conversions { + if node.typ !in info.conversions { + info.conversions[node.typ] = info.types.filter(it in sym_info.types) + } + } + expr_type_sym.info = info } else if mut expr_type_sym.info is ast.Interface && node.expr_type != node.typ { dot := if node.expr_type.is_ptr() { '->' } else { '.' } if node.expr.has_fn_call() && !g.is_cc_msvc { diff --git a/vlib/v/gen/c/infix.v b/vlib/v/gen/c/infix.v index 07ccd4504..e8b0478c3 100644 --- a/vlib/v/gen/c/infix.v +++ b/vlib/v/gen/c/infix.v @@ -1113,11 +1113,10 @@ fn (mut g Gen) gen_interface_is_op(node ast.InfixExpr) { right_sym := g.table.sym(node.right_type) mut info := left_sym.info as ast.Interface + right_info := right_sym.info as ast.Interface lock info.conversions { common_variants := info.conversions[node.right_type] or { - left_variants := g.table.iface_types[left_sym.name] - right_variants := g.table.iface_types[right_sym.name] - c := left_variants.filter(it in right_variants) + c := info.types.filter(it in right_info.types) info.conversions[node.right_type] = c c } diff --git a/vlib/v/tests/interfaces/empty_interface_ierror_or_block_test.v b/vlib/v/tests/interfaces/empty_interface_ierror_or_block_test.v new file mode 100644 index 000000000..783ce7571 --- /dev/null +++ b/vlib/v/tests/interfaces/empty_interface_ierror_or_block_test.v @@ -0,0 +1,18 @@ +interface EverythingImplementsThis {} + +fn throw_error() ! { + return error('bro') +} + +fn test_empty_interface_accepts_ierror_in_or_block_map_assignment() { + mut res := map[int]EverythingImplementsThis{} + for i := 0; i < 10; i++ { + throw_error() or { res[i] = err } + } + assert res.len == 10 + for i := 0; i < 10; i++ { + assert res[i] is IError + converted := res[i] as IError + assert converted.msg() == 'bro' + } +} -- 2.39.5