From 20b913c45ea8265e7635d6dcd130b429811f5a56 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 23:23:17 +0300 Subject: [PATCH] checker: fix implicit cast inconsistency (fixes #26759) --- vlib/v/ast/table.v | 29 +++++++++- vlib/v/checker/checker.v | 16 +++--- vlib/v/gen/c/cgen.v | 56 ++++++++----------- ...interface_embedding_implicit_upcast_test.v | 18 ++++-- 4 files changed, 72 insertions(+), 47 deletions(-) diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 2f9543181..2a9790692 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -1721,6 +1721,30 @@ pub fn (mut t Table) bitsize_to_type(bit_size int) Type { } } +pub fn (t &Table) interface_inherits_interface(typ Type, inter_typ Type) bool { + if typ.idx() == inter_typ.idx() { + return true + } + sym := t.sym(typ) + if sym.kind != .interface || sym.info !is Interface { + return false + } + info := sym.info as Interface + for embed in info.embeds { + if embed.idx() == inter_typ.idx() || t.interface_inherits_interface(embed, inter_typ) { + return true + } + } + if info.parent_type != 0 && info.parent_type.idx() != 0 && info.parent_type != typ { + parent_sym := t.sym(info.parent_type) + if parent_sym.kind == .interface + && t.interface_inherits_interface(info.parent_type, inter_typ) { + return true + } + } + return false +} + pub fn (t &Table) does_type_implement_interface(typ Type, inter_typ Type) bool { if typ.idx() == inter_typ.idx() { // same type -> already casted to the interface @@ -1741,7 +1765,8 @@ pub fn (t &Table) does_type_implement_interface(typ Type, inter_typ Type) bool { } } mut inter_sym := t.sym(inter_typ) - if sym.kind == .interface && inter_sym.kind == .interface { + is_interface_upcast := sym.kind == .interface && inter_sym.kind == .interface + if is_interface_upcast && !t.interface_inherits_interface(typ, inter_typ) { return false } if mut inter_sym.info is Interface { @@ -1801,7 +1826,7 @@ pub fn (t &Table) does_type_implement_interface(typ Type, inter_typ Type) bool { } return false } - if typ != voidptr_type && typ != nil_type && typ != none_type + if !is_interface_upcast && typ != voidptr_type && typ != nil_type && typ != none_type && !inter_sym.info.types.contains(typ) { inter_sym.info.types << typ } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 500c7f21c..a2c4eae4f 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1553,13 +1553,12 @@ fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos to // `none` "implements" the Error interface return true } - if typ_sym.kind == .interface && inter_sym.kind == .interface && !styp.starts_with('JS.') - && !inter_sym.name.starts_with('JS.') { - if c.interface_embeds_interface(utyp, interface_type) { - return true - } + 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, interface_type) { c.error('cannot implement interface `${inter_sym.name}` with a different interface `${styp}`', pos) + return false } imethods := if inter_sym.kind == .interface { (inter_sym.info as ast.Interface).methods @@ -1620,8 +1619,8 @@ fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos to pos) } } - if utyp != ast.voidptr_type && utyp != ast.nil_type && utyp != ast.none_type - && !inter_sym.info.types.contains(utyp) { + if !is_interface_upcast && utyp != ast.voidptr_type && utyp != ast.nil_type + && utyp != ast.none_type && !inter_sym.info.types.contains(utyp) { inter_sym.info.types << utyp } if !inter_sym.info.types.contains(ast.voidptr_type) { @@ -4144,6 +4143,9 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { } } } else { + if from_sym.kind == .interface && to_sym.kind == .interface { + return to_type + } ft := c.table.type_to_str(from_type) tt := c.table.type_to_str(to_type) c.error('`${ft}` does not implement interface `${tt}`, cannot cast `${ft}` to interface `${tt}`', diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 3979a19ef..cdcc5d52b 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -3278,6 +3278,27 @@ fn (mut g Gen) expr_with_fixed_array(expr ast.Expr, got_type_raw ast.Type, expec return tmp_var } +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) + g.write('I_${expr_type_sym.cname}_as_I_${to_sym.cname}(') + if expr_type.is_ptr() { + g.write('*') + } + g.expr(expr) + g.write(')') + + mut info := expr_type_sym.info as ast.Interface + lock info.conversions { + if to_type !in info.conversions { + left_variants := g.table.iface_types[expr_type_sym.name] + right_variants := g.table.iface_types[to_sym.name] + info.conversions[to_type] = left_variants.filter(it in right_variants) + } + } + expr_type_sym.info = info +} + // 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) @@ -3343,23 +3364,7 @@ fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_typ if got_sym.info is ast.Interface && exp_sym.info is ast.Interface && got_type.idx() != expected_type.idx() && !got_type.has_flag(.shared_f) && !expected_type.has_flag(.shared_f) { - g.write('I_${got_sym.cname}_as_I_${exp_sym.cname}(') - if got_type.is_ptr() { - g.write('*') - } - g.expr(expr) - g.write(')') - - mut got_interface_sym := g.table.sym(got_type) - mut info := got_interface_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) - } - } - got_interface_sym.info = info + g.gen_interface_to_interface_conversion(expr, got_type, expected_type) return } // cast to sum type @@ -8295,22 +8300,7 @@ 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.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 - lock info.conversions { - if node.typ !in info.conversions { - left_variants := g.table.iface_types[expr_type_sym.name] - right_variants := g.table.iface_types[sym.name] - info.conversions[node.typ] = left_variants.filter(it in right_variants) - } - } - expr_type_sym.info = info + g.gen_interface_to_interface_conversion(node.expr, node.expr_type, node.typ) } 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/tests/interfaces/interface_embedding_implicit_upcast_test.v b/vlib/v/tests/interfaces/interface_embedding_implicit_upcast_test.v index 071bad0bb..b7ed94524 100644 --- a/vlib/v/tests/interfaces/interface_embedding_implicit_upcast_test.v +++ b/vlib/v/tests/interfaces/interface_embedding_implicit_upcast_test.v @@ -16,18 +16,26 @@ fn greet(x Base) bool { return x is Empty } +fn pass_through(x Solid) Base { + return x +} + fn test_interface_embedding_implicit_upcast() { solid := Solid(Empty{}) + assert greet(Empty{}) assert greet(solid) - - mut base := Base(Empty{}) - base = solid + assert greet(Solid(Empty{})) + base := pass_through(solid) assert base is Empty + + mut base2 := Base(Empty{}) + base2 = solid + assert base2 is Empty assert (solid as Base) is Empty dense := Dense(Empty{}) assert greet(dense) - base = dense - assert base is Empty + base2 = dense + assert base2 is Empty assert (dense as Base) is Empty } -- 2.39.5