all: generic functions

pull/4967/head
Alexander Medvednikov 2020-05-21 03:58:50 +02:00
parent b8c028c727
commit ce1a181699
10 changed files with 89 additions and 32 deletions

View File

@ -226,6 +226,7 @@ pub:
pos token.Position
body_pos token.Position
file string
is_generic bool
pub mut:
return_type table.Type
}
@ -251,6 +252,7 @@ pub mut:
receiver_type table.Type // User
return_type table.Type
should_be_skipped bool
generic_type table.Type // TODO array, to support multiple types
}
pub struct CallArg {

View File

@ -37,7 +37,11 @@ pub fn (node &FnDecl) str(t &table.Table) string {
else if node.language == .js {
name = 'JS.$name'
}
f.write('fn ${receiver}${name}(')
f.write('fn ${receiver}${name}')
if node.is_generic {
f.write('<T>')
}
f.write('(')
for i, arg in node.args {
// skip receiver
// if (node.is_method || node.is_interface) && i == 0 {

View File

@ -320,7 +320,6 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type {
} else {
info = type_sym.info as table.Struct
}
if struct_init.is_short && struct_init.fields.len > info.fields.len {
c.error('too many fields', struct_init.pos)
}
@ -889,6 +888,13 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type {
c.error('unknown function: $fn_name', call_expr.pos)
return table.void_type
}
if call_expr.generic_type != table.void_type && f.return_type != 0 { // table.t_type {
// Handle `foo<T>() T` => `foo<int>() int` => return int
sym := c.table.get_type_symbol(f.return_type)
if sym.name == 'T' {
return call_expr.generic_type
}
}
if !found_in_args && call_expr.mod in ['builtin', 'main'] {
scope := c.file.scope.innermost(call_expr.pos.pos)
if _ := scope.find_var(fn_name) {
@ -896,8 +902,8 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type {
call_expr.pos)
}
}
if !f.is_pub && f.language == .v && !c.is_builtin_mod && !c.pref.is_test && f.mod != c.mod && f.name !=
'' && f.mod != '' {
if !f.is_pub && f.language == .v && !c.is_builtin_mod && !c.pref.is_test && f.mod != c.mod &&
f.name != '' && f.mod != '' {
c.warn('function `$f.name` is private. curmod=$c.mod fmod=$f.mod', call_expr.pos)
}
call_expr.return_type = f.return_type
@ -1536,7 +1542,8 @@ fn (mut c Checker) stmt(node ast.Stmt) {
mut scope := c.file.scope.innermost(it.pos.pos)
sym := c.table.get_type_symbol(typ)
if sym.kind == .map && !(it.key_var.len > 0 && it.val_var.len > 0) {
c.error('for in: cannot use one variable in map', it.pos)
c.error('declare a key and a value variable when ranging a map: `for key, val in map {`\n' +
'use `_` if you do not need the variable', it.pos)
}
if it.key_var.len > 0 {
key_type := match sym.kind {

View File

@ -95,6 +95,7 @@ mut:
is_builtin_mod bool
hotcode_fn_names []string
fn_main &ast.FnDecl // the FnDecl of the main function. Needed in order to generate the main function code *last*
cur_generic_type table.Type // `int`, `string`, etc in `foo<T>()`
}
const (
@ -290,6 +291,10 @@ pub fn (mut g Gen) write_typeof_functions() {
// V type to C type
fn (mut g Gen) typ(t table.Type) string {
mut styp := g.base_type(t)
if styp.len == 1 && g.cur_generic_type != 0 {
// T => int etc
return g.typ(g.cur_generic_type)
}
base := styp
if t.flag_is(.optional) {
if t.is_ptr() {
@ -365,12 +370,10 @@ typedef struct {
.alias {
parent := &g.table.types[typ.parent_idx]
styp := typ.name.replace('.', '__')
is_c_parent := parent.name.len > 2 && parent.name[0] == `C` && parent.name[1] == `.`
parent_styp := if is_c_parent {
'struct ' + parent.name[2..].replace('.', '__')
} else {
parent.name.replace('.', '__')
}
is_c_parent := parent.name.len > 2 && parent.name[0] == `C` && parent.name[1] ==
`.`
parent_styp := if is_c_parent { 'struct ' + parent.name[2..].replace('.', '__') } else { parent.name.replace('.',
'__') }
g.definitions.writeln('typedef $parent_styp $styp;')
}
.array {
@ -1712,8 +1715,8 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
g.expr(node.right)
g.write(')')
} else if node.op in [.plus, .minus, .mul, .div, .mod] && (left_sym.name[0].is_capital() ||
left_sym.name.contains('.')) && left_sym.kind != .alias ||
left_sym.kind == .alias && (left_sym.info as table.Alias).language == .c {
left_sym.name.contains('.')) && left_sym.kind != .alias || left_sym.kind == .alias && (left_sym.info as table.Alias).language ==
.c {
// !left_sym.is_number() {
g.write(g.typ(node.left_type))
g.write('_')
@ -2258,7 +2261,9 @@ fn (mut g Gen) go_back_out(n int) {
}
fn (mut g Gen) struct_init(struct_init ast.StructInit) {
skip_init := ['strconv__ftoa__Uf32', 'strconv__ftoa__Uf64', 'strconv__Float64u', 'struct stat', 'struct addrinfo']
skip_init := ['strconv__ftoa__Uf32', 'strconv__ftoa__Uf64', 'strconv__Float64u', 'struct stat',
'struct addrinfo'
]
styp := g.typ(struct_init.typ)
if styp in skip_init {
g.go_back_out(3)
@ -2274,7 +2279,7 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) {
g.writeln('($styp){')
}
// mut fields := []string{}
mut inited_fields := map[string]int // TODO this is done in checker, move to ast node
mut inited_fields := map[string]int{} // TODO this is done in checker, move to ast node
/*
if struct_init.fields.len == 0 && struct_init.exprs.len > 0 {
// Get fields for {a,b} short syntax. Fields array wasn't set in the parser.
@ -2378,7 +2383,7 @@ fn (mut g Gen) assoc(node ast.Assoc) {
}
styp := g.typ(node.typ)
g.writeln('($styp){')
mut inited_fields := map[string]int
mut inited_fields := map[string]int{}
for i, field in node.fields {
inited_fields[field] = i
}
@ -2863,7 +2868,9 @@ fn (mut g Gen) gen_map(node ast.CallExpr) {
g.expr(node.args[0].expr)
}
}
else { g.expr(node.args[0].expr) }
else {
g.expr(node.args[0].expr)
}
}
g.writeln(';')
g.writeln('\tarray_push(&$tmp, &ti);')
@ -2901,7 +2908,9 @@ fn (mut g Gen) gen_filter(node ast.CallExpr) {
g.expr(node.args[0].expr)
}
}
else { g.expr(node.args[0].expr) }
else {
g.expr(node.args[0].expr)
}
}
g.writeln(') array_push(&$tmp, &it); \n }')
g.write(s)
@ -3815,7 +3824,7 @@ fn (g &Gen) interface_table() string {
mut methods_struct_def := strings.new_builder(100)
methods_struct_def.writeln('$methods_struct_name {')
mut imethods := map[string]string{} // a map from speak -> _Speaker_speak_fn
mut methodidx := map[string]int
mut methodidx := map[string]int{}
for k, method in ityp.methods {
methodidx[method.name] = k
typ_name := '_${interface_name}_${method.name}_fn'

View File

@ -13,6 +13,16 @@ fn (mut g Gen) gen_fn_decl(it ast.FnDecl) {
return
}
is_main := it.name == 'main'
if it.is_generic && g.cur_generic_type == 0 { // need the cur_generic_type check to avoid inf. recursion
// loop thru each generic type and generate a function
for gen_type in g.table.fn_gen_types[it.name] {
g.cur_generic_type = gen_type
g.gen_fn_decl(it)
println(gen_type)
}
g.cur_generic_type = 0
return
}
//
if is_main && g.pref.is_liveshared {
return
@ -61,6 +71,10 @@ fn (mut g Gen) gen_fn_decl(it ast.FnDecl) {
} else {
name = c_name(name)
}
if g.cur_generic_type != 0 {
// foo<T>() => foo_int(), foo_string() etc
name += '_' + g.typ(g.cur_generic_type)
}
// if g.pref.show_cc && it.is_builtin {
// println(name)
// }
@ -293,7 +307,7 @@ fn (mut g Gen) method_call(node ast.CallExpr) {
verror('method receiver type is 0, this means there are some uchecked exprs')
}
// mut receiver_type_name := g.cc_type(node.receiver_type)
//mut receiver_type_name := g.typ(node.receiver_type)
// mut receiver_type_name := g.typ(node.receiver_type)
typ_sym := g.table.get_type_symbol(node.receiver_type)
mut receiver_type_name := typ_sym.name.replace('.', '__')
if typ_sym.kind == .interface_ {
@ -429,6 +443,10 @@ fn (mut g Gen) fn_call(node ast.CallExpr) {
// `json__encode` => `json__encode_User`
name += '_' + json_type_str.replace('.', '__')
}
if node.generic_type != table.void_type {
// `foo<int>()` => `foo_int()`
name += '_' + g.typ(node.generic_type)
}
// Generate tmp vars for values that have to be freed.
/*
mut tmps := []string{}

View File

@ -25,11 +25,13 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp
p.expr_mod = ''
is_or_block_used = true
}
mut generic_type := table.void_type
if p.tok.kind == .lt {
// `foo<int>(10)`
p.next() // `<`
p.parse_type()
generic_type = p.parse_type()
p.check(.gt) // `>`
p.table.register_fn_gen_type(fn_name, generic_type)
}
p.check(.lpar)
args := p.call_args()
@ -40,6 +42,7 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp
pos: first_pos.pos
len: last_pos.pos - first_pos.pos + last_pos.len
}
// `foo() or {}``
mut or_stmts := []ast.Stmt{}
if p.tok.kind == .key_orelse {
p.inside_or_expr = true
@ -80,6 +83,7 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp
stmts: or_stmts
is_used: is_or_block_used
}
generic_type: generic_type
}
return node
}
@ -284,6 +288,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
args: args
is_deprecated: is_deprecated
is_pub: is_pub
is_generic: is_generic
is_variadic: is_variadic
receiver: ast.Field{
name: rec_name

View File

@ -804,7 +804,7 @@ pub fn (mut p Parser) name_expr() ast.Expr {
// 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_tok.pos == p.peek_tok2.pos - 1) { // foo() or foo<int>()
p.peek_tok.pos == p.peek_tok2.pos - 1) { // foo() or foo<int>() TODO remove whitespace sensitivity
mut name := p.tok.lit
if mod.len > 0 {
name = '${mod}.$name'

View File

@ -191,6 +191,7 @@ pub const (
array_type_idx = 20
map_type_idx = 21
any_type_idx = 22
t_type_idx = 23
)
pub const (
@ -237,6 +238,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)
)
pub const (

View File

@ -15,7 +15,7 @@ pub mut:
modules []string // List of all modules registered by the application
cflags []cflag.CFlag
redefined_fns []string
fn_gen_types map[string][]string
fn_gen_types map[string][]Type
}
pub struct Fn {
@ -569,3 +569,9 @@ pub fn (table &Table) qualify_module(mod, file_path string) string {
}
return mod
}
pub fn (table &Table) register_fn_gen_type(fn_name string, typ Type) {
mut a := table.fn_gen_types[fn_name]
a << typ
table.fn_gen_types[fn_name] = a
}

View File

@ -1,11 +1,15 @@
fn test_todo() {}
fn test_todo() {
}
/*
// QTODO
fn simple<T>(p T) T {
return p
}
fn test_generic_fn() {
assert simple<int>(1) == 1
}
/*
fn sum<T>(l []T) T {
mut r := T(0)
for e in l {