From 47848d3719363e23385d9b75b9c80c83d0165307 Mon Sep 17 00:00:00 2001 From: Raul Hernandez Date: Sun, 15 May 2022 11:57:56 +0200 Subject: [PATCH 1/2] checker,gen: allow using methods as function pointers --- vlib/v/checker/checker.v | 6 ++++ vlib/v/gen/c/cgen.v | 46 +++++++++++++++++++++++++++ vlib/v/tests/methods_as_fields_test.v | 42 ++++++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 vlib/v/tests/methods_as_fields_test.v diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index f2c475b720fb9c..58cf4e35b48cca 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1850,6 +1850,12 @@ 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) { + 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/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 6b383ef5733562..42bbeab589ddb0 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -3339,6 +3339,52 @@ 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 { + expr_styp := g.typ(node.expr_type) + data_styp := g.typ(m.params[0].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 ') + } + sb.write_string('${expr_styp}_${m.name}(') + if !m.params[0].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, memdup(&') + g.expr(node.expr) + g.write(', sizeof($expr_styp)))') + return + } } n_ptr := node.expr_type.nr_muls() - 1 if n_ptr > 0 { 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 00000000000000..72f7457e4ce1e8 --- /dev/null +++ b/vlib/v/tests/methods_as_fields_test.v @@ -0,0 +1,42 @@ +struct Foo { + s string + 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 test_methods_as_fields() { + f := Foo{ + s: 'hello' + i: 1 + } + + get_s := f.get_s + get_s_ref := f.get_s_ref + add := f.add + add_ref := f.add_ref + + 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 +} From acd4e04ddc7656c428de9d3f2232efe17e04adb7 Mon Sep 17 00:00:00 2001 From: Raul Hernandez Date: Sun, 15 May 2022 13:34:44 +0200 Subject: [PATCH 2/2] checker,gen,markused: various fixes --- vlib/v/checker/checker.v | 15 +++++ .../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 | 22 +++++-- vlib/v/markused/walker.v | 5 ++ vlib/v/tests/methods_as_fields_test.v | 61 ++++++++++++++++++- .../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, 146 insertions(+), 9 deletions(-) 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/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 58cf4e35b48cca..837e30d878bd5b 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1851,6 +1851,21 @@ pub fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { 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)) 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 00000000000000..7a9984638402f2 --- /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 00000000000000..4dc4209c1c9249 --- /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 9296065456f5b5..d5cb82dd7a75ac 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 42bbeab589ddb0..234bc69e262a29 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -3347,8 +3347,9 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) { } } if !has_embeds { - expr_styp := g.typ(node.expr_type) - data_styp := g.typ(m.params[0].typ.idx()) + 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}(') @@ -3363,9 +3364,11 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) { 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 !m.params[0].typ.is_ptr() { + if !receiver.typ.is_ptr() { sb.write_string('*') } for i in 0 .. m.params.len { @@ -3380,9 +3383,18 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) { g.anon_fn_definitions << sb.str() g.nr_closures++ - g.write('__closure_create($name, memdup(&') + 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) - g.write(', sizeof($expr_styp)))') + if !receiver.typ.is_ptr() { + g.write(', sizeof($expr_styp))') + } + g.write(')') return } } diff --git a/vlib/v/markused/walker.v b/vlib/v/markused/walker.v index 67e7447e746b89..d893f05a17f7f4 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 index 72f7457e4ce1e8..7676825f6efb5d 100644 --- a/vlib/v/tests/methods_as_fields_test.v +++ b/vlib/v/tests/methods_as_fields_test.v @@ -1,5 +1,6 @@ struct Foo { s string +mut: i int } @@ -19,16 +20,62 @@ 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() { - f := Foo{ + 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 := f.get_s_ref + get_s_ref := unsafe { f.get_s_ref } add := f.add - add_ref := f.add_ref + 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' @@ -39,4 +86,12 @@ fn test_methods_as_fields() { 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 00000000000000..e69de29bb2d1d6 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 00000000000000..e69de29bb2d1d6 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 00000000000000..6121e806692aa2 --- /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 +}