generic structs: initial implementation

pull/5570/head
joe-conigliaro 2020-06-30 04:09:09 +10:00 committed by GitHub
parent 76176eddab
commit ab37dcaa9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 289 additions and 78 deletions

View File

@ -14,6 +14,7 @@ pub fn (mut b Builder) gen_c(v_files []string) string {
parse_time := t1 - t0 parse_time := t1 - t0
b.info('PARSE: ${parse_time}ms') b.info('PARSE: ${parse_time}ms')
// //
b.instantiate_generic_structs()
b.checker.check_files(b.parsed_files) b.checker.check_files(b.parsed_files)
t2 := time.ticks() t2 := time.ticks()
check_time := t2 - t1 check_time := t2 - t1

View File

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

View File

@ -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) { (exp_idx == table.char_type_idx && got_idx == table.charptr_type_idx) {
return true return true
} }
if expected == table.t_type && got == table.t_type { // TODO: this should no longer be needed
return true // if expected == table.t_type && got == table.t_type {
} // return true
// }
// # NOTE: use symbols from this point on for perf // # NOTE: use symbols from this point on for perf
got_type_sym := t.get_type_symbol(got) got_type_sym := t.get_type_symbol(got)
exp_type_sym := t.get_type_symbol(expected) exp_type_sym := t.get_type_symbol(expected)

View File

@ -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 { pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type {
left_type := c.expr(call_expr.left) 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 call_expr.left_type = left_type
left_type_sym := c.table.get_type_symbol(c.unwrap_generic(left_type)) left_type_sym := c.table.get_type_symbol(c.unwrap_generic(left_type))
method_name := call_expr.name 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 { if is_generic {
// We need the receiver to be T in cgen. // 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 { } else {
call_expr.receiver_type = method.args[0].typ 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) // TODO: impl typeof properly (probably not going to be a fn call)
return table.string_type 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' { if c.mod != '' && c.mod != 'main' {
// Need to prepend the module when adding a generic type to a function // Need to prepend the module when adding a generic type to a function
// `fn_gen_types['mymod.myfn'] == ['string', 'int']` // `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 { if f.is_deprecated {
c.warn('function `$f.name` has been deprecated', call_expr.pos) c.warn('function `$f.name` has been deprecated', call_expr.pos)
} }
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 call_expr.return_type = f.return_type
}
if f.return_type == table.void_type && if f.return_type == table.void_type &&
f.ctdefine.len > 0 && f.ctdefine !in c.pref.compile_defines { f.ctdefine.len > 0 && f.ctdefine !in c.pref.compile_defines {
call_expr.should_be_skipped = true 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 return f.return_type
} }
@ -1899,9 +1924,9 @@ fn (mut c Checker) stmts(stmts []ast.Stmt) {
[inline] [inline]
pub fn (c &Checker) unwrap_generic(typ table.Type) table.Type { 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
return c.cur_generic_type.derive(typ) return c.cur_generic_type.derive(typ).clear_flag(.generic)
} }
return typ return typ
} }

View File

