From aba7e450f5aa925a82a122e4e266fe8cc5039a20 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 11 Mar 2026 12:55:58 +0300 Subject: [PATCH] checker: error: field does not exist with embedded structure and sumtype variants (fixes #11290) --- vlib/v/ast/table.v | 35 ++++++++ vlib/v/checker/checker.v | 89 +++++++++++++------ .../tests/incorrect_smartcast2_err.out | 14 --- .../checker/tests/incorrect_smartcast2_err.vv | 30 ------- .../tests/match_sumtype_multiple_types.out | 14 --- .../builtin_arrays/array_of_sumtypes_test.v | 37 ++++++++ 6 files changed, 132 insertions(+), 87 deletions(-) delete mode 100644 vlib/v/checker/tests/incorrect_smartcast2_err.out delete mode 100644 vlib/v/checker/tests/incorrect_smartcast2_err.vv diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index e38ee344c..2a8fd46e7 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -762,6 +762,41 @@ pub fn (t &Table) resolve_common_sumtype_fields(mut sym TypeSymbol) { sym.info = info } +// find_single_field_variant returns a field that exists in exactly one aggregate or sumtype variant. +pub fn (t &Table) find_single_field_variant(sym &TypeSymbol, field_name string) !(Type, StructField, []Type) { + variants := match sym.info { + Aggregate { sym.info.types } + SumType { sym.info.variants } + else { []Type{} } + } + if variants.len == 0 { + return error('') + } + mut found_variant := Type(0) + mut found_field := StructField{} + mut found_embed_types := []Type{} + for variant in variants { + variant_sym := t.final_sym(variant) + mut field := StructField{} + mut embed_types := []Type{} + if f := t.find_field(variant_sym, field_name) { + field = f + } else { + field, embed_types = t.find_field_from_embeds(variant_sym, field_name) or { continue } + } + if found_variant != 0 { + return error('') + } + found_variant = variant + found_field = field + found_embed_types = embed_types.clone() + } + if found_variant == 0 { + return error('') + } + return found_variant, found_field, found_embed_types +} + @[inline] pub fn (t &Table) find_type(name string) Type { return idx_to_type(t.type_idxs[name]) diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index af527216b..b4cfb4af8 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2085,8 +2085,8 @@ fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { } } field_name := node.field_name - sym := c.table.sym(typ) - final_sym := c.table.final_sym(typ) + mut sym := c.table.sym(typ) + mut final_sym := c.table.final_sym(typ) if (typ.has_flag(.variadic) || final_sym.kind == .array_fixed) && field_name == 'len' { node.typ = ast.int_type return ast.int_type @@ -2103,10 +2103,11 @@ fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { mut unknown_field_msg := '' mut has_field := false mut field := ast.StructField{} - if field_name.len > 0 && sym.info is ast.Struct && sym.language == .v + if field_name.len > 0 && final_sym.kind == .struct && sym.language == .v && field_name[0].is_capital() { // x.Foo.y => access the embedded struct - for embed in sym.info.embeds { + struct_info := final_sym.info as ast.Struct + for embed in struct_info.embeds { embed_sym := c.table.sym(embed) if embed_sym.embed_name() == field_name { node.typ = embed @@ -2118,34 +2119,63 @@ fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { has_field = true field = f } else { + field_err := err // look for embedded field - has_field = true - mut embed_types := []ast.Type{} - field, embed_types = c.table.find_field_from_embeds(sym, field_name) or { - if err.msg() != '' { - c.error(err.msg(), node.pos) - } + if field_, embed_types := c.table.find_field_from_embeds(sym, field_name) { + has_field = true + field = field_ + node.from_embed_types = embed_types + } else { + first_err := err has_field = false - ast.StructField{}, []ast.Type{} - } - node.from_embed_types = embed_types - if sym.kind in [.aggregate, .sum_type] { - unknown_field_msg = err.msg() - // TODO need a better way to check that we need to display sum type variants info - if unknown_field_msg.contains('does not exist or have the same type in all sumtype') { - info := sym.info as ast.SumType - missing_variants := c.table.find_missing_variants(info, field_name) - unknown_field_msg += missing_variants + if final_sym.kind in [.aggregate, .sum_type] { + if variant_typ, variant_field, variant_embed_types := c.table.find_single_field_variant(final_sym, + field_name) + { + old_expr := node.expr + node.expr = ast.ParExpr{ + expr: ast.AsCast{ + expr: old_expr + typ: variant_typ + expr_type: typ + pos: old_expr.pos() + } + } + typ = variant_typ + sym = c.table.sym(typ) + final_sym = c.table.final_sym(typ) + node.expr_type = typ + field = variant_field + node.from_embed_types = variant_embed_types + has_field = true + } + } + if !has_field && final_sym.kind in [.aggregate, .sum_type] { + unknown_field_msg = if field_err.msg() != '' { + field_err.msg() + } else { + first_err.msg() + } + // TODO need a better way to check that we need to display sum type variants info + if final_sym.kind == .sum_type + && unknown_field_msg.contains('does not exist or have the same type in all sumtype') + && !unknown_field_msg.contains('variants: ') { + info := final_sym.info as ast.SumType + missing_variants := c.table.find_missing_variants(info, field_name) + unknown_field_msg += missing_variants + } + } + if !has_field && first_err.msg() != '' { + c.error(first_err.msg(), node.pos) } } } - if !c.inside_unsafe { - if sym.info is ast.Struct { - if sym.info.is_union && node.next_token !in token.assign_tokens { - if !c.pref.translated && !c.file.is_translated { - c.warn('reading a union field (or its address) requires `unsafe`', - node.pos) - } + if !c.inside_unsafe && final_sym.kind == .struct { + struct_info := final_sym.info as ast.Struct + if struct_info.is_union && node.next_token !in token.assign_tokens { + if !c.pref.translated && !c.file.is_translated { + c.warn('reading a union field (or its address) requires `unsafe`', + node.pos) } } } @@ -2260,12 +2290,13 @@ fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { } unknown_field_msg = 'type `${sym.name}` has no field named `${field_name}`' } - if sym.info is ast.Struct { + if final_sym.kind == .struct { if c.smartcast_mut_pos != token.Pos{} { c.note('smartcasting requires either an immutable value, or an explicit mut keyword before the value', c.smartcast_mut_pos) } - suggestion := util.new_suggestion(field_name, sym.info.fields.map(it.name)) + struct_info := final_sym.info as ast.Struct + suggestion := util.new_suggestion(field_name, struct_info.fields.map(it.name)) c.error(suggestion.say(unknown_field_msg), node.pos) return ast.void_type } diff --git a/vlib/v/checker/tests/incorrect_smartcast2_err.out b/vlib/v/checker/tests/incorrect_smartcast2_err.out deleted file mode 100644 index 781f2cb6d..000000000 --- a/vlib/v/checker/tests/incorrect_smartcast2_err.out +++ /dev/null @@ -1,14 +0,0 @@ -vlib/v/checker/tests/incorrect_smartcast2_err.vv:24:17: error: field `error` does not exist or have the same type in these sumtype `Either[int, int]` variants: Right[int] - 22 | match v[0] { - 23 | Left[int] { - 24 | println(v[0].error) - | ~~~~~ - 25 | } - 26 | else {} -vlib/v/checker/tests/incorrect_smartcast2_err.vv:24:4: error: `println` can not print void expressions - 22 | match v[0] { - 23 | Left[int] { - 24 | println(v[0].error) - | ~~~~~~~~~~~~~~~~~~~ - 25 | } - 26 | else {} diff --git a/vlib/v/checker/tests/incorrect_smartcast2_err.vv b/vlib/v/checker/tests/incorrect_smartcast2_err.vv deleted file mode 100644 index 05293cc85..000000000 --- a/vlib/v/checker/tests/incorrect_smartcast2_err.vv +++ /dev/null @@ -1,30 +0,0 @@ -struct Left[E] { - error E -} - -struct Right[T] { - inner T -} - -type Either[T, E] = Left[E] | Right[T] - -fn works(v []Either[int, int]) { - first := v[0] - match first { - Left[int] { - println(first.error) - } - else {} - } -} - -fn doesntwork(v []Either[int, int]) { - match v[0] { - Left[int] { - println(v[0].error) - } - else {} - } -} - -fn main() {} diff --git a/vlib/v/checker/tests/match_sumtype_multiple_types.out b/vlib/v/checker/tests/match_sumtype_multiple_types.out index a156c11e1..0a84394db 100644 --- a/vlib/v/checker/tests/match_sumtype_multiple_types.out +++ b/vlib/v/checker/tests/match_sumtype_multiple_types.out @@ -1,17 +1,3 @@ -vlib/v/checker/tests/match_sumtype_multiple_types.vv:26:13: error: type `Charlie` has no field or method `char` - 24 | match l { - 25 | Alfa, Charlie { - 26 | assert l.char == `a` - | ~~~~ - 27 | assert l.letter() == 'a' - 28 | } -vlib/v/checker/tests/match_sumtype_multiple_types.vv:26:11: error: assert can be used only with `bool` expressions, but found `void` instead - 24 | match l { - 25 | Alfa, Charlie { - 26 | assert l.char == `a` - | ~~~~~~~~~~~~~ - 27 | assert l.letter() == 'a' - 28 | } vlib/v/checker/tests/match_sumtype_multiple_types.vv:27:13: error: unknown method: `Charlie.letter` 25 | Alfa, Charlie { 26 | assert l.char == `a` diff --git a/vlib/v/tests/builtin_arrays/array_of_sumtypes_test.v b/vlib/v/tests/builtin_arrays/array_of_sumtypes_test.v index b8280d95d..f80bd0ff5 100644 --- a/vlib/v/tests/builtin_arrays/array_of_sumtypes_test.v +++ b/vlib/v/tests/builtin_arrays/array_of_sumtypes_test.v @@ -9,6 +9,16 @@ struct Text { type Content = Node | Text +struct Left { + error string +} + +struct Right { + value int +} + +type Either = Left | Right + fn test_push_prepend_insert() { mut body := Node{} body.content << Node{ @@ -18,3 +28,30 @@ fn test_push_prepend_insert() { body.content.insert(1, Node{ tag: 'c' }) assert body.content.len == 3 } + +fn test_nested_selector_access_on_sumtype_array_elements() { + mut body := Node{ + tag: 'body' + } + body.content << Node{ + tag: 'p' + } + body.content[0].content << Node{ + tag: 'i' + } + body.content[0].content[0].content << Node{ + tag: 'j' + } + body.content[0].content[0].content[0].content << Node{ + tag: 'k' + } + body.content[0].content[0].content[0].content[0].content << Text{} + assert ((body.content[0] as Node).content[0] as Node).content.len == 1 +} + +fn test_selector_access_on_indexed_sumtype_variants() { + items := [Either(Left{ + error: 'boom' + })] + assert items[0].error == 'boom' +} -- 2.39.5