From ce1a18169975386b4280c804ea70eb846d52ac6d Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Thu, 21 May 2020 03:58:50 +0200 Subject: [PATCH] all: generic functions --- vlib/v/ast/ast.v | 2 ++ vlib/v/ast/str.v | 6 +++++- vlib/v/checker/checker.v | 15 +++++++++---- vlib/v/gen/cgen.v | 43 ++++++++++++++++++++++--------------- vlib/v/gen/fn.v | 20 ++++++++++++++++- vlib/v/parser/fn.v | 7 +++++- vlib/v/parser/parser.v | 2 +- vlib/v/table/atypes.v | 2 ++ vlib/v/table/table.v | 8 ++++++- vlib/v/tests/generic_test.v | 16 ++++++++------ 10 files changed, 89 insertions(+), 32 deletions(-) diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 9318b67336..93f8886313 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -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 { diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index 184cb82048..3603a0035d 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -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('') + } + f.write('(') for i, arg in node.args { // skip receiver // if (node.is_method || node.is_interface) && i == 0 { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 8388376b05..7647532880 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -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` => `foo() 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 { diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 54ce09d698..745b1e5425 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -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()` } 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 } @@ -2860,10 +2865,12 @@ fn (mut g Gen) gen_map(node ast.CallExpr) { if it.kind == .function { g.writeln('${it.name}(it)') } else { - g.expr(node.args[0].expr) + 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);') @@ -2898,10 +2905,12 @@ fn (mut g Gen) gen_filter(node ast.CallExpr) { if it.kind == .function { g.writeln('${node.args[0]}(it)') } else { - g.expr(node.args[0].expr) + 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' @@ -3948,7 +3957,7 @@ fn (mut g Gen) array_init(it ast.ArrayInit) { g.write('0, ') } g.write('sizeof($elem_type_str), ') - if it.has_default || (it.has_len && it.elem_type == table.string_type) { + if it.has_default || (it.has_len && it.elem_type == table.string_type) { g.write('&_val_$it.pos.pos)') } else { g.write('0)') diff --git a/vlib/v/gen/fn.v b/vlib/v/gen/fn.v index 84b031a3fd..f8b8f1df8f 100644 --- a/vlib/v/gen/fn.v +++ b/vlib/v/gen/fn.v @@ -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() => 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()` => `foo_int()` + name += '_' + g.typ(node.generic_type) + } // Generate tmp vars for values that have to be freed. /* mut tmps := []string{} diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index c0f8efd0a9..18ba3fa713 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -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(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 diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 5dc6915862..209695cad4 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -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() + p.peek_tok.pos == p.peek_tok2.pos - 1) { // foo() or foo() TODO remove whitespace sensitivity mut name := p.tok.lit if mod.len > 0 { name = '${mod}.$name' diff --git a/vlib/v/table/atypes.v b/vlib/v/table/atypes.v index a3843fd3e6..79572338f3 100644 --- a/vlib/v/table/atypes.v +++ b/vlib/v/table/atypes.v @@ -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 ( diff --git a/vlib/v/table/table.v b/vlib/v/table/table.v index 24d12a8f22..564cb4a8d5 100644 --- a/vlib/v/table/table.v +++ b/vlib/v/table/table.v @@ -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 +} diff --git a/vlib/v/tests/generic_test.v b/vlib/v/tests/generic_test.v index 98afaa0ac8..ca3711a3de 100644 --- a/vlib/v/tests/generic_test.v +++ b/vlib/v/tests/generic_test.v @@ -1,11 +1,15 @@ -fn test_todo() {} - -/* -// QTODO -fn simple(p T) T { - return p +fn test_todo() { } +fn simple(p T) T { + return p +} + +fn test_generic_fn() { + assert simple(1) == 1 +} + +/* fn sum(l []T) T { mut r := T(0) for e in l {