From b5a1544bf8964e142cbc68a894dfb6643395eb09 Mon Sep 17 00:00:00 2001 From: Tanel Liiv <204019+dkull@users.noreply.github.com> Date: Wed, 6 May 2020 21:09:29 +0300 Subject: [PATCH] parser: allow re-assigning fns to variables --- vlib/v/checker/checker.v | 46 ++++++----- vlib/v/gen/cgen.v | 22 +++++- vlib/v/tests/fn_high_test.v | 147 ++++++++++++++++++++++++++++++++++++ vlib/v/tests/fn_test.v | 101 ------------------------- 4 files changed, 196 insertions(+), 120 deletions(-) create mode 100644 vlib/v/tests/fn_high_test.v diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 8314eb2e9e..24d5ac909c 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1728,6 +1728,11 @@ pub fn (mut c Checker) ident(ident mut ast.Ident) table.Type { info := ident.info as ast.IdentFn return info.typ } else if ident.kind == .unresolved { + // prepend mod to look for fn call or const + mut name := ident.name + if !name.contains('.') && ident.mod !in ['builtin', 'main'] { + name = '${ident.mod}.$ident.name' + } // first use start_scope := c.file.scope.innermost(ident.pos.pos) if obj := start_scope.find(ident.name) { @@ -1737,27 +1742,34 @@ pub fn (mut c Checker) ident(ident mut ast.Ident) table.Type { if typ == 0 { typ = c.expr(it.expr) } - is_optional := typ.flag_is(.optional) - ident.kind = .variable - ident.info = ast.IdentVar{ - typ: typ - is_optional: is_optional + sym := c.table.get_type_symbol(typ) + if sym.info is table.FnType { + // anon/local fn assigned to new variable uses this + info := sym.info as table.FnType + fn_type := table.new_type(c.table.find_or_register_fn_type(info.func, true, true)) + ident.kind = .function + ident.info = ast.IdentFn{ + typ: fn_type + } + return fn_type + } else { + is_optional := typ.flag_is(.optional) + ident.kind = .variable + ident.info = ast.IdentVar{ + typ: typ + is_optional: is_optional + } + it.typ = typ + // unwrap optional (`println(x)`) + if is_optional { + return typ.set_flag(.unset) + } + return typ } - it.typ = typ - // unwrap optional (`println(x)`) - if is_optional { - return typ.set_flag(.unset) - } - return typ } else {} } } - // prepend mod to look for fn call or const - mut name := ident.name - if !name.contains('.') && ident.mod !in ['builtin', 'main'] { - name = '${ident.mod}.$ident.name' - } if obj := c.file.global_scope.find(name) { match obj { ast.GlobalDecl { @@ -1783,7 +1795,7 @@ pub fn (mut c Checker) ident(ident mut ast.Ident) table.Type { else {} } } - // Function object (not a call), e.g. `onclick(my_click)` + // Non-anon-function object (not a call), e.g. `onclick(my_click)` if func := c.table.find_fn(name) { fn_type := table.new_type(c.table.find_or_register_fn_type(func, false, true)) ident.name = name diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 0dc0d26bf7..0062f9ad00 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -367,7 +367,8 @@ typedef struct { sym := g.table.get_type_symbol(func.return_type) is_multi := sym.kind == .multi_return is_fn_sig := func.name == '' - if !info.has_decl && (!info.is_anon || is_fn_sig) && !is_multi { + not_anon := !info.is_anon + if !info.has_decl && !is_multi && (not_anon || is_fn_sig) { fn_name := if func.is_c { func.name.replace('.', '__') } else if info.is_anon { @@ -890,8 +891,8 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { or_stmts = it.or_block.stmts return_type = it.return_type } + // TODO: no buffer fiddling ast.AnonFn { - // TODO: no buffer fiddling if blank_assign { g.write('{') } @@ -908,6 +909,23 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { } continue } + ast.Ident { + if it.info is ast.IdentFn { + thing := it.info as ast.IdentFn + sym := g.table.get_type_symbol(thing.typ) + info := sym.info as table.FnType + func := info.func + ret_styp := g.typ(func.return_type) + g.write('$ret_styp (*$ident.name) (') + def_pos := g.definitions.len + g.fn_args(func.args, func.is_variadic) + g.definitions.go_back(g.definitions.len - def_pos) + g.write(') = ') + g.expr(*it) + g.writeln(';') + continue + } + } else {} } gen_or := is_call && return_type.flag_is(.optional) diff --git a/vlib/v/tests/fn_high_test.v b/vlib/v/tests/fn_high_test.v new file mode 100644 index 0000000000..e60c230c6e --- /dev/null +++ b/vlib/v/tests/fn_high_test.v @@ -0,0 +1,147 @@ +// helper +fn sqr(x int) int { + return x * x +} + +fn high_fn(f fn(int) int) { + x := f(111) + println('x == $x') +} + +fn high_fn_no_ret(f fn(int)) { + f(111) +} + +fn high_fn_array(f fn(a []int) []int) { + +} + +fn high_fn_multi_return(a int, b fn (c []int, d []string) ([]int, []string)) { + +} + +fn high_fn_return_single_anon() (fn(int)f32) { + _ := 1 + correct := fn(n int)f32 { + return n * n + } + return correct +} +fn high_fn_return_multi_anons() (fn(int)f32, fn(int)string) { + // parsing trap + _ := fn(n int)byte { + return 0x00 + } + correct_second := fn(n int)string { + return '$n' + } + correct_first := fn(n int)f32 { + return n * n + } + // parsing trap + _ := fn(n int)[]int { + return [n] + } + return correct_first, correct_second +} +fn high_fn_return_named_fn() (fn(int)int) { + return sqr +} +fn test_high_fn_ret_anons() { + param := 13 + func_sqr1 := high_fn_return_single_anon() + assert func_sqr1(param) == param * param + + func_sqr2, func_repr := high_fn_return_multi_anons() + assert func_sqr2(param) == (param * param) + assert func_repr(param) == '$param' + + top_lvl_sqr := high_fn_return_named_fn() + assert top_lvl_sqr(param) == param * param +} + +fn high_fn_applier(arg int, func fn(a int)string) string { + return func(arg) +} +fn test_high_fn_applier() { + arg := 13 + expect := '$arg $arg' + func := fn (arg int) string { + return '$arg $arg' + } + assert expect == high_fn_applier(arg, func) +} + +fn test_fns() { + // no asserts for now, just test function declarations above + high_fn(sqr) +} + +fn test_anon_fn() { + f1 := fn(a int){ + println('hello from f1') + } + f1(1) + + f2 := fn(a int) int { + println('hello from f2') + return 10 + } + f2res := f2(1) + println('f2res == $f2res') + // TODO/FIXME: assert bug? uncomment to see + // assert f2res == 10 + + high_fn(fn (x int) int { + return x + 1 + }) + + high_fn_no_ret(fn (x int) { + println('hello $x') + }) +} + +// +// Test assigning functions (IdentFn) +// + +fn simple_fn1() int { + return 1 +} + +fn simple_fn2(n f32) (int, string) { + return 1 + n, "fish" +} + +fn test_assigning_fns() { + func1 := simple_fn1 + assert func1() == 1 + + func2 := simple_fn2 + res2_1, res2_2 := func2(13.0) + assert res2_1 == 14.0 + assert res2_2 == "fish" + + anon_func1 := fn(s string)int { + return s.len + } + func3 := anon_func1 + res3 := func3("fish") + assert res3 == 4 +} + +// +// End assigning functions (IdentFn) +// + + + + + + + + + + + + diff --git a/vlib/v/tests/fn_test.v b/vlib/v/tests/fn_test.v index 1ee3ea0b2e..d1312e5a2f 100644 --- a/vlib/v/tests/fn_test.v +++ b/vlib/v/tests/fn_test.v @@ -111,107 +111,6 @@ fn test_mut_ptr() { assert buf[0] == 77 } -fn high_fn(f fn(int) int) { - x := f(111) - println('x == $x') -} - -fn high_fn_no_ret(f fn(int)) { - f(111) -} - -fn high_fn_array(f fn(a []int) []int) { - -} - -fn high_fn_multi_return(a int, b fn (c []int, d []string) ([]int, []string)) { - -} - -fn high_fn_return_single_anon() (fn(int)f32) { - _ := 1 - correct := fn(n int)f32 { - return n * n - } - return correct -} -fn high_fn_return_multi_anons() (fn(int)f32, fn(int)string) { - // parsing trap - _ := fn(n int)byte { - return 0x00 - } - correct_second := fn(n int)string { - return '$n' - } - correct_first := fn(n int)f32 { - return n * n - } - // parsing trap - _ := fn(n int)[]int { - return [n] - } - return correct_first, correct_second -} -fn high_fn_return_named_fn() (fn(int)int) { - return sqr -} -fn test_high_fn_ret_anons() { - param := 13 - func_sqr1 := high_fn_return_single_anon() - assert func_sqr1(param) == param * param - - func_sqr2, func_repr := high_fn_return_multi_anons() - assert func_sqr2(param) == (param * param) - assert func_repr(param) == '$param' - - top_lvl_sqr := high_fn_return_named_fn() - assert top_lvl_sqr(param) == param * param -} - -fn high_fn_applier(arg int, func fn(a int)string) string { - return func(arg) -} -fn test_high_fn_applier() { - arg := 13 - expect := '$arg $arg' - func := fn (arg int) string { - return '$arg $arg' - } - assert expect == high_fn_applier(arg, func) -} - -fn sqr(x int) int { - return x * x -} - -fn test_fns() { - // no asserts for now, just test function declarations above - high_fn(sqr) -} - -fn test_anon_fn() { - f1 := fn(a int){ - println('hello from f1') - } - f1(1) - - f2 := fn(a int) int { - println('hello from f2') - return 10 - } - f2res := f2(1) - println('f2res == $f2res') - // TODO/FIXME: assert bug? uncomment to see - // assert f2res == 10 - - high_fn(fn (x int) int { - return x + 1 - }) - - high_fn_no_ret(fn (x int) { - println('hello $x') - }) -} fn assert_in_bool_fn(v int) bool { assert v < 3