From 164d7bf5fbf8056c76955206deac5fb343d5f0ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=C3=A4schle?= Date: Mon, 4 Jan 2021 19:19:03 +0100 Subject: [PATCH] parser: struct updating syntax with `Abc{...oldabc newfield: val}` (#7865) --- vlib/v/ast/ast.v | 15 ++++++----- vlib/v/checker/checker.v | 11 ++++++++ .../tests/struct_init_update_struct_err.out | 7 +++++ .../tests/struct_init_update_struct_err.vv | 15 +++++++++++ .../tests/struct_init_update_type_err.out | 7 +++++ .../tests/struct_init_update_type_err.vv | 15 +++++++++++ vlib/v/fmt/fmt.v | 12 ++++++++- vlib/v/fmt/tests/struct_update_keep.vv | 17 ++++++++++++ vlib/v/gen/cgen.v | 12 ++++++++- vlib/v/parser/struct.v | 26 ++++++++++++++----- vlib/v/parser/tests/struct_update_err.out | 7 +++++ vlib/v/parser/tests/struct_update_err.vv | 17 ++++++++++++ vlib/v/tests/struct_test.v | 12 +++++++++ 13 files changed, 158 insertions(+), 15 deletions(-) create mode 100644 vlib/v/checker/tests/struct_init_update_struct_err.out create mode 100644 vlib/v/checker/tests/struct_init_update_struct_err.vv create mode 100644 vlib/v/checker/tests/struct_init_update_type_err.out create mode 100644 vlib/v/checker/tests/struct_init_update_type_err.vv create mode 100644 vlib/v/fmt/tests/struct_update_keep.vv create mode 100644 vlib/v/parser/tests/struct_update_err.out create mode 100644 vlib/v/parser/tests/struct_update_err.vv diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 7c3fa31688..47f23209d1 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -247,13 +247,16 @@ pub mut: pub struct StructInit { pub: - pos token.Position - is_short bool + pos token.Position + is_short bool pub mut: - pre_comments []Comment - typ table.Type - fields []StructInitField - embeds []StructInitEmbed + pre_comments []Comment + typ table.Type + update_expr Expr + update_expr_type table.Type + has_update_expr bool + fields []StructInitField + embeds []StructInitEmbed } // import statement diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 79caecdafe..f940104f0b 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -641,6 +641,17 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type { } else {} } + if struct_init.has_update_expr { + update_type := c.expr(struct_init.update_expr) + struct_init.update_expr_type = update_type + update_sym := c.table.get_type_symbol(update_type) + sym := c.table.get_type_symbol(struct_init.typ) + if update_sym.kind != .struct_ { + c.error('expected struct `$sym.name`, found `$update_sym.name`', struct_init.update_expr.position()) + } else if update_type != struct_init.typ { + c.error('expected struct `$sym.name`, found struct `$update_sym.name`', struct_init.update_expr.position()) + } + } return struct_init.typ } diff --git a/vlib/v/checker/tests/struct_init_update_struct_err.out b/vlib/v/checker/tests/struct_init_update_struct_err.out new file mode 100644 index 0000000000..5d7956d276 --- /dev/null +++ b/vlib/v/checker/tests/struct_init_update_struct_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/struct_init_update_struct_err.vv:11:6: error: expected struct `Foo`, found struct `Foo2` + 9 | f3 := Foo2{} + 10 | _ := Foo{ + 11 | ...f3 + | ~~ + 12 | name: 'f2' + 13 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/struct_init_update_struct_err.vv b/vlib/v/checker/tests/struct_init_update_struct_err.vv new file mode 100644 index 0000000000..9b73579e6e --- /dev/null +++ b/vlib/v/checker/tests/struct_init_update_struct_err.vv @@ -0,0 +1,15 @@ +struct Foo { + name string + age int +} + +struct Foo2 {} + +fn main() { + f3 := Foo2{} + _ := Foo{ + ...f3 + name: 'f2' + } +} + diff --git a/vlib/v/checker/tests/struct_init_update_type_err.out b/vlib/v/checker/tests/struct_init_update_type_err.out new file mode 100644 index 0000000000..c75fb0abdb --- /dev/null +++ b/vlib/v/checker/tests/struct_init_update_type_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/struct_init_update_type_err.vv:11:6: error: expected struct `Foo`, found `int` + 9 | f3 := 2 + 10 | _ := Foo{ + 11 | ...f3 + | ~~ + 12 | name: 'f2' + 13 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/struct_init_update_type_err.vv b/vlib/v/checker/tests/struct_init_update_type_err.vv new file mode 100644 index 0000000000..a7988d9da7 --- /dev/null +++ b/vlib/v/checker/tests/struct_init_update_type_err.vv @@ -0,0 +1,15 @@ +struct Foo { + name string + age int +} + +struct Foo2 {} + +fn main() { + f3 := 2 + _ := Foo{ + ...f3 + name: 'f2' + } +} + diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index b86dc14979..3015af32c8 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -2015,7 +2015,7 @@ pub fn (mut f Fmt) struct_init(it ast.StructInit) { if name == 'void' { name = '' } - if it.fields.len == 0 { + if it.fields.len == 0 && !it.has_update_expr { // `Foo{}` on one line if there are no fields or comments if it.pre_comments.len == 0 { f.write('$name{}') @@ -2029,6 +2029,11 @@ pub fn (mut f Fmt) struct_init(it ast.StructInit) { // if name != '' { f.write('$name{') // } + if it.has_update_expr { + f.write('...') + f.expr(it.update_expr) + f.write(', ') + } for i, field in it.fields { f.prefix_expr_cast_expr(field.expr) if i < it.fields.len - 1 { @@ -2049,6 +2054,11 @@ pub fn (mut f Fmt) struct_init(it ast.StructInit) { } init_start := f.out.len f.indent++ + if it.has_update_expr { + f.write('...') + f.expr(it.update_expr) + f.writeln('') + } short_args_loop: for { f.comments(it.pre_comments, inline: true, has_nl: true, level: .keep) for i, field in it.fields { diff --git a/vlib/v/fmt/tests/struct_update_keep.vv b/vlib/v/fmt/tests/struct_update_keep.vv new file mode 100644 index 0000000000..e550ac165c --- /dev/null +++ b/vlib/v/fmt/tests/struct_update_keep.vv @@ -0,0 +1,17 @@ +struct Foo { + name string + age int +} + +struct Foo2 {} + +fn main() { + f := Foo{ + name: 'test' + age: 18 + } + f2 := Foo{ + ...f + name: 'f2' + } +} diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index e9d546162d..83b49bf036 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -4529,7 +4529,17 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) { if field.typ in info.embeds { continue } - g.zero_struct_field(field) + if struct_init.has_update_expr { + g.expr(struct_init.update_expr) + if struct_init.update_expr_type.is_ptr() { + g.write('->') + } else { + g.write('.') + } + g.write(field.name) + } else { + g.zero_struct_field(field) + } if is_multiline { g.writeln(',') } else { diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index 7ba4e1b6c7..0dff6345cb 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -331,21 +331,29 @@ fn (mut p Parser) struct_init(short_syntax bool) ast.StructInit { pre_comments := p.eat_comments() mut fields := []ast.StructInitField{} mut i := 0 - no_keys := p.peek_tok.kind != .colon && p.tok.kind != .rcbr // `Vec{a,b,c} + no_keys := p.peek_tok.kind != .colon && p.tok.kind != .rcbr && p.tok.kind != .ellipsis // `Vec{a,b,c} // p.warn(is_short_syntax.str()) saved_is_amp := p.is_amp p.is_amp = false + mut update_expr := ast.Expr{} + mut has_update_expr := false for p.tok.kind !in [.rcbr, .rpar, .eof] { mut field_name := '' mut expr := ast.Expr{} mut field_pos := token.Position{} mut comments := []ast.Comment{} mut nline_comments := []ast.Comment{} + is_update_expr := fields.len == 0 && p.tok.kind == .ellipsis if no_keys { // name will be set later in checker expr = p.expr(0) field_pos = expr.position() comments = p.eat_line_end_comments() + } else if is_update_expr { + // struct updating syntax; f2 := Foo{ ...f, name: 'f2' } + p.check(.ellipsis) + update_expr = p.expr(0) + has_update_expr = true } else { first_field_pos := p.tok.position() field_name = p.check_name() @@ -367,12 +375,14 @@ fn (mut p Parser) struct_init(short_syntax bool) ast.StructInit { } comments << p.eat_line_end_comments() nline_comments << p.eat_comments() - fields << ast.StructInitField{ - name: field_name - expr: expr - pos: field_pos - comments: comments - next_comments: nline_comments + if !is_update_expr { + fields << ast.StructInitField{ + name: field_name + expr: expr + pos: field_pos + comments: comments + next_comments: nline_comments + } } } last_pos := p.tok.position() @@ -383,6 +393,8 @@ fn (mut p Parser) struct_init(short_syntax bool) ast.StructInit { node := ast.StructInit{ typ: typ fields: fields + update_expr: update_expr + has_update_expr: has_update_expr pos: token.Position{ line_nr: first_pos.line_nr pos: first_pos.pos diff --git a/vlib/v/parser/tests/struct_update_err.out b/vlib/v/parser/tests/struct_update_err.out new file mode 100644 index 0000000000..bea1bba502 --- /dev/null +++ b/vlib/v/parser/tests/struct_update_err.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/struct_update_err.vv:15:3: error: unexpected `...`, expecting `name` + 13 | f2 := Foo{ + 14 | name: 'f2' + 15 | ...f + | ~~~ + 16 | } + 17 | } \ No newline at end of file diff --git a/vlib/v/parser/tests/struct_update_err.vv b/vlib/v/parser/tests/struct_update_err.vv new file mode 100644 index 0000000000..46aa523d48 --- /dev/null +++ b/vlib/v/parser/tests/struct_update_err.vv @@ -0,0 +1,17 @@ +struct Foo { + name string + age int +} + +struct Foo2 {} + +fn main() { + f := Foo{ + name: 'test' + age: 18 + } + f2 := Foo{ + name: 'f2' + ...f + } +} diff --git a/vlib/v/tests/struct_test.v b/vlib/v/tests/struct_test.v index 37881c947c..52db2ccd93 100644 --- a/vlib/v/tests/struct_test.v +++ b/vlib/v/tests/struct_test.v @@ -366,3 +366,15 @@ fn test_fields_array_of_fn() { println(commands.show) assert '$commands.show' == '[fn () string, fn () string]' } + +fn test_struct_update() { + c := Country{name: 'test'} + c2 := Country{ + ...c + capital: City{ + name: 'city' + } + } + assert c2.capital.name == 'city' + assert c2.name == 'test' +}