diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 64cfead4abefea..f62f906ebc25fe 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -834,6 +834,7 @@ pub mut: is_return_used bool // return value is used for another expr // is_expand_simple_interpolation bool // true, when the function/method is marked as @[expand_simple_interpolation] + is_unwrapped_fn_selector bool // true, when the call is from an unwrapped selector (e.g. if t.foo != none { t.foo() }) // Calls to it with an interpolation argument like `b.f('x ${y}')`, will be converted to `b.f('x ')` followed by `b.f(y)`. // The same type, has to support also a .write_decimal(n i64) method. } diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 55547a06b3fb05..12d9aa2c182eb5 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -2364,10 +2364,19 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) // call struct field fn type // TODO: can we use SelectorExpr for all? this dosent really belong here if field := c.table.find_field_with_embeds(left_sym, method_name) { + mut field_typ := field.typ if field.typ.has_flag(.option) { - c.error('Option function field must be unwrapped first', node.pos) + // unwrapped callback (if f.func != none {}) + if scope_field := node.scope.find_struct_field(node.left.str(), node.left_type, + method_name) + { + field_typ = scope_field.smartcasts.last() + node.is_unwrapped_fn_selector = true + } else { + c.error('Option function field must be unwrapped first', node.pos) + } } - field_sym := c.table.sym(c.unwrap_generic(field.typ)) + field_sym := c.table.sym(c.unwrap_generic(field_typ)) if field_sym.kind == .function { node.is_method = false node.is_field = true diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 37a9a014a18f34..e7ff56aaf7b6ad 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -2077,11 +2077,20 @@ fn (mut g Gen) fn_call(node ast.CallExpr) { if field := g.table.find_field_with_embeds(left_sym, node.name) { fn_typ = field.typ } + if node.is_unwrapped_fn_selector { + fn_typ = fn_typ.clear_option_and_result() + } } if left_sym.kind == .interface || fn_typ.is_ptr() { is_interface_call = true g.write('(*') } + if node.is_unwrapped_fn_selector { + callback_sym := g.table.final_sym(fn_typ) + if callback_sym.info is ast.FnType { + g.write('(*(${g.styp(fn_typ)}*)') + } + } g.expr(node.left) if node.left_type.is_ptr() { g.write('->') @@ -2387,6 +2396,9 @@ fn (mut g Gen) fn_call(node ast.CallExpr) { g.write(g.get_ternary_name(name)) } } + if node.is_unwrapped_fn_selector { + g.write('.data)') + } if is_interface_call { g.write(')') } diff --git a/vlib/v/tests/options/option_method_selector_test.v b/vlib/v/tests/options/option_method_selector_test.v new file mode 100644 index 00000000000000..6daa94e29a99eb --- /dev/null +++ b/vlib/v/tests/options/option_method_selector_test.v @@ -0,0 +1,29 @@ +struct Foo { +mut: + func ?fn (voidptr) bool = unsafe { nil } +} + +fn callback(foo &Foo) bool { + return foo.func? == callback +} + +type OptFn = fn (&Foo) bool + +fn test_main() { + t := Foo{ + func: callback + } + assert t.func? == callback + if t.func != none { + assert t.func(&t) + } else { + assert false + } + + a := ?OptFn(callback) + if a != none { + assert a(&t) + } else { + assert false + } +}