From 8a31ee4b53c3869b649e6a8fa072731796efaf45 Mon Sep 17 00:00:00 2001 From: Simon Heuser <39399016+shsr04@users.noreply.github.com> Date: Sun, 20 Oct 2019 09:19:37 +0200 Subject: [PATCH] generic functions --- examples/hot_reload/bounce.v | 8 +- vlib/compiler/comptime.v | 19 +- vlib/compiler/fn.v | 465 ++++++++++++++++----- vlib/compiler/main.v | 42 +- vlib/compiler/parser.v | 414 +++++++++--------- vlib/compiler/table.v | 60 +-- vlib/compiler/tests/generic_test.v | 58 +++ vlib/compiler/tests/print_test.v | 3 + vlib/compiler/tests/repl/error.repl | 2 +- vlib/compiler/tests/repl/error_nosave.repl | 2 +- 10 files changed, 705 insertions(+), 368 deletions(-) create mode 100644 vlib/compiler/tests/generic_test.v create mode 100644 vlib/compiler/tests/print_test.v diff --git a/examples/hot_reload/bounce.v b/examples/hot_reload/bounce.v index 6e74cdf3ed..a2017b08e8 100644 --- a/examples/hot_reload/bounce.v +++ b/examples/hot_reload/bounce.v @@ -62,22 +62,22 @@ fn main() { } const ( - W = 50 + width = 50 ) [live] fn (game &Game) draw() { - game.gg.draw_rect(game.x, game.y, W, W, gx.rgb(255, 0, 0)) + game.gg.draw_rect(game.x, game.y, width, width, gx.rgb(255, 0, 0)) } fn (game mut Game) run() { for { game.x += game.dx game.y += game.dy - if game.y >= game.height - W || game.y <= 0 { + if game.y >= game.height - width || game.y <= 0 { game.dy = - game.dy } - if game.x >= game.width - W || game.x <= 0 { + if game.x >= game.width - width || game.x <= 0 { game.dx = - game.dx } // Refresh diff --git a/vlib/compiler/comptime.v b/vlib/compiler/comptime.v index c8172ce5cf..28b1062bf2 100644 --- a/vlib/compiler/comptime.v +++ b/vlib/compiler/comptime.v @@ -41,17 +41,17 @@ fn (p mut Parser) comp_time() { stack++ } else if p.tok == .rcbr { stack-- - } + } if p.tok == .eof { break - } + } if stack <= 0 && p.tok == .rcbr { //p.warn('exiting $stack') p.next() break - } + } p.next() - } + } } else { p.statements_no_rcbr() } @@ -185,8 +185,9 @@ fn (p mut Parser) chash() { flag = flag.replace('@VROOT', p.vroot) flag = flag.replace('@VMOD', v_modules_path) //p.log('adding flag "$flag"') - _ = p.table.parse_cflag(flag, p.mod) or { + _p := p.table.parse_cflag(flag, p.mod) or { p.error_with_token_index(err, p.cur_tok_index()-1) + return } } return @@ -219,16 +220,16 @@ fn (p mut Parser) chash() { $if js { for p.tok != .eof { p.next() - } + } } $else { p.next() - } + } } else { $if !js { if !p.can_chash { println('hash="$hash"') - println(hash.starts_with('include')) + if hash.starts_with('include') { println("include") } else {} p.error('bad token `#` (embedding C code is no longer supported)') } } @@ -307,7 +308,7 @@ fn (p mut Parser) gen_struct_str(typ Type) { is_public: true receiver_typ: typ.name }) - + mut sb := strings.new_builder(typ.fields.len * 20) sb.writeln('fn (a $typ.name) str() string {\nreturn') sb.writeln("'{") diff --git a/vlib/compiler/fn.v b/vlib/compiler/fn.v index 64a590dcfd..e7e2d9f891 100644 --- a/vlib/compiler/fn.v +++ b/vlib/compiler/fn.v @@ -32,10 +32,33 @@ mut: returns_error bool is_decl bool // type myfn fn(int, int) defer_text []string - //gen_types []string + is_generic bool + type_pars []string + type_inst []TypeInst + dispatch_of TypeInst // current type inst of this generic instance + body_idx int // idx of the first body statement fn_name_token_idx int // used by error reporting } +struct TypeInst { +mut: + // an instantiation of generic params (e.g. ["int","int","double"]) + inst map[string]string + done bool +} + +fn (a []TypeInst) str() string { + mut r := []string + for t in a { + mut s := ' | ' + for k in t.inst.keys() { + s += k+' -> '+ t.inst[k] +' | ' + } + r << s + } + return r.str() +} + fn (p &Parser) find_var(name string) ?Var { for i in 0 .. p.var_idx { if p.local_vars[i].name == name { @@ -71,21 +94,21 @@ fn (p mut Parser) open_scope() { fn (p mut Parser) mark_var_used(v Var) { if v.idx == -1 || v.idx >= p.local_vars.len { return - } + } p.local_vars[v.idx].is_used = true } fn (p mut Parser) mark_var_returned(v Var) { if v.idx == -1 || v.idx >= p.local_vars.len { return - } + } p.local_vars[v.idx].is_returned = true } fn (p mut Parser) mark_var_changed(v Var) { if v.idx == -1 || v.idx >= p.local_vars.len { return - } + } p.local_vars[v.idx].is_changed = true } @@ -95,15 +118,22 @@ fn (p mut Parser) mark_arg_moved(v Var) { //println('setting f $p.cur_fn.name arg $arg.name to is_mut') p.cur_fn.args[i].is_moved = true break - } - } + } + } p.table.fns[p.cur_fn.name] = p.cur_fn } fn (p mut Parser) known_var(name string) bool { _ = p.find_var(name) or { return false - } + } + return true +} + +fn (p mut Parser) known_var_check_new_var(name string) bool { + _ = p.find_var_check_new_var(name) or { + return false + } return true } @@ -138,7 +168,7 @@ fn (p mut Parser) clear_vars() { fn (p mut Parser) fn_decl() { p.clear_vars() // clear local vars every time a new fn is started p.fgen('fn ') - + //defer { p.fgenln('\n') } // If we are in the first pass, create a new function. // In the second pass fetch the one we created. @@ -149,12 +179,12 @@ fn (p mut Parser) fn_decl() { is_public: p.tok == .key_pub } else { - } + } */ mut f := Fn{ - mod: p.mod - is_public: p.tok == .key_pub - } + mod: p.mod + is_public: p.tok == .key_pub + } is_live := p.attr == 'live' && !p.pref.is_so && p.pref.is_live if p.attr == 'live' && p.first_pass() && !p.pref.is_live && !p.pref.is_so { println('INFO: run `v -live program.v` if you want to use [live] functions') @@ -243,7 +273,7 @@ fn (p mut Parser) fn_decl() { } if f.name[0] == `_` { p.error('function names cannot start with `_`, use snake_case instead') - } + } if f.name.contains('__') { p.error('function names cannot contain double underscores, use single underscores instead') } @@ -272,21 +302,23 @@ fn (p mut Parser) fn_decl() { } } // Generic? - mut is_generic := false if p.tok == .lt { - is_generic = true + f.is_generic = true p.next() - gen_type := p.check_name() - if gen_type != 'T' { - p.error('only `T` is allowed as a generic type for now') + for { + type_par := p.check_name() + if type_par.len > 1 || !(type_par in reserved_type_param_names) { + p.error('type parameters must be single-character, upper-case letters of the following set: $reserved_type_param_names') + } + if type_par in f.type_pars { + p.error('redeclaration of type parameter `$type_par`') + } + f.type_pars << type_par + if p.tok == .gt { break } + p.check(.comma) } + p.set_current_fn(f) p.check(.gt) - if p.first_pass() { - p.table.register_generic_fn(f.name) - } else { - //gen_types := p.table.fn_gen_types(f.name) - //println(gen_types) - } } // Args (...) p.fn_args(mut f) @@ -326,13 +358,7 @@ fn (p mut Parser) fn_decl() { p.error_with_token_index('fn main must have no arguments and no return values', f.fn_name_token_idx) } } - dll_export_linkage := if p.pref.ccompiler == 'msvc' && p.attr == 'live' && p.pref.is_so { - '__declspec(dllexport) ' - } else if p.attr == 'inline' { - 'static inline ' - } else { - '' - } + dll_export_linkage := p.get_linkage_prefix() if !p.is_vweb { p.set_current_fn( f ) } @@ -345,25 +371,18 @@ fn (p mut Parser) fn_decl() { if p.pref.obfuscate { p.genln('; // $f.name') } - // Generate this function's body for all generic types - if is_generic { - gen_types := p.table.fn_gen_types(f.name) - // Remember current scanner position, go back here for each type - // TODO remove this once tokens are cached in `new_parser()` - cur_pos := p.scanner.pos - cur_tok := p.tok - cur_lit := p.lit - for gen_type in gen_types { - p.genln('$dll_export_linkage$typ ${fn_name_cgen}_$gen_type($str_args) {') - p.genln('// T start $p.pass ${p.strtok()}') - p.cur_gen_type = gen_type // TODO support more than T - p.statements() - p.scanner.pos = cur_pos - p.tok = cur_tok - p.lit = cur_lit + // Generic functions are inserted as needed from the call site + if f.is_generic { + if p.first_pass() { + f.body_idx = p.cur_tok_index()+1 + p.table.register_fn(f) } - } - else { + p.check_unused_variables() + p.set_current_fn( EmptyFn ) + p.returns = false + p.skip_fn_body() + return + } else { p.gen_fn_decl(f, typ, str_args) } } @@ -441,10 +460,6 @@ fn (p mut Parser) fn_decl() { f.defer_text[f.scope_level] = ' ${cgen_name}_time += time__ticks() - _PROF_START;' } } - if is_generic { - // Don't need to generate body for the actual generic definition - p.cgen.nogen = true - } p.statements_no_rcbr() //p.cgen.nogen = false // Print counting result after all statements in main @@ -473,17 +488,13 @@ fn (p mut Parser) fn_decl() { } // Make sure all vars in this function are used (only in main for now) if p.mod != 'main' { - if !is_generic { - p.genln('}') - } + p.genln('}') return } + p.genln('}') p.check_unused_variables() p.set_current_fn( EmptyFn ) p.returns = false - if !is_generic { - p.genln('}') - } } [inline] @@ -515,6 +526,16 @@ fn (p mut Parser) skip_fn_body() { } } +fn (p Parser) get_linkage_prefix() string { + return if p.pref.ccompiler == 'msvc' && p.attr == 'live' && p.pref.is_so { + '__declspec(dllexport) ' + } else if p.attr == 'inline' { + 'static inline ' + } else { + '' + } +} + fn (p mut Parser) check_unused_variables() { for var in p.local_vars { if var.name == '' { @@ -609,7 +630,7 @@ fn (p mut Parser) async_fn_call(f Fn, method_ph int, receiver_var, receiver_type } // p.tok == fn_name -fn (p mut Parser) fn_call(f Fn, method_ph int, receiver_var, receiver_type string) { +fn (p mut Parser) fn_call(f mut Fn, method_ph int, receiver_var, receiver_type string) { if !f.is_public && !f.is_c && !p.pref.is_test && !f.is_interface && f.mod != p.mod { if f.name == 'contains' { println('use `value in numbers` instead of `numbers.contains(value)`') @@ -624,40 +645,48 @@ fn (p mut Parser) fn_call(f Fn, method_ph int, receiver_var, receiver_type strin p.error('use `malloc()` instead of `C.malloc()`') } } - mut cgen_name := p.table.fn_gen_name(f) - p.next() - mut gen_type := '' + cgen_name := p.table.fn_gen_name(f) + p.next() // fn name if p.tok == .lt { - p.check(.lt) - gen_type = p.check_name() - // run => run_App - if gen_type == 'T' && p.cur_gen_type != '' { - gen_type = p.cur_gen_type + 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 += 1 } - // `foo()` - // If we are in the first pass, we need to add `Bar` type to the generic function `foo`, - // so that generic `foo`s body can be generated for each type in the second pass. - if p.first_pass() { - println('registering $gen_type in $f.name fname=$f.name') - p.table.register_generic_fn_type(f.name, gen_type) - // Function bodies are skipped in the first passed, we only need to register the generic type here. - return - } - cgen_name += '_' + gen_type - p.check(.gt) } // if p.pref.is_prof { // p.cur_fn.called_fns << cgen_name // } - // Normal function call - if !f.is_method { - p.gen(cgen_name) - p.gen('(') - // p.fgen(f.name) + + $if windows { // TODO fix segfault caused by `dispatch_generic_fn_instance` on Windows + if f.is_generic { + p.check(.lpar) + mut b := 1 + for b > 0 { + if p.tok == .rpar { + b -= 1 + } else if p.tok == .lpar { + b += 1 + } + p.next() + } + p.gen('/* SKIPPED */') + p.warn('skipped call to generic function `$f.name`\n\tReason: generic functions are currently broken on Windows 10\n') + return + } } + // If we have a method placeholder, // we need to preappend "method(receiver, ...)" - else { + if f.is_method { + if f.is_generic { + p.error('generic methods are not yet implemented') + } receiver := f.args.first() //println('r=$receiver.typ RT=$receiver_type') if receiver.is_mut && !p.expr_var.is_mut { @@ -672,10 +701,23 @@ fn (p mut Parser) fn_call(f Fn, method_ph int, receiver_var, receiver_type strin if !p.expr_var.is_changed { p.mark_var_changed(p.expr_var) } - p.gen_method_call(receiver_type, f.typ, cgen_name, receiver, method_ph) + met_name := if f.is_generic { f.name } else { cgen_name } + p.gen_method_call(receiver_type, f.typ, met_name, receiver, method_ph) + } else { + // Normal function call + p.gen('$cgen_name (') } + // foo() + // 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) + if generic { + p.cgen.resetln(p.cgen.cur_line.replace('$cgen_name (', '$f.name (')) + // println('calling inst $f.name: $p.cgen.cur_line') + } + p.gen(')') p.calling_c = false // println('end of fn call typ=$f.typ') @@ -726,7 +768,7 @@ fn (p mut Parser) fn_args(f mut Fn) { p.fspace() is_mut := p.tok == .key_mut if is_mut { - p.next() + p.check(.key_mut) } mut typ := '' // variadic arg @@ -745,7 +787,7 @@ fn (p mut Parser) fn_args(f mut Fn) { } else { typ = p.get_type() } - + p.check_and_register_used_imported_type(typ) if is_mut && is_primitive_type(typ) { p.error('mutable arguments are only allowed for arrays, maps, and structs.' + @@ -771,7 +813,7 @@ fn (p mut Parser) fn_args(f mut Fn) { f.args << v } if p.tok == .comma { - p.next() + p.check(.comma) } // unnamed (C definition) if p.tok == .ellipsis { @@ -788,7 +830,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 { +fn (p mut Parser) fn_call_args(f mut Fn) { // println('fn_call_args() name=$f.name args.len=$f.args.len') // C func. # of args is not known p.check(.lpar) @@ -808,14 +850,14 @@ fn (p mut Parser) fn_call_args(f mut Fn) &Fn { // Cast V byteptr to C char* (byte is unsigned in V, that led to C warnings) if typ == 'byte*' { p.cgen.set_placeholder(ph, '(char*)') - } + } if p.tok == .comma { p.gen(', ') p.check(.comma) } } p.check(.rpar) - return f + return } // add debug information to panic when -g arg is passed if p.v.pref.is_debug && f.name == 'panic' && !p.is_js { @@ -827,6 +869,7 @@ fn (p mut Parser) fn_call_args(f mut Fn) &Fn { 'panic_debug ($p.scanner.line_nr, tos3("$file_path"), tos3("$mod_name"), tos2((byte *)"$fn_name"), ' )) } + mut saved_args := []string for i, arg in f.args { // Receiver is the first arg // Skip the receiver, because it was already generated in the expression @@ -857,7 +900,7 @@ fn (p mut Parser) fn_call_args(f mut Fn) &Fn { if i < f.args.len - 1 { dots_example = dots_example + ',..' } - p.error('`$arg.name` is a mutable argument, you need to provide `mut`: `$f.name($dots_example)`') + p.error('`$arg.name` is a mutable argument, you need to provide `mut`: `$f.name($dots_example)`') } if p.peek() != .name { p.error('`$arg.name` is a mutable argument, you need to provide a variable to modify: `$f.name(... mut a...)`') @@ -876,7 +919,7 @@ fn (p mut Parser) fn_call_args(f mut Fn) &Fn { clone := p.pref.autofree && arg.typ == 'string' && arg.is_moved && p.mod != 'builtin' if clone { p.gen('/*YY f=$f.name arg=$arg.name is_moved=$arg.is_moved*/string_clone(') - } + } mut typ := p.bool_expression() if typ.starts_with('...') { typ = typ.right(3) } if clone { @@ -923,7 +966,7 @@ fn (p mut Parser) fn_call_args(f mut Fn) &Fn { p.cgen.set_placeholder(ph, '${typ}_str(') p.gen(')') continue - } + } error_msg := ('`$typ` needs to have method `str() string` to be printable') p.error(error_msg) } @@ -937,11 +980,15 @@ fn (p mut Parser) fn_call_args(f mut Fn) &Fn { got_ptr := got.ends_with('*') exp_ptr := expected.ends_with('*') // println('fn arg got="$got" exp="$expected"') - if !p.check_types_no_throw(got, expected) { + type_mismatch := !p.check_types_no_throw(got, expected) + if type_mismatch && f.is_generic { + // println("argument `$arg.name` is generic") + saved_args << got + } else if type_mismatch { mut j := i if f.is_method { j-- - } + } mut nr := '${i+1}th' if j == 0 { nr = 'first' @@ -949,9 +996,11 @@ fn (p mut Parser) fn_call_args(f mut Fn) &Fn { nr = 'second' } else if j == 2 { nr = 'third' - } + } p.error('cannot use type `$typ` as type `$arg.typ` in $nr ' + 'argument to `$f.name()`') + } else { + saved_args << '' } is_interface := p.table.is_interface(arg.typ) // Automatically add `&` or `*` before an argument. @@ -1019,7 +1068,105 @@ fn (p mut Parser) fn_call_args(f mut Fn) &Fn { p.error('wrong number of arguments for fn `$f.name`: expected $f.args.len, but got more') } p.check(.rpar) - return f // TODO is return f right? + if f.is_generic { + type_map := p.extract_type_inst(f, saved_args) + p.dispatch_generic_fn_instance(mut f, type_map) + } +} + +// From a given generic function and an argument list matching its signature, +// create a type instantiation +fn (p mut Parser) extract_type_inst(f &Fn, args_ []string) TypeInst { + mut r := TypeInst{} + mut i := 0 + mut args := args_ + args << f.typ + for ai, e in args { + if e == '' { continue } + tp := f.type_pars[i] + mut ti := e + if ti.starts_with('fn (') { + fn_args := ti.right(4).all_before(') ').split(',') + mut found := false + for fa_ in fn_args { + mut fa := fa_ + for fa.starts_with('array_') { fa = fa.right(6) } + if fa == tp { + r.inst[tp] = fa + found = true + i += 1 + break + } + } + if found { continue } + ti = ti.all_after(') ') + } + for ti.starts_with('array_') { ti = ti.right(6) } + if r.inst[tp] != '' { + if r.inst[tp] != ti { + p.error('type parameter `$tp` has type ${r.inst[tp]}, not `$ti`') + } + continue + } + // println("extracted $tp => $ti") + r.inst[tp] = ti + i += 1 + if i >= f.type_pars.len { break } + } + if r.inst[f.typ] == '' && f.typ in f.type_pars { + r.inst[f.typ] = '_ANYTYPE_' + } + return r +} + +// Replace type params of a given generic function using a TypeInst +fn (p mut Parser) replace_type_params(f &Fn, ti TypeInst) []string { + mut sig := []string + for a in f.args { + sig << a.typ + } + sig << f.typ + mut r := []string + for _, a in sig { + mut fi := a + mut fr := '' + if fi.starts_with('fn (') { + fr += 'fn (' + mut fn_args := fi.right(4).all_before(') ').split(',') + fn_args << fi.all_after(') ') + for i, fa_ in fn_args { + mut fna := fa_.trim_space() + for fna.starts_with('array_') { + fna = fna.right(6) + fr += 'array_' + } + if fna in ti.inst.keys() { + fr += ti.inst[fna] + } else { + fr += fna + } + if i <= fn_args.len-3 { + fr += ',' + } else if i == fn_args.len-2 { + fr += ') ' + } + } + r << fr + continue + } + for fi.starts_with('array_') { + fi = fi.right(6) + fr += 'array_' + } + if fi in ti.inst.keys() { + fr += ti.inst[fi] + // println("replaced $a => $fr") + } else { + fr += fi + } + r << fr + } + return r } fn (p mut Parser) fn_register_vargs_stuct(f &Fn, typ string, values []string) string { @@ -1067,7 +1214,7 @@ fn (p mut Parser) fn_gen_caller_vargs(f mut Fn) { } vargs_struct := p.fn_register_vargs_stuct(f, varg_def_type, values) p.cgen.gen('&($vargs_struct){.len=$values.len,.args={'+values.join(',')+'}}') - + } fn (p mut Parser) register_multi_return_stuct(types []string) string { @@ -1085,6 +1232,118 @@ fn (p mut Parser) register_multi_return_stuct(types []string) string { return typ } +fn (p mut Parser) dispatch_generic_fn_instance(f mut Fn, ti TypeInst) { + $if windows { + p.error('feature disabled on Windows') + } + mut new_inst := true + for e in f.type_inst { + if e.inst.str() == ti.inst.str() { + new_inst = false + break + } + } + + if !new_inst { + f.name = f.name + '_T' + for k in ti.inst.keys() { + f.name = f.name + '_' + type_to_safe_str(ti.inst[k]) + } + _f := p.table.find_fn(f.name) or { + p.error('function instance `$f.name` not found') + return + } + f.args = _f.args + f.typ = _f.typ + f.is_generic = false + f.type_inst = []TypeInst + f.dispatch_of = ti + // println('using existing inst $f.name(${f.str_args(p.table)}) $f.typ') + return + } + + f.type_inst << ti + p.table.register_fn(f) + // Remember current scanner position, go back here for each type instance + // TODO remove this once tokens are cached in `new_parser()` + saved_tok_idx := p.cur_tok_index() + saved_fn := p.cur_fn + saved_var_idx := p.var_idx + saved_local_vars := p.local_vars + p.clear_vars() + saved_line := p.cgen.cur_line + saved_lines := p.cgen.lines + saved_is_tmp := p.cgen.is_tmp + saved_tmp_line := p.cgen.tmp_line + returns := p.returns // should be always false + + f.name = f.name + '_T' + for k in ti.inst.keys() { + f.name = f.name + '_' + type_to_safe_str(ti.inst[k]) + } + f.is_generic = false // the instance is a normal function + f.type_inst = []TypeInst + f.scope_level = 0 + f.dispatch_of = ti + old_args := f.args + new_types := p.replace_type_params(f, ti) + f.args = []Var + for i in 0..new_types.len-1 { + mut v := old_args[i] + v.typ = new_types[i] + f.args << v + } + f.typ = new_types.last() + if f.typ in f.type_pars { f.typ = '_ANYTYPE_' } + + if f.typ in ti.inst { + f.typ = ti.inst[f.typ] + } + p.table.register_fn(f) + // println("generating gen inst $f.name(${f.str_args(p.table)}) $f.typ : $ti.inst") + + p.cgen.is_tmp = false + p.returns = false + p.cgen.tmp_line = '' + p.cgen.cur_line = '' + p.cgen.lines = []string + p.cur_fn = *f + for arg in f.args { + p.register_var(arg) + } + p.token_idx = f.body_idx-1 + p.next() // re-initializes the parser properly + str_args := f.str_args(p.table) + + p.in_dispatch = true + p.genln('${p.get_linkage_prefix()}$f.typ $f.name($str_args) {') + // p.genln('/* generic fn instance $f.name : $ti.inst */') + p.statements() + p.in_dispatch = false + + if f.typ == '_ANYTYPE_' { + f.typ = p.cur_fn.typ + f.name = f.name.replace('_ANYTYPE_', type_to_safe_str(f.typ)) + p.cgen.lines[0] = p.cgen.lines[0].replace('_ANYTYPE_', f.typ) + p.table.register_fn(f) + } + for l in p.cgen.lines { + p.cgen.fns << l + } + + p.token_idx = saved_tok_idx-1 + p.next() + p.check(.rpar) // end of the arg list which caused this dispatch + p.cur_fn = saved_fn + p.var_idx = saved_var_idx + p.local_vars = saved_local_vars + p.cgen.lines = saved_lines + p.cgen.cur_line = saved_line + p.cgen.is_tmp = saved_is_tmp + p.cgen.tmp_line = saved_tmp_line + p.returns = false +} + // "fn (int, string) int" fn (f &Fn) typ_str() string { mut sb := strings.new_builder(50) @@ -1162,7 +1421,7 @@ fn (fns []Fn) contains(f Fn) bool { for ff in fns { if ff.name == f.name { return true - } - } + } + } return false -} +} diff --git a/vlib/compiler/main.v b/vlib/compiler/main.v index 82520562cf..616f88186d 100644 --- a/vlib/compiler/main.v +++ b/vlib/compiler/main.v @@ -89,14 +89,14 @@ pub mut: is_run bool show_c_cmd bool // `v -show_c_cmd` prints the C command to build program.v.c sanitize bool // use Clang's new "-fsanitize" option - + is_debug bool // false by default, turned on by -g or -cg, it tells v to pass -g to the C backend compiler. is_vlines bool // turned on by -g, false by default (it slows down .tmp.c generation slightly). is_keep_c bool // -keep_c , tell v to leave the generated .tmp.c alone (since by default v will delete them after c backend finishes) // NB: passing -cg instead of -g will set is_vlines to false and is_g to true, thus making v generate cleaner C files, // which are sometimes easier to debug / inspect manually than the .tmp.c files by plain -g (when/if v line number generation breaks). is_cache bool // turns on v usage of the module cache to speed up compilation. - + is_stats bool // `v -stats file_test.v` will produce more detailed statistics for the tests that were run no_auto_free bool // `v -nofree` disable automatic `free()` insertion for better performance in some applications (e.g. compilers) cflags string // Additional options which will be passed to the C compiler. @@ -114,7 +114,7 @@ pub mut: } // Should be called by main at the end of the compilation process, to cleanup -pub fn (v mut V) finalize_compilation(){ +pub fn (v mut V) finalize_compilation(){ // TODO remove if v.pref.autofree { println('started freeing v struct') @@ -126,12 +126,12 @@ pub fn (v mut V) finalize_compilation(){ //f.local_vars.free() f.args.free() //f.defer_text.free() - } + } v.table.fns.free() free(v.table) - //for p in parsers {} + //for p in parsers {} println('done!') - } + } } pub fn (v mut V) add_parser(parser Parser) { @@ -142,7 +142,7 @@ pub fn (v &V) get_file_parser_index(file string) ?int { for i, p in v.parsers { if os.realpath(p.file_path_id) == os.realpath(file) { return i - } + } } return error('parser for "$file" not found') } @@ -184,11 +184,11 @@ pub fn (v mut V) compile() { println('\nparsers:') for q in v.parsers { println(q.file_name) - } + } println('\nfiles:') for q in v.files { println(q) - } + } } */ // First pass (declarations) @@ -251,7 +251,7 @@ pub fn (v mut V) compile() { mut defs_pos := cgen.lines.len - 1 if defs_pos == -1 { defs_pos = 0 - } + } cgen.nogen = q for file in v.files { v.parse(file, .main) @@ -272,7 +272,7 @@ pub fn (v mut V) compile() { v.vgen_buf.free() vgen_parser.parse(.main) // v.parsers.add(vgen_parser) - + // All definitions mut def := strings.new_builder(10000)// Avoid unnecessary allocations $if !js { @@ -303,7 +303,7 @@ pub fn (v mut V) compile() { } $if js { cgen.genln('main__main();') - } + } cgen.save() v.cc() } @@ -420,13 +420,13 @@ pub fn (v mut V) generate_main() { } // Generate a C `main`, which calls every single test function v.gen_main_start(false) - + if v.pref.is_stats { cgen.genln('BenchedTests bt = main__start_testing();') } - + for _, f in v.table.fns { if f.name.starts_with('main__test_') { if v.pref.is_stats { cgen.genln('BenchedTests_testing_step_start(&bt, tos3("$f.name"));') } - cgen.genln('$f.name();') + cgen.genln('$f.name();') if v.pref.is_stats { cgen.genln('BenchedTests_testing_step_end(&bt);') } } } @@ -471,7 +471,7 @@ pub fn final_target_out_name(out_name string) string { pub fn (v V) run_compiled_executable_and_exit() { if v.pref.is_verbose { println('============ running $v.out_name ============') - } + } mut cmd := '"' + final_target_out_name(v.out_name).replace('.exe','') + '"' if os.args.len > 3 { cmd += ' ' + os.args.right(3).join(' ') @@ -631,7 +631,7 @@ pub fn (v &V) get_user_files() []string { user_files << os.join(v.vroot, 'vlib', 'benchmark', 'tests', 'always_imported.v') } - + // v volt/slack_test.v: compile all .v files to get the environment // I need to implement user packages! TODO is_test_with_imports := dir.ends_with('_test.v') && @@ -754,14 +754,14 @@ pub fn new_v(args[]string) &V { os.mkdir(v_modules_path) os.mkdir('$v_modules_path${os.path_separator}cache') } - + mut vgen_buf := strings.new_builder(1000) vgen_buf.writeln('module main\nimport strings') - + joined_args := args.join(' ') target_os := get_arg(joined_args, 'os', '') mut out_name := get_arg(joined_args, 'o', 'a.out') - + mut dir := args.last() if 'run' in args { dir = get_param_after(joined_args, 'run', '') @@ -891,7 +891,7 @@ pub fn new_v(args[]string) &V { is_vlines: '-g' in args && !('-cg' in args) is_keep_c: '-keep_c' in args is_cache: '-cache' in args - + is_stats: '-stats' in args obfuscate: obfuscate is_prof: '-prof' in args diff --git a/vlib/compiler/parser.v b/vlib/compiler/parser.v index 47b7a22555..fb97e43eab 100644 --- a/vlib/compiler/parser.v +++ b/vlib/compiler/parser.v @@ -59,9 +59,9 @@ mut: calling_c bool cur_fn Fn local_vars []Var // local function variables - var_idx int - returns bool - vroot string + var_idx int + returns bool + vroot string is_c_struct_init bool is_empty_c_struct_init bool is_c_fn_call bool @@ -71,7 +71,7 @@ mut: var_decl_name string // To allow declaring the variable so that it can be used in the struct initialization is_alloc bool // Whether current expression resulted in an allocation is_const_literal bool // `1`, `2.0` etc, so that `u64_var == 0` works - cur_gen_type string // "App" to replace "T" in current generic function + in_dispatch bool // dispatching generic instance? is_vweb bool is_sql bool is_js bool @@ -671,7 +671,11 @@ fn (p mut Parser) struct_decl() { } mut typ := p.table.find_type(name) if p.pass == .decl && p.table.known_type_fast(typ) { - p.error('`$name` redeclared') + if name in reserved_type_param_names { + p.error('name `$name` is reserved for type parameters') + } else { + p.error('type `$name` redeclared') + } } if is_objc { // Forward declaration of an Objective-C interface with `@class` :) @@ -1030,7 +1034,8 @@ fn (p mut Parser) get_type() string { p.register_map(typ) return typ } - // + + // ptr/ref mut warn := false for p.tok == .mul { if p.first_pass() { @@ -1045,7 +1050,16 @@ fn (p mut Parser) get_type() string { nr_muls++ p.check(.amp) } - typ += p.lit + + // Generic type check + ti := p.cur_fn.dispatch_of.inst + if p.lit in ti.keys() { + typ += ti[p.lit] + // println('cur dispatch: $p.lit => $typ') + } else { + typ += p.lit + } + if !p.is_struct_init { // Otherwise we get `foo := FooFoo{` because `Foo` was already // generated in name_expr() @@ -1164,8 +1178,7 @@ fn (p mut Parser) statements_no_rcbr() string { mut last_st_typ := '' for p.tok != .rcbr && p.tok != .eof && p.tok != .key_case && p.tok != .key_default && p.peek() != .arrow { - // println(p.tok.str()) - // p.print_tok() + // println('stm: '+p.tok.str()+', next: '+p.peek().str()) last_st_typ = p.statement(true) // println('last st typ=$last_st_typ') if !p.inside_if_expr { @@ -1186,7 +1199,6 @@ fn (p mut Parser) statements_no_rcbr() string { // p.check(.rcbr) } //p.fmt_dec() - // println('close scope line=$p.scanner.line_nr') p.close_scope() return last_st_typ @@ -1255,7 +1267,9 @@ fn (p mut Parser) statement(add_semi bool) string { if p.returns && !p.is_vweb { p.error('unreachable code') } - p.cgen.is_tmp = false + // if !p.in_dispatch { + p.cgen.is_tmp = false + // } tok := p.tok mut q := '' switch tok { @@ -1719,73 +1733,43 @@ fn (p mut Parser) name_expr() string { // known_type := p.table.known_type(name) orig_name := name is_c := name == 'C' && p.peek() == .dot - mut is_c_struct_init := is_c && ptr// a := &C.mycstruct{} + if is_c { - p.next() + p.check(.name) p.check(.dot) name = p.lit - p.fgen(name) - // Currently struct init is set to true only we have `&C.Foo{}`, handle `C.Foo{}`: - if !is_c_struct_init && p.peek() == .lcbr { - is_c_struct_init = true + // C struct initialization + if p.peek() == .lcbr && p.table.known_type(name) { + return p.get_struct_type(name, true, ptr) } + // C function + if p.peek() == .lpar { + return p.get_c_func_type(name) + } + // C const (`C.GLFW_KEY_LEFT`) + p.gen(name) + p.next() + return 'int' } + // enum value? (`color == .green`) if p.tok == .dot { - //println('got enum dot val $p.left_type pass=$p.pass $p.scanner.line_nr left=$p.left_type') - T := p.find_type(p.expected_type) - if T.cat == .enum_ { - p.check(.dot) - val := p.check_name() - // Make sure this enum value exists - if !T.has_enum_val(val) { - p.error('enum `$T.name` does not have value `$val`') - } - p.gen(mod_gen_name(T.mod) + '__' + p.expected_type + '_' + val) + if p.table.known_type(p.expected_type) { + p.check_enum_member_access() + // println("found enum value: $p.expected_type") + return p.expected_type + } else { + p.error("unknown enum: `$p.expected_type`") } - return p.expected_type } + // Variable, checked before modules, so module shadowing is allowed. // (`gg = gg.newcontext(); gg.draw_rect(...)`) - for { // TODO remove - mut v := p.find_var_check_new_var(name) or { break } - if name == '_' { - p.error('cannot use `_` as value') + if p.known_var_check_new_var(name) { + rtyp := p.get_var_type(name, ptr, deref) + return rtyp } - if ptr { - p.gen('&') - } - else if deref { - p.gen('*') - } - if p.pref.autofree && v.typ == 'string' && v.is_arg && - p.assigned_type == 'string' { - p.warn('setting moved ' + v.typ) - p.mark_arg_moved(v) - } - mut typ := p.var_expr(v) - // *var - if deref { - if !typ.contains('*') && !typ.ends_with('ptr') { - println('name="$name", t=$v.typ') - p.error('dereferencing requires a pointer, but got `$typ`') - } - typ = typ.replace('ptr', '')// TODO - typ = typ.replace('*', '')// TODO - } - // &var - else if ptr { - typ += '*' - } - if p.inside_return_expr { - //println('marking $v.name returned') - p.mark_var_returned(v) - // v.is_returned = true // TODO modifying a local variable - // that's not used afterwards, this should be a compilation - // error - } - return typ - } // TODO REMOVE for{} + // Module? if p.peek() == .dot && ((name == p.mod && p.table.known_mod(name)) || p.import_table.known_alias(name)) && !is_c { @@ -1801,6 +1785,7 @@ fn (p mut Parser) name_expr() string { p.fgen(name) name = prepend_mod(mod_gen_name(mod), name) } + // Unknown name, try prepending the module name to it // TODO perf else if !p.table.known_type(name) && @@ -1809,52 +1794,15 @@ fn (p mut Parser) name_expr() string { name = p.prepend_mod(name) } - // Variable, checked before modules, so module shadowing is allowed. - // (`gg = gg.newcontext(); gg.draw_rect(...)`) - for { // TODO remove - mut v := p.find_var_check_new_var(name) or { break } - if name == '_' { - p.error('cannot use `_` as value') + // re-check + if p.known_var_check_new_var(name) { + return p.get_var_type(name, ptr, deref) } - if ptr { - p.gen('&') - } - else if deref { - p.gen('*') - } - if p.pref.autofree && v.typ == 'string' && v.is_arg && - p.assigned_type == 'string' { - p.warn('setting moved ' + v.typ) - p.mark_arg_moved(v) - } - mut typ := p.var_expr(v) - // *var - if deref { - if !typ.contains('*') && !typ.ends_with('ptr') { - println('name="$name", t=$v.typ') - p.error('dereferencing requires a pointer, but got `$typ`') - } - typ = typ.replace('ptr', '')// TODO - typ = typ.replace('*', '')// TODO - } - // &var - else if ptr { - typ += '*' - } - if p.inside_return_expr { - //println('marking $v.name returned') - p.mark_var_returned(v) - // v.is_returned = true // TODO modifying a local variable - // that's not used afterwards, this should be a compilation - // error - } - return typ - } // TODO REMOVE for{} // if known_type || is_c_struct_init || (p.first_pass() && p.peek() == .lcbr) { // known type? int(4.5) or Color.green (enum) if p.table.known_type(name) { - // float(5), byte(0), (*int)(ptr) etc + // cast expression: float(5), byte(0), (*int)(ptr) etc if !is_c && ( p.peek() == .lpar || (deref && p.peek() == .rpar) ) { if deref { name += '*' @@ -1885,87 +1833,20 @@ fn (p mut Parser) name_expr() string { p.next() return enum_type.name } - // struct initialization + // normal struct init (non-C) else if p.peek() == .lcbr { - if ptr { - name += '*' // `&User{}` => type `User*` - } - if name == 'T' { - name = p.cur_gen_type - } - p.is_c_struct_init = is_c_struct_init - return p.struct_init(name) + return p.get_struct_type(name, false, ptr) } } - if is_c { - // C const (`C.GLFW_KEY_LEFT`) - if p.peek() != .lpar { - p.gen(name) - p.next() - return 'int' - } - // C function - f := Fn { - name: name - is_c: true - } - p.is_c_fn_call = true - p.fn_call(f, 0, '', '') - p.is_c_fn_call = false - // Try looking it up. Maybe its defined with "C.fn_name() fn_type", - // then we know what type it returns - cfn := p.table.find_fn(name) or { - // Not Found? Return 'void*' - //return 'cvoid' //'void*' - if false { - p.warn('\ndefine imported C function with ' + - '`fn C.$name([args]) [return_type]`\n') - } - return 'void*' - } - return cfn.typ - } + // Constant - for { - c := p.table.find_const(name) or { break } - if ptr && !c.is_global { - p.error('cannot take the address of constant `$c.name`') - } else if ptr && c.is_global { - // c.ptr = true - p.gen('& /*const*/ ') - } - mut typ := p.var_expr(c) - if ptr { - typ += '*' - } - return typ + if p.table.known_const(name) { + return p.get_const_type(name, ptr) } - // Function (not method btw, methods are handled in `dot()`) + + // Function (not method btw, methods are handled in dot()) mut f := p.table.find_fn_is_script(name, p.v_script) or { - // We are in the second pass, that means this function was not defined, - // throw an error. - if !p.first_pass() { - // check for misspelled function / variable / module - suggested := p.identify_typo(name, p.import_table) - if suggested != '' { - p.error('undefined: `$name`. did you mean:$suggested') - } - // If orig_name is a mod, then printing undefined: `mod` tells us nothing - if p.table.known_mod(orig_name) || p.import_table.known_alias(orig_name) { - name = name.replace('__', '.') - p.error('undefined: `$name`') - } - else { - p.error('undefined: `$orig_name`') - } - } else { - p.next() - // First pass, the function can be defined later. - // Only in const definitions? (since fn bodies are skipped - // in the first pass). - return 'void' - } - return 'void' + return p.get_undefined_fn_type(name, orig_name) } // no () after func, so func is an argument, just gen its name // TODO verify this and handle errors @@ -1986,8 +1867,20 @@ fn (p mut Parser) name_expr() string { if f.typ == 'void' && !p.inside_if_expr { // p.error('`$f.name` used as value') } - //p.log('calling function') - p.fn_call(f, 0, '', '') + + // println('call to fn $f.name of type $f.typ') + // TODO replace the following dirty hacks (needs ptr access to fn table) + new_f := f + p.fn_call(mut new_f, 0, '', '') + if f.is_generic { + f2 := p.table.find_fn(f.name) or { + return '' + } + // println('after call of generic instance $new_f.name(${new_f.str_args(p.table)}) $new_f.typ') + // println(' from $f2.name(${f2.str_args(p.table)}) $f2.typ : $f2.type_inst') + } + f = new_f + // dot after a function call: `get_user().age` if p.tok == .dot { mut typ := '' @@ -2005,6 +1898,146 @@ fn (p mut Parser) name_expr() string { return f.typ } +fn (p mut Parser) get_struct_type(name_ string, is_c bool, is_ptr bool) string { + mut name := name_ + if is_ptr { + name += '*' // `&User{}` => type `User*` + } + if name in reserved_type_param_names { + p.warn('name `$name` is reserved for type parameters') + } + p.is_c_struct_init = is_c + return p.struct_init(name) +} + +fn (p mut Parser) check_enum_member_access() { + T := p.find_type(p.expected_type) + if T.cat == .enum_ { + p.check(.dot) + val := p.check_name() + // Make sure this enum value exists + if !T.has_enum_val(val) { + p.error('enum `$T.name` does not have value `$val`') + } + p.gen(mod_gen_name(T.mod) + '__' + p.expected_type + '_' + val) + } else { + p.error('`$T.name` is not an enum') + } +} + +fn (p mut Parser) get_var_type(name string, is_ptr bool, is_deref bool) string { + v := p.find_var_check_new_var(name) or { return "" } + if name == '_' { + p.error('cannot use `_` as value') + } + if is_ptr { + p.gen('&') + } + else if is_deref { + p.gen('*') + } + if p.pref.autofree && v.typ == 'string' && v.is_arg && + p.assigned_type == 'string' { + p.warn('setting moved ' + v.typ) + p.mark_arg_moved(v) + } + mut typ := p.var_expr(v) + // *var + if is_deref { + if !typ.contains('*') && !typ.ends_with('ptr') { + println('name="$name", t=$v.typ') + p.error('dereferencing requires a pointer, but got `$typ`') + } + typ = typ.replace('ptr', '')// TODO + typ = typ.replace('*', '')// TODO + } + // &var + else if is_ptr { + typ += '*' + } + if p.inside_return_expr { + //println('marking $v.name returned') + p.mark_var_returned(v) + // v.is_returned = true // TODO modifying a local variable + // that's not used afterwards, this should be a compilation + // error + } + return typ +} + +fn (p mut Parser) get_const_type(name string, is_ptr bool) string { + c := p.table.find_const(name) or { return "" } + if is_ptr && !c.is_global { + p.error('cannot take the address of constant `$c.name`') + } else if is_ptr && c.is_global { + // c.ptr = true + p.gen('& /*const*/ ') + } + mut typ := p.var_expr(c) + if is_ptr { + typ += '*' + } + return typ +} + +fn (p mut Parser) get_c_func_type(name string) string { + f := Fn { + name: name + is_c: true + } + p.is_c_fn_call = true + p.fn_call(mut f, 0, '', '') + p.is_c_fn_call = false + // Try looking it up. Maybe its defined with "C.fn_name() fn_type", + // then we know what type it returns + cfn := p.table.find_fn(name) or { + // Not Found? Return 'void*' + //return 'cvoid' //'void*' + if false { + p.warn('\ndefine imported C function with ' + + '`fn C.$name([args]) [return_type]`\n') + } + return 'void*' + } + // println("C fn $name has type $cfn.typ") + return cfn.typ +} + +fn (p mut Parser) get_undefined_fn_type(name string, orig_name string) string { + if p.first_pass() { + p.next() + // First pass, the function can be defined later. + return 'void' + } else { + // We are in the second pass, that means this function was not defined, throw an error. + + // V script? Try os module. + // TODO + if p.v_script { + //name = name.replace('main__', 'os__') + //f = p.table.find_fn(name) + } + + // check for misspelled function / variable / module + suggested := p.identify_typo(name, p.import_table) + if suggested != '' { + p.error('undefined function: `$name`. did you mean: `$suggested`') + } + + // If orig_name is a mod, then printing undefined: `mod` tells us nothing + // if p.table.known_mod(orig_name) { + if p.table.known_mod(orig_name) || p.import_table.known_alias(orig_name) { + m_name := mod_gen_name_rev(name.replace('__', '.')) + p.error('undefined function: `$m_name` (in module `$orig_name`)') + } else if orig_name in reserved_type_param_names { + p.error('the letter `$orig_name` is reserved for type parameters') + } else { + p.error('undefined symbol: `$orig_name`') + } + return 'void' + } +} + fn (p mut Parser) var_expr(v Var) string { //p.log('\nvar_expr() v.name="$v.name" v.typ="$v.typ"') // println('var expr is_tmp=$p.cgen.is_tmp\n') @@ -2088,11 +2121,6 @@ fn (p mut Parser) var_expr(v Var) string { return typ } -// for debugging only -fn (p &Parser) fileis(s string) bool { - return p.scanner.file_path.contains(s) -} - // user.name => `str_typ` is `User` // user.company.name => `str_typ` is `Company` fn (p mut Parser) dot(str_typ_ string, method_ph int) string { @@ -2203,7 +2231,7 @@ struct $typ.name { p.error_with_token_index('could not find method `$field_name`', fname_tidx) // should never happen exit(1) } - p.fn_call(method, method_ph, '', str_typ) + p.fn_call(mut method, method_ph, '', str_typ) // Methods returning `array` should return `array_string` if method.typ == 'array' && typ.name.starts_with('array_') { return typ.name @@ -2404,6 +2432,11 @@ struct IndexCfg { } +// for debugging only +fn (p &Parser) fileis(s string) bool { + return p.scanner.file_path.contains(s) +} + // in and dot have higher priority than `!` fn (p mut Parser) indot_expr() string { ph := p.cgen.add_placeholder() @@ -3905,6 +3938,7 @@ fn (p mut Parser) return_st() { is_none := p.tok == .key_none p.expected_type = p.cur_fn.typ mut expr_type := p.bool_expression() + // println('$p.cur_fn.name returns type $expr_type, should be $p.cur_fn.typ') mut types := []string mut mr_values := [p.cgen.cur_line.right(ph).trim_space()] types << expr_type diff --git a/vlib/compiler/table.v b/vlib/compiler/table.v index 8f780b8ec0..5e44f92330 100644 --- a/vlib/compiler/table.v +++ b/vlib/compiler/table.v @@ -12,7 +12,6 @@ mut: typesmap map[string]Type consts []Var fns map[string]Fn - generic_fns []GenTable //map[string]GenTable // generic_fns['listen_and_serve'] == ['Blog', 'Forum'] obf_ids map[string]int // obf_ids['myfunction'] == 23 modules []string // List of all modules registered by the application imports []string // List of all imports @@ -40,12 +39,6 @@ enum NameCategory { struct Name { cat NameCategory idx int // e.g. typ := types[name.idx] -} - -struct GenTable { - fn_name string -mut: - types []string } // Holds import information scoped to the parsed file @@ -125,7 +118,7 @@ mut: // This information is needed in the first pass. is_placeholder bool gen_str bool // needs `.str()` method generation - + } struct TypeNode { @@ -218,6 +211,7 @@ fn (t &Table) debug_fns() string { const ( number_types = ['number', 'int', 'i8', 'i16', 'u16', 'u32', 'byte', 'i64', 'u64', 'f32', 'f64'] float_types = ['f32', 'f64'] + reserved_type_param_names = ['R', 'S', 'T', 'U', 'W'] ) fn is_number_type(typ string) bool { @@ -254,8 +248,10 @@ fn new_table(obfuscate bool) &Table { t.register_type('bool') t.register_type('void') t.register_type('voidptr') - t.register_type('T') t.register_type('va_list') + for c in reserved_type_param_names { + t.register_type(c) + } t.register_const('stdin', 'int', 'main') t.register_const('stdout', 'int', 'main') t.register_const('stderr', 'int', 'main') @@ -585,6 +581,13 @@ fn (p mut Parser) check_types2(got_, expected_ string, throw bool) bool { if p.pref.translated { return true } + + // generic return type + if expected == '_ANYTYPE_' { + p.cur_fn.typ = got + return true + } + // variadic if expected.starts_with('...') { expected = expected.right(3) @@ -665,7 +668,7 @@ fn (p mut Parser) check_types2(got_, expected_ string, throw bool) bool { if got.starts_with('fn ') && (expected.ends_with('fn') || expected.ends_with('Fn')) { return true - } + } // Allow pointer arithmetic if expected=='void*' && got=='int' { return true @@ -676,7 +679,7 @@ fn (p mut Parser) check_types2(got_, expected_ string, throw bool) bool { //} if is_number_type(got) && is_number_type(expected) && p.is_const_literal { return true - } + } expected = expected.replace('*', '') got = got.replace('*', '') if got != expected { @@ -749,7 +752,7 @@ fn (t &Table) has_at_least_one_test_fn() bool { for _, f in t.fns { if f.name.starts_with('main__test_') { return true - } + } } return false } @@ -778,7 +781,7 @@ fn (table &Table) cgen_name_type_pair(name, typ string) string { else if typ.starts_with('fn (') { T := table.find_type(typ) if T.name == '' { - println('this should never happen') + eprintln('function type `$typ` not found') exit(1) } str_args := T.func.str_args(table) @@ -811,32 +814,6 @@ fn is_valid_int_const(val, typ string) bool { return true } -fn (t mut Table) register_generic_fn(fn_name string) { - t.generic_fns << GenTable{fn_name, []string} -} - -fn (t &Table) fn_gen_types(fn_name string) []string { - for _, f in t.generic_fns { - if f.fn_name == fn_name { - return f.types - } - } - verror('function $fn_name not found') - return []string -} - -// `foo()` -// fn_name == 'foo' -// typ == 'Bar' -fn (t mut Table) register_generic_fn_type(fn_name, typ string) { - for i, f in t.generic_fns { - if f.fn_name == fn_name { - t.generic_fns[i].types << typ - return - } - } -} - fn (p mut Parser) typ_to_fmt(typ string, level int) string { t := p.table.find_type(typ) if t.cat == .enum_ { @@ -866,6 +843,11 @@ fn (p mut Parser) typ_to_fmt(typ string, level int) string { return '' } +fn type_to_safe_str(typ string) string { + r := typ.replace(' ','').replace('(','_').replace(')','_') + return r +} + fn is_compile_time_const(s_ string) bool { s := s_.trim_space() if s == '' { diff --git a/vlib/compiler/tests/generic_test.v b/vlib/compiler/tests/generic_test.v new file mode 100644 index 0000000000..9c7ace8db1 --- /dev/null +++ b/vlib/compiler/tests/generic_test.v @@ -0,0 +1,58 @@ +fn simple(p T) T { + return p +} + +fn sum(l []T, nil T) T { + mut r := nil + for e in l { + r += e + } + return r +} + +fn map_f(l []T, f fn(T)U) []U { + mut r := []U + for e in l { + r << f(e) + } + return r +} + +fn foldl(l []T, nil T, f fn(T,T)T) T { + mut r := nil + for e in l { + r = f(r, e) + } + return r +} + +fn plus(a T, b T) T { + return a+b +} + +fn square(x int) int { + return x*x +} + +fn mul_int(x int, y int) int { + return x*y +} + +fn assert_eq(a, b T) { + r := a == b + println('$a == $b: ${r.str()}') + assert r +} + +fn test_generic_fn() { + assert_eq(simple(0+1), 1) + assert_eq(simple('g') + 'h', 'gh') + assert_eq(sum([5.1,6.2,7.0], 0.0), 18.3) + assert_eq(plus(i64(4), i64(6)), i64(10)) + a := [1,2,3,4] + $if !windows { + b := map_f(a, square) + assert_eq(sum(b, 0), 30) // 1+4+9+16 = 30 + assert_eq(foldl(b, 1, mul_int), 576) // 1*4*9*16 = 576 + } +} diff --git a/vlib/compiler/tests/print_test.v b/vlib/compiler/tests/print_test.v new file mode 100644 index 0000000000..ee87ceab0e --- /dev/null +++ b/vlib/compiler/tests/print_test.v @@ -0,0 +1,3 @@ +fn test_print() { + println(2.0) +} diff --git a/vlib/compiler/tests/repl/error.repl b/vlib/compiler/tests/repl/error.repl index 715b2e07de..0f806dae68 100644 --- a/vlib/compiler/tests/repl/error.repl +++ b/vlib/compiler/tests/repl/error.repl @@ -1,3 +1,3 @@ println(a) ===output=== -.vrepl.v:2:9: undefined: `a` +.vrepl.v:2:9: undefined symbol: `a` diff --git a/vlib/compiler/tests/repl/error_nosave.repl b/vlib/compiler/tests/repl/error_nosave.repl index fb6a5158b5..fb009d92e1 100644 --- a/vlib/compiler/tests/repl/error_nosave.repl +++ b/vlib/compiler/tests/repl/error_nosave.repl @@ -1,5 +1,5 @@ a 33 ===output=== -.vrepl_temp.v:3:9: undefined: `a` +.vrepl_temp.v:3:9: undefined symbol: `a` 33