diff --git a/cmd/tools/vtest-self.v b/cmd/tools/vtest-self.v index 99cc83cbc5..387f68957e 100644 --- a/cmd/tools/vtest-self.v +++ b/cmd/tools/vtest-self.v @@ -97,6 +97,7 @@ const ( skip_on_windows = [ 'vlib/orm/orm_test.v', 'vlib/v/tests/orm_sub_struct_test.v', + 'vlib/v/tests/closure_test.v', 'vlib/net/websocket/ws_test.v', 'vlib/net/unix/unix_test.v', 'vlib/net/websocket/websocket_test.v', diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 7c91771bc7..fac9fd2925 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -22,8 +22,6 @@ pub type Stmt = AsmStmt | AssertStmt | AssignStmt | Block | BranchStmt | CompFor GlobalDecl | GotoLabel | GotoStmt | HashStmt | Import | InterfaceDecl | Module | NodeError | Return | SqlStmt | StructDecl | TypeDecl -// NB: when you add a new Expr or Stmt type with a .pos field, remember to update -// the .position() token.Position methods too. pub type ScopeObject = AsmRegister | ConstField | GlobalField | Var // TODO: replace Param @@ -353,9 +351,10 @@ pub: // anonymous function pub struct AnonFn { pub mut: - decl FnDecl - typ Type // the type of anonymous fn. Both .typ and .decl.name are auto generated - has_gen bool // has been generated + decl FnDecl + inherited_vars []Param + typ Type // the type of anonymous fn. Both .typ and .decl.name are auto generated + has_gen bool // has been generated } // function or method declaration @@ -498,6 +497,7 @@ pub: is_autofree_tmp bool is_arg bool // fn args should not be autofreed is_auto_deref bool + is_inherited bool pub mut: typ Type orig_type Type // original sumtype type; 0 if it's not a sumtype @@ -1539,7 +1539,7 @@ pub fn (expr Expr) position() token.Position { AnonFn { return expr.decl.pos } - EmptyExpr { + CTempVar, EmptyExpr { // println('compiler bug, unhandled EmptyExpr position()') return token.Position{} } @@ -1571,9 +1571,6 @@ pub fn (expr Expr) position() token.Position { last_line: right_pos.last_line } } - CTempVar { - return token.Position{} - } // Please, do NOT use else{} here. // This match is exhaustive *on purpose*, to help force // maintaining/implementing proper .pos fields. diff --git a/vlib/v/ast/scope.v b/vlib/v/ast/scope.v index a02460c90f..cc8dd2db21 100644 --- a/vlib/v/ast/scope.v +++ b/vlib/v/ast/scope.v @@ -41,20 +41,6 @@ fn (s &Scope) dont_lookup_parent() bool { return isnil(s.parent) || s.detached_from_parent } -pub fn (s &Scope) find_with_scope(name string) ?(ScopeObject, &Scope) { - mut sc := s - for { - if name in sc.objects { - return sc.objects[name], sc - } - if sc.dont_lookup_parent() { - break - } - sc = sc.parent - } - return none -} - pub fn (s &Scope) find(name string) ?ScopeObject { for sc := s; true; sc = sc.parent { if name in sc.objects { @@ -82,14 +68,6 @@ pub fn (s &Scope) find_struct_field(name string, struct_type Type, field_name st return none } -pub fn (s &Scope) is_known(name string) bool { - if _ := s.find(name) { - return true - } else { - } - return false -} - pub fn (s &Scope) find_var(name string) ?&Var { if obj := s.find(name) { match obj { @@ -121,23 +99,16 @@ pub fn (s &Scope) find_const(name string) ?&ConstField { } pub fn (s &Scope) known_var(name string) bool { - if _ := s.find_var(name) { - return true - } - return false + s.find_var(name) or { return false } + return true } pub fn (mut s Scope) update_var_type(name string, typ Type) { - s.end_pos = s.end_pos // TODO mut bug mut obj := s.objects[name] - match mut obj { - Var { - if obj.typ == typ { - return - } + if mut obj is Var { + if obj.typ != typ { obj.typ = typ } - else {} } } @@ -152,29 +123,10 @@ pub fn (mut s Scope) register_struct_field(name string, field ScopeStructField) } pub fn (mut s Scope) register(obj ScopeObject) { - name := if obj is ConstField { - obj.name - } else if obj is GlobalField { - obj.name - } else { - (obj as Var).name - } - if name == '_' { + if obj.name == '_' || obj.name in s.objects { return } - if name in s.objects { - // println('existing obect: $name') - return - } - s.objects[name] = obj -} - -pub fn (s &Scope) outermost() &Scope { - mut sc := s - for !sc.dont_lookup_parent() { - sc = sc.parent - } - return sc + s.objects[obj.name] = obj } // returns the innermost scope containing pos @@ -211,6 +163,17 @@ pub fn (s &Scope) contains(pos int) bool { return pos >= s.start_pos && pos <= s.end_pos } +pub fn (s &Scope) has_inherited_vars() bool { + for _, obj in s.objects { + if obj is Var { + if obj.is_inherited { + return true + } + } + } + return false +} + pub fn (sc Scope) show(depth int, max_depth int) string { mut out := '' mut indent := '' diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index 2e2510dbea..6364dd5137 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -34,50 +34,61 @@ pub fn (node &CallExpr) fkey() string { } // These methods are used only by vfmt, vdoc, and for debugging. +pub fn (node &AnonFn) stringify(t &Table, cur_mod string, m2a map[string]string) string { + mut f := strings.new_builder(30) + f.write_string('fn ') + if node.inherited_vars.len > 0 { + f.write_string('[') + for i, var in node.inherited_vars { + if i > 0 { + f.write_string(', ') + } + if var.is_mut { + f.write_string('mut ') + } + f.write_string(var.name) + } + f.write_string('] ') + } + stringify_fn_after_name(node.decl, mut f, t, cur_mod, m2a) + return f.str() +} + pub fn (node &FnDecl) stringify(t &Table, cur_mod string, m2a map[string]string) string { mut f := strings.new_builder(30) if node.is_pub { f.write_string('pub ') } - mut receiver := '' + f.write_string('fn ') if node.is_method { + f.write_string('(') mut styp := util.no_cur_mod(t.type_to_code(node.receiver.typ.clear_flag(.shared_f)), cur_mod) - m := if node.rec_mut { node.receiver.typ.share().str() + ' ' } else { '' } if node.rec_mut { + f.write_string(node.receiver.typ.share().str() + ' ') styp = styp[1..] // remove & } + f.write_string(node.receiver.name + ' ') styp = util.no_cur_mod(styp, cur_mod) if node.params[0].is_auto_rec { styp = styp.trim('&') } - receiver = '($m$node.receiver.name $styp) ' - /* - sym := t.get_type_symbol(node.receiver.typ) - name := sym.name.after('.') - mut m := if node.rec_mut { 'mut ' } else { '' } - if !node.rec_mut && node.receiver.typ.is_ptr() { - m = '&' - } - receiver = '($node.receiver.name $m$name) ' - */ + f.write_string(styp + ') ') } - mut name := if node.is_anon { '' } else { node.name } - if !node.is_anon && !node.is_method && node.language == .v { - name = node.name.all_after_last('.') + name := if !node.is_method && node.language == .v { + node.name.all_after_last('.') + } else { + node.name } - // mut name := if node.is_anon { '' } else { node.name.after_char(`.`) } - // if !node.is_method { - // if node.language == .c { - // name = 'C.$name' - // } else if node.language == .js { - // name = 'JS.$name' - // } - // } - f.write_string('fn $receiver$name') + f.write_string(name) if name in ['+', '-', '*', '/', '%', '<', '>', '==', '!=', '>=', '<='] { f.write_string(' ') } + stringify_fn_after_name(node, mut f, t, cur_mod, m2a) + return f.str() +} + +fn stringify_fn_after_name(node &FnDecl, mut f strings.Builder, t &Table, cur_mod string, m2a map[string]string) { mut add_para_types := true if node.generic_names.len > 0 { if node.is_method { @@ -151,7 +162,6 @@ pub fn (node &FnDecl) stringify(t &Table, cur_mod string, m2a map[string]string) } f.write_string(' ' + rs) } - return f.str() } // Expressions in string interpolations may have to be put in braces if they diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 3690911bf2..9c65fe208a 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2737,7 +2737,7 @@ pub fn (mut c Checker) fn_call(mut call_expr ast.CallExpr) ast.Type { } call_expr.is_noreturn = func.is_noreturn if !found_in_args { - if _ := call_expr.scope.find_var(fn_name) { + if call_expr.scope.known_var(fn_name) { c.error('ambiguous call to: `$fn_name`, may refer to fn `$fn_name` or variable `$fn_name`', call_expr.pos) } @@ -5210,14 +5210,7 @@ pub fn (mut c Checker) expr(node ast.Expr) ast.Type { return node.typ } ast.AnonFn { - c.inside_anon_fn = true - keep_fn := c.table.cur_fn - c.table.cur_fn = unsafe { &node.decl } - c.stmts(node.decl.stmts) - c.fn_decl(mut node.decl) - c.table.cur_fn = keep_fn - c.inside_anon_fn = false - return node.typ + return c.anon_fn(mut node) } ast.ArrayDecompose { typ := c.expr(node.expr) @@ -8167,6 +8160,30 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { node.source_file = c.file } +fn (mut c Checker) anon_fn(mut node ast.AnonFn) ast.Type { + keep_fn := c.table.cur_fn + keep_inside_anon := c.inside_anon_fn + defer { + c.table.cur_fn = keep_fn + c.inside_anon_fn = keep_inside_anon + } + c.table.cur_fn = unsafe { &node.decl } + c.inside_anon_fn = true + for mut var in node.inherited_vars { + parent_var := node.decl.scope.parent.find_var(var.name) or { + panic('unexpected checker error: cannot find parent of inherited variable `$var.name`') + } + if var.is_mut && !parent_var.is_mut { + c.error('original `$parent_var.name` is immutable, declare it with `mut` to make it mutable', + var.pos) + } + var.typ = parent_var.typ + } + c.stmts(node.decl.stmts) + c.fn_decl(mut node.decl) + return node.typ +} + // NB: has_top_return/1 should be called on *already checked* stmts, // which do have their stmt.expr.is_noreturn set properly: fn has_top_return(stmts []ast.Stmt) bool { diff --git a/vlib/v/checker/tests/closure_immutable.out b/vlib/v/checker/tests/closure_immutable.out new file mode 100644 index 0000000000..bb93b27597 --- /dev/null +++ b/vlib/v/checker/tests/closure_immutable.out @@ -0,0 +1,21 @@ +vlib/v/checker/tests/closure_immutable.vv:4:3: error: `a` is immutable, declare it with `mut` to make it mutable + 2 | a := 1 + 3 | f1 := fn [a] () { + 4 | a++ + | ^ + 5 | println(a) + 6 | } +vlib/v/checker/tests/closure_immutable.vv:7:16: error: original `a` is immutable, declare it with `mut` to make it mutable + 5 | println(a) + 6 | } + 7 | f2 := fn [mut a] () { + | ^ + 8 | a++ + 9 | println(a) +vlib/v/checker/tests/closure_immutable.vv:13:3: error: `b` is immutable, declare it with `mut` to make it mutable + 11 | mut b := 2 + 12 | f3 := fn [b] () { + 13 | b++ + | ^ + 14 | println(b) + 15 | } diff --git a/vlib/v/checker/tests/closure_immutable.vv b/vlib/v/checker/tests/closure_immutable.vv new file mode 100644 index 0000000000..7b7369de86 --- /dev/null +++ b/vlib/v/checker/tests/closure_immutable.vv @@ -0,0 +1,19 @@ +fn my_fn() { + a := 1 + f1 := fn [a] () { + a++ + println(a) + } + f2 := fn [mut a] () { + a++ + println(a) + } + mut b := 2 + f3 := fn [b] () { + b++ + println(b) + } + f1() + f2() + f3() +} diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 5941be6d2a..308de2638f 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -391,8 +391,7 @@ pub fn (mut f Fmt) stmt(node ast.Stmt) { eprintln('stmt: ${node.pos:-42} | node: ${node.type_name():-20}') } match node { - ast.NodeError {} - ast.EmptyStmt {} + ast.EmptyStmt, ast.NodeError {} ast.AsmStmt { f.asm_stmt(node) } @@ -491,7 +490,7 @@ pub fn (mut f Fmt) expr(node ast.Expr) { ast.NodeError {} ast.EmptyExpr {} ast.AnonFn { - f.fn_decl(node.decl) + f.anon_fn(node) } ast.ArrayDecompose { f.array_decompose(node) @@ -868,6 +867,15 @@ pub fn (mut f Fmt) enum_decl(node ast.EnumDecl) { pub fn (mut f Fmt) fn_decl(node ast.FnDecl) { f.attrs(node.attrs) f.write(node.stringify(f.table, f.cur_mod, f.mod2alias)) // `Expr` instead of `ast.Expr` in mod ast + f.fn_body(node) +} + +pub fn (mut f Fmt) anon_fn(node ast.AnonFn) { + f.write(node.stringify(f.table, f.cur_mod, f.mod2alias)) // `Expr` instead of `ast.Expr` in mod ast + f.fn_body(node.decl) +} + +fn (mut f Fmt) fn_body(node ast.FnDecl) { if node.language == .v { if !node.no_body { f.write(' {') @@ -1007,7 +1015,7 @@ pub fn (mut f Fmt) global_decl(node ast.GlobalDecl) { pub fn (mut f Fmt) go_expr(node ast.GoExpr) { f.write('go ') - f.expr(node.call_expr) + f.call_expr(node.call_expr) } pub fn (mut f Fmt) goto_label(node ast.GotoLabel) { @@ -1500,7 +1508,7 @@ pub fn (mut f Fmt) call_expr(node ast.CallExpr) { } else { f.write_language_prefix(node.language) if node.left is ast.AnonFn { - f.fn_decl(node.left.decl) + f.anon_fn(node.left) } else if node.language != .v { f.write('${node.name.after_char(`.`)}') } else { diff --git a/vlib/v/fmt/tests/closure_keep.vv b/vlib/v/fmt/tests/closure_keep.vv new file mode 100644 index 0000000000..ec05f855c7 --- /dev/null +++ b/vlib/v/fmt/tests/closure_keep.vv @@ -0,0 +1,10 @@ +my_var := 1 +my_simple_closure := fn [my_var] () { + println(my_var) +} +my_closure_returns := fn [my_var] () int { + return my_var +} +my_closure_has_params := fn [my_var, my_closure_returns] (add int) { + println(my_var + my_closure_returns() + add) +} diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 087cb71778..1b639f432f 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -180,6 +180,7 @@ mut: defer_vars []string anon_fn bool array_sort_fn map[string]bool + nr_closures int } pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { @@ -381,6 +382,10 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { } } if g.anon_fn_definitions.len > 0 { + if g.nr_closures > 0 { + b.writeln('\n// V closure helpers') + b.writeln(c_closure_helpers()) + } for fn_def in g.anon_fn_definitions { b.writeln(fn_def) } @@ -613,9 +618,7 @@ fn (mut g Gen) generic_fn_name(types []ast.Type, before string, is_decl bool) st fn (mut g Gen) expr_string(expr ast.Expr) string { pos := g.out.len g.expr(expr) - expr_str := g.out.after(pos) - g.out.go_back(expr_str.len) - return expr_str.trim_space() + return g.out.cut_to(pos).trim_space() } // Surround a potentially multi-statement expression safely with `prepend` and `append`. @@ -623,13 +626,13 @@ fn (mut g Gen) expr_string(expr ast.Expr) string { fn (mut g Gen) expr_string_surround(prepend string, expr ast.Expr, append string) string { pos := g.out.len g.stmt_path_pos << pos + defer { + g.stmt_path_pos.delete_last() + } g.write(prepend) g.expr(expr) g.write(append) - expr_str := g.out.after(pos) - g.out.go_back(expr_str.len) - g.stmt_path_pos.delete_last() - return expr_str + return g.out.cut_to(pos) } // TODO this really shouldnt be seperate from typ @@ -823,8 +826,7 @@ pub fn (mut g Gen) write_typedef_types() { if elem_sym.info is ast.FnType { pos := g.out.len g.write_fn_ptr_decl(&elem_sym.info, '') - fixed = g.out.after(pos) - g.out.go_back(fixed.len) + fixed = g.out.cut_to(pos) mut def_str := 'typedef $fixed;' def_str = def_str.replace_once('(*)', '(*$styp[$len])') g.type_definitions.writeln(def_str) @@ -2623,7 +2625,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { ret_styp := g.typ(val.decl.return_type) g.write('$ret_styp (*$ident.name) (') def_pos := g.definitions.len - g.fn_args(val.decl.params, val.decl.is_variadic, voidptr(0)) + g.fn_args(val.decl.params, voidptr(0)) g.definitions.go_back(g.definitions.len - def_pos) g.write(') = ') } else { @@ -2764,7 +2766,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { ret_styp := g.typ(func.func.return_type) g.write('$ret_styp (*${g.get_ternary_name(ident.name)}) (') def_pos := g.definitions.len - g.fn_args(func.func.params, func.func.is_variadic, voidptr(0)) + g.fn_args(func.func.params, voidptr(0)) g.definitions.go_back(g.definitions.len - def_pos) g.write(')') } else { @@ -3084,6 +3086,10 @@ fn (mut g Gen) autofree_scope_vars2(scope &ast.Scope, start_pos int, end_pos int g.trace_autofree('// skipping tmp var "$obj.name"') continue } + if obj.is_inherited { + g.trace_autofree('// skipping inherited var "$obj.name"') + continue + } // if var.typ == 0 { // // TODO why 0? // continue @@ -3194,19 +3200,6 @@ fn (mut g Gen) autofree_var_call(free_fn_name string, v ast.Var) { } } -fn (mut g Gen) gen_anon_fn_decl(mut node ast.AnonFn) { - if !node.has_gen { - pos := g.out.len - g.anon_fn = true - g.stmt(node.decl) - g.anon_fn = false - fn_body := g.out.after(pos) - g.out.go_back(fn_body.len) - g.anon_fn_definitions << fn_body - node.has_gen = true - } -} - fn (mut g Gen) map_fn_ptrs(key_typ ast.TypeSymbol) (string, string, string, string) { mut hash_fn := '' mut key_eq_fn := '' @@ -3270,10 +3263,7 @@ fn (mut g Gen) expr(node ast.Expr) { g.error('g.expr(): unhandled EmptyExpr', token.Position{}) } ast.AnonFn { - // TODO: dont fiddle with buffers - g.gen_anon_fn_decl(mut node) - fsym := g.table.get_type_symbol(node.typ) - g.write(fsym.name) + g.gen_anon_fn(mut node) } ast.ArrayDecompose { g.expr(node.expr) @@ -4439,6 +4429,9 @@ fn (mut g Gen) ident(node ast.Ident) { return } } + if v.is_inherited { + g.write(closure_ctx + '->') + } } } else if node_info is ast.IdentFn { if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') { @@ -5703,8 +5696,7 @@ fn (mut g Gen) write_types(types []ast.TypeSymbol) { // optional and we dont want that styp, base := g.optional_type_name(field.typ) if styp !in g.optionals { - last_text := g.type_definitions.after(start_pos).clone() - g.type_definitions.go_back_to(start_pos) + last_text := g.type_definitions.cut_to(start_pos).clone() g.optionals << styp g.typedefs2.writeln('typedef struct $styp $styp;') g.type_definitions.writeln('${g.optional_type_text(styp, @@ -5796,8 +5788,7 @@ fn (mut g Gen) write_types(types []ast.TypeSymbol) { if elem_sym.info is ast.FnType { pos := g.out.len g.write_fn_ptr_decl(&elem_sym.info, '') - fixed_elem_name = g.out.after(pos) - g.out.go_back(fixed_elem_name.len) + fixed_elem_name = g.out.cut_to(pos) mut def_str := 'typedef $fixed_elem_name;' def_str = def_str.replace_once('(*)', '(*$styp[$len])') g.type_definitions.writeln(def_str) @@ -5883,9 +5874,7 @@ fn (g &Gen) nth_stmt_pos(n int) int { fn (mut g Gen) go_before_stmt(n int) string { stmt_pos := g.nth_stmt_pos(n) - cur_line := g.out.after(stmt_pos) - g.out.go_back(cur_line.len) - return cur_line + return g.out.cut_to(stmt_pos) } [inline] @@ -6167,8 +6156,7 @@ fn (mut g Gen) go_expr(node ast.GoExpr) { name = receiver_sym.name + '_' + name } else if mut expr.left is ast.AnonFn { g.gen_anon_fn_decl(mut expr.left) - fsym := g.table.get_type_symbol(expr.left.typ) - name = fsym.name + name = expr.left.decl.name } name = util.no_dots(name) if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') { @@ -6465,7 +6453,7 @@ fn (mut g Gen) interface_table() string { arg := method.params[i] methods_struct_def.write_string(', ${g.typ(arg.typ)} $arg.name') } - // TODO g.fn_args(method.args[1..], method.is_variadic) + // TODO g.fn_args(method.args[1..]) methods_struct_def.writeln(');') } methods_struct_def.writeln('};') @@ -6603,7 +6591,7 @@ static inline $interface_name I_${cctype}_to_Interface_${interface_name}($cctype ...params[0] typ: params[0].typ.set_nr_muls(1) } - fargs, _, _ := g.fn_args(params, false, voidptr(0)) // second argument is ignored anyway + fargs, _, _ := g.fn_args(params, voidptr(0)) methods_wrapper.write_string(g.out.cut_last(g.out.len - params_start_pos)) methods_wrapper.writeln(') {') methods_wrapper.write_string('\t') diff --git a/vlib/v/gen/c/cheaders.v b/vlib/v/gen/c/cheaders.v index 7db49bde72..951b3f168a 100644 --- a/vlib/v/gen/c/cheaders.v +++ b/vlib/v/gen/c/cheaders.v @@ -1,5 +1,7 @@ module c +import strings + // NB: @@@ here serve as placeholders. // They will be replaced with correct strings // for each constant, during C code generation. @@ -49,6 +51,82 @@ static inline void __sort_ptr(uintptr_t a[], bool b[], int l) { } ' +// Heavily based on Chris Wellons's work +// https://nullprogram.com/blog/2017/01/08/ + +fn c_closure_helpers() string { + $if windows { + verror('closures are not implemented on Windows yet') + } + $if !x64 { + verror('closures are not implemented on this architecture yet') + } + mut builder := strings.new_builder(1024) + $if !windows { + builder.writeln('#include ') + } + $if x64 { + builder.write_string(' +static unsigned char __closure_thunk[6][13] = { + { + 0x48, 0x8b, 0x3d, 0xe9, 0xff, 0xff, 0xff, + 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff + }, { + 0x48, 0x8b, 0x35, 0xe9, 0xff, 0xff, 0xff, + 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff + }, { + 0x48, 0x8b, 0x15, 0xe9, 0xff, 0xff, 0xff, + 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff + }, { + 0x48, 0x8b, 0x0d, 0xe9, 0xff, 0xff, 0xff, + 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff + }, { + 0x4C, 0x8b, 0x05, 0xe9, 0xff, 0xff, 0xff, + 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff + }, { + 0x4C, 0x8b, 0x0d, 0xe9, 0xff, 0xff, 0xff, + 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff + }, +}; +') + } + builder.write_string(' +static void __closure_set_data(void *closure, void *data) { + void **p = closure; + p[-2] = data; +} + +static void __closure_set_function(void *closure, void *f) { + void **p = closure; + p[-1] = f; +} +') + $if !windows { + builder.write_string(' +static void * __closure_create(void *f, int nargs, void *userdata) { + long page_size = sysconf(_SC_PAGESIZE); + int prot = PROT_READ | PROT_WRITE; + int flags = MAP_ANONYMOUS | MAP_PRIVATE; + char *p = mmap(0, page_size * 2, prot, flags, -1, 0); + if (p == MAP_FAILED) + return 0; + void *closure = p + page_size; + memcpy(closure, __closure_thunk[nargs - 1], sizeof(__closure_thunk[0])); + mprotect(closure, page_size, PROT_READ | PROT_EXEC); + __closure_set_function(closure, f); + __closure_set_data(closure, userdata); + return closure; +} + +static void __closure_destroy(void *closure) { + long page_size = sysconf(_SC_PAGESIZE); + munmap((char *)closure - page_size, page_size * 2); +} +') + } + return builder.str() +} + const c_common_macros = ' #define EMPTY_VARG_INITIALIZATION 0 #define EMPTY_STRUCT_DECLARATION diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index c801d0fb44..ad724e8d3c 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -3,6 +3,7 @@ // that can be found in the LICENSE file. module c +import strings import v.ast import v.util @@ -39,7 +40,6 @@ fn (mut g Gen) process_fn_decl(node ast.FnDecl) { return } g.gen_attrs(node.attrs) - // g.tmp_count = 0 TODO mut skip := false pos := g.out.len should_bundle_module := util.should_bundle_module(node.mod) @@ -166,6 +166,15 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) { g.table.cur_fn = node } fn_start_pos := g.out.len + is_closure := node.scope.has_inherited_vars() + mut cur_closure_ctx := '' + if is_closure { + cur_closure_ctx = closure_ctx_struct(node) + // declare the struct before its implementation + g.definitions.write_string(cur_closure_ctx) + g.definitions.writeln(';') + } + g.write_v_source_line_info(node.pos) msvc_attrs := g.write_fn_attrs(node.attrs) // Live @@ -264,7 +273,19 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) { g.write(fn_header) } arg_start_pos := g.out.len - fargs, fargtypes, heap_promoted := g.fn_args(node.params, node.is_variadic, node.scope) + fargs, fargtypes, heap_promoted := g.fn_args(node.params, node.scope) + if is_closure { + mut s := '$cur_closure_ctx *$c.closure_ctx' + if node.params.len > 0 { + s = ', ' + s + } else { + // remove generated `void` + g.out.cut_to(arg_start_pos) + } + g.definitions.write_string(s) + g.write(s) + g.nr_closures++ + } arg_str := g.out.after(arg_start_pos) if node.no_body || ((g.pref.use_cache && g.pref.build_mode != .build_module) && node.is_builtin && !g.is_test) || skip { @@ -386,6 +407,59 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) { } } +const closure_ctx = '_V_closure_ctx' + +fn closure_ctx_struct(node ast.FnDecl) string { + return 'struct _V_${node.name}_Ctx' +} + +fn (mut g Gen) gen_anon_fn(mut node ast.AnonFn) { + g.gen_anon_fn_decl(mut node) + if !node.decl.scope.has_inherited_vars() { + g.write(node.decl.name) + return + } + // it may be possible to optimize `memdup` out if the closure never leaves current scope + ctx_var := g.new_tmp_var() + cur_line := g.go_before_stmt(0) + ctx_struct := closure_ctx_struct(node.decl) + g.writeln('$ctx_struct* $ctx_var = ($ctx_struct*) memdup(&($ctx_struct){') + g.indent++ + for var in node.inherited_vars { + g.writeln('.$var.name = $var.name,') + } + g.indent-- + g.writeln('}, sizeof($ctx_struct));') + g.empty_line = false + g.write(cur_line) + // TODO in case of an assignment, this should only call "__closure_set_data" and "__closure_set_function" (and free the former data) + g.write('__closure_create($node.decl.name, ${node.decl.params.len + 1}, $ctx_var)') +} + +fn (mut g Gen) gen_anon_fn_decl(mut node ast.AnonFn) { + if node.has_gen { + return + } + node.has_gen = true + mut builder := strings.new_builder(256) + if node.inherited_vars.len > 0 { + ctx_struct := closure_ctx_struct(node.decl) + builder.writeln('$ctx_struct {') + for var in node.inherited_vars { + styp := g.typ(var.typ) + builder.writeln('\t$styp $var.name;') + } + builder.writeln('};\n') + } + pos := g.out.len + was_anon_fn := g.anon_fn + g.anon_fn = true + g.process_fn_decl(node.decl) + g.anon_fn = was_anon_fn + builder.write_string(g.out.cut_to(pos)) + g.anon_fn_definitions << builder.str() +} + fn (g &Gen) defer_flag_var(stmt &ast.DeferStmt) string { return '${g.last_fn_c_name}_defer_$stmt.idx_in_fn' } @@ -405,7 +479,7 @@ fn (mut g Gen) write_defer_stmts_when_needed() { } // fn decl args -fn (mut g Gen) fn_args(args []ast.Param, is_variadic bool, scope &ast.Scope) ([]string, []string, []bool) { +fn (mut g Gen) fn_args(args []ast.Param, scope &ast.Scope) ([]string, []string, []bool) { mut fargs := []string{} mut fargtypes := []string{} mut heap_promoted := []bool{} @@ -423,7 +497,7 @@ fn (mut g Gen) fn_args(args []ast.Param, is_variadic bool, scope &ast.Scope) ([] func := info.func g.write('${g.typ(func.return_type)} (*$caname)(') g.definitions.write_string('${g.typ(func.return_type)} (*$caname)(') - g.fn_args(func.params, func.is_variadic, voidptr(0)) + g.fn_args(func.params, voidptr(0)) g.write(')') g.definitions.write_string(')') fargs << caname diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index f24e1572b9..cd3fc35a0c 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -633,9 +633,11 @@ fn (mut p Parser) anon_fn() ast.AnonFn { old_inside_defer := p.inside_defer p.inside_defer = false p.open_scope() - if !p.pref.backend.is_js() { - p.scope.detached_from_parent = true + defer { + p.close_scope() } + p.scope.detached_from_parent = true + inherited_vars := if p.tok.kind == .lsbr { p.closure_vars() } else { []ast.Param{} } // TODO generics args, _, is_variadic := p.fn_args() for arg in args { @@ -691,7 +693,6 @@ fn (mut p Parser) anon_fn() ast.AnonFn { p.label_names = tmp } p.cur_fn_name = keep_fn_name - p.close_scope() func.name = name idx := p.table.find_or_register_fn_type(p.mod, func, true, false) typ := ast.new_type(idx) @@ -714,6 +715,7 @@ fn (mut p Parser) anon_fn() ast.AnonFn { scope: p.scope label_names: label_names } + inherited_vars: inherited_vars typ: typ } } @@ -910,6 +912,50 @@ fn (mut p Parser) fn_args() ([]ast.Param, bool, bool) { return args, types_only, is_variadic } +fn (mut p Parser) closure_vars() []ast.Param { + p.check(.lsbr) + mut vars := []ast.Param{cap: 5} + for { + is_shared := p.tok.kind == .key_shared + is_atomic := p.tok.kind == .key_atomic + is_mut := p.tok.kind == .key_mut || is_shared || is_atomic + // FIXME is_shared & is_atomic aren't used further + if is_mut { + p.next() + } + var_pos := p.tok.position() + p.check(.name) + var_name := p.prev_tok.lit + mut var := p.scope.parent.find_var(var_name) or { + p.error_with_pos('undefined ident: `$var_name`', p.prev_tok.position()) + continue + } + var.is_used = true + if is_mut { + var.is_changed = true + } + p.scope.register(ast.Var{ + ...(*var) + pos: var_pos + is_inherited: true + is_used: false + is_changed: false + is_mut: is_mut + }) + vars << ast.Param{ + pos: var_pos + name: var_name + is_mut: is_mut + } + if p.tok.kind != .comma { + break + } + p.next() + } + p.check(.rsbr) + return vars +} + fn (mut p Parser) check_fn_mutable_arguments(typ ast.Type, pos token.Position) { sym := p.table.get_type_symbol(typ) if sym.kind in [.array, .array_fixed, .interface_, .map, .placeholder, .struct_, .generic_inst, diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index e3e22cc3f7..29cd2696cd 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -1784,49 +1784,49 @@ pub fn (mut p Parser) parse_ident(language ast.Language) ast.Ident { if is_static { p.next() } - if p.tok.kind == .name { - pos := p.tok.position() - mut name := p.check_name() - if name == '_' { - return ast.Ident{ - tok_kind: p.tok.kind - name: '_' - comptime: p.comp_if_cond - kind: .blank_ident - pos: pos - info: ast.IdentVar{ - is_mut: false - is_static: false - } - scope: p.scope - } - } - if p.inside_match_body && name == 'it' { - // p.warn('it') - } - if p.expr_mod.len > 0 { - name = '${p.expr_mod}.$name' + if p.tok.kind != .name { + p.error('unexpected token `$p.tok.lit`') + return ast.Ident{ + scope: p.scope } + } + pos := p.tok.position() + mut name := p.check_name() + if name == '_' { return ast.Ident{ tok_kind: p.tok.kind - kind: .unresolved - name: name + name: '_' comptime: p.comp_if_cond - language: language - mod: p.mod + kind: .blank_ident pos: pos - is_mut: is_mut - mut_pos: mut_pos info: ast.IdentVar{ - is_mut: is_mut - is_static: is_static - share: ast.sharetype_from_flags(is_shared, is_atomic) + is_mut: false + is_static: false } scope: p.scope } } - p.error('unexpected token `$p.tok.lit`') + if p.inside_match_body && name == 'it' { + // p.warn('it') + } + if p.expr_mod.len > 0 { + name = '${p.expr_mod}.$name' + } return ast.Ident{ + tok_kind: p.tok.kind + kind: .unresolved + name: name + comptime: p.comp_if_cond + language: language + mod: p.mod + pos: pos + is_mut: is_mut + mut_pos: mut_pos + info: ast.IdentVar{ + is_mut: is_mut + is_static: is_static + share: ast.sharetype_from_flags(is_shared, is_atomic) + } scope: p.scope } } diff --git a/vlib/v/parser/tests/closure_not_declared.out b/vlib/v/parser/tests/closure_not_declared.out new file mode 100644 index 0000000000..a90ceacf06 --- /dev/null +++ b/vlib/v/parser/tests/closure_not_declared.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/closure_not_declared.vv:4:9: error: undefined ident: `a` + 2 | a := 1 + 3 | f := fn () { + 4 | print(a) + | ^ + 5 | } + 6 | f() diff --git a/vlib/v/parser/tests/closure_not_declared.vv b/vlib/v/parser/tests/closure_not_declared.vv new file mode 100644 index 0000000000..b0ed7a5676 --- /dev/null +++ b/vlib/v/parser/tests/closure_not_declared.vv @@ -0,0 +1,8 @@ +fn my_fn() { + a := 1 + f := fn () { + print(a) + } + f() + _ = a +} diff --git a/vlib/v/parser/tests/closure_not_used.out b/vlib/v/parser/tests/closure_not_used.out new file mode 100644 index 0000000000..2cd7a06629 --- /dev/null +++ b/vlib/v/parser/tests/closure_not_used.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/closure_not_used.vv:3:11: error: unused variable: `a` + 1 | fn my_fn() { + 2 | a := 1 + 3 | f := fn [a] () { + | ^ + 4 | print("hello") + 5 | } diff --git a/vlib/v/parser/tests/closure_not_used.vv b/vlib/v/parser/tests/closure_not_used.vv new file mode 100644 index 0000000000..076f53a17a --- /dev/null +++ b/vlib/v/parser/tests/closure_not_used.vv @@ -0,0 +1,7 @@ +fn my_fn() { + a := 1 + f := fn [a] () { + print("hello") + } + f() +} diff --git a/vlib/v/parser/tests/closure_undefined_var.out b/vlib/v/parser/tests/closure_undefined_var.out new file mode 100644 index 0000000000..0eeeb82a26 --- /dev/null +++ b/vlib/v/parser/tests/closure_undefined_var.out @@ -0,0 +1,6 @@ +vlib/v/parser/tests/closure_undefined_var.vv:2:11: error: undefined ident: `dont_exist` + 1 | fn my_fn() { + 2 | f := fn [dont_exist] () { + | ~~~~~~~~~~ + 3 | print(dont_exist) + 4 | } diff --git a/vlib/v/parser/tests/closure_undefined_var.vv b/vlib/v/parser/tests/closure_undefined_var.vv new file mode 100644 index 0000000000..3edb4456ae --- /dev/null +++ b/vlib/v/parser/tests/closure_undefined_var.vv @@ -0,0 +1,6 @@ +fn my_fn() { + f := fn [dont_exist] () { + print(dont_exist) + } + f() +} diff --git a/vlib/v/tests/closure_test.v b/vlib/v/tests/closure_test.v new file mode 100644 index 0000000000..483f92ebab --- /dev/null +++ b/vlib/v/tests/closure_test.v @@ -0,0 +1,153 @@ +fn test_decl_assignment() { + my_var := 12 + c1 := fn [my_var] () int { + return my_var + } + assert c1() == 12 +} + +fn test_assignment() { + v1 := 1 + mut c := fn [v1] () int { + return v1 + } + v2 := 3 + c = fn [v2] () int { + return v2 + } + assert c() == 3 +} + +fn call_anon_with_3(f fn (int) int) int { + return f(3) +} + +fn test_closure_in_call() { + my_var := int(12) + r1 := call_anon_with_3(fn [my_var] (add int) int { + return my_var + add + }) + assert r1 == 15 +} + +fn create_simple_counter() fn () int { + mut c := -1 + return fn [mut c] () int { + c++ + return c + } +} + +fn override_stack() { + // just create some variables to modify the stack + a := 1 + b := a + 2 + c := b + 3 + d := c + 4 + e := d + 5 + f := e + 6 + g := f + 7 + _ = g +} + +fn test_closure_exit_original_scope() { + mut c := create_simple_counter() + assert c() == 0 + override_stack() + assert c() == 1 +} + +fn test_vars_are_changed() { + mut my_var := 1 + f1 := fn [mut my_var] (expected int) { + assert my_var == expected + } + f1(1) + my_var = 3 + f1(1) + my_var = -1 + f1(1) +} + +struct Counter { +mut: + i u64 +} + +fn (mut c Counter) incr() { + c.i++ +} + +fn (mut c Counter) next() u64 { + c.incr() + return c.i +} + +fn test_call_methods() { + mut c := Counter{} + f1 := fn [mut c] () u64 { + return c.next() + } + assert f1() == 1 + assert f1() == 2 + c.incr() + assert f1() == 3 +} + +fn test_call_methods_on_pointer() { + mut c := &Counter{} + f1 := fn [mut c] () u64 { + return c.next() + } + assert f1() == 1 + assert f1() == 2 + c.incr() + assert f1() == 4 +} + +/* +fn test_methods_as_variables() { + mut c := Counter{} + f1 := c.next + assert f1() == 1 + assert f1() == 2 + c.incr() + assert f1() == 3 +} +*/ + +/* +fn takes_callback(get fn () u64) u64 { + return get() +} + +fn test_methods_as_callback() { + mut c := Counter{} + assert takes_callback(c.next) == 1 +} +*/ + +struct ZeroSize {} + +fn test_zero_size_ctx() { + ctx := ZeroSize{} + c1 := fn [ctx] () int { + return 123 + } + assert c1() == 123 +} + +/* +fn test_go_call_closure() { + my_var := 12 + ch := chan int{} + go fn [my_var, ch] () { + ch <- my_var + }() + assert <-ch == 12 + go fn [ch] (arg int) { + ch <- arg + }(15) + assert <-ch == 15 +} +*/ diff --git a/vlib/v/token/position.v b/vlib/v/token/position.v index 71cbf602ed..9e15d3e618 100644 --- a/vlib/v/token/position.v +++ b/vlib/v/token/position.v @@ -13,10 +13,6 @@ pub mut: last_line int // the line number where the ast object ends (used by vfmt) } -pub fn (pos Position) str() string { - return 'Position{ line_nr: $pos.line_nr, last_line: $pos.last_line, pos: $pos.pos, col: $pos.col, len: $pos.len }' -} - pub fn (pos Position) extend(end Position) Position { return Position{ ...pos @@ -29,9 +25,9 @@ pub fn (pos Position) extend_with_last_line(end Position, last_line int) Positio return Position{ len: end.pos - pos.pos + end.len line_nr: pos.line_nr - last_line: last_line - 1 pos: pos.pos col: pos.col + last_line: last_line - 1 } }