Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions vlib/v/checker/checker.v
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
7 changes: 7 additions & 0 deletions vlib/v/checker/tests/unsafe_method_as_field.out
Original file line number Diff line number Diff line change
@@ -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{}
27 changes: 27 additions & 0 deletions vlib/v/checker/tests/unsafe_method_as_field.vv
Original file line number Diff line number Diff line change
@@ -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
}
6 changes: 5 additions & 1 deletion vlib/v/gen/c/assign.v
Original file line number Diff line number Diff line change
Expand Up @@ -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(';}')
}
Expand Down
58 changes: 58 additions & 0 deletions vlib/v/gen/c/cgen.v
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions vlib/v/markused/walker.v
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
97 changes: 97 additions & 0 deletions vlib/v/tests/methods_as_fields_test.v
Original file line number Diff line number Diff line change
@@ -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
}
Empty file.
Empty file.
12 changes: 12 additions & 0 deletions vlib/v/tests/skip_unused/method_as_fn_pointer.vv
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
struct Foo {
x int
}

fn (f Foo) hi() int {
return f.x
}

fn main() {
f := Foo{123}
_ = f.hi
}