diff --git a/doc/docs.md b/doc/docs.md index 15af611430..5c05dc3569 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -1209,7 +1209,7 @@ mut: struct Button { Widget - title string + title string } mut button := Button{ diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 29e1efaa76..522edcd823 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -103,16 +103,17 @@ pub: // `foo.bar` pub struct SelectorExpr { pub: - pos token.Position - expr Expr // expr.field_name - field_name string - is_mut bool // is used for the case `if mut ident.selector is MyType {`, it indicates if the root ident is mutable - mut_pos token.Position + pos token.Position + expr Expr // expr.field_name + field_name string + is_mut bool // is used for the case `if mut ident.selector is MyType {`, it indicates if the root ident is mutable + mut_pos token.Position pub mut: - expr_type table.Type // type of `Foo` in `Foo.bar` - typ table.Type // type of the entire thing (`Foo.bar`) - name_type table.Type // T in `T.name` or typeof in `typeof(expr).name` - scope &Scope + expr_type table.Type // type of `Foo` in `Foo.bar` + typ table.Type // type of the entire thing (`Foo.bar`) + name_type table.Type // T in `T.name` or typeof in `typeof(expr).name` + scope &Scope + from_embed_type table.Type // holds the type of the embed that the method is called from } // root_ident returns the origin ident where the selector started. @@ -144,7 +145,6 @@ pub: has_default_expr bool attrs []table.Attr is_public bool - is_embed bool pub mut: name string typ table.Type @@ -192,10 +192,17 @@ pub: is_union bool attrs []table.Attr end_comments []Comment + embeds []Embed pub mut: fields []StructField } +pub struct Embed { +pub: + typ table.Type + pos token.Position +} + pub struct StructEmbedding { pub: name string @@ -225,6 +232,18 @@ pub mut: expected_type table.Type } +pub struct StructInitEmbed { +pub: + expr Expr + pos token.Position + comments []Comment + next_comments []Comment +pub mut: + name string + typ table.Type + expected_type table.Type +} + pub struct StructInit { pub: pos token.Position @@ -233,6 +252,7 @@ pub: pub mut: typ table.Type fields []StructInitField + embeds []StructInitEmbed } // import statement @@ -320,6 +340,7 @@ pub mut: generic_list_pos token.Position free_receiver bool // true if the receiver expression needs to be freed scope &Scope + from_embed_type table.Type // holds the type of the embed that the method is called from } /* diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 7cf5e4f7f9..86bc116731 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -371,27 +371,17 @@ pub fn (mut c Checker) struct_decl(decl ast.StructDecl) { } mut struct_sym := c.table.find_type(decl.name) or { table.TypeSymbol{} } if mut struct_sym.info is table.Struct { + for embed in decl.embeds { + embed_sym := c.table.get_type_symbol(embed.typ) + if embed_sym.kind != .struct_ { + c.error('`$embed_sym.name` is not a struct', embed.pos) + } + } for i, field in decl.fields { - if decl.language == .v && !field.is_embed { + if decl.language == .v { c.check_valid_snake_case(field.name, 'field name', field.pos) } sym := c.table.get_type_symbol(field.typ) - if field.is_embed { - if mut sym.info is table.Struct { - for embed_field in sym.info.fields { - already_exists := struct_sym.info.fields.filter(it.name == embed_field.name).len > - 0 - if !already_exists { - struct_sym.info.fields << { - embed_field | - embed_alias_for: field.name - } - } - } - } else { - c.error('`$sym.name` is not a struct', field.pos) - } - } for j in 0 .. i { if field.name == decl.fields[j].name { c.error('field name `$field.name` duplicate', field.pos) @@ -528,6 +518,8 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type { mut inited_fields := []string{} for i, field in struct_init.fields { mut info_field := table.Field{} + mut embed_type := table.Type(0) + mut is_embed := false mut field_name := '' if struct_init.is_short { if i >= info.fields.len { @@ -548,31 +540,17 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type { break } } - /* - if c.pref.is_verbose { - for f in info.fields { - if f.name == field_name { - if f.embed_alias_for.len != 0 { - mut has_embed_init := false - for embedding in struct_init.fields { - if embedding.name == f.embed_alias_for { - has_embed_init = true - } - } - if !has_embed_init { - n := { - f | - embed_alias_for: '' - } - println(field) - // struct_init.fields << { f | embed_alias_for: '' } - } - } + if !exists { + for embed in info.embeds { + embed_sym := c.table.get_type_symbol(embed) + if embed_sym.embed_name() == field_name { + exists = true + embed_type = embed + is_embed = true break } } } - */ if !exists { c.error('unknown field `$field.name` in struct literal of type `$type_sym.name`', field.pos) @@ -584,29 +562,43 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type { continue } } - inited_fields << field_name - field_type_sym := c.table.get_type_symbol(info_field.typ) - c.expected_type = info_field.typ - expr_type := c.expr(field.expr) - expr_type_sym := c.table.get_type_symbol(expr_type) - if field_type_sym.kind == .interface_ { - c.type_implements(expr_type, info_field.typ, field.pos) - } else if expr_type != table.void_type && expr_type_sym.kind != .placeholder { - c.check_expected(expr_type, info_field.typ) or { - c.error('cannot assign to field `$info_field.name`: $err', field.pos) + if is_embed { + c.expected_type = embed_type + expr_type := c.expr(field.expr) + expr_type_sym := c.table.get_type_symbol(expr_type) + if expr_type != table.void_type && expr_type_sym.kind != .placeholder { + c.check_expected(expr_type, embed_type) or { + c.error('cannot assign to field `$info_field.name`: $err', + field.pos) + } } + struct_init.fields[i].typ = expr_type + struct_init.fields[i].expected_type = embed_type + } else { + inited_fields << field_name + field_type_sym := c.table.get_type_symbol(info_field.typ) + c.expected_type = info_field.typ + expr_type := c.expr(field.expr) + expr_type_sym := c.table.get_type_symbol(expr_type) + if field_type_sym.kind == .interface_ { + c.type_implements(expr_type, info_field.typ, field.pos) + } else if expr_type != table.void_type && expr_type_sym.kind != .placeholder { + c.check_expected(expr_type, info_field.typ) or { + c.error('cannot assign to field `$info_field.name`: $err', + field.pos) + } + } + if info_field.typ.is_ptr() && !expr_type.is_ptr() && !expr_type.is_pointer() && + !expr_type.is_number() { + c.error('ref', field.pos) + } + struct_init.fields[i].typ = expr_type + struct_init.fields[i].expected_type = info_field.typ } - if info_field.typ.is_ptr() && !expr_type.is_ptr() && !expr_type.is_pointer() && - !expr_type.is_number() { - c.error('ref', field.pos) - } - struct_init.fields[i].typ = expr_type - struct_init.fields[i].expected_type = info_field.typ } // Check uninitialized refs for field in info.fields { - if field.has_default_expr || field.name in inited_fields || field.embed_alias_for != - '' { + if field.has_default_expr || field.name in inited_fields { continue } if field.typ.is_ptr() && !c.pref.translated { @@ -992,7 +984,23 @@ fn (mut c Checker) fail_if_immutable(expr ast.Expr) (string, token.Position) { match typ_sym.kind { .struct_ { struct_info := typ_sym.info as table.Struct - field_info := struct_info.find_field(expr.field_name) or { + mut has_field := true + mut field_info := struct_info.find_field(expr.field_name) or { + has_field = false + table.Field{} + } + if !has_field { + for embed in struct_info.embeds { + embed_sym := c.table.get_type_symbol(embed) + embed_struct_info := embed_sym.info as table.Struct + if embed_field_info := embed_struct_info.find_field(expr.field_name) { + has_field = true + field_info = embed_field_info + break + } + } + } + if !has_field { type_str := c.table.type_to_str(expr.expr_type) c.error('unknown field `${type_str}.$expr.field_name`', expr.pos) return '', pos @@ -1238,7 +1246,36 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type { c.error('cannot $method_name `$arg_sym.name` to `$left_type_sym.name`', arg_expr.position()) } } - if method := c.table.type_find_method(left_type_sym, method_name) { + mut method := table.Fn{} + mut has_method := false + if m := c.table.type_find_method(left_type_sym, method_name) { + method = m + has_method = true + } else { + if left_type_sym.info is table.Struct { + mut found_methods := []table.Fn{} + mut embed_of_found_methods := []table.Type{} + for embed in left_type_sym.info.embeds { + embed_sym := c.table.get_type_symbol(embed) + if m := c.table.type_find_method(embed_sym, method_name) { + found_methods << m + embed_of_found_methods << embed + } + } + if found_methods.len == 1 { + method = found_methods[0] + has_method = true + call_expr.from_embed_type = embed_of_found_methods[0] + } else if found_methods.len > 1 { + c.error('ambiguous method `$method_name`', call_expr.pos) + } + } + if left_type_sym.kind == .aggregate { + // the error message contains the problematic type + unknown_method_msg = err + } + } + if has_method { if !method.is_pub && !c.is_builtin_mod && !c.pref.is_test && left_type_sym.mod != c.mod && left_type_sym.mod != '' { // method.mod != c.mod { // If a private method is called outside of the module @@ -1337,15 +1374,11 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type { // TODO: cant we just set all these to the concrete type in checker? then no need in gen call_expr.receiver_type = left_type.derive(method.params[0].typ).set_flag(.generic) } else { + // note: correct receiver type is automatically set here on struct embed calls call_expr.receiver_type = method.params[0].typ } call_expr.return_type = method.return_type return method.return_type - } else { - if left_type_sym.kind == .aggregate { - // the error message contains the problematic type - unknown_method_msg = err - } } // TODO: str methods if method_name == 'str' { @@ -1846,7 +1879,47 @@ pub fn (mut c Checker) selector_expr(mut selector_expr ast.SelectorExpr) table.T } } mut unknown_field_msg := 'type `$sym.name` has no field or method `$field_name`' - if field := c.table.struct_find_field(sym, field_name) { + mut has_field := false + mut field := table.Field{} + if field_name.len > 0 && field_name[0].is_capital() && sym.info is table.Struct { + // x.Foo.y => access the embedded struct + sym_info := sym.info as table.Struct + for embed in sym_info.embeds { + embed_sym := c.table.get_type_symbol(embed) + if embed_sym.embed_name() == field_name { + selector_expr.typ = embed + return embed + } + } + } else { + if f := c.table.struct_find_field(sym, field_name) { + has_field = true + field = f + } else { + if sym.info is table.Struct { + mut found_fields := []table.Field{} + mut embed_of_found_fields := []table.Type{} + for embed in sym.info.embeds { + embed_sym := c.table.get_type_symbol(embed) + if f := c.table.struct_find_field(embed_sym, field_name) { + found_fields << f + embed_of_found_fields << embed + } + } + if found_fields.len == 1 { + field = found_fields[0] + has_field = true + selector_expr.from_embed_type = embed_of_found_fields[0] + } else if found_fields.len > 1 { + c.error('ambiguous field `$field_name`', selector_expr.pos) + } + } + if sym.kind == .aggregate { + unknown_field_msg = err + } + } + } + if has_field { if sym.mod != c.mod && !field.is_pub && sym.language != .c { c.error('field `${sym.name}.$field_name` is not public', selector_expr.pos) } @@ -1860,19 +1933,14 @@ pub fn (mut c Checker) selector_expr(mut selector_expr ast.SelectorExpr) table.T } selector_expr.typ = field.typ return field.typ - } else { - if sym.kind == .aggregate { - unknown_field_msg = err - } } if sym.kind !in [.struct_, .aggregate] { if sym.kind != .placeholder { c.error('`$sym.name` is not a struct', selector_expr.pos) } } else { - if sym.kind == .struct_ { - sss := sym.info as table.Struct - suggestion := util.new_suggestion(field_name, sss.fields.map(it.name)) + if sym.info is table.Struct { + suggestion := util.new_suggestion(field_name, sym.info.fields.map(it.name)) c.error(suggestion.say(unknown_field_msg), selector_expr.pos) } c.error(unknown_field_msg, selector_expr.pos) diff --git a/vlib/v/checker/tests/ambiguous_field_method_err.out b/vlib/v/checker/tests/ambiguous_field_method_err.out new file mode 100644 index 0000000000..019eef5761 --- /dev/null +++ b/vlib/v/checker/tests/ambiguous_field_method_err.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/ambiguous_field_method_err.vv:22:4: error: ambiguous method `test` + 20 | fn main() { + 21 | b := Bar{} + 22 | b.test() + | ~~~~~~ + 23 | n := b.name + 24 | } +vlib/v/checker/tests/ambiguous_field_method_err.vv:23:9: error: ambiguous field `name` + 21 | b := Bar{} + 22 | b.test() + 23 | n := b.name + | ~~~~ + 24 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/ambiguous_field_method_err.vv b/vlib/v/checker/tests/ambiguous_field_method_err.vv new file mode 100644 index 0000000000..fa32755c9f --- /dev/null +++ b/vlib/v/checker/tests/ambiguous_field_method_err.vv @@ -0,0 +1,24 @@ +struct Foo { + name int = 5 +} +struct Bar { + Foo + Foo2 +} + +struct Foo2 { + name string +} +fn (f Foo2) test() { + println(f) +} + +fn (f Foo) test() { + println(f) +} + +fn main() { + b := Bar{} + b.test() + n := b.name +} \ No newline at end of file diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index eb0f4c3fb1..3df8eb4a65 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -630,13 +630,11 @@ pub fn (mut f Fmt) struct_decl(node ast.StructDecl) { max_type = ft.len } } - for field in node.fields.filter(it.is_embed) { - f.writeln('\t$field.name') + for embed in node.embeds { + styp := f.table.type_to_str(embed.typ) + f.writeln('\t$styp') } for i, field in node.fields { - if field.is_embed { - continue - } if i == node.mut_pos { f.writeln('mut:') } else if i == node.pub_pos { diff --git a/vlib/v/fmt/tests/struct_embed_input.vv b/vlib/v/fmt/tests/struct_embed_input.vv deleted file mode 100644 index ec721f056d..0000000000 --- a/vlib/v/fmt/tests/struct_embed_input.vv +++ /dev/null @@ -1,12 +0,0 @@ -struct Foo { - x int -} - -struct Test {} - -struct Bar { - y int - Foo - z string - Test -} diff --git a/vlib/v/fmt/tests/struct_embed_expected.vv b/vlib/v/fmt/tests/struct_embed_keep.vv similarity index 73% rename from vlib/v/fmt/tests/struct_embed_expected.vv rename to vlib/v/fmt/tests/struct_embed_keep.vv index a7dae553b0..8c2fc02173 100644 --- a/vlib/v/fmt/tests/struct_embed_expected.vv +++ b/vlib/v/fmt/tests/struct_embed_keep.vv @@ -7,6 +7,6 @@ struct Test {} struct Bar { Foo Test - y int - z string + y int + z string } diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index aef163b1e3..3620140280 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -2664,93 +2664,7 @@ fn (mut g Gen) expr(node ast.Expr) { g.struct_init(node) } ast.SelectorExpr { - prevent_sum_type_unwrapping_once := g.prevent_sum_type_unwrapping_once - g.prevent_sum_type_unwrapping_once = false - if node.name_type > 0 { - g.type_name(node.name_type) - return - } - if node.expr_type == 0 { - g.checker_bug('unexpected SelectorExpr.expr_type = 0', node.pos) - } - sym := g.table.get_type_symbol(node.expr_type) - // if node expr is a root ident and an optional - mut is_optional := node.expr is ast.Ident && node.expr_type.has_flag(.optional) - if is_optional { - opt_base_typ := g.base_type(node.expr_type) - g.writeln('(*($opt_base_typ*)') - } - if sym.kind == .array_fixed { - assert node.field_name == 'len' - info := sym.info as table.ArrayFixed - g.write('$info.size') - return - } - if sym.kind == .chan && node.field_name == 'len' { - g.write('sync__Channel_len(') - g.expr(node.expr) - g.write(')') - return - } - mut sum_type_deref_field := '' - if f := g.table.struct_find_field(sym, node.field_name) { - field_sym := g.table.get_type_symbol(f.typ) - if field_sym.kind == .sum_type { - if !prevent_sum_type_unwrapping_once { - // check first if field is sum type because scope searching is expensive - scope := g.file.scope.innermost(node.pos.pos) - if field := scope.find_struct_field(node.expr_type, node.field_name) { - // union sum type deref - for i, typ in field.sum_type_casts { - g.write('(*') - cast_sym := g.table.get_type_symbol(typ) - if i != 0 { - dot := if field.typ.is_ptr() { '->' } else { '.' } - sum_type_deref_field += ')$dot' - } - if mut cast_sym.info is table.Aggregate { - agg_sym := g.table.get_type_symbol(cast_sym.info.types[g.aggregate_type_idx]) - sum_type_deref_field += '_$agg_sym.cname' - // sum_type_deref_field += '_${cast_sym.info.types[g.aggregate_type_idx]}' - } else { - sum_type_deref_field += '_$cast_sym.cname' - } - } - } - } - } - } - g.expr(node.expr) - if is_optional { - g.write('.data)') - } - // struct embedding - if sym.kind == .struct_ { - sym_info := sym.info as table.Struct - x := sym_info.fields.filter(it.name == node.field_name) - if x.len > 0 { - field := x[0] - if field.embed_alias_for != '' { - g.write('.$field.embed_alias_for') - } - } - } - if node.expr_type.is_ptr() || sym.kind == .chan { - g.write('->') - } else { - // g.write('. /*typ= $it.expr_type */') // ${g.typ(it.expr_type)} /') - g.write('.') - } - if node.expr_type.has_flag(.shared_f) { - g.write('val.') - } - if node.expr_type == 0 { - verror('cgen: SelectorExpr | expr_type: 0 | it.expr: `$node.expr` | field: `$node.field_name` | file: $g.file.path | line: $node.pos.line_nr') - } - g.write(c_name(node.field_name)) - if sum_type_deref_field != '' { - g.write('.$sum_type_deref_field)') - } + g.selector_expr(node) } ast.Type { // match sum Type @@ -2813,6 +2727,93 @@ fn (mut g Gen) typeof_expr(node ast.TypeOf) { } } +fn (mut g Gen) selector_expr(node ast.SelectorExpr) { + prevent_sum_type_unwrapping_once := g.prevent_sum_type_unwrapping_once + g.prevent_sum_type_unwrapping_once = false + if node.name_type > 0 { + g.type_name(node.name_type) + return + } + if node.expr_type == 0 { + g.checker_bug('unexpected SelectorExpr.expr_type = 0', node.pos) + } + sym := g.table.get_type_symbol(node.expr_type) + // if node expr is a root ident and an optional + mut is_optional := node.expr is ast.Ident && node.expr_type.has_flag(.optional) + if is_optional { + opt_base_typ := g.base_type(node.expr_type) + g.writeln('(*($opt_base_typ*)') + } + if sym.kind == .array_fixed { + assert node.field_name == 'len' + info := sym.info as table.ArrayFixed + g.write('$info.size') + return + } + if sym.kind == .chan && node.field_name == 'len' { + g.write('sync__Channel_len(') + g.expr(node.expr) + g.write(')') + return + } + mut sum_type_deref_field := '' + if f := g.table.struct_find_field(sym, node.field_name) { + field_sym := g.table.get_type_symbol(f.typ) + if field_sym.kind == .sum_type { + if !prevent_sum_type_unwrapping_once { + // check first if field is sum type because scope searching is expensive + scope := g.file.scope.innermost(node.pos.pos) + if field := scope.find_struct_field(node.expr_type, node.field_name) { + // union sum type deref + for i, typ in field.sum_type_casts { + g.write('(*') + cast_sym := g.table.get_type_symbol(typ) + if i != 0 { + dot := if field.typ.is_ptr() { '->' } else { '.' } + sum_type_deref_field += ')$dot' + } + if mut cast_sym.info is table.Aggregate { + agg_sym := g.table.get_type_symbol(cast_sym.info.types[g.aggregate_type_idx]) + sum_type_deref_field += '_$agg_sym.cname' + // sum_type_deref_field += '_${cast_sym.info.types[g.aggregate_type_idx]}' + } else { + sum_type_deref_field += '_$cast_sym.cname' + } + } + } + } + } + } + g.expr(node.expr) + if is_optional { + g.write('.data)') + } + // struct embedding + if sym.info is table.Struct { + if node.from_embed_type != 0 { + embed_sym := g.table.get_type_symbol(node.from_embed_type) + embed_name := embed_sym.embed_name() + g.write('.$embed_name') + } + } + if node.expr_type.is_ptr() || sym.kind == .chan { + g.write('->') + } else { + // g.write('. /*typ= $it.expr_type */') // ${g.typ(it.expr_type)} /') + g.write('.') + } + if node.expr_type.has_flag(.shared_f) { + g.write('val.') + } + if node.expr_type == 0 { + verror('cgen: SelectorExpr | expr_type: 0 | it.expr: `$node.expr` | field: `$node.field_name` | file: $g.file.path | line: $node.pos.line_nr') + } + g.write(c_name(node.field_name)) + if sum_type_deref_field != '' { + g.write('.$sum_type_deref_field)') + } +} + fn (mut g Gen) enum_expr(node ast.Expr) { match node { ast.EnumVal { g.write(node.val) } @@ -4347,16 +4348,6 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) { mut initialized := false for i, field in struct_init.fields { inited_fields[field.name] = i - if mut sym.info is table.Struct { - equal_fields := sym.info.fields.filter(it.name == field.name) - if equal_fields.len == 0 { - continue - } - tfield := equal_fields[0] - if tfield.embed_alias_for.len != 0 { - continue - } - } if sym.kind != .struct_ { field_name := c_name(field.name) g.write('.$field_name = ') @@ -4396,6 +4387,23 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) { if info.is_union && struct_init.fields.len > 1 { verror('union must not have more than 1 initializer') } + for embed in info.embeds { + embed_sym := g.table.get_type_symbol(embed) + embed_name := embed_sym.embed_name() + if embed_name !in inited_fields { + default_init := ast.StructInit{ + typ: embed + } + g.write('.$embed_name = ') + g.struct_init(default_init) + if is_multiline { + g.writeln(',') + } else { + g.write(',') + } + initialized = true + } + } // g.zero_struct_fields(info, inited_fields) // nr_fields = info.fields.len for field in info.fields { @@ -4404,10 +4412,6 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) { if equal_fields.len == 0 { continue } - tfield := equal_fields[0] - if tfield.embed_alias_for.len != 0 { - continue - } } if field.name in inited_fields { sfield := struct_init.fields[inited_fields[field.name]] @@ -4792,8 +4796,8 @@ fn (mut g Gen) write_types(types []table.TypeSymbol) { } else { g.type_definitions.writeln('struct $name {') } - if typ.info.fields.len > 0 { - for field in typ.info.fields.filter(it.embed_alias_for == '') { + if typ.info.fields.len > 0 || typ.info.embeds.len > 0 { + for field in typ.info.fields { // Some of these structs may want to contain // optionals that may not be defined at this point // if this is the case then we are going to @@ -4887,6 +4891,14 @@ fn (g &Gen) sort_structs(typesa []table.TypeSymbol) []table.TypeSymbol { // if info.is_interface { // continue // } + for embed in t.info.embeds { + dep := g.table.get_type_symbol(embed).name + // skip if not in types list or already in deps + if dep !in type_names || dep in field_deps { + continue + } + field_deps << dep + } for field in t.info.fields { dep := g.table.get_type_symbol(field.typ).name // skip if not in types list or already in deps diff --git a/vlib/v/gen/fn.v b/vlib/v/gen/fn.v index 9f95f8698b..7057c2216f 100644 --- a/vlib/v/gen/fn.v +++ b/vlib/v/gen/fn.v @@ -457,6 +457,10 @@ fn (mut g Gen) method_call(node ast.CallExpr) { g.write('/*af receiver arg*/' + arg_name) } else { g.expr(node.left) + if node.from_embed_type != 0 { + embed_name := typ_sym.embed_name() + g.write('.$embed_name') + } } if has_cast { g.write(')') diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index e720d678a6..697dd22088 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -77,7 +77,9 @@ fn (mut p Parser) struct_decl() ast.StructDecl { // println('struct decl $name') mut ast_fields := []ast.StructField{} mut fields := []table.Field{} - mut embedded_structs := []table.Type{} + mut embed_types := []table.Type{} + mut embeds := []ast.Embed{} + mut embed_field_names := []string{} mut mut_pos := -1 mut pub_pos := -1 mut pub_mut_pos := -1 @@ -156,35 +158,38 @@ fn (mut p Parser) struct_decl() ast.StructDecl { field_start_pos := p.tok.position() is_embed := ((p.tok.lit.len > 1 && p.tok.lit[0].is_capital()) || p.peek_tok.kind == .dot) && - language == .v + language == .v && ast_fields.len == 0 mut field_name := '' mut typ := table.Type(0) mut type_pos := token.Position{} mut field_pos := token.Position{} if is_embed { // struct embedding + type_pos = p.tok.position() typ = p.parse_type() - sym := p.table.get_type_symbol(typ) - // main.Abc => Abc - mut symbol_name := sym.name.split('.')[1] - // remove generic part from name - if '<' in symbol_name { - symbol_name = symbol_name.split('<')[0] - } for p.tok.kind == .comment { comments << p.comment() if p.tok.kind == .rcbr { break } } - type_pos = p.prev_tok.position() - field_pos = p.prev_tok.position() - field_name = symbol_name - if typ in embedded_structs { - p.error_with_pos('cannot embed `$field_name` more than once', type_pos) + type_pos = type_pos.extend(p.prev_tok.position()) + sym := p.table.get_type_symbol(typ) + if typ in embed_types { + p.error_with_pos('cannot embed `$sym.name` more than once', type_pos) return ast.StructDecl{} } - embedded_structs << typ + field_name = sym.embed_name() + if field_name in embed_field_names { + p.error_with_pos('duplicate field `$field_name`', type_pos) + return ast.StructDecl{} + } + embed_field_names << field_name + embed_types << typ + embeds << ast.Embed{ + typ: typ + pos: type_pos + } } else { // struct field field_name = p.check_name() @@ -225,20 +230,20 @@ fn (mut p Parser) struct_decl() ast.StructDecl { } has_default_expr = true } + // TODO merge table and ast Fields? + ast_fields << ast.StructField{ + name: field_name + pos: field_pos + type_pos: type_pos + typ: typ + comments: comments + default_expr: default_expr + has_default_expr: has_default_expr + attrs: p.attrs + is_public: is_field_pub + } } - // TODO merge table and ast Fields? - ast_fields << ast.StructField{ - name: field_name - pos: field_pos - type_pos: type_pos - typ: typ - comments: comments - default_expr: default_expr - has_default_expr: has_default_expr - attrs: p.attrs - is_public: is_field_pub - is_embed: is_embed - } + // save embeds as table fields too, it will be used in generation phase fields << table.Field{ name: field_name typ: typ @@ -248,7 +253,6 @@ fn (mut p Parser) struct_decl() ast.StructDecl { is_mut: is_field_mut is_global: is_field_global attrs: p.attrs - is_embed: is_embed } p.attrs = [] } @@ -270,6 +274,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl { cname: util.no_dots(name) mod: p.mod info: table.Struct{ + embeds: embed_types fields: fields is_typedef: attrs.contains('typedef') is_union: is_union @@ -301,6 +306,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl { attrs: attrs end_comments: end_comments gen_types: generic_types + embeds: embeds } } diff --git a/vlib/v/parser/tests/duplicate_field_embed_err.out b/vlib/v/parser/tests/duplicate_field_embed_err.out new file mode 100644 index 0000000000..7fc63118e2 --- /dev/null +++ b/vlib/v/parser/tests/duplicate_field_embed_err.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/duplicate_field_embed_err.vv:8:2: error: duplicate field `ModFileAndFolder` + 6 | struct Bar { + 7 | ModFileAndFolder + 8 | vmod.ModFileAndFolder + | ~~~~~~~~~~~~~~~~~~~~~ + 9 | } \ No newline at end of file diff --git a/vlib/v/parser/tests/duplicate_field_embed_err.vv b/vlib/v/parser/tests/duplicate_field_embed_err.vv new file mode 100644 index 0000000000..d71112f619 --- /dev/null +++ b/vlib/v/parser/tests/duplicate_field_embed_err.vv @@ -0,0 +1,9 @@ +import v.vmod + +struct ModFileAndFolder { + name int = 5 +} +struct Bar { + ModFileAndFolder + vmod.ModFileAndFolder +} diff --git a/vlib/v/table/types.v b/vlib/v/table/types.v index aec412fe14..394d67de26 100644 --- a/vlib/v/table/types.v +++ b/vlib/v/table/types.v @@ -606,6 +606,7 @@ pub fn (kinds []Kind) str() string { pub struct Struct { pub mut: + embeds []Type fields []Field is_typedef bool // C. [typedef] is_union bool @@ -663,8 +664,6 @@ pub mut: is_pub bool is_mut bool is_global bool - is_embed bool - embed_alias_for string // name of the struct which contains this field name } fn (f &Field) equals(o &Field) bool { @@ -850,6 +849,17 @@ pub fn (t &Table) fn_signature(func &Fn, opts FnSignatureOpts) string { return sb.str() } +pub fn (t &TypeSymbol) embed_name() string { + // main.Abc => Abc + mut embed_name := t.name.split('.').last() + // remove generic part from name + // Abc => Abc + if '<' in embed_name { + embed_name = embed_name.split('<')[0] + } + return embed_name +} + pub fn (t &TypeSymbol) has_method(name string) bool { t.find_method(name) or { return false } return true diff --git a/vlib/v/tests/struct_embed_test.v b/vlib/v/tests/struct_embed_test.v index e660c50d47..a8870d076a 100644 --- a/vlib/v/tests/struct_embed_test.v +++ b/vlib/v/tests/struct_embed_test.v @@ -7,7 +7,6 @@ struct Foo { fn (f Foo) foo() {} - struct Bar { Foo } @@ -15,7 +14,7 @@ struct Bar { fn test_embed() { b := Bar{} assert b.x == 0 - //b.foo() // TODO methods + b.foo() } fn test_embed_direct_access() { @@ -27,7 +26,7 @@ fn test_default_value() { b := Bar{Foo: Foo{}} assert b.y == 5 } -/* +/* TODO fn test_initialize() { b := Bar{x: 1, y: 2} assert b.x == 1 @@ -59,5 +58,19 @@ fn test_generic_embed() { b := BarGenericContainer{} assert b.BarGeneric.foo == 0 assert b.foo == 0 - println('ok') +} + +struct Upper { +mut: + x int +} + +struct UpperHolder { + Upper +} + +fn test_assign() { + mut h := UpperHolder{} + h.x = 5 + assert h.x == 5 }