@ -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 | 2 |
3 | fn main() { 3 | fn main() {
4 | x := A('test') 4 | x := AA('test')
| ~~~~~~ | ~~~~~~
5 | _ = match x { 5 | _ = match x {
6 | int { 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) 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() { 3 | fn main() {
4 | x := A('test') 4 | x := AA('test')
5 | _ = match x { 5 | _ = match x {
| ~~~~~~~~~ | ~~~~~~~~~
6 | int { 6 | int {

View File

@ -1,7 +1,7 @@
type A = int | string | f64 type AA = int | string | f64
fn main() { fn main() {
x := A('test') x := AA('test')
_ = match x { _ = match x {
int { int {
'int' 'int'

View File

@ -392,6 +392,26 @@ fn (mut g Gen) register_optional(t table.Type) string {
fn (g &Gen) cc_type(t table.Type) string { fn (g &Gen) cc_type(t table.Type) string {
sym := g.table.get_type_symbol(g.unwrap_generic(t)) sym := g.table.get_type_symbol(g.unwrap_generic(t))
mut styp := sym.name.replace('.', '__') 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__') { if styp.starts_with('C__') {
styp = styp[3..] styp = styp[3..]
if sym.kind == .struct_ { if sym.kind == .struct_ {
@ -2815,10 +2835,17 @@ fn (mut g Gen) write_types(types []table.TypeSymbol) {
continue continue
} }
// sym := g.table.get_type_symbol(typ) // sym := g.table.get_type_symbol(typ)
name := typ.name.replace('.', '__') mut name := typ.name.replace('.', '__')
match typ.info { match typ.info as info {
table.Struct { 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 // TODO avoid buffer manip
start_pos := g.type_definitions.len start_pos := g.type_definitions.len
if info.is_union { if info.is_union {

View File

@ -82,7 +82,7 @@ fn (mut g Gen) gen_fn_decl(it ast.FnDecl) {
// foo<T>() => foo_int(), foo_string() etc // foo<T>() => foo_int(), foo_string() etc
gen_name := g.typ(g.cur_generic_type) gen_name := g.typ(g.cur_generic_type)
name += '_' + gen_name 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 { // if g.pref.show_cc && it.is_builtin {
// println(name) // println(name)
@ -339,9 +339,9 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
[inline] [inline]
pub fn (g &Gen) unwrap_generic(typ table.Type) table.Type { 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
return g.cur_generic_type.derive(typ) return g.cur_generic_type.derive(typ).clear_flag(.generic)
} }
return typ return typ
} }

View File

@ -238,6 +238,7 @@ pub fn (mut g JsGen) typ(t table.Type) string {
.struct_ { .struct_ {
styp = g.struct_typ(sym.name) styp = g.struct_typ(sym.name)
} }
.generic_struct_instance {}
// 'multi_return_int_int' => '[number, number]' // 'multi_return_int_int' => '[number, number]'
.multi_return { .multi_return {
info := sym.info as table.MultiReturn info := sym.info as table.MultiReturn

View File

@ -34,7 +34,7 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp
p.check(.gt) // `>` p.check(.gt) // `>`
// In case of `foo<T>()` // In case of `foo<T>()`
// T is unwrapped and registered in the checker. // 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) 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() pos := p.tok.position()
mut arg_type := p.parse_type() mut arg_type := p.parse_type()
if is_mut { if is_mut {
if arg_type != table.t_type { if !arg_type.has_flag(.generic) {
p.check_fn_mutable_arguments(arg_type, pos) p.check_fn_mutable_arguments(arg_type, pos)
} }
// if arg_type.is_ptr() { // if arg_type.is_ptr() {
@ -444,7 +444,7 @@ fn (mut p Parser) fn_args() ([]table.Arg, bool, bool) {
pos := p.tok.position() pos := p.tok.position()
mut typ := p.parse_type() mut typ := p.parse_type()
if is_mut { if is_mut {
if typ != table.t_type { if !typ.has_flag(.generic) {
p.check_fn_mutable_arguments(typ, pos) p.check_fn_mutable_arguments(typ, pos)
} }
typ = typ.set_nr_muls(1) typ = typ.set_nr_muls(1)

View File

@ -246,6 +246,20 @@ pub fn (mut p Parser) parse_any_type(language table.Language, is_ptr bool) table
return table.bool_type return table.bool_type
} }
else { else {
if name.len == 1 && name[0].is_capital() {
return p.parse_generic_template_type(name)
}
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 / placeholder
// struct / enum // struct / enum
mut idx := p.table.find_type_idx(name) mut idx := p.table.find_type_idx(name)
@ -256,8 +270,58 @@ pub fn (mut p Parser) parse_any_type(language table.Language, is_ptr bool) table
idx = p.table.add_placeholder_type(name) idx = p.table.add_placeholder_type(name)
// println('NOT FOUND: $name - adding placeholder - $idx') // println('NOT FOUND: $name - adding placeholder - $idx')
return table.new_type(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)
} }

View File

@ -876,10 +876,12 @@ pub fn (mut p Parser) name_expr() ast.Expr {
p.check(.dot) p.check(.dot)
p.expr_mod = mod p.expr_mod = mod
} }
lit0_is_capital := p.tok.lit[0].is_capital()
// p.warn('name expr $p.tok.lit $p.peek_tok.str()') // p.warn('name expr $p.tok.lit $p.peek_tok.str()')
// fn call or type cast // fn call or type cast
if p.peek_tok.kind == .lpar || 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<int>() // foo() or foo<int>()
mut name := p.tok.lit mut name := p.tok.lit
if mod.len > 0 { if mod.len > 0 {
@ -922,10 +924,10 @@ pub fn (mut p Parser) name_expr() ast.Expr {
// println('calling $p.tok.lit') // println('calling $p.tok.lit')
node = p.call_expr(language, mod) 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) { !p.inside_for { // && (p.tok.lit[0].is_capital() || p.builtin_mod) {
return p.struct_init(false) // short_syntax: false 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` // `Color.green`
mut enum_name := p.check_name() mut enum_name := p.check_name()
if mod != '' { if mod != '' {
@ -1523,6 +1525,9 @@ fn (mut p Parser) type_decl() ast.TypeDecl {
end_pos := p.tok.position() end_pos := p.tok.position()
decl_pos := start_pos.extend(end_pos) decl_pos := start_pos.extend(end_pos)
name := p.check_name() 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{} mut sum_variants := []table.Type{}
if p.tok.kind == .assign { if p.tok.kind == .assign {
p.next() // TODO require `=` p.next() // TODO require `=`

View File

@ -32,13 +32,31 @@ fn (mut p Parser) struct_decl() ast.StructDecl {
p.next() // C || JS p.next() // C || JS
p.next() // . p.next() // .
} }
is_typedef := 'typedef' in p.attrs 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 { if language == .v && no_body {
p.error('`$p.tok.lit` lacks 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() { 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) 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_typedef: is_typedef
is_union: is_union is_union: is_union
is_ref_only: 'ref_only' in p.attrs is_ref_only: 'ref_only' in p.attrs
generic_types: generic_types
} }
mod: p.mod mod: p.mod
is_public: is_pub is_public: is_pub

View File

@ -16,7 +16,7 @@ import strings
pub type Type int pub type Type int
pub type TypeInfo = Alias | Array | ArrayFixed | Enum | FnType | Interface | Map | MultiReturn | pub type TypeInfo = Alias | Array | ArrayFixed | Enum | FnType | Interface | Map | MultiReturn |
Struct | SumType Struct | GenericStructInstance | SumType
pub enum Language { pub enum Language {
v v
@ -134,7 +134,7 @@ pub fn (t Type) derive(t_from Type) Type {
[inline] [inline]
pub fn new_type(idx int) Type { pub fn new_type(idx int) Type {
if idx < 1 || idx > 65535 { 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 return idx
} }
@ -215,9 +215,9 @@ pub const (
array_type_idx = 20 array_type_idx = 20
map_type_idx = 21 map_type_idx = 21
any_type_idx = 22 any_type_idx = 22
t_type_idx = 23 // t_type_idx = 23
any_flt_type_idx = 24 any_flt_type_idx = 23
any_int_type_idx = 25 any_int_type_idx = 24
) )
pub const ( pub const (
@ -267,7 +267,7 @@ pub const (
array_type = new_type(array_type_idx) array_type = new_type(array_type_idx)
map_type = new_type(map_type_idx) map_type = new_type(map_type_idx)
any_type = new_type(any_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_flt_type = new_type(any_flt_type_idx)
any_int_type = new_type(any_int_type_idx) any_int_type = new_type(any_int_type_idx)
) )
@ -321,6 +321,7 @@ pub enum Kind {
map map
any any
struct_ struct_
generic_struct_instance
multi_return multi_return
sum_type sum_type
alias alias
@ -505,12 +506,12 @@ pub fn (mut t Table) register_builtin_type_symbols() {
name: 'any' name: 'any'
mod: 'builtin' mod: 'builtin'
}) })
t.register_type_symbol({ // t.register_type_symbol({
kind: .any // kind: .any
name: 'T' // name: 'T'
mod: 'builtin' // mod: 'builtin'
is_public: true // is_public: true
}) // })
t.register_type_symbol({ t.register_type_symbol({
kind: .any_float kind: .any_float
name: 'any_float' name: 'any_float'
@ -619,6 +620,13 @@ pub mut:
is_typedef bool // C. [typedef] is_typedef bool // C. [typedef]
is_union bool is_union bool
is_ref_only bool is_ref_only bool
generic_types []Type
}
pub struct GenericStructInstance {
pub mut:
parent_idx int
generic_types []Type
} }
pub struct Interface { pub struct Interface {

View File

@ -40,22 +40,22 @@ fn test_foo() {
fn create<T>() { fn create<T>() {
a := T{} a := T{}
println(a.foo) println(a.name)
mut xx := T{} mut xx := T{}
xx.foo = 'foo' xx.name = 'foo'
println(xx.foo) println(xx.name)
assert xx.foo == 'foo' assert xx.name == 'foo'
xx.init() xx.init()
} }
struct User { struct User {
mut: mut:
foo string name string
} }
struct City { struct City {
mut: mut:
foo string name string
} }
fn (u User) init() { fn (u User) init() {
@ -65,12 +65,12 @@ fn (c City) init() {
} }
fn mut_arg<T>(mut x T) { fn mut_arg<T>(mut x T) {
println(x.foo) // = 'foo' println(x.name) // = 'foo'
} }
fn mut_arg2<T>(mut x T) T { fn mut_arg2<T>(mut x T) T {
println(x.foo) // = 'foo' println(x.name) // = 'foo'
return x return x
} }
@ -182,40 +182,65 @@ fn test_generic_fn_in_for_in_expression() {
assert value == 'a' assert value == 'a'
} }
} }
*/
// test generic struct // test generic struct
struct DB { struct DB {
driver string driver string
} }
struct User { struct Group {
db DB pub mut:
mut: name string
group_name string
}
struct Permission {
pub mut:
name string name string
} }
struct Repo<T> { struct Repo<T,U> {
db DB db DB
mut: pub mut:
model T model T
permission U
} }
fn new_repo<U>(db DB) Repo<U> { // TODO: multiple type generic struct needs fixing in return for fn
return Repo<U>{db: db} // fn new_repo<T>(db DB) Repo<T,U> {
} // return Repo<T,Permission>{db: db}
// }
fn test_generic_struct() { fn test_generic_struct() {
mut a := new_repo<User>(DB{}) mut a := Repo<User,Permission>{
a.model.name = 'joe' model: User{name: 'joe'}
mut b := Repo<User>{db: DB{}
} }
b.model.name = 'joe' // a.model.name = 'joe'
assert a.model.name == 'joe' assert a.model.name == 'joe'
assert b.model.name == 'joe' println('a.model.name: $a.model.name')
mut b := Repo<Group,Permission>{
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<User>(DB{})
// x.model.name = 'joe2'
// println(x.model.name)
} }
// /*
struct Abc{ x int y int z int } struct Abc{ x int y int z int }
fn p<T>(args ...T) { fn p<T>(args ...T) {