From ab37dcaa9c0de86ee3be7673fdc42d8d0ba84cef Mon Sep 17 00:00:00 2001 From: joe-conigliaro Date: Tue, 30 Jun 2020 04:09:09 +1000 Subject: [PATCH] generic structs: initial implementation --- vlib/v/builder/c.v | 1 + vlib/v/builder/generics.v | 35 ++++++++++ vlib/v/checker/check_types.v | 7 +- vlib/v/checker/checker.v | 37 +++++++++-- vlib/v/checker/tests/match_expr_else.out | 8 +-- vlib/v/checker/tests/match_expr_else.vv | 4 +- vlib/v/gen/cgen.v | 33 +++++++++- vlib/v/gen/fn.v | 6 +- vlib/v/gen/js/js.v | 1 + vlib/v/parser/fn.v | 6 +- vlib/v/parser/parse_type.v | 82 +++++++++++++++++++++--- vlib/v/parser/parser.v | 11 +++- vlib/v/parser/struct.v | 25 +++++++- vlib/v/table/atypes.v | 40 +++++++----- vlib/v/tests/generics_test.v | 71 +++++++++++++------- 15 files changed, 289 insertions(+), 78 deletions(-) create mode 100644 vlib/v/builder/generics.v diff --git a/vlib/v/builder/c.v b/vlib/v/builder/c.v index c8d9e67de3..54c5e8c69f 100644 --- a/vlib/v/builder/c.v +++ b/vlib/v/builder/c.v @@ -14,6 +14,7 @@ pub fn (mut b Builder) gen_c(v_files []string) string { parse_time := t1 - t0 b.info('PARSE: ${parse_time}ms') // + b.instantiate_generic_structs() b.checker.check_files(b.parsed_files) t2 := time.ticks() check_time := t2 - t1 diff --git a/vlib/v/builder/generics.v b/vlib/v/builder/generics.v new file mode 100644 index 0000000000..fad76765ae --- /dev/null +++ b/vlib/v/builder/generics.v @@ -0,0 +1,35 @@ +module builder + +import v.table + +pub fn (b &Builder) instantiate_generic_structs() { + for idx, _ in b.table.types { + mut typ := &b.table.types[idx] + if typ.kind == .generic_struct_instance { + info := typ.info as table.GenericStructInstance + parent := b.table.types[info.parent_idx] + mut parent_info := *(parent.info as table.Struct) + mut fields := parent_info.fields.clone() + for i, _ in parent_info.fields { + mut field := fields[i] + if field.typ.has_flag(.generic) { + if parent_info.generic_types.len != info.generic_types.len { + // TODO: proper error + panic('generic template mismatch') + } + for j, gp in parent_info.generic_types { + if gp == field.typ { + field.typ = info.generic_types[j] + break + } + } + } + fields[i] = field + } + parent_info.generic_types = [] + typ.is_public = true + typ.kind = .struct_ + typ.info = {parent_info| fields: fields} + } + } +} \ No newline at end of file diff --git a/vlib/v/checker/check_types.v b/vlib/v/checker/check_types.v index d116e904d2..d63b48af09 100644 --- a/vlib/v/checker/check_types.v +++ b/vlib/v/checker/check_types.v @@ -53,9 +53,10 @@ pub fn (c &Checker) check_basic(got, expected table.Type) bool { (exp_idx == table.char_type_idx && got_idx == table.charptr_type_idx) { return true } - if expected == table.t_type && got == table.t_type { - return true - } + // TODO: this should no longer be needed + // if expected == table.t_type && got == table.t_type { + // return true + // } // # NOTE: use symbols from this point on for perf got_type_sym := t.get_type_symbol(got) exp_type_sym := t.get_type_symbol(expected) diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 2ff04da620..fa0f84f90e 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -743,7 +743,7 @@ fn (mut c Checker) check_map_and_filter(is_map bool, elem_typ table.Type, call_e pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type { left_type := c.expr(call_expr.left) - is_generic := left_type == table.t_type + is_generic := left_type.has_flag(.generic) call_expr.left_type = left_type left_type_sym := c.table.get_type_symbol(c.unwrap_generic(left_type)) method_name := call_expr.name @@ -886,7 +886,8 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type { } if is_generic { // We need the receiver to be T in cgen. - call_expr.receiver_type = table.t_type.derive(method.args[0].typ) + // 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.args[0].typ).set_flag(.generic) } else { call_expr.receiver_type = method.args[0].typ } @@ -932,7 +933,7 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type { // TODO: impl typeof properly (probably not going to be a fn call) return table.string_type } - if call_expr.generic_type == table.t_type { + if call_expr.generic_type.has_flag(.generic) { if c.mod != '' && c.mod != 'main' { // Need to prepend the module when adding a generic type to a function // `fn_gen_types['mymod.myfn'] == ['string', 'int']` @@ -1015,7 +1016,28 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type { if f.is_deprecated { c.warn('function `$f.name` has been deprecated', call_expr.pos) } - call_expr.return_type = f.return_type + if f.is_generic { + rts := c.table.get_type_symbol(f.return_type) + if rts.kind == .struct_ { + rts_info := rts.info as table.Struct + if rts_info.generic_types.len > 0 { + // TODO: multiple generic types + // for gt in rts_info.generic_types { + // gtss := c.table.get_type_symbol(gt) + // } + gts := c.table.get_type_symbol(call_expr.generic_type) + nrt := '${rts.name}<$gts.name>' + idx := c.table.type_idxs[nrt] + if idx == 0 { + c.error('unknown type: $nrt', call_expr.pos) + } + call_expr.return_type = table.new_type(idx).derive(f.return_type) + } + } + } + else { + call_expr.return_type = f.return_type + } if f.return_type == table.void_type && f.ctdefine.len > 0 && f.ctdefine !in c.pref.compile_defines { call_expr.should_be_skipped = true @@ -1123,6 +1145,9 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type { } } } + if f.is_generic { + return call_expr.return_type + } return f.return_type } @@ -1899,9 +1924,9 @@ fn (mut c Checker) stmts(stmts []ast.Stmt) { [inline] pub fn (c &Checker) unwrap_generic(typ table.Type) table.Type { - if typ.idx() == table.t_type_idx { + if typ.has_flag(.generic) { // return c.cur_generic_type - return c.cur_generic_type.derive(typ) + return c.cur_generic_type.derive(typ).clear_flag(.generic) } return typ } diff --git a/vlib/v/checker/tests/match_expr_else.out b/vlib/v/checker/tests/match_expr_else.out index f62e043fd8..6e4473eda5 100644 --- a/vlib/v/checker/tests/match_expr_else.out +++ b/vlib/v/checker/tests/match_expr_else.out @@ -1,13 +1,13 @@ -vlib/v/checker/tests/match_expr_else.v:4:9: error: cannot cast a string +vlib/v/checker/tests/match_expr_else.v:4:10: error: cannot cast a string 2 | 3 | fn main() { - 4 | x := A('test') - | ~~~~~~ + 4 | x := AA('test') + | ~~~~~~ 5 | _ = match x { 6 | int { vlib/v/checker/tests/match_expr_else.v:5:6: error: match must be exhaustive (add match branches for: `f64` or `else {}` at the end) 3 | fn main() { - 4 | x := A('test') + 4 | x := AA('test') 5 | _ = match x { | ~~~~~~~~~ 6 | int { diff --git a/vlib/v/checker/tests/match_expr_else.vv b/vlib/v/checker/tests/match_expr_else.vv index af97dcf3da..7c5551e2e1 100644 --- a/vlib/v/checker/tests/match_expr_else.vv +++ b/vlib/v/checker/tests/match_expr_else.vv @@ -1,7 +1,7 @@ -type A = int | string | f64 +type AA = int | string | f64 fn main() { - x := A('test') + x := AA('test') _ = match x { int { 'int' diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 318c9b8fc7..b99b22b4c7 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -392,6 +392,26 @@ fn (mut g Gen) register_optional(t table.Type) string { fn (g &Gen) cc_type(t table.Type) string { sym := g.table.get_type_symbol(g.unwrap_generic(t)) mut styp := sym.name.replace('.', '__') + if sym.kind == .struct_ { + // TODO: maybe keep c name in info ( this is yuck ) + info := sym.info as table.Struct + if info.generic_types.len > 0 { + mut sgts := '_T' + for gt in info.generic_types { + gts := g.table.get_type_symbol(if gt.has_flag(.generic) { + g.unwrap_generic(gt) + } else { + gt + }) + sgts += '_$gts.name' + } + styp += sgts + } + else { + // TODO: maybe keep c name in info ( this is yuck ) + styp = styp.replace('<', '_T_').replace('>', '').replace(',', '_') + } + } if styp.starts_with('C__') { styp = styp[3..] if sym.kind == .struct_ { @@ -2815,10 +2835,17 @@ fn (mut g Gen) write_types(types []table.TypeSymbol) { continue } // sym := g.table.get_type_symbol(typ) - name := typ.name.replace('.', '__') - match typ.info { + mut name := typ.name.replace('.', '__') + match typ.info as info { table.Struct { - info := typ.info as table.Struct + if info.generic_types.len > 0 { + continue + } + // TODO: maybe keep c name in info ( this is yuck ) + name = name.replace('<', '_T_').replace('>', '').replace(',', '_') + if name.contains('_T_') { + g.typedefs.writeln('typedef struct $name $name;') + } // TODO avoid buffer manip start_pos := g.type_definitions.len if info.is_union { diff --git a/vlib/v/gen/fn.v b/vlib/v/gen/fn.v index 460085ee8b..8da057d45c 100644 --- a/vlib/v/gen/fn.v +++ b/vlib/v/gen/fn.v @@ -82,7 +82,7 @@ fn (mut g Gen) gen_fn_decl(it ast.FnDecl) { // foo() => foo_int(), foo_string() etc gen_name := g.typ(g.cur_generic_type) name += '_' + gen_name - type_name = type_name.replace('T', gen_name) + // type_name = type_name.replace('T', gen_name) } // if g.pref.show_cc && it.is_builtin { // println(name) @@ -339,9 +339,9 @@ fn (mut g Gen) call_expr(node ast.CallExpr) { [inline] pub fn (g &Gen) unwrap_generic(typ table.Type) table.Type { - if typ.idx() == table.t_type_idx { + if typ.has_flag(.generic) { // return g.cur_generic_type - return g.cur_generic_type.derive(typ) + return g.cur_generic_type.derive(typ).clear_flag(.generic) } return typ } diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index e06630bbad..8dc1492112 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -238,6 +238,7 @@ pub fn (mut g JsGen) typ(t table.Type) string { .struct_ { styp = g.struct_typ(sym.name) } + .generic_struct_instance {} // 'multi_return_int_int' => '[number, number]' .multi_return { info := sym.info as table.MultiReturn diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 534fb166f9..dffb5c0808 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -34,7 +34,7 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp p.check(.gt) // `>` // In case of `foo()` // T is unwrapped and registered in the checker. - if generic_type != table.t_type { + if !generic_type.has_flag(.generic) { p.table.register_fn_gen_type(fn_name, generic_type) } } @@ -391,7 +391,7 @@ fn (mut p Parser) fn_args() ([]table.Arg, bool, bool) { pos := p.tok.position() mut arg_type := p.parse_type() if is_mut { - if arg_type != table.t_type { + if !arg_type.has_flag(.generic) { p.check_fn_mutable_arguments(arg_type, pos) } // if arg_type.is_ptr() { @@ -444,7 +444,7 @@ fn (mut p Parser) fn_args() ([]table.Arg, bool, bool) { pos := p.tok.position() mut typ := p.parse_type() if is_mut { - if typ != table.t_type { + if !typ.has_flag(.generic) { p.check_fn_mutable_arguments(typ, pos) } typ = typ.set_nr_muls(1) diff --git a/vlib/v/parser/parse_type.v b/vlib/v/parser/parse_type.v index e1484d2b60..9d3bd347b7 100644 --- a/vlib/v/parser/parse_type.v +++ b/vlib/v/parser/parse_type.v @@ -246,18 +246,82 @@ pub fn (mut p Parser) parse_any_type(language table.Language, is_ptr bool) table return table.bool_type } else { - // struct / enum / placeholder - // struct / enum - mut idx := p.table.find_type_idx(name) - if idx > 0 { - return table.new_type(idx) + if name.len == 1 && name[0].is_capital() { + return p.parse_generic_template_type(name) } - // not found - add placeholder - idx = p.table.add_placeholder_type(name) - // println('NOT FOUND: $name - adding placeholder - $idx') - return table.new_type(idx) + if p.peek_tok.kind == .lt { + return p.parse_generic_struct_inst_type(name) + } + return p.parse_enum_or_struct_type(name) } } } } } + +pub fn (mut p Parser) parse_enum_or_struct_type(name string) table.Type { + // struct / enum / placeholder + // struct / enum + mut idx := p.table.find_type_idx(name) + if idx > 0 { + return table.new_type(idx) + } + // not found - add placeholder + idx = p.table.add_placeholder_type(name) + // println('NOT FOUND: $name - adding placeholder - $idx') + return table.new_type(idx) +} + +pub fn (mut p Parser) parse_generic_template_type(name string) table.Type { + mut idx := p.table.find_type_idx(name) + if idx > 0 { + return table.new_type(idx).set_flag(.generic) + } + idx = p.table.register_type_symbol(table.TypeSymbol{ + name: name + kind: .any + is_public: true + }) + return table.new_type(idx).set_flag(.generic) +} + +pub fn (mut p Parser) parse_generic_struct_inst_type(name string) table.Type { + mut bs_name := name + p.next() + bs_name += '<' + mut generic_types := []table.Type{} + mut is_instance := false + for { + gt := p.parse_type() + if !gt.has_flag(.generic) { + is_instance = true + } + gts := p.table.get_type_symbol(gt) + bs_name += gts.name + generic_types << gt + if p.tok.kind != .comma { + break + } + p.next() + bs_name += ',' + } + p.check(.gt) + bs_name += '>' + if is_instance && generic_types.len > 0 { + mut gt_idx := p.table.find_type_idx(bs_name) + if gt_idx > 0 { + return table.new_type(gt_idx) + } + gt_idx = p.table.add_placeholder_type(bs_name) + idx := p.table.register_type_symbol(table.TypeSymbol{ + kind: .generic_struct_instance + name: bs_name + info: table.GenericStructInstance{ + parent_idx: p.table.type_idxs[name] + generic_types: generic_types + } + }) + return table.new_type(idx) + } + return p.parse_enum_or_struct_type(name) +} diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index d6bd73fd89..a34554d7e8 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -876,10 +876,12 @@ pub fn (mut p Parser) name_expr() ast.Expr { p.check(.dot) p.expr_mod = mod } + lit0_is_capital := p.tok.lit[0].is_capital() // p.warn('name expr $p.tok.lit $p.peek_tok.str()') // fn call or type cast if p.peek_tok.kind == .lpar || - (p.peek_tok.kind == .lt && p.peek_tok2.kind == .name && p.peek_tok3.kind == .gt) { + (p.peek_tok.kind == .lt && !lit0_is_capital && p.peek_tok2.kind == .name && + p.peek_tok3.kind == .gt) { // foo() or foo() mut name := p.tok.lit if mod.len > 0 { @@ -922,10 +924,10 @@ pub fn (mut p Parser) name_expr() ast.Expr { // println('calling $p.tok.lit') node = p.call_expr(language, mod) } - } else if p.peek_tok.kind == .lcbr && !p.inside_match && !p.inside_match_case && !p.inside_if && + } else if (p.peek_tok.kind == .lcbr || (p.peek_tok.kind == .lt && lit0_is_capital)) && !p.inside_match && !p.inside_match_case && !p.inside_if && !p.inside_for { // && (p.tok.lit[0].is_capital() || p.builtin_mod) { return p.struct_init(false) // short_syntax: false - } else if p.peek_tok.kind == .dot && (p.tok.lit[0].is_capital() && !known_var && language == .v) { + } else if p.peek_tok.kind == .dot && (lit0_is_capital && !known_var && language == .v) { // `Color.green` mut enum_name := p.check_name() if mod != '' { @@ -1523,6 +1525,9 @@ fn (mut p Parser) type_decl() ast.TypeDecl { end_pos := p.tok.position() decl_pos := start_pos.extend(end_pos) name := p.check_name() + if name.len == 1 && name[0].is_capital() { + p.error_with_pos('single letter capital names are reserved for generic template types.', decl_pos) + } mut sum_variants := []table.Type{} if p.tok.kind == .assign { p.next() // TODO require `=` diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index 392c334c1b..e142a8dceb 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -32,13 +32,31 @@ fn (mut p Parser) struct_decl() ast.StructDecl { p.next() // C || JS p.next() // . } + is_typedef := 'typedef' in p.attrs - no_body := p.peek_tok.kind != .lcbr + end_pos := p.tok.position() + mut name := p.check_name() + if name.len == 1 && name[0].is_capital() { + p.error_with_pos('single letter capital names are reserved for generic template types.', end_pos) + } + mut generic_types := []table.Type{} + if p.tok.kind == .lt { + p.next() + for { + generic_types << p.parse_type() + if p.tok.kind != .comma { + break + } + p.next() + } + p.check(.gt) + } + + no_body := p.tok.kind != .lcbr if language == .v && no_body { p.error('`$p.tok.lit` lacks body') } - end_pos := p.tok.position() - mut name := p.check_name() + if language == .v && p.mod != 'builtin' && name.len > 0 && !name[0].is_capital() { p.error_with_pos('struct name `$name` must begin with capital letter', end_pos) } @@ -215,6 +233,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl { is_typedef: is_typedef is_union: is_union is_ref_only: 'ref_only' in p.attrs + generic_types: generic_types } mod: p.mod is_public: is_pub diff --git a/vlib/v/table/atypes.v b/vlib/v/table/atypes.v index 318f7321da..f92be44b87 100644 --- a/vlib/v/table/atypes.v +++ b/vlib/v/table/atypes.v @@ -16,7 +16,7 @@ import strings pub type Type int pub type TypeInfo = Alias | Array | ArrayFixed | Enum | FnType | Interface | Map | MultiReturn | - Struct | SumType + Struct | GenericStructInstance | SumType pub enum Language { v @@ -134,7 +134,7 @@ pub fn (t Type) derive(t_from Type) Type { [inline] pub fn new_type(idx int) Type { if idx < 1 || idx > 65535 { - panic('new_type_id: idx must be between 1 & 65535') + panic('new_type: idx must be between 1 & 65535') } return idx } @@ -215,9 +215,9 @@ pub const ( array_type_idx = 20 map_type_idx = 21 any_type_idx = 22 - t_type_idx = 23 - any_flt_type_idx = 24 - any_int_type_idx = 25 + // t_type_idx = 23 + any_flt_type_idx = 23 + any_int_type_idx = 24 ) pub const ( @@ -267,7 +267,7 @@ pub const ( array_type = new_type(array_type_idx) map_type = new_type(map_type_idx) any_type = new_type(any_type_idx) - t_type = new_type(t_type_idx) + // t_type = new_type(t_type_idx) any_flt_type = new_type(any_flt_type_idx) any_int_type = new_type(any_int_type_idx) ) @@ -321,6 +321,7 @@ pub enum Kind { map any struct_ + generic_struct_instance multi_return sum_type alias @@ -505,12 +506,12 @@ pub fn (mut t Table) register_builtin_type_symbols() { name: 'any' mod: 'builtin' }) - t.register_type_symbol({ - kind: .any - name: 'T' - mod: 'builtin' - is_public: true - }) + // t.register_type_symbol({ + // kind: .any + // name: 'T' + // mod: 'builtin' + // is_public: true + // }) t.register_type_symbol({ kind: .any_float name: 'any_float' @@ -615,10 +616,17 @@ pub fn (kinds []Kind) str() string { pub struct Struct { pub mut: - fields []Field - is_typedef bool // C. [typedef] - is_union bool - is_ref_only bool + fields []Field + is_typedef bool // C. [typedef] + is_union bool + is_ref_only bool + generic_types []Type +} + +pub struct GenericStructInstance { +pub mut: + parent_idx int + generic_types []Type } pub struct Interface { diff --git a/vlib/v/tests/generics_test.v b/vlib/v/tests/generics_test.v index b9b9241c55..8fafd01247 100644 --- a/vlib/v/tests/generics_test.v +++ b/vlib/v/tests/generics_test.v @@ -40,22 +40,22 @@ fn test_foo() { fn create() { a := T{} - println(a.foo) + println(a.name) mut xx := T{} - xx.foo = 'foo' - println(xx.foo) - assert xx.foo == 'foo' + xx.name = 'foo' + println(xx.name) + assert xx.name == 'foo' xx.init() } struct User { mut: - foo string + name string } struct City { mut: - foo string + name string } fn (u User) init() { @@ -65,12 +65,12 @@ fn (c City) init() { } fn mut_arg(mut x T) { - println(x.foo) // = 'foo' + println(x.name) // = 'foo' } fn mut_arg2(mut x T) T { - println(x.foo) // = 'foo' + println(x.name) // = 'foo' return x } @@ -182,40 +182,65 @@ fn test_generic_fn_in_for_in_expression() { assert value == 'a' } } +*/ // test generic struct struct DB { driver string } -struct User { - db DB -mut: +struct Group { +pub mut: + name string + group_name string +} + +struct Permission { +pub mut: name string } -struct Repo { +struct Repo { db DB -mut: +pub mut: model T + permission U } -fn new_repo(db DB) Repo { - return Repo{db: db} -} +// TODO: multiple type generic struct needs fixing in return for fn +// fn new_repo(db DB) Repo { +// return Repo{db: db} +// } + fn test_generic_struct() { - mut a := new_repo(DB{}) - a.model.name = 'joe' - mut b := Repo{db: DB{} + mut a := Repo{ + model: User{name: 'joe'} } - b.model.name = 'joe' + // a.model.name = 'joe' assert a.model.name == 'joe' - assert b.model.name == 'joe' + println('a.model.name: $a.model.name') + + mut b := Repo{ + permission: Permission{name: 'superuser'} + } + b.model.name = 'admins' + assert b.model.name == 'admins' + assert b.permission.name == 'superuser' + println('b.model.name: $b.model.name') + println('b.permission.name: $b.permission.name') + + assert typeof(a.model) == 'User' + assert typeof(b.model) == 'Group' + println('typeof(a.model): ' + typeof(a.model)) + println('typeof(b.model): ' + typeof(b.model)) + + // mut x := new_repo(DB{}) + // x.model.name = 'joe2' + // println(x.model.name) } -// - +/* struct Abc{ x int y int z int } fn p(args ...T) {