From 2c75b1397cbd8b940bdf357ebb74d59660c53f51 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Fri, 30 Oct 2020 07:09:26 +0100 Subject: [PATCH] all: struct embedding --- 0.3_roadmap.txt | 3 +- vlib/v/ast/ast.v | 13 +++- vlib/v/checker/checker.v | 51 +++++++++++- .../tests/struct_embed_invalid_type.out | 6 ++ .../tests/struct_embed_invalid_type.vv | 5 ++ vlib/v/fmt/fmt.v | 6 ++ vlib/v/fmt/tests/struct_embed_expected.vv | 13 ++++ vlib/v/fmt/tests/struct_embed_input.vv | 12 +++ vlib/v/gen/cgen.v | 25 +++++- vlib/v/parser/parse_type.v | 1 - vlib/v/parser/struct.v | 78 ++++++++++++++----- vlib/v/parser/tests/c_struct_no_embed.out | 5 ++ vlib/v/parser/tests/c_struct_no_embed.vv | 7 ++ .../v/parser/tests/struct_embed_duplicate.out | 7 ++ vlib/v/parser/tests/struct_embed_duplicate.vv | 9 +++ .../tests/struct_embed_unknown_module.out | 5 ++ .../tests/struct_embed_unknown_module.vv | 3 + vlib/v/table/atypes.v | 2 + vlib/v/tests/struct_embed_test.v | 57 ++++++++++++++ 19 files changed, 279 insertions(+), 29 deletions(-) create mode 100644 vlib/v/checker/tests/struct_embed_invalid_type.out create mode 100644 vlib/v/checker/tests/struct_embed_invalid_type.vv create mode 100644 vlib/v/fmt/tests/struct_embed_expected.vv create mode 100644 vlib/v/fmt/tests/struct_embed_input.vv create mode 100644 vlib/v/parser/tests/c_struct_no_embed.out create mode 100644 vlib/v/parser/tests/c_struct_no_embed.vv create mode 100644 vlib/v/parser/tests/struct_embed_duplicate.out create mode 100644 vlib/v/parser/tests/struct_embed_duplicate.vv create mode 100644 vlib/v/parser/tests/struct_embed_unknown_module.out create mode 100644 vlib/v/parser/tests/struct_embed_unknown_module.vv create mode 100644 vlib/v/tests/struct_embed_test.v diff --git a/0.3_roadmap.txt b/0.3_roadmap.txt index 078a26b793..8eed7fcafa 100644 --- a/0.3_roadmap.txt +++ b/0.3_roadmap.txt @@ -14,7 +14,8 @@ - parallel parser (and maybe checker/gen?) - `recover()` from panics - IO streams -- struct and interface embedding ++ struct embedding +- interface embedding - interfaces: allow struct fields (not just methods) - vfmt: fix common errors automatically to save time (make vars mutable and vice versa, add missing imports etc) - method expressions with an explicit receiver as the first argument diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index c1dd31a21a..5650fed3f6 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -121,7 +121,6 @@ pub: pub struct StructField { pub: - name string pos token.Position type_pos token.Position comments []Comment @@ -129,7 +128,9 @@ pub: has_default_expr bool attrs []table.Attr is_public bool + is_embed bool pub mut: + name string typ table.Type } @@ -166,7 +167,6 @@ pub struct StructDecl { pub: pos token.Position name string - fields []StructField is_pub bool mut_pos int // mut: pub_pos int // pub: @@ -175,6 +175,15 @@ pub: is_union bool attrs []table.Attr end_comments []Comment +pub mut: + fields []StructField +} + +pub struct StructEmbedding { +pub: + name string + typ table.Type + pos token.Position } pub struct InterfaceDecl { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index b278b106be..a391eebccf 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -327,16 +327,35 @@ pub fn (mut c Checker) struct_decl(decl ast.StructDecl) { if decl.language == .v && !c.is_builtin_mod { c.check_valid_pascal_case(decl.name, 'struct name', decl.pos) } + struct_sym := c.table.find_type(decl.name) or { + table.TypeSymbol{} + } + mut struct_info := struct_sym.info as table.Struct for i, field in decl.fields { - if decl.language == .v { + if decl.language == .v && !field.is_embed { c.check_valid_snake_case(field.name, 'field name', field.pos) } + sym := c.table.get_type_symbol(field.typ) + if field.is_embed { + if sym.info is table.Struct as sym_info { + for embed_field in sym_info.fields { + already_exists := struct_info.fields.filter(it.name == embed_field.name).len > 0 + if !already_exists { + struct_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) } } - sym := c.table.get_type_symbol(field.typ) if sym.kind == .placeholder && decl.language != .c && !sym.name.starts_with('C.') { c.error(util.new_suggestion(sym.source_name, c.table.known_type_names()).say('unknown type `$sym.source_name`'), field.type_pos) @@ -485,6 +504,31 @@ 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: '' } + } + } + break + } + } + } + */ if !exists { c.error('unknown field `$field.name` in struct literal of type `$type_sym.source_name`', field.pos) @@ -514,7 +558,8 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type { } // Check uninitialized refs for field in info.fields { - if field.has_default_expr || field.name in inited_fields { + if field.has_default_expr || field.name in inited_fields || field.embed_alias_for != + '' { continue } if field.typ.is_ptr() && !c.pref.translated { diff --git a/vlib/v/checker/tests/struct_embed_invalid_type.out b/vlib/v/checker/tests/struct_embed_invalid_type.out new file mode 100644 index 0000000000..d85190fa35 --- /dev/null +++ b/vlib/v/checker/tests/struct_embed_invalid_type.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/struct_embed_invalid_type.vv:4:2: error: `Foo` is not a struct + 2 | + 3 | struct Bar { + 4 | Foo + | ~~~ + 5 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/struct_embed_invalid_type.vv b/vlib/v/checker/tests/struct_embed_invalid_type.vv new file mode 100644 index 0000000000..13f3c52b77 --- /dev/null +++ b/vlib/v/checker/tests/struct_embed_invalid_type.vv @@ -0,0 +1,5 @@ +type Foo = int + +struct Bar { + Foo +} diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 7503889cf1..cf4533992b 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -598,7 +598,13 @@ 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 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_expected.vv b/vlib/v/fmt/tests/struct_embed_expected.vv new file mode 100644 index 0000000000..154c736569 --- /dev/null +++ b/vlib/v/fmt/tests/struct_embed_expected.vv @@ -0,0 +1,13 @@ +struct Foo { + x int +} + +struct Test { +} + +struct Bar { + Foo + Test + y int + z string +} diff --git a/vlib/v/fmt/tests/struct_embed_input.vv b/vlib/v/fmt/tests/struct_embed_input.vv new file mode 100644 index 0000000000..ec721f056d --- /dev/null +++ b/vlib/v/fmt/tests/struct_embed_input.vv @@ -0,0 +1,12 @@ +struct Foo { + x int +} + +struct Test {} + +struct Bar { + y int + Foo + z string + Test +} diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index d20bb3b14c..240fa714d2 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -2323,6 +2323,17 @@ fn (mut g Gen) expr(node ast.Expr) { return } g.expr(node.expr) + // 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 { @@ -3874,6 +3885,12 @@ 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 sym.info is table.Struct as struct_info { + tfield := struct_info.fields.filter(it.name == field.name)[0] + if tfield.embed_alias_for.len != 0 { + continue + } + } if sym.kind != .struct_ { field_name := c_name(field.name) g.write('.$field_name = ') @@ -3913,6 +3930,12 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) { // g.zero_struct_fields(info, inited_fields) // nr_fields = info.fields.len for field in info.fields { + if sym.info is table.Struct as struct_info { + tfield := struct_info.fields.filter(it.name == field.name)[0] + if tfield.embed_alias_for.len != 0 { + continue + } + } if field.name in inited_fields { sfield := struct_init.fields[inited_fields[field.name]] field_name := c_name(sfield.name) @@ -4230,7 +4253,7 @@ fn (mut g Gen) write_types(types []table.TypeSymbol) { g.type_definitions.writeln('struct $name {') } if info.fields.len > 0 { - for field in info.fields { + for field in info.fields.filter(it.embed_alias_for == '') { // 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 diff --git a/vlib/v/parser/parse_type.v b/vlib/v/parser/parse_type.v index c7a6525ee5..502f9e4aa2 100644 --- a/vlib/v/parser/parse_type.v +++ b/vlib/v/parser/parse_type.v @@ -199,7 +199,6 @@ pub fn (mut p Parser) parse_any_type(language table.Language, is_ptr bool, check // `module.Type` // /if !(p.tok.lit in p.table.imports) { if !p.known_import(name) { - println(p.table.imports) p.error('unknown module `$p.tok.lit`') } if p.tok.lit in p.imports { diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index 3bb8b5920a..8d650c76f3 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -71,6 +71,7 @@ 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 mut_pos := -1 mut pub_pos := -1 mut pub_mut_pos := -1 @@ -142,18 +143,50 @@ fn (mut p Parser) struct_decl() ast.StructDecl { } } field_start_pos := p.tok.position() - field_name := p.check_name() - // p.warn('field $field_name') - for p.tok.kind == .comment { - comments << p.comment() - if p.tok.kind == .rcbr { - break + is_embed := ((p.tok.lit.len > 1 && p.tok.lit[0].is_capital()) || + p.peek_tok.kind == .dot) && + language == .v + mut field_name := '' + mut typ := table.Type(0) + mut type_pos := token.Position{} + mut field_pos := token.Position{} + if is_embed { + // struct embedding + 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) + } + embedded_structs << typ + } else { + // struct field + field_name = p.check_name() + for p.tok.kind == .comment { + comments << p.comment() + if p.tok.kind == .rcbr { + break + } + } + typ = p.parse_type() + type_pos = p.prev_tok.position() + field_pos = field_start_pos.extend(type_pos) } // println(p.tok.position()) - typ := p.parse_type() - type_pos := p.prev_tok.position() - field_pos := field_start_pos.extend(type_pos) // Comments after type (same line) comments << p.eat_comments() if p.tok.kind == .lsbr { @@ -162,18 +195,20 @@ fn (mut p Parser) struct_decl() ast.StructDecl { } mut default_expr := ast.Expr{} mut has_default_expr := false - if p.tok.kind == .assign { - // Default value - p.next() - // default_expr = p.tok.lit - // p.expr(0) - default_expr = p.expr(0) - match mut default_expr { - ast.EnumVal { default_expr.typ = typ } - // TODO: implement all types?? - else {} + if !is_embed { + if p.tok.kind == .assign { + // Default value + p.next() + // default_expr = p.tok.lit + // p.expr(0) + default_expr = p.expr(0) + match mut default_expr { + ast.EnumVal { default_expr.typ = typ } + // TODO: implement all types?? + else {} + } + has_default_expr = true } - has_default_expr = true } // TODO merge table and ast Fields? ast_fields << ast.StructField{ @@ -186,6 +221,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl { has_default_expr: has_default_expr attrs: p.attrs is_public: is_field_pub + is_embed: is_embed } fields << table.Field{ name: field_name @@ -196,9 +232,9 @@ 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 = [] - // println('struct field $ti.name $field_name') } p.top_level_statement_end() p.check(.rcbr) diff --git a/vlib/v/parser/tests/c_struct_no_embed.out b/vlib/v/parser/tests/c_struct_no_embed.out new file mode 100644 index 0000000000..193bd21868 --- /dev/null +++ b/vlib/v/parser/tests/c_struct_no_embed.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/c_struct_no_embed.vv:7:1: error: bad type syntax + 5 | struct C.Unknown { + 6 | Foo + 7 | } + | ^ \ No newline at end of file diff --git a/vlib/v/parser/tests/c_struct_no_embed.vv b/vlib/v/parser/tests/c_struct_no_embed.vv new file mode 100644 index 0000000000..020dce7525 --- /dev/null +++ b/vlib/v/parser/tests/c_struct_no_embed.vv @@ -0,0 +1,7 @@ +struct Foo { + x int +} + +struct C.Unknown { + Foo +} \ No newline at end of file diff --git a/vlib/v/parser/tests/struct_embed_duplicate.out b/vlib/v/parser/tests/struct_embed_duplicate.out new file mode 100644 index 0000000000..752ed817e6 --- /dev/null +++ b/vlib/v/parser/tests/struct_embed_duplicate.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/struct_embed_duplicate.vv:7:2: error: cannot embed `Abc` more than once + 5 | struct Xyz { + 6 | Abc + 7 | Abc + | ~~~ + 8 | bar int + 9 | } \ No newline at end of file diff --git a/vlib/v/parser/tests/struct_embed_duplicate.vv b/vlib/v/parser/tests/struct_embed_duplicate.vv new file mode 100644 index 0000000000..46563a4526 --- /dev/null +++ b/vlib/v/parser/tests/struct_embed_duplicate.vv @@ -0,0 +1,9 @@ +struct Abc { + foo int = 5 +} + +struct Xyz { + Abc + Abc + bar int +} diff --git a/vlib/v/parser/tests/struct_embed_unknown_module.out b/vlib/v/parser/tests/struct_embed_unknown_module.out new file mode 100644 index 0000000000..5d4542c328 --- /dev/null +++ b/vlib/v/parser/tests/struct_embed_unknown_module.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/struct_embed_unknown_module.vv:2:2: error: unknown module `custom` + 1 | struct WithEmbed { + 2 | custom.Foo + | ~~~~~~ + 3 | } \ No newline at end of file diff --git a/vlib/v/parser/tests/struct_embed_unknown_module.vv b/vlib/v/parser/tests/struct_embed_unknown_module.vv new file mode 100644 index 0000000000..1e084a8873 --- /dev/null +++ b/vlib/v/parser/tests/struct_embed_unknown_module.vv @@ -0,0 +1,3 @@ +struct WithEmbed { + custom.Foo +} diff --git a/vlib/v/table/atypes.v b/vlib/v/table/atypes.v index 23a7e6d35b..4dcaf3974d 100644 --- a/vlib/v/table/atypes.v +++ b/vlib/v/table/atypes.v @@ -757,6 +757,8 @@ 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 { diff --git a/vlib/v/tests/struct_embed_test.v b/vlib/v/tests/struct_embed_test.v new file mode 100644 index 0000000000..8572e779cf --- /dev/null +++ b/vlib/v/tests/struct_embed_test.v @@ -0,0 +1,57 @@ +import flag + +struct Foo { + x int + y int = 5 +} + +struct Bar { + Foo +} + +fn test_embed() { + b := Bar{} + assert b.x == 0 +} + +fn test_embed_direct_access() { + b := Bar{Foo: Foo{}} + assert b.Foo.y == 5 +} + +fn test_default_value() { + b := Bar{Foo: Foo{}} + assert b.y == 5 +} +/* +fn test_initialize() { + b := Bar{x: 1, y: 2} + assert b.x == 1 + assert b.y == 2 +} +*/ +struct Bar3 { + Foo + y string = 'test' +} + +fn test_overwrite_field() { + b := Bar3{} + assert b.y == 'test' +} + +struct TestEmbedFromModule { + flag.Flag +} + +struct BarGeneric { +pub: + foo T +} +struct BarGenericContainer { + BarGeneric +} +fn test_generic_embed() { + b := BarGenericContainer{} + assert b.BarGeneric.foo == 0 +}