1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

implement generic structs

This commit is contained in:
joe-conigliaro 2019-12-21 11:53:58 +11:00 committed by Alexander Medvednikov
parent b3a402eb82
commit fbd9fedbfb
11 changed files with 246 additions and 41 deletions

View File

@ -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

View File

@ -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)
}
}

View File

@ -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)};'

View File

@ -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 {

View File

@ -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('*')

View File

@ -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]
}

View File

@ -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]
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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<T> {
db DB
mut:
model T
}
fn new_repo<U>(db DB) Repo<U> {
return Repo<U>{db: db}
}
fn test_generic_struct() {
mut a := new_repo<User>(DB{})
a.model.name = 'joe'
mut b := Repo<User>{db: DB{}}
b.model.name = 'joe'
assert a.model.name == 'joe'
assert b.model.name == 'joe'
}