checker,gen: allow using methods as function pointers (#14407)
parent
a133f038bd
commit
ec2346d45c
|
@ -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))
|
||||
|
|
|
@ -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{}
|
|
@ -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
|
||||
}
|
|
@ -210,8 +210,12 @@ fn (mut g Gen) gen_assign_stmt(node_ ast.AssignStmt) {
|
|||
g.is_void_expr_stmt = old_is_void_expr_stmt
|
||||
} else if g.inside_for_c_stmt {
|
||||
g.expr(val)
|
||||
} else {
|
||||
if left_sym.kind == .function {
|
||||
g.write('{void* _ = ')
|
||||
} else {
|
||||
g.write('{$styp _ = ')
|
||||
}
|
||||
g.expr(val)
|
||||
g.writeln(';}')
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
struct Foo {
|
||||
x int
|
||||
}
|
||||
|
||||
fn (f Foo) hi() int {
|
||||
return f.x
|
||||
}
|
||||
|
||||
fn main() {
|
||||
f := Foo{123}
|
||||
_ = f.hi
|
||||
}
|
Loading…
Reference in New Issue