Skip to content

Commit c01a8a1

Browse files
authored
checker,gen: allow using methods as function pointers (#14407)
1 parent c2bc9f4 commit c01a8a1

10 files changed

Lines changed: 232 additions & 1 deletion

vlib/v/checker/checker.v

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1850,6 +1850,27 @@ pub fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type {
18501850
node.typ = field.typ
18511851
return field.typ
18521852
}
1853+
if mut method := c.table.find_method(sym, field_name) {
1854+
receiver := method.params[0].typ
1855+
if receiver.nr_muls() > 0 {
1856+
if !c.inside_unsafe {
1857+
rec_sym := c.table.sym(receiver.set_nr_muls(0))
1858+
if !rec_sym.is_heap() {
1859+
suggestion := if rec_sym.kind == .struct_ {
1860+
'declaring `$rec_sym.name` as `[heap]`'
1861+
} else {
1862+
'wrapping the `$rec_sym.name` object in a `struct` declared as `[heap]`'
1863+
}
1864+
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}.',
1865+
node.expr.pos().extend(node.pos))
1866+
}
1867+
}
1868+
}
1869+
method.params = method.params[1..]
1870+
fn_type := ast.new_type(c.table.find_or_register_fn_type(c.mod, method, false,
1871+
true))
1872+
return fn_type
1873+
}
18531874
if sym.kind !in [.struct_, .aggregate, .interface_, .sum_type] {
18541875
if sym.kind != .placeholder {
18551876
unwrapped_sym := c.table.sym(c.unwrap_generic(typ))
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
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]`.
2+
21 | f := Foo{}
3+
22 | _ := f.no_ref // no error
4+
23 | _ := f.ref // error
5+
| ~~~~~
6+
24 |
7+
25 | b := Bar{}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
struct Foo {
2+
}
3+
4+
fn (f Foo) no_ref() int {
5+
return 1
6+
}
7+
8+
fn (f &Foo) ref() int {
9+
return 1
10+
}
11+
12+
[heap]
13+
struct Bar {
14+
}
15+
16+
fn (f &Bar) ref() int {
17+
return 1
18+
}
19+
20+
fn main() {
21+
f := Foo{}
22+
_ := f.no_ref // no error
23+
_ := f.ref // error
24+
25+
b := Bar{}
26+
_ := b.ref // no error
27+
}

vlib/v/gen/c/assign.v

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,11 @@ fn (mut g Gen) gen_assign_stmt(node_ ast.AssignStmt) {
211211
} else if g.inside_for_c_stmt {
212212
g.expr(val)
213213
} else {
214-
g.write('{$styp _ = ')
214+
if left_sym.kind == .function {
215+
g.write('{void* _ = ')
216+
} else {
217+
g.write('{$styp _ = ')
218+
}
215219
g.expr(val)
216220
g.writeln(';}')
217221
}

vlib/v/gen/c/cgen.v

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3339,6 +3339,64 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) {
33393339
}
33403340
}
33413341
}
3342+
} else if m := g.table.find_method(sym, node.field_name) {
3343+
mut has_embeds := false
3344+
if sym.info in [ast.Struct, ast.Aggregate] {
3345+
if node.from_embed_types.len > 0 {
3346+
has_embeds = true
3347+
}
3348+
}
3349+
if !has_embeds {
3350+
receiver := m.params[0]
3351+
expr_styp := g.typ(node.expr_type.idx())
3352+
data_styp := g.typ(receiver.typ.idx())
3353+
mut sb := strings.new_builder(256)
3354+
name := '_V_closure_${expr_styp}_${m.name}_$node.pos.pos'
3355+
sb.write_string('${g.typ(m.return_type)} ${name}(')
3356+
for i in 1 .. m.params.len {
3357+
param := m.params[i]
3358+
if i != 1 {
3359+
sb.write_string(', ')
3360+
}
3361+
sb.write_string('${g.typ(param.typ)} a$i')
3362+
}
3363+
sb.writeln(') {')
3364+
sb.writeln('\t$data_styp* a0 = *($data_styp**)(__RETURN_ADDRESS() - __CLOSURE_DATA_OFFSET);')
3365+
if m.return_type != ast.void_type {
3366+
sb.write_string('\treturn ')
3367+
} else {
3368+
sb.write_string('\t')
3369+
}
3370+
sb.write_string('${expr_styp}_${m.name}(')
3371+
if !receiver.typ.is_ptr() {
3372+
sb.write_string('*')
3373+
}
3374+
for i in 0 .. m.params.len {
3375+
if i != 0 {
3376+
sb.write_string(', ')
3377+
}
3378+
sb.write_string('a$i')
3379+
}
3380+
sb.writeln(');')
3381+
sb.writeln('}')
3382+
3383+
g.anon_fn_definitions << sb.str()
3384+
g.nr_closures++
3385+
3386+
g.write('__closure_create($name, ')
3387+
if !receiver.typ.is_ptr() {
3388+
g.write('memdup(')
3389+
}
3390+
if !node.expr_type.is_ptr() {
3391+
g.write('&')
3392+
}
3393+
g.expr(node.expr)
3394+
if !receiver.typ.is_ptr() {
3395+
g.write(', sizeof($expr_styp))')
3396+
}
3397+
g.write(')')
3398+
return
3399+
}
33423400
}
33433401
n_ptr := node.expr_type.nr_muls() - 1
33443402
if n_ptr > 0 {

vlib/v/markused/walker.v

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,11 @@ fn (mut w Walker) expr(node_ ast.Expr) {
378378
}
379379
ast.SelectorExpr {
380380
w.expr(node.expr)
381+
if node.expr_type != 0 {
382+
if method := w.table.find_method(w.table.sym(node.expr_type), node.field_name) {
383+
w.fn_by_name(method.fkey())
384+
}
385+
}
381386
}
382387
ast.SqlExpr {
383388
w.expr(node.db_expr)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
struct Foo {
2+
s string
3+
mut:
4+
i int
5+
}
6+
7+
fn (f Foo) get_s() string {
8+
return f.s
9+
}
10+
11+
fn (f &Foo) get_s_ref() string {
12+
return f.s
13+
}
14+
15+
fn (f Foo) add(a int) int {
16+
return a + f.i
17+
}
18+
19+
fn (f &Foo) add_ref(a int) int {
20+
return a + f.i
21+
}
22+
23+
fn (mut f Foo) set(a int) {
24+
f.i = a
25+
}
26+
27+
fn (f_ Foo) set_val(a int) int {
28+
mut f := unsafe { &f_ }
29+
old := f.i
30+
f.i = a
31+
return old
32+
}
33+
34+
fn test_methods_as_fields() {
35+
mut f := Foo{
36+
s: 'hello'
37+
i: 1
38+
}
39+
40+
get_s := f.get_s
41+
get_s_ref := unsafe { f.get_s_ref }
42+
add := f.add
43+
add_ref := unsafe { f.add_ref }
44+
set := unsafe { f.set }
45+
set_val := f.set_val
46+
47+
assert typeof(get_s).str() == 'fn () string'
48+
assert typeof(get_s_ref).str() == 'fn () string'
49+
assert typeof(add).str() == 'fn (int) int'
50+
assert typeof(add_ref).str() == 'fn (int) int'
51+
52+
assert get_s() == 'hello'
53+
assert get_s_ref() == 'hello'
54+
assert add(2) == 3
55+
assert add_ref(2) == 3
56+
57+
assert f.i == 1
58+
set(2)
59+
assert f.i == 2
60+
old := set_val(3)
61+
assert f.i == 2
62+
new := set_val(5)
63+
assert old == new && old == 1
64+
}
65+
66+
// the difference between these two tests is that here `f` is &Foo
67+
fn test_methods_as_fields_ref() {
68+
mut f := &Foo{
69+
s: 'hello'
70+
i: 1
71+
}
72+
73+
get_s := f.get_s
74+
get_s_ref := unsafe { f.get_s_ref }
75+
add := f.add
76+
add_ref := unsafe { f.add_ref }
77+
set := unsafe { f.set }
78+
set_val := f.set_val
79+
80+
assert typeof(get_s).str() == 'fn () string'
81+
assert typeof(get_s_ref).str() == 'fn () string'
82+
assert typeof(add).str() == 'fn (int) int'
83+
assert typeof(add_ref).str() == 'fn (int) int'
84+
85+
assert get_s() == 'hello'
86+
assert get_s_ref() == 'hello'
87+
assert add(2) == 3
88+
assert add_ref(2) == 3
89+
90+
assert f.i == 1
91+
set(2)
92+
assert f.i == 2
93+
old := set_val(3)
94+
assert f.i == 2
95+
new := set_val(5)
96+
assert old == new && old == 1
97+
}

vlib/v/tests/skip_unused/method_as_fn_pointer.run.out

Whitespace-only changes.

vlib/v/tests/skip_unused/method_as_fn_pointer.skip_unused.run.out

Whitespace-only changes.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
struct Foo {
2+
x int
3+
}
4+
5+
fn (f Foo) hi() int {
6+
return f.x
7+
}
8+
9+
fn main() {
10+
f := Foo{123}
11+
_ = f.hi
12+
}

0 commit comments

Comments
 (0)