From ec2346d45c1b51d306d0819f93409f9d470a88b1 Mon Sep 17 00:00:00 2001 From: spaceface Date: Sun, 15 May 2022 17:28:37 +0200 Subject: [PATCH] checker,gen: allow using methods as function pointers (#14407) --- vlib/v/checker/checker.v | 21 ++++ .../checker/tests/unsafe_method_as_field.out | 7 ++ .../v/checker/tests/unsafe_method_as_field.vv | 27 ++++++ vlib/v/gen/c/assign.v | 6 +- vlib/v/gen/c/cgen.v | 58 +++++++++++ vlib/v/markused/walker.v | 5 + vlib/v/tests/methods_as_fields_test.v | 97 +++++++++++++++++++ .../skip_unused/method_as_fn_pointer.run.out | 0 .../method_as_fn_pointer.skip_unused.run.out | 0 .../tests/skip_unused/method_as_fn_pointer.vv | 12 +++ 10 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 vlib/v/checker/tests/unsafe_method_as_field.out create mode 100644 vlib/v/checker/tests/unsafe_method_as_field.vv create mode 100644 vlib/v/tests/methods_as_fields_test.v create mode 100644 vlib/v/tests/skip_unused/method_as_fn_pointer.run.out create mode 100644 vlib/v/tests/skip_unused/method_as_fn_pointer.skip_unused.run.out create mode 100644 vlib/v/tests/skip_unused/method_as_fn_pointer.vv diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index f2c475b720..837e30d878 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1850,6 +1850,27 @@ pub fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { node.typ = field.typ return field.typ } + if mut method := c.table.find_method(sym, field_name) { + receiver := method.params[0].typ + if receiver.nr_muls() > 0 { + if !c.inside_unsafe { + rec_sym := c.table.sym(receiver.set_nr_muls(0)) + if !rec_sym.is_heap() { + suggestion := if rec_sym.kind == .struct_ { + 'declaring `$rec_sym.name` as `[heap]`' + } else { + 'wrapping the `$rec_sym.name` object in a `struct` declared as `[heap]`' + } + c.error('method `${c.table.type_to_str(receiver.idx())}.$method.name` cannot be used as a variable outside `unsafe` blocks as its receiver might refer to an object stored on stack. Consider ${suggestion}.', + node.expr.pos().extend(node.pos)) + } + } + } + method.params = method.params[1..] + fn_type := ast.new_type(c.table.find_or_register_fn_type(c.mod, method, false, + true)) + return fn_type + } if sym.kind !in [.struct_, .aggregate, .interface_, .sum_type] { if sym.kind != .placeholder { unwrapped_sym := c.table.sym(c.unwrap_generic(typ)) diff --git a/vlib/v/checker/tests/unsafe_method_as_field.out b/vlib/v/checker/tests/unsafe_method_as_field.out new file mode 100644 index 0000000000..7a99846384 --- /dev/null +++ b/vlib/v/checker/tests/unsafe_method_as_field.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/unsafe_method_as_field.vv:23:7: error: method `Foo.ref` cannot be used as a variable outside `unsafe` blocks as its receiver might refer to an object stored on stack. Consider declaring `Foo` as `[heap]`. + 21 | f := Foo{} + 22 | _ := f.no_ref // no error + 23 | _ := f.ref // error + | ~~~~~ + 24 | + 25 | b := Bar{} \ No newline at end of file diff --git a/vlib/v/checker/tests/unsafe_method_as_field.vv b/vlib/v/checker/tests/unsafe_method_as_field.vv new file mode 100644 index 0000000000..4dc4209c1c --- /dev/null +++ b/vlib/v/checker/tests/unsafe_method_as_field.vv @@ -0,0 +1,27 @@ +struct Foo { +} + +fn (f Foo) no_ref() int { + return 1 +} + +fn (f &Foo) ref() int { + return 1 +} + +[heap] +struct Bar { +} + +fn (f &Bar) ref() int { + return 1 +} + +fn main() { + f := Foo{} + _ := f.no_ref // no error + _ := f.ref // error + + b := Bar{} + _ := b.ref // no error +} diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index 9296065456..d5cb82dd7a 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -211,7 +211,11 @@ fn (mut g Gen) gen_assign_stmt(node_ ast.AssignStmt) { } else if g.inside_for_c_stmt { g.expr(val) } else { - g.write('{$styp _ = ') + if left_sym.kind == .function { + g.write('{void* _ = ') + } else { + g.write('{$styp _ = ') + } g.expr(val) g.writeln(';}') } diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 6b383ef573..234bc69e26 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -3339,6 +3339,64 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) { } } } + } else if m := g.table.find_method(sym, node.field_name) { + mut has_embeds := false + if sym.info in [ast.Struct, ast.Aggregate] { + if node.from_embed_types.len > 0 { + has_embeds = true + } + } + if !has_embeds { + receiver := m.params[0] + expr_styp := g.typ(node.expr_type.idx()) + data_styp := g.typ(receiver.typ.idx()) + mut sb := strings.new_builder(256) + name := '_V_closure_${expr_styp}_${m.name}_$node.pos.pos' + sb.write_string('${g.typ(m.return_type)} ${name}(') + for i in 1 .. m.params.len { + param := m.params[i] + if i != 1 { + sb.write_string(', ') + } + sb.write_string('${g.typ(param.typ)} a$i') + } + sb.writeln(') {') + sb.writeln('\t$data_styp* a0 = *($data_styp**)(__RETURN_ADDRESS() - __CLOSURE_DATA_OFFSET);') + if m.return_type != ast.void_type { + sb.write_string('\treturn ') + } else { + sb.write_string('\t') + } + sb.write_string('${expr_styp}_${m.name}(') + if !receiver.typ.is_ptr() { + sb.write_string('*') + } + for i in 0 .. m.params.len { + if i != 0 { + sb.write_string(', ') + } + sb.write_string('a$i') + } + sb.writeln(');') + sb.writeln('}') + + g.anon_fn_definitions << sb.str() + g.nr_closures++ + + g.write('__closure_create($name, ') + if !receiver.typ.is_ptr() { + g.write('memdup(') + } + if !node.expr_type.is_ptr() { + g.write('&') + } + g.expr(node.expr) + if !receiver.typ.is_ptr() { + g.write(', sizeof($expr_styp))') + } + g.write(')') + return + } } n_ptr := node.expr_type.nr_muls() - 1 if n_ptr > 0 { diff --git a/vlib/v/markused/walker.v b/vlib/v/markused/walker.v index 67e7447e74..d893f05a17 100644 --- a/vlib/v/markused/walker.v +++ b/vlib/v/markused/walker.v @@ -378,6 +378,11 @@ fn (mut w Walker) expr(node_ ast.Expr) { } ast.SelectorExpr { w.expr(node.expr) + if node.expr_type != 0 { + if method := w.table.find_method(w.table.sym(node.expr_type), node.field_name) { + w.fn_by_name(method.fkey()) + } + } } ast.SqlExpr { w.expr(node.db_expr) diff --git a/vlib/v/tests/methods_as_fields_test.v b/vlib/v/tests/methods_as_fields_test.v new file mode 100644 index 0000000000..7676825f6e --- /dev/null +++ b/vlib/v/tests/methods_as_fields_test.v @@ -0,0 +1,97 @@ +struct Foo { + s string +mut: + i int +} + +fn (f Foo) get_s() string { + return f.s +} + +fn (f &Foo) get_s_ref() string { + return f.s +} + +fn (f Foo) add(a int) int { + return a + f.i +} + +fn (f &Foo) add_ref(a int) int { + return a + f.i +} + +fn (mut f Foo) set(a int) { + f.i = a +} + +fn (f_ Foo) set_val(a int) int { + mut f := unsafe { &f_ } + old := f.i + f.i = a + return old +} + +fn test_methods_as_fields() { + mut f := Foo{ + s: 'hello' + i: 1 + } + + get_s := f.get_s + get_s_ref := unsafe { f.get_s_ref } + add := f.add + add_ref := unsafe { f.add_ref } + set := unsafe { f.set } + set_val := f.set_val + + assert typeof(get_s).str() == 'fn () string' + assert typeof(get_s_ref).str() == 'fn () string' + assert typeof(add).str() == 'fn (int) int' + assert typeof(add_ref).str() == 'fn (int) int' + + assert get_s() == 'hello' + assert get_s_ref() == 'hello' + assert add(2) == 3 + assert add_ref(2) == 3 + + assert f.i == 1 + set(2) + assert f.i == 2 + old := set_val(3) + assert f.i == 2 + new := set_val(5) + assert old == new && old == 1 +} + +// the difference between these two tests is that here `f` is &Foo +fn test_methods_as_fields_ref() { + mut f := &Foo{ + s: 'hello' + i: 1 + } + + get_s := f.get_s + get_s_ref := unsafe { f.get_s_ref } + add := f.add + add_ref := unsafe { f.add_ref } + set := unsafe { f.set } + set_val := f.set_val + + assert typeof(get_s).str() == 'fn () string' + assert typeof(get_s_ref).str() == 'fn () string' + assert typeof(add).str() == 'fn (int) int' + assert typeof(add_ref).str() == 'fn (int) int' + + assert get_s() == 'hello' + assert get_s_ref() == 'hello' + assert add(2) == 3 + assert add_ref(2) == 3 + + assert f.i == 1 + set(2) + assert f.i == 2 + old := set_val(3) + assert f.i == 2 + new := set_val(5) + assert old == new && old == 1 +} diff --git a/vlib/v/tests/skip_unused/method_as_fn_pointer.run.out b/vlib/v/tests/skip_unused/method_as_fn_pointer.run.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vlib/v/tests/skip_unused/method_as_fn_pointer.skip_unused.run.out b/vlib/v/tests/skip_unused/method_as_fn_pointer.skip_unused.run.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vlib/v/tests/skip_unused/method_as_fn_pointer.vv b/vlib/v/tests/skip_unused/method_as_fn_pointer.vv new file mode 100644 index 0000000000..6121e80669 --- /dev/null +++ b/vlib/v/tests/skip_unused/method_as_fn_pointer.vv @@ -0,0 +1,12 @@ +struct Foo { + x int +} + +fn (f Foo) hi() int { + return f.x +} + +fn main() { + f := Foo{123} + _ = f.hi +}