From df9f1d48760ef7de1cf19ef1ee989d20c0fb9ff8 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 15 Apr 2026 15:49:00 +0300 Subject: [PATCH] cgen: fix interface deep embedded fields bug (fixes #17667) --- vlib/v/gen/c/cgen.v | 71 +++++++++++-------- ...ace_deep_embedded_fields_regression_test.v | 45 ++++++++++++ 2 files changed, 85 insertions(+), 31 deletions(-) create mode 100644 vlib/v/tests/interfaces/interface_deep_embedded_fields_regression_test.v diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 71aa21182..6068aaaa5 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -10785,6 +10785,44 @@ fn (mut g Gen) register_auto_str_for_interfaces() { } } +fn (mut g Gen) interface_field_ptr_expr(st ast.Type, cctype string, field ast.StructField) string { + cname := c_name(field.name) + field_styp := g.styp(field.typ) + resolved_st_sym := g.table.final_sym(st) + if _ := resolved_st_sym.find_field(field.name) { + return '(${field_styp}*)((char*)x + __offsetof_ptr(x, ${cctype}, ${cname}))' + } + if resolved_st_sym.kind == .array + && field.name in ['element_size', 'data', 'offset', 'len', 'cap', 'flags'] { + // Array interfaces expose these common fields directly in C. + return '(${field_styp}*)((char*)x + __offsetof_ptr(x, ${cctype}, ${cname}))' + } + if st in [ast.voidptr_type, ast.nil_type] { + return '0' + } + if resolved_st_sym.info is ast.Struct { + if _, embeds := g.table.find_field_from_embeds(resolved_st_sym, field.name) { + mut ptr_expr := strings.new_builder(100) + ptr_expr.write_string('(${field_styp}*)((char*)x') + mut typ_name := '' + for i, embed in embeds { + esym := g.table.sym(embed) + if i == 0 { + ptr_expr.write_string(' + __offsetof_ptr(x, ${cctype}, ${esym.embed_name()})') + } else { + ptr_expr.write_string(' + __offsetof_ptr(x, ${typ_name}, ${esym.embed_name()})') + } + typ_name = esym.cname + } + ptr_expr.write_string(' + __offsetof_ptr(x, ${typ_name}, ${cname}))') + return ptr_expr.str() + } + } + g.checker_bug('interface field `${field.name}` is not reachable in `${resolved_st_sym.name}` during cast generation', + field.pos) + return '0' +} + // Generates interface table and interface indexes fn (mut g Gen) interface_table() string { util.timing_start(@METHOD) @@ -10908,37 +10946,8 @@ fn (mut g Gen) interface_table() string { if cctype == cctype2 { for field in inter_info.fields { cname := c_name(field.name) - field_styp := g.styp(field.typ) - if _ := st_sym.find_field(field.name) { - cast_struct.writeln('\t\t.${cname} = (${field_styp}*)((char*)x + __offsetof_ptr(x, ${cctype2}, ${cname})),') - } else if st_sym.kind == .array - && field.name in ['element_size', 'data', 'offset', 'len', 'cap', 'flags'] { - // Manually checking, we already knows array contains above fields - cast_struct.writeln('\t\t.${cname} = (${field_styp}*)((char*)x + __offsetof_ptr(x, ${cctype2}, ${cname})),') - } else { - // the field is embedded in another struct - cast_struct.write_string('\t\t.${cname} = (${field_styp}*)((char*)x') - if st != ast.voidptr_type && st != ast.nil_type { - if st_sym.kind == .struct { - if _, embeds := g.table.find_field_from_embeds(st_sym, field.name) { - mut typ_name := '' - for i, embed in embeds { - esym := g.table.sym(embed) - if i == 0 { - cast_struct.write_string(' + __offsetof_ptr(x, ${cctype}, ${esym.embed_name()})') - } else { - cast_struct.write_string(' + __offsetof_ptr(x, ${typ_name}, ${esym.embed_name()})') - } - typ_name = esym.cname - } - if embeds.len > 0 { - cast_struct.write_string(' + __offsetof_ptr(x, ${typ_name}, ${cname})') - } - } - } - } - cast_struct.writeln('),') - } + cast_struct.writeln('\t\t.${cname} = ${g.interface_field_ptr_expr(st, cctype, + field)},') } } cast_struct.write_string('\t}') diff --git a/vlib/v/tests/interfaces/interface_deep_embedded_fields_regression_test.v b/vlib/v/tests/interfaces/interface_deep_embedded_fields_regression_test.v new file mode 100644 index 000000000..11ebc86b9 --- /dev/null +++ b/vlib/v/tests/interfaces/interface_deep_embedded_fields_regression_test.v @@ -0,0 +1,45 @@ +interface ILevel { +mut: + uid int + parent int +} + +struct Level0 { +mut: + uid int + parent int + x f32 +} + +struct Level1 { + Level0 +mut: + y f32 +} + +struct Level2 { + Level1 +mut: + z f32 +} + +type AliasLevel2 = Level2 + +fn mutate_level(mut level ILevel) { + level.uid = 11 + level.parent = 22 +} + +fn test_interface_cast_with_deeply_embedded_fields() { + mut level := Level2{} + mutate_level(mut level) + assert level.uid == 11 + assert level.parent == 22 +} + +fn test_interface_cast_with_deeply_embedded_fields_through_alias() { + mut level := AliasLevel2{} + mutate_level(mut level) + assert level.uid == 11 + assert level.parent == 22 +} -- 2.39.5