diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index ef9a3fe35a..451e082577 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -459,6 +459,7 @@ pub mut: typ Type // the type of this struct update_expr Expr // `a` in `...a` update_expr_type Type + update_expr_pos token.Pos update_expr_comments []Comment is_update_embed bool has_update_expr bool // has `...a` diff --git a/vlib/v/checker/struct.v b/vlib/v/checker/struct.v index df435a069f..9254724009 100644 --- a/vlib/v/checker/struct.v +++ b/vlib/v/checker/struct.v @@ -334,8 +334,9 @@ fn (mut c Checker) struct_init(mut node ast.StructInit) ast.Type { } } // allow init structs from generic if they're private except the type is from builtin module - if !type_sym.is_pub && type_sym.kind != .placeholder && type_sym.language != .c - && (type_sym.mod != c.mod && !(node.typ.has_flag(.generic) && type_sym.mod != 'builtin')) { + if !node.has_update_expr && !type_sym.is_pub && type_sym.kind != .placeholder + && type_sym.language != .c && (type_sym.mod != c.mod && !(node.typ.has_flag(.generic) + && type_sym.mod != 'builtin')) { c.error('type `${type_sym.name}` is private', node.pos) } if type_sym.kind == .struct_ { @@ -591,6 +592,9 @@ fn (mut c Checker) struct_init(mut node ast.StructInit) ast.Type { if node.has_update_expr { update_type := c.expr(node.update_expr) node.update_expr_type = update_type + if node.update_expr is ast.ComptimeSelector { + c.error('cannot use struct update syntax in compile time expressions', node.update_expr_pos) + } if c.table.final_sym(update_type).kind != .struct_ { s := c.table.type_to_str(update_type) c.error('expected struct, found `${s}`', node.update_expr.pos()) diff --git a/vlib/v/checker/tests/struct_update_comptime_err.out b/vlib/v/checker/tests/struct_update_comptime_err.out new file mode 100644 index 0000000000..8d64ec49f0 --- /dev/null +++ b/vlib/v/checker/tests/struct_update_comptime_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/struct_update_comptime_err.vv:18:4: error: cannot use struct update syntax in compile time expressions + 16 | $for field in U.fields { + 17 | _ = struct { + 18 | ...val.$(field.name) + | ~~~ + 19 | } + 20 | } diff --git a/vlib/v/checker/tests/struct_update_comptime_err.vv b/vlib/v/checker/tests/struct_update_comptime_err.vv new file mode 100644 index 0000000000..a615ed320c --- /dev/null +++ b/vlib/v/checker/tests/struct_update_comptime_err.vv @@ -0,0 +1,32 @@ +struct Aa { + sub Bb +} + +struct AaWithAliasType { + sub Bb +} + +type AliasType = Bb + +struct Bb { + a int +} + +fn encode_struct[U](val U) { + $for field in U.fields { + _ = struct { + ...val.$(field.name) + } + } +} + +fn main() { + aa := Aa{} + bb := Bb{} + aa_with_alias_type := AaWithAliasType{} + + encode_struct(aa) + encode_struct(aa_with_alias_type) + + encode_struct(bb) +} diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index 823cc94554..5376f97c3a 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -408,6 +408,7 @@ fn (mut p Parser) struct_init(typ_str string, kind ast.StructInitKind) ast.Struc mut update_expr := ast.empty_expr mut update_expr_comments := []ast.Comment{} mut has_update_expr := false + mut update_expr_pos := token.Pos{} for p.tok.kind !in [.rcbr, .rpar, .eof] { mut field_name := '' mut expr := ast.empty_expr @@ -424,6 +425,7 @@ fn (mut p Parser) struct_init(typ_str string, kind ast.StructInitKind) ast.Struc comments = p.eat_comments(same_line: true) } else if is_update_expr { // struct updating syntax; f2 := Foo{ ...f, name: 'f2' } + update_expr_pos = p.tok.pos() p.check(.ellipsis) update_expr = p.expr(0) update_expr_comments << p.eat_comments(same_line: true) @@ -475,6 +477,7 @@ fn (mut p Parser) struct_init(typ_str string, kind ast.StructInitKind) ast.Struc typ: typ fields: fields update_expr: update_expr + update_expr_pos: update_expr_pos update_expr_comments: update_expr_comments has_update_expr: has_update_expr name_pos: first_pos