checker,gen: allow using methods as function pointers (#14407)

master
spaceface 2022-05-15 17:28:37 +02:00 committed by GitHub
parent c2bc9f4960
commit c01a8a1737
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 232 additions and 1 deletions

View File

@ -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))

View File

@ -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{}

View File

@ -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
}

View File

@ -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(';}')
}

View File

@ -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 {

View File

@ -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)

View File

@ -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
}

View File

@ -0,0 +1,12 @@
struct Foo {
x int
}
fn (f Foo) hi() int {
return f.x
}
fn main() {
f := Foo{123}
_ = f.hi
}