From d477e525bbe5d64e4d3fed14440c1a2f1c01ebd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=C3=A4schle?= Date: Tue, 2 Feb 2021 14:42:00 +0100 Subject: [PATCH] checker/gen: fix generic struct init (#8322) --- cmd/tools/gen_vc.v | 2 +- vlib/v/ast/ast.v | 1 + vlib/v/ast/init.v | 72 ++++++++++++ vlib/v/checker/check_types.v | 1 + vlib/v/checker/checker.v | 22 ++-- .../checker/tests/check_generic_int_init.out | 6 + .../v/checker/tests/check_generic_int_init.vv | 7 ++ vlib/v/gen/cgen.v | 110 ++++++++++-------- vlib/v/gen/fn.v | 5 +- vlib/v/gen/js/js.v | 13 ++- vlib/v/parser/struct.v | 4 +- vlib/v/tests/generics_method_test.v | 21 +++- vlib/v/tests/generics_test.v | 22 ++++ 13 files changed, 211 insertions(+), 75 deletions(-) create mode 100644 vlib/v/ast/init.v create mode 100644 vlib/v/checker/tests/check_generic_int_init.out create mode 100644 vlib/v/checker/tests/check_generic_int_init.vv diff --git a/cmd/tools/gen_vc.v b/cmd/tools/gen_vc.v index 08acbb0648..dac8a9ded8 100644 --- a/cmd/tools/gen_vc.v +++ b/cmd/tools/gen_vc.v @@ -90,7 +90,7 @@ mut: struct WebhookServer { vweb.Context mut: - gen_vc &GenVC + gen_vc &GenVC = 0 // initialized in init_once } // storage for flag options diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index e297b59263..fe7f2e3c28 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -264,6 +264,7 @@ pub: pos token.Position is_short bool pub mut: + unresolved bool pre_comments []Comment typ table.Type update_expr Expr diff --git a/vlib/v/ast/init.v b/vlib/v/ast/init.v new file mode 100644 index 0000000000..b730d988ca --- /dev/null +++ b/vlib/v/ast/init.v @@ -0,0 +1,72 @@ +module ast + +import v.table + +pub fn resolve_init(node StructInit, typ table.Type, t &table.Table) Expr { + type_sym := t.get_type_symbol(typ) + if type_sym.kind == .array { + array_info := type_sym.info as table.Array + mut has_len := false + mut has_cap := false + mut has_default := false + mut len_expr := Expr{} + mut cap_expr := Expr{} + mut default_expr := Expr{} + mut exprs := []Expr{} + for field in node.fields { + match field.name { + 'len' { + has_len = true + len_expr = field.expr + } + 'cap' { + has_cap = true + len_expr = field.expr + } + 'default' { + has_default = true + len_expr = field.expr + } + else { + exprs << field.expr + } + } + } + return ArrayInit{ + // TODO: mod is not being set for now, we could need this in future + // mod: mod + pos: node.pos + typ: typ + elem_type: array_info.elem_type + has_len: has_len + has_cap: has_cap + has_default: has_default + len_expr: len_expr + cap_expr: cap_expr + default_expr: default_expr + exprs: exprs + } + } else if type_sym.kind == .map { + map_info := type_sym.info as table.Map + mut keys := []Expr{} + mut vals := []Expr{} + for field in node.fields { + keys << StringLiteral{ + val: field.name + } + vals << field.expr + } + return MapInit{ + typ: typ + key_type: map_info.key_type + value_type: map_info.value_type + keys: keys + vals: vals + } + } + // struct / other (sumtype?) + return StructInit{ + ...node + unresolved: false + } +} diff --git a/vlib/v/checker/check_types.v b/vlib/v/checker/check_types.v index 990aad0d2e..1e90230b34 100644 --- a/vlib/v/checker/check_types.v +++ b/vlib/v/checker/check_types.v @@ -460,6 +460,7 @@ pub fn (mut c Checker) infer_fn_types(f table.Fn, mut call_expr ast.CallExpr) { } if typ == table.void_type { c.error('could not infer generic type `$gt_name` in call to `$f.name`', call_expr.pos) + return } if c.pref.is_verbose { s := c.table.type_to_str(typ) diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index edc49f7550..b29af85438 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -508,7 +508,8 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type { if struct_init.typ == 0 { c.error('unknown type', struct_init.pos) } - type_sym := c.table.get_type_symbol(struct_init.typ) + utyp := c.unwrap_generic(struct_init.typ) + type_sym := c.table.get_type_symbol(utyp) if type_sym.kind == .sum_type && struct_init.fields.len == 1 { sexpr := struct_init.fields[0].expr.str() c.error('cast to sum type using `${type_sym.name}($sexpr)` not `$type_sym.name{$sexpr}`', @@ -517,15 +518,16 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type { if type_sym.kind == .interface_ { c.error('cannot instantiate interface `$type_sym.name`', struct_init.pos) } - if type_sym.kind == .alias { - info := type_sym.info as table.Alias - if info.parent_type.is_number() { + if type_sym.info is table.Alias { + if type_sym.info.parent_type.is_number() { c.error('cannot instantiate number type alias `$type_sym.name`', struct_init.pos) return table.void_type } } - if !type_sym.is_public && type_sym.kind != .placeholder && type_sym.mod != c.mod - && type_sym.language != .c { + // allow init structs from generic if they're private except the type is from builtin module + if !type_sym.is_public && type_sym.kind != .placeholder && type_sym.language != .c + && (type_sym.mod != c.mod && !(struct_init.typ.has_flag(.generic) + && type_sym.mod != 'builtin')) { c.error('type `$type_sym.name` is private', struct_init.pos) } if type_sym.kind == .struct_ { @@ -3349,14 +3351,11 @@ fn (mut c Checker) stmts(stmts []ast.Stmt) { pub fn (c &Checker) unwrap_generic(typ table.Type) table.Type { if typ.has_flag(.generic) { sym := c.table.get_type_symbol(typ) - mut idx := 0 for i, generic_param in c.cur_fn.generic_params { if generic_param.name == sym.name { - idx = i - break + return c.cur_generic_types[i].derive(typ).clear_flag(.generic) } } - return c.cur_generic_types[idx].derive(typ).clear_flag(.generic) } return typ } @@ -3583,6 +3582,9 @@ pub fn (mut c Checker) expr(node ast.Expr) table.Type { return c.string_inter_lit(mut node) } ast.StructInit { + if node.unresolved { + return c.expr(ast.resolve_init(node, c.unwrap_generic(node.typ), c.table)) + } return c.struct_init(mut node) } ast.Type { diff --git a/vlib/v/checker/tests/check_generic_int_init.out b/vlib/v/checker/tests/check_generic_int_init.out new file mode 100644 index 0000000000..8542862d5d --- /dev/null +++ b/vlib/v/checker/tests/check_generic_int_init.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/check_generic_int_init.vv:2:9: error: type `int` is private + 1 | fn test() T { + 2 | return T{} + | ~~~ + 3 | } + 4 | diff --git a/vlib/v/checker/tests/check_generic_int_init.vv b/vlib/v/checker/tests/check_generic_int_init.vv new file mode 100644 index 0000000000..f1518c799b --- /dev/null +++ b/vlib/v/checker/tests/check_generic_int_init.vv @@ -0,0 +1,7 @@ +fn test() T { + return T{} +} + +fn main() { + _ := test() +} diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 4371b7924d..addecdb6a7 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -2721,55 +2721,7 @@ fn (mut g Gen) expr(node ast.Expr) { g.match_expr(node) } ast.MapInit { - key_typ_str := g.typ(node.key_type) - value_typ_str := g.typ(node.value_type) - value_typ := g.table.get_type_symbol(node.value_type) - key_typ := g.table.get_type_symbol(node.key_type) - hash_fn, key_eq_fn, clone_fn, free_fn := g.map_fn_ptrs(key_typ) - size := node.vals.len - mut shared_styp := '' // only needed for shared &[]{...} - mut styp := '' - is_amp := g.is_amp - g.is_amp = false - if is_amp { - g.out.go_back(1) // delete the `&` already generated in `prefix_expr() - } - if g.is_shared { - mut shared_typ := node.typ.set_flag(.shared_f) - shared_styp = g.typ(shared_typ) - g.writeln('($shared_styp*)__dup_shared_map(&($shared_styp){.val = ') - } else if is_amp { - styp = g.typ(node.typ) - g.write('($styp*)memdup(ADDR($styp, ') - } - if size > 0 { - if value_typ.kind == .function { - g.write('new_map_init_2($hash_fn, $key_eq_fn, $clone_fn, $free_fn, $size, sizeof($key_typ_str), sizeof(voidptr), _MOV(($key_typ_str[$size]){') - } else { - g.write('new_map_init_2($hash_fn, $key_eq_fn, $clone_fn, $free_fn, $size, sizeof($key_typ_str), sizeof($value_typ_str), _MOV(($key_typ_str[$size]){') - } - for expr in node.keys { - g.expr(expr) - g.write(', ') - } - if value_typ.kind == .function { - g.write('}), _MOV((voidptr[$size]){') - } else { - g.write('}), _MOV(($value_typ_str[$size]){') - } - for expr in node.vals { - g.expr(expr) - g.write(', ') - } - g.write('}))') - } else { - g.write('new_map_2(sizeof($key_typ_str), sizeof($value_typ_str), $hash_fn, $key_eq_fn, $clone_fn, $free_fn)') - } - if g.is_shared { - g.write('}, sizeof($shared_styp))') - } else if is_amp { - g.write('), sizeof($styp))') - } + g.map_init(node) } ast.None { g.write('opt_none()') @@ -2863,8 +2815,12 @@ fn (mut g Gen) expr(node ast.Expr) { g.string_inter_literal(node) } ast.StructInit { - // `user := User{name: 'Bob'}` - g.struct_init(node) + if node.unresolved { + g.expr(ast.resolve_init(node, g.unwrap_generic(node.typ), g.table)) + } else { + // `user := User{name: 'Bob'}` + g.struct_init(node) + } } ast.SelectorExpr { g.selector_expr(node) @@ -3645,6 +3601,58 @@ fn (mut g Gen) match_expr_classic(node ast.MatchExpr, is_expr bool, cond_var str } } +fn (mut g Gen) map_init(node ast.MapInit) { + key_typ_str := g.typ(node.key_type) + value_typ_str := g.typ(node.value_type) + value_typ := g.table.get_type_symbol(node.value_type) + key_typ := g.table.get_type_symbol(node.key_type) + hash_fn, key_eq_fn, clone_fn, free_fn := g.map_fn_ptrs(key_typ) + size := node.vals.len + mut shared_styp := '' // only needed for shared &[]{...} + mut styp := '' + is_amp := g.is_amp + g.is_amp = false + if is_amp { + g.out.go_back(1) // delete the `&` already generated in `prefix_expr() + } + if g.is_shared { + mut shared_typ := node.typ.set_flag(.shared_f) + shared_styp = g.typ(shared_typ) + g.writeln('($shared_styp*)__dup_shared_map(&($shared_styp){.val = ') + } else if is_amp { + styp = g.typ(node.typ) + g.write('($styp*)memdup(ADDR($styp, ') + } + if size > 0 { + if value_typ.kind == .function { + g.write('new_map_init_2($hash_fn, $key_eq_fn, $clone_fn, $free_fn, $size, sizeof($key_typ_str), sizeof(voidptr), _MOV(($key_typ_str[$size]){') + } else { + g.write('new_map_init_2($hash_fn, $key_eq_fn, $clone_fn, $free_fn, $size, sizeof($key_typ_str), sizeof($value_typ_str), _MOV(($key_typ_str[$size]){') + } + for expr in node.keys { + g.expr(expr) + g.write(', ') + } + if value_typ.kind == .function { + g.write('}), _MOV((voidptr[$size]){') + } else { + g.write('}), _MOV(($value_typ_str[$size]){') + } + for expr in node.vals { + g.expr(expr) + g.write(', ') + } + g.write('}))') + } else { + g.write('new_map_2(sizeof($key_typ_str), sizeof($value_typ_str), $hash_fn, $key_eq_fn, $clone_fn, $free_fn)') + } + if g.is_shared { + g.write('}, sizeof($shared_styp))') + } else if is_amp { + g.write('), sizeof($styp))') + } +} + fn (mut g Gen) select_expr(node ast.SelectExpr) { is_expr := node.is_expr || g.inside_ternary > 0 cur_line := if is_expr { diff --git a/vlib/v/gen/fn.v b/vlib/v/gen/fn.v index 6936cf65c5..67f3d2c91a 100644 --- a/vlib/v/gen/fn.v +++ b/vlib/v/gen/fn.v @@ -354,14 +354,11 @@ fn (mut g Gen) call_expr(node ast.CallExpr) { pub fn (g &Gen) unwrap_generic(typ table.Type) table.Type { if typ.has_flag(.generic) { sym := g.table.get_type_symbol(typ) - mut idx := 0 for i, generic_param in g.cur_fn.generic_params { if generic_param.name == sym.name { - idx = i - break + return g.cur_generic_types[i].derive(typ).clear_flag(.generic) } } - return g.cur_generic_types[idx].derive(typ).clear_flag(.generic) } return typ } diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 7166d3f9ee..eaec7f0448 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -558,6 +558,13 @@ fn (mut g JsGen) expr(node ast.Expr) { g.write("string('$text')") } ast.StructInit { + // TODO: once generic fns/unwrap_generic is implemented + // if node.unresolved { + // g.expr(ast.resolve_init(node, g.unwrap_generic(node.typ), g.table)) + // } else { + // // `user := User{name: 'Bob'}` + // g.gen_struct_init(node) + // } // `user := User{name: 'Bob'}` g.gen_struct_init(node) } @@ -1037,11 +1044,7 @@ fn (mut g JsGen) gen_struct_decl(node ast.StructDecl) { g.inc_indent() g.write('return `$js_name {') for i, field in node.fields { - g.write(if i == 0 { - ' ' - } else { - ', ' - }) + g.write(if i == 0 { ' ' } else { ', ' }) match g.typ(field.typ).split('.').last() { 'string' { g.write('$field.name: "\${this["$field.name"].toString()}"') } else { g.write('$field.name: \${this["$field.name"].toString()} ') } diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index 996dd28650..60a811e2a6 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -409,7 +409,8 @@ fn (mut p Parser) struct_init(short_syntax bool) ast.StructInit { p.check(.rcbr) } p.is_amp = saved_is_amp - node := ast.StructInit{ + return ast.StructInit{ + unresolved: typ.has_flag(.generic) typ: typ fields: fields update_expr: update_expr @@ -419,7 +420,6 @@ fn (mut p Parser) struct_init(short_syntax bool) ast.StructInit { is_short: no_keys pre_comments: pre_comments } - return node } fn (mut p Parser) interface_decl() ast.InterfaceDecl { diff --git a/vlib/v/tests/generics_method_test.v b/vlib/v/tests/generics_method_test.v index d3e556fc34..b4941a410c 100644 --- a/vlib/v/tests/generics_method_test.v +++ b/vlib/v/tests/generics_method_test.v @@ -44,10 +44,27 @@ fn (v Foo) new() T { fn test_generic_method_with_map_type() { foo := Foo{} - assert foo.new() == map[string]string{} + mut a := foo.new() + assert a == map[string]string{} + assert a.len == 0 + a['a'] = 'a' + assert a.len == 1 + assert a['a'] == 'a' } fn test_generic_method_with_array_type() { foo := Foo{} - assert foo.new<[]string>() == []string{} + mut a := foo.new<[]string>() + assert a == []string{} + assert a.len == 0 + a << 'a' + assert a.len == 1 + assert a[0] == 'a' +} + +fn test_generic_method_with_struct_type() { + foo := Foo{} + mut a := foo.new() + a.name = 'a' + assert a.name == 'a' } diff --git a/vlib/v/tests/generics_test.v b/vlib/v/tests/generics_test.v index aec6945664..153e0033b5 100644 --- a/vlib/v/tests/generics_test.v +++ b/vlib/v/tests/generics_test.v @@ -386,3 +386,25 @@ fn test_multi_generic_args() { assert multi_generic_args("Super", 2021) } +fn new() T { + return T{} +} + +fn test_generic_init() { + // array init + mut a := new<[]string>() + assert a.len == 0 + a << 'a' + assert a.len == 1 + assert a[0] == 'a' + // map init + mut b := new() + assert b.len == 0 + b['b'] = 'b' + assert b.len == 1 + assert b['b'] == 'b' + // struct init + mut c := new() + c.name = 'c' + assert c.name == 'c' +}