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
b.info('PARSE: ${parse_time}ms')
//
b.instantiate_generic_structs()
b.checker.check_files(b.parsed_files)
t2 := time.ticks()
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) {
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)

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

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

View File

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

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

View File

@ -82,7 +82,7 @@ fn (mut g Gen) gen_fn_decl(it ast.FnDecl) {
// foo<T>() => 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
}

View File

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

View File

@ -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>()`
// 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)

View File

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

View File

@ -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<int>()
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 `=`

View File

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

View File

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

View File

@ -40,22 +40,22 @@ fn test_foo() {
fn create<T>() {
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<T>(mut x T) {
println(x.foo) // = 'foo'
println(x.name) // = 'foo'
}
fn mut_arg2<T>(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<T> {
struct Repo<T,U> {
db DB
mut:
pub mut:
model T
permission U
}
fn new_repo<U>(db DB) Repo<U> {
return Repo<U>{db: db}
}
// TODO: multiple type generic struct needs fixing in return for fn
// fn new_repo<T>(db DB) Repo<T,U> {
// return Repo<T,Permission>{db: db}
// }
fn test_generic_struct() {
mut a := new_repo<User>(DB{})
a.model.name = 'joe'
mut b := Repo<User>{db: DB{}
mut a := Repo<User,Permission>{
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<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 }
fn p<T>(args ...T) {