diff --git a/vlib/compiler/cgen.v b/vlib/compiler/cgen.v index 593e829a62..e2ad829959 100644 --- a/vlib/compiler/cgen.v +++ b/vlib/compiler/cgen.v @@ -401,7 +401,7 @@ fn (v &V) type_definitions() string { } // everything except builtin will get sorted for t_name, t in v.table.typesmap { - if t_name in builtins { + if t_name in builtins || t.is_generic { continue } types << t diff --git a/vlib/compiler/expression.v b/vlib/compiler/expression.v index 796506d33d..c8b5bbc5ec 100644 --- a/vlib/compiler/expression.v +++ b/vlib/compiler/expression.v @@ -214,8 +214,8 @@ fn (p mut Parser) name_expr() string { p.error('cannot use `_` as value') } // generic type check - if name in p.cur_fn.dispatch_of.inst.keys() { - name = p.cur_fn.dispatch_of.inst[name] + if name in p.generic_dispatch.inst.keys() { + name = p.generic_dispatch.inst[name] } // Raw string (`s := r'hello \n ') if name == 'r' && p.peek() == .str && p.prev_tok != .str_dollar { @@ -349,7 +349,7 @@ fn (p mut Parser) name_expr() string { return enum_type.name } // normal struct init (non-C) - else if p.peek() == .lcbr { + else if p.peek() == .lcbr || p.peek() == .lt { return p.get_struct_type(name, false, ptr) } } diff --git a/vlib/compiler/fn.v b/vlib/compiler/fn.v index 9bcd9dd26a..a17c59fc23 100644 --- a/vlib/compiler/fn.v +++ b/vlib/compiler/fn.v @@ -38,7 +38,6 @@ mut: defer_text []string type_pars []string type_inst []TypeInst - dispatch_of TypeInst // current type inst of this generic instance generic_fn_idx int parser_idx int fn_name_token_idx int // used by error reporting @@ -349,8 +348,7 @@ fn (p mut Parser) fn_decl() { if p.tok == .lt { // instance (dispatch) if p.generic_dispatch.inst.size > 0 { - f.dispatch_of = p.generic_dispatch - rename_generic_fn_instance(mut f, f.dispatch_of) + rename_generic_fn_instance(mut f, &p.generic_dispatch) } else { f.is_generic = true @@ -475,8 +473,9 @@ fn (p mut Parser) fn_decl() { p.table.register_fn(f) } } - p.set_current_fn(EmptyFn) - p.skip_fn_body() + p.set_current_fn( EmptyFn ) + //p.skip_fn_body() + p.skip_block(true) return } else { @@ -604,6 +603,22 @@ fn (p mut Parser) fn_decl() { p.returns = false } +[inline] +fn (p mut Parser) skip_block(inside_lcbr bool) { + mut cbr_depth := if inside_lcbr { 1 } else { 0 } + for { + if p.tok == .lcbr { + cbr_depth++ + } + if p.tok == .rcbr { + cbr_depth-- + if cbr_depth == 0 { break } + } + p.next() + } + p.check(.rcbr) +} + [inline] // Skips the entire function's body in the first pass. fn (p mut Parser) skip_fn_body() { @@ -769,10 +784,16 @@ fn (p mut Parser) fn_call(f mut Fn, method_ph int, receiver_var, receiver_type s } f.is_used = true cgen_name := p.table.fn_gen_name(f) - p.next() // fn name + p.next() // fn name + mut generic_param_types := []string if p.tok == .lt { - mut i := p.token_idx + p.check(.lt) + mut i := 0 for { + param_type := p.check_name() + generic_param_types << param_type + if p.tok != .comma { break } + p.check(.comma) if p.tokens[i].tok == .gt { p.error('explicit type arguments are not allowed; remove `<...>`') } @@ -782,6 +803,17 @@ fn (p mut Parser) fn_call(f mut Fn, method_ph int, receiver_var, receiver_type s } i++ } + p.check(.gt) + // mut i := p.token_idx + // for { + // if p.tokens[i].tok == .gt { + // //p.error('explicit type arguments are not allowed; remove `<...>`') + // } else if p.tokens[i].tok == .lpar { + // // probably a typo, do not concern the user with the above error message + // break + // } + // i++ + // } } // if p.pref.is_prof { // p.cur_fn.called_fns << cgen_name @@ -838,7 +870,7 @@ fn (p mut Parser) fn_call(f mut Fn, method_ph int, receiver_var, receiver_type s // if f is generic, the name is changed to a suitable instance in dispatch_generic_fn_instance() // we then replace `cgen_name` with the instance's name generic := f.is_generic - p.fn_call_args(mut f) + p.fn_call_args(mut f, generic_param_types) if generic { line := if p.cgen.is_tmp { p.cgen.tmp_line } else { p.cgen.cur_line } p.cgen.resetln(line.replace('$cgen_name (', '$f.name (')) @@ -975,7 +1007,7 @@ fn (p mut Parser) fn_args(f mut Fn) { } // foo *(1, 2, 3, mut bar)* -fn (p mut Parser) fn_call_args(f mut Fn) { +fn (p mut Parser) fn_call_args(f mut Fn, generic_param_types []string) { // println('fn_call_args() name=$f.name args.len=$f.args.len') // C func. # of args is not known p.check(.lpar) @@ -1007,7 +1039,8 @@ fn (p mut Parser) fn_call_args(f mut Fn) { file_path := cescaped_path(p.file_path) p.cgen.resetln(p.cgen.cur_line.replace('v_panic (', 'panic_debug ($p.scanner.line_nr, tos3("$file_path"), tos3("$mod_name"), tos2((byte *)"$fn_name"), ')) } - mut saved_args := []string + //mut saved_args := []string + mut saved_args := generic_param_types for i, arg in f.args { // Receiver is the first arg // Skip the receiver, because it was already generated in the expression @@ -1382,6 +1415,10 @@ fn replace_generic_type_params(f mut Fn, ti &TypeInst) { } f.args = args f.typ = replace_generic_type(f.typ, ti) + if f.typ.ends_with('_T') { + par := ti.inst.keys()[0] + f.typ = f.typ + '_' + ti.inst[par] + } } fn (p mut Parser) register_vargs_stuct(typ string, len int) string { @@ -1483,9 +1520,6 @@ fn (p mut Parser) register_multi_return_stuct(types []string) string { } fn rename_generic_fn_instance(f mut Fn, ti &TypeInst) { - if f.is_method && f.dispatch_of.inst.size == 0 { - f.name = f.receiver_typ + '_' + f.name - } f.name = f.name + '_T' for k in ti.inst.keys() { f.name = f.name + '_' + type_to_safe_str(ti.inst[k]) @@ -1512,9 +1546,9 @@ fn (p mut Parser) dispatch_generic_fn_instance(f mut Fn, ti &TypeInst) { } f.type_inst << *ti p.table.register_fn(f) + if f.is_method { f.name = f.receiver_typ + '_' + f.name } rename_generic_fn_instance(mut f, ti) replace_generic_type_params(mut f, ti) - f.dispatch_of = *ti // TODO: Handle case where type not defined yet, see above // if f.typ in f.type_pars { f.typ = '_ANYTYPE_' } // if f.typ in ti.inst { @@ -1529,12 +1563,13 @@ fn (p mut Parser) dispatch_generic_fn_instance(f mut Fn, ti &TypeInst) { } mut gp := p.v.parsers[f.parser_idx] gp.is_vgen = true - gp.generic_dispatch = *ti saved_state := p.save_state() p.clear_state(false, true) gp.token_idx = f.generic_fn_idx + gp.generic_dispatch = *ti gp.next() gp.fn_decl() + gp.generic_dispatch = TypeInst{} p.cgen.lines_extra << p.cgen.lines p.restore_state(saved_state, false, true) p.cgen.fns << '${p.fn_signature(f)};' diff --git a/vlib/compiler/gen_c.v b/vlib/compiler/gen_c.v index c312e56082..f153355c90 100644 --- a/vlib/compiler/gen_c.v +++ b/vlib/compiler/gen_c.v @@ -472,7 +472,7 @@ fn (p mut Parser) gen_array_set(typ string, is_ptr, is_map bool, fn_ph, assign_p } // returns true in case of an early return -fn (p mut Parser) gen_struct_init(typ string, t Type) bool { +fn (p mut Parser) gen_struct_init(typ string, t &Type) bool { // TODO hack. If it's a C type, we may need to add "struct" before declaration: // a := &C.A{} ==> struct A* a = malloc(sizeof(struct A)); if p.is_c_struct_init { diff --git a/vlib/compiler/gen_js.v b/vlib/compiler/gen_js.v index c99915b383..d7418d1a21 100644 --- a/vlib/compiler/gen_js.v +++ b/vlib/compiler/gen_js.v @@ -163,7 +163,7 @@ fn (p mut Parser) gen_array_set(typ string, is_ptr, is_map bool,fn_ph, assign_po } // returns true in case of an early return -fn (p mut Parser) gen_struct_init(typ string, t Type) bool { +fn (p mut Parser) gen_struct_init(typ string, t &Type) bool { p.next() p.check(.lcbr) ptr := typ.contains('*') diff --git a/vlib/compiler/get_type.v b/vlib/compiler/get_type.v index e4dd53455f..0154006d74 100644 --- a/vlib/compiler/get_type.v +++ b/vlib/compiler/get_type.v @@ -125,7 +125,7 @@ fn (p mut Parser) get_type2() Type { p.check(.amp) } // generic type check - ti := p.cur_fn.dispatch_of.inst + ti := p.generic_dispatch.inst if p.lit in ti.keys() { typ += ti[p.lit] } diff --git a/vlib/compiler/main.v b/vlib/compiler/main.v index 7b680b59cc..76dcfcc42b 100644 --- a/vlib/compiler/main.v +++ b/vlib/compiler/main.v @@ -154,14 +154,17 @@ pub fn (v &V) finalize_compilation() { } pub fn (v mut V) add_parser(parser Parser) int { + pidx := v.parsers.len v.parsers << parser - pidx := v.parsers.len - 1 - v.file_parser_idx[os.realpath(parser.file_path)] = pidx + file_path := if filepath.is_abs(parser.file_path) { + parser.file_path } else { os.realpath(parser.file_path) } + v.file_parser_idx[file_path] = pidx return pidx } pub fn (v &V) get_file_parser_index(file string) ?int { - file_path := os.realpath(file) + file_path := if filepath.is_abs(file) { + file } else { os.realpath(file) } if file_path in v.file_parser_idx { return v.file_parser_idx[file_path] } diff --git a/vlib/compiler/parser.v b/vlib/compiler/parser.v index f0b5648c3a..80a00d1c71 100644 --- a/vlib/compiler/parser.v +++ b/vlib/compiler/parser.v @@ -473,7 +473,7 @@ fn (p mut Parser) parse(pass Pass) { p.const_decl() } .key_struct, .key_union, .key_interface { - p.struct_decl() + p.struct_decl([]) } .key_enum { p.enum_decl(false) @@ -497,7 +497,7 @@ fn (p mut Parser) parse(pass Pass) { p.attribute() } .key_struct, .key_interface, .key_union, .lsbr { - p.struct_decl() + p.struct_decl([]) } .key_const { p.const_decl() @@ -1035,7 +1035,7 @@ fn (p mut Parser) get_type() string { p.check(.amp) } // generic type check - ti := p.cur_fn.dispatch_of.inst + ti := p.generic_dispatch.inst if p.lit in ti.keys() { typ += ti[p.lit] } @@ -1090,6 +1090,25 @@ fn (p mut Parser) get_type() string { p.error('type `$t.name` is private') } } + // generic struct + if p.peek() == .lt { + p.next() + p.check(.lt) + typ = '${typ}_T' + mut type_params := []string + for p.tok != .gt { + type_param := p.check_name() + type_params << type_param + if p.generic_dispatch.inst.size > 0 { + if type_param in p.generic_dispatch.inst { + typ = '${typ}_' + p.generic_dispatch.inst[type_param] + } + } + if p.tok == .comma { p.check(.comma) } + } + p.check(.gt) + return typ + } if typ == 'void' { p.error('unknown type `$typ`') } @@ -1829,7 +1848,7 @@ fn (p mut Parser) var_expr(v Var) string { if p.base_type(typ).starts_with('fn ') && p.tok == .lpar { T := p.table.find_type(p.base_type(typ)) p.gen('(') - p.fn_call_args(mut T.func) + p.fn_call_args(mut T.func, []) p.gen(')') typ = T.func.typ } @@ -1839,7 +1858,7 @@ fn (p mut Parser) var_expr(v Var) string { if p.base_type(typ).starts_with('fn ') && p.tok == .lpar { T := p.table.find_type(p.base_type(typ)) p.gen('(') - p.fn_call_args(mut T.func) + p.fn_call_args(mut T.func, []) p.gen(')') typ = T.func.typ } @@ -2014,7 +2033,7 @@ pub: p.gen('$dot$field.name') p.gen('(') p.check(.name) - p.fn_call_args(mut f) + p.fn_call_args(mut f, []) p.gen(')') return f.typ } @@ -2915,7 +2934,7 @@ fn (p mut Parser) attribute() { return } else if p.tok == .key_struct { - p.struct_decl() + p.struct_decl([]) p.attr = '' return } diff --git a/vlib/compiler/struct.v b/vlib/compiler/struct.v index dc229edb7e..084f71d369 100644 --- a/vlib/compiler/struct.v +++ b/vlib/compiler/struct.v @@ -7,9 +7,8 @@ import ( strings ) // also unions and interfaces - - -fn (p mut Parser) struct_decl() { +fn (p mut Parser) struct_decl(generic_param_types []string) { + decl_tok_idx := p.cur_tok_index() is_pub := p.tok == .key_pub if is_pub { p.next() @@ -40,6 +39,31 @@ fn (p mut Parser) struct_decl() { if is_interface && !name.ends_with('er') { p.error('interface names temporarily have to end with `er` (e.g. `Speaker`, `Reader`)') } + + mut generic_types := map[string]string + mut is_generic := false + if p.tok == .lt { + p.check(.lt) + mut i := 0 + for { + if generic_param_types.len > 0 && i != generic_param_types.len-1 { + p.error('mismatched generic type params') + } + type_param := p.check_name() + if generic_param_types.len > 0 { + generic_types[type_param] = generic_param_types[i] + } else { + generic_types[type_param] = '' + } + if p.tok != .comma { break } + p.check(.comma) + i++ + } + p.check(.gt) + is_generic = true + } + is_generic_instance := is_generic && generic_param_types.len > 0 + is_c := name == 'C' && p.tok == .dot if is_c { /* @@ -81,6 +105,11 @@ fn (p mut Parser) struct_decl() { kind := if is_union { 'union' } else { 'struct' } p.gen_typedef('typedef $kind $name $name;') } + parser_idx := p.v.get_file_parser_index(p.file_path) or { 0 } + //if !p.scanner.is_vh { + // parser_idx = p.v.get_file_parser_index(p.file_path) or { panic('cant find parser idx for $p.file_path') } + // println('HERE: $parser_idx') + //} // Register the type mut is_ph := false if typ.is_placeholder { @@ -93,6 +122,9 @@ fn (p mut Parser) struct_decl() { typ.cat = cat typ.parent = objc_parent typ.is_public = is_pub || p.is_vh + typ.is_generic = is_generic && !is_generic_instance + typ.decl_tok_idx = decl_tok_idx + typ.parser_idx = parser_idx p.table.rewrite_type(typ) } else { @@ -103,6 +135,9 @@ fn (p mut Parser) struct_decl() { cat: cat parent: objc_parent is_public: is_pub || p.is_vh + is_generic: is_generic && !is_generic_instance + decl_tok_idx: decl_tok_idx + parser_idx: parser_idx } } // Struct `C.Foo` declaration, no body @@ -110,6 +145,18 @@ fn (p mut Parser) struct_decl() { p.table.register_type(typ) return } + // generic template + if is_generic && !is_generic_instance { + p.table.register_type(typ) + p.table.generic_struct_params[typ.name] = generic_types.keys() + //} + // TODO: re are skipping genrcic struct in gen (cgen.v) we can go as normal and remove this + p.skip_block(false) + return + } + if is_generic_instance { + typ.rename_generic_struct(generic_types) + } p.fspace() p.check(.lcbr) // Struct fields @@ -119,7 +166,7 @@ fn (p mut Parser) struct_decl() { mut names := []string // to avoid dup names TODO alloc perf mut fmt_max_len := p.table.max_field_len[name] // println('fmt max len = $max_len nrfields=$typ.fields.len pass=$p.pass') - if !is_ph && p.first_pass() { + if (!is_ph && p.first_pass()) || is_generic { p.table.register_type(typ) // println('registering 1 nrfields=$typ.fields.len') } @@ -206,6 +253,12 @@ fn (p mut Parser) struct_decl() { // `pub` access mod // access_mod := if is_pub_field { AccessMod.public } else { AccessMod.private} p.fspace() + defer { if is_generic_instance { p.generic_dispatch=TypeInst{} } } + if is_generic_instance { + p.generic_dispatch=TypeInst{ + inst: generic_types + } + } tt := p.get_type2() field_type := tt.name if field_type == name { @@ -260,8 +313,9 @@ fn (p mut Parser) struct_decl() { } did_gen_something = true is_mut := access_mod in [.private_mut, .public_mut, .global] - if p.first_pass() { - p.table.add_field(typ.name, field_name, field_type, is_mut, attr, access_mod) + if p.first_pass() || is_generic { + p.table.add_field(typ.name, field_name, field_type, is_mut, + attr, access_mod) } p.fgen_nl() // newline between struct fields } @@ -278,13 +332,34 @@ fn (p mut Parser) struct_decl() { // p.fgenln('//kek') } // `User{ foo: bar }` -fn (p mut Parser) struct_init(typ string) string { +fn (p mut Parser) struct_init(typ_ string) string { p.is_struct_init = true - t := p.table.find_type(typ) + mut typ := typ_ + mut t := p.table.find_type(typ) if !t.is_public && t.mod != p.mod { p.warn('type `$t.name` is private') } - if p.gen_struct_init(typ, t) { + + // generic struct init + if p.peek() == .lt { + p.next() + p.check(.lt) + mut type_params := []string + for { + mut type_param := p.check_name() + if type_param in p.generic_dispatch.inst { + type_param = p.generic_dispatch.inst[type_param] + } + type_params << type_param + if p.tok != .comma { break } + p.check(.comma) + } + p.dispatch_generic_struct(mut t, type_params) + t = p.table.find_type(t.name) + typ = t.name + } + + if p.gen_struct_init(typ, &t) { return typ } ptr := typ.contains('*') @@ -416,3 +491,41 @@ fn (p mut Parser) struct_init(typ string) string { return typ } +fn (t mut Type) rename_generic_struct(generic_types map[string]string) { + t.name = t.name + '_T' + for _, v in generic_types { + t.name = t.name + '_' + type_to_safe_str(v) + } +} + +fn (p mut Parser) dispatch_generic_struct(t mut Type, type_params []string) { + mut generic_types := map[string]string + if t.name in p.table.generic_struct_params { + mut i := 0 + for _,v in p.table.generic_struct_params[t.name] { + generic_types[v] = type_params[i] + i++ + } + t.rename_generic_struct(generic_types) + if p.table.known_type(t.name) { + return + } + p.cgen.typedefs << 'typedef struct $t.name $t.name;\n' + } + + mut gp := p.v.parsers[t.parser_idx] + gp.is_vgen = true + saved_state := p.save_state() + p.clear_state(false, true) + gp.token_idx = t.decl_tok_idx + // FIXME: TODO: why are tokens cleared? + if gp.tokens.len == 0 { + gp.scanner.pos = 0 + gp.scan_tokens() + } + gp.next() + gp.struct_decl(type_params) + p.cgen.lines_extra << p.cgen.lines + p.restore_state(saved_state, false, true) +} + diff --git a/vlib/compiler/table.v b/vlib/compiler/table.v index c0ac6d99c1..9415bb339a 100644 --- a/vlib/compiler/table.v +++ b/vlib/compiler/table.v @@ -20,6 +20,7 @@ pub mut: // enum_vals map[string][]string // names []Name max_field_len map[string]int // for vfmt: max_field_len['Parser'] == 12 + generic_struct_params map[string][]string } struct VargAccess { @@ -114,6 +115,8 @@ pub mut: enum_vals []string gen_types []string default_vals []string // `struct Foo { bar int = 2 }` + parser_idx int + decl_tok_idx int // `is_placeholder` is used for types that are not defined yet but are known to exist. // It allows having things like `fn (f Foo) bar()` before `Foo` is defined. // This information is needed in the first pass. @@ -121,6 +124,7 @@ pub mut: gen_str bool // needs `.str()` method generation is_flag bool // enum bitfield flag // max_field_len int + is_generic bool } struct TypeNode { diff --git a/vlib/compiler/tests/generic_test.v b/vlib/compiler/tests/generic_test.v index 4d642b8ab0..670cc1bc24 100644 --- a/vlib/compiler/tests/generic_test.v +++ b/vlib/compiler/tests/generic_test.v @@ -98,3 +98,34 @@ fn test_generic_fn_in_for_in_expression() { assert value == 'a' } } + +// test generic struct +struct DB { + driver string +} + +struct User { + db DB +mut: + name string +} + +struct Repo { + db DB +mut: + model T +} + +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{}} + b.model.name = 'joe' + assert a.model.name == 'joe' + assert b.model.name == 'joe' +} +