From d48d789285f1311ec69eec318bb98974342ebd63 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Mon, 8 Jun 2026 23:52:29 +0300 Subject: [PATCH] cgen: don't unwrap option struct fields inside interface casts Commit 62ec38ab4 (#27352) added `inside_interface_cast`, which leaked into struct-init field value codegen. When an `&Struct{...}` value with an option pointer field was converted to an interface, the option field was wrongly unwrapped (`field = *(T**)val.data`), emitting invalid C: `assigning to '_option_..._ptr' from incompatible type 'T*'`. A field value's option-ness depends on the field's own type, not on the surrounding cast, so reset `inside_interface_cast` while generating struct-init field values (mirroring `inside_cast_in_heap`). Fixes building vlang/v-analyzer (analyzer/psi/element_factory.v). --- vlib/v/gen/c/struct.v | 7 +++ .../option_ptr_field_in_interface_cast_test.v | 53 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 vlib/v/tests/options/option_ptr_field_in_interface_cast_test.v diff --git a/vlib/v/gen/c/struct.v b/vlib/v/gen/c/struct.v index 9a45d0926..55927e782 100644 --- a/vlib/v/gen/c/struct.v +++ b/vlib/v/gen/c/struct.v @@ -1089,6 +1089,12 @@ fn (mut g Gen) struct_init_field_value(sfield ast.StructInitField) { if !cloned { inside_cast_in_heap := g.inside_cast_in_heap g.inside_cast_in_heap = 0 // prevent use of pointers in child structs + // A field value's option-ness is governed by the field's own type, not by an + // outer `&Struct{...}` -> interface conversion. Without resetting this, an + // option field initialized from an option value gets wrongly unwrapped while + // `inside_interface_cast` is still set from the surrounding cast. + inside_interface_cast := g.inside_interface_cast + g.inside_interface_cast = false field_unwrap_typ := g.unwrap_generic(sfield.typ) field_unwrap_sym := g.table.final_sym(field_unwrap_typ) @@ -1151,6 +1157,7 @@ fn (mut g Gen) struct_init_field_value(sfield ast.StructInitField) { g.struct_init_field_default(field_unwrap_typ, sfield, field_unwrap_sym) } g.inside_cast_in_heap = inside_cast_in_heap // restore value for further struct inits + g.inside_interface_cast = inside_interface_cast } } diff --git a/vlib/v/tests/options/option_ptr_field_in_interface_cast_test.v b/vlib/v/tests/options/option_ptr_field_in_interface_cast_test.v new file mode 100644 index 000000000..457de949e --- /dev/null +++ b/vlib/v/tests/options/option_ptr_field_in_interface_cast_test.v @@ -0,0 +1,53 @@ +// Regression test: an option pointer struct field must be copied (not unwrapped) +// when an `&Struct{...}` value containing it is converted to an interface. +// Previously `inside_interface_cast` leaked into the struct field value codegen, +// emitting `field = *(T**)val.data` for an option field, producing invalid C +// (`assigning to _option_..._ptr from incompatible type T*`). +// This broke building vlang/v-analyzer (analyzer/psi/element_factory.v). + +interface Element { + id() int + file_name() string +} + +@[heap] +struct File { + name string +} + +@[heap] +struct Node { + id_val int + containing_file ?&File +} + +fn (n &Node) id() int { + return n.id_val +} + +fn (n &Node) file_name() string { + f := n.containing_file or { return '' } + return f.name +} + +fn make_node(id_val int, containing_file ?&File) Element { + return &Node{ + id_val: id_val + containing_file: containing_file + } +} + +fn test_option_field_preserved_through_interface_cast() { + f := &File{ + name: 'main.v' + } + e := make_node(7, f) + assert e.id() == 7 + assert e.file_name() == 'main.v' +} + +fn test_none_option_field_through_interface_cast() { + e := make_node(9, none) + assert e.id() == 9 + assert e.file_name() == '' +} -- 2.39.5