From 986153bec4ba578b080619e5ebf939583af37002 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 14 Apr 2026 12:45:31 +0300 Subject: [PATCH] all: About calling `?fn` type function/method parsing, checking, and code generation problems (fixes #20328) --- vlib/v/checker/checker.v | 18 ++++-- vlib/v/checker/fn.v | 37 +++++++++++- ...assign_type_mismatch_with_generics_err.out | 7 --- vlib/v/gen/c/fn.v | 22 +++++++ vlib/v/parser/expr.v | 58 ++++++++---------- .../fns/optional_fn_selector_call_test.v | 59 +++++++++++++++++++ 6 files changed, 156 insertions(+), 45 deletions(-) create mode 100644 vlib/v/tests/fns/optional_fn_selector_call_test.v diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 955ed1ef8..759ac5733 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2308,7 +2308,11 @@ fn (mut c Checker) check_expr_option_or_result_call(expr ast.Expr, ret_type ast. c.check_or_expr(expr.or_block, ret_type, expr_ret_type, expr) c.cur_or_expr = last_cur_or_expr } - return ret_type.clear_flag(.result) + return if expr.or_block.kind == .absent { + ret_type.clear_flag(.result) + } else { + ret_type.clear_flag(.option).clear_flag(.result) + } } else { c.expr_or_block_err(expr.or_block.kind, expr.name, expr.or_block.pos, false) } @@ -2338,7 +2342,11 @@ fn (mut c Checker) check_expr_option_or_result_call(expr ast.Expr, ret_type ast. c.cur_or_expr = last_cur_or_expr } } - return ret_type.clear_flag(.result) + return if expr.or_block.kind == .absent { + ret_type.clear_flag(.result) + } else { + ret_type.clear_flag(.option).clear_flag(.result) + } } else { c.expr_or_block_err(expr.or_block.kind, expr.field_name, expr.or_block.pos, true) @@ -2924,10 +2932,12 @@ fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { if scope_field != unsafe { nil } { sf_smartcast_type := c.exposed_smartcast_type(scope_field.orig_type, scope_field.smartcasts.last(), scope_field.is_mut) - if c.inside_sql { + if c.inside_sql && node.or_block.kind == .absent { node.typ = sf_smartcast_type } - return sf_smartcast_type + if node.or_block.kind == .absent { + return sf_smartcast_type + } } } } diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index fbf39adea..ab8637c82 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -1121,8 +1121,24 @@ fn (mut c Checker) call_expr(mut node ast.CallExpr) ast.Type { } } // If the left expr has an or_block, it needs to be checked for legal or_block statement. - left_type := c.expr(mut node.left) - c.check_expr_option_or_result_call(node.left, left_type) + mut left_type := ast.void_type + match mut node.left { + ast.SelectorExpr { + if node.name == '' && node.left.or_block.kind != .absent { + left_type = c.selector_expr(mut node.left) + } else { + left_type = c.expr(mut node.left) + } + } + else { + left_type = c.expr(mut node.left) + } + } + if node.name == '' { + left_type = c.check_expr_option_or_result_call(node.left, left_type) + } else { + c.check_expr_option_or_result_call(node.left, left_type) + } // TODO: merge logic from method_call and fn_call // First check everything that applies to both fns and methods old_inside_fn_arg := c.inside_fn_arg @@ -1527,6 +1543,23 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. } } } + if !found && node.name == '' && node.left_type != 0 { + left_sym := c.table.final_sym(c.unwrap_generic(node.left_type)) + if left_sym.info is ast.FnType { + func = left_sym.info.func + found = true + node.is_fn_var = true + node.fn_var_type = node.left_type + } else if left_sym.info is ast.GenericInst { + parent_sym := c.table.sym(ast.new_type(left_sym.info.parent_idx)) + if parent_sym.info is ast.FnType { + func = parent_sym.info.func + found = true + node.is_fn_var = true + node.fn_var_type = node.left_type + } + } + } // already prefixed (mod.fn) or C/builtin/main if !found { if f := c.table.find_fn(fn_name) { diff --git a/vlib/v/checker/tests/assign_type_mismatch_with_generics_err.out b/vlib/v/checker/tests/assign_type_mismatch_with_generics_err.out index ea79902e8..476d478a2 100644 --- a/vlib/v/checker/tests/assign_type_mismatch_with_generics_err.out +++ b/vlib/v/checker/tests/assign_type_mismatch_with_generics_err.out @@ -5,13 +5,6 @@ vlib/v/checker/tests/assign_type_mismatch_with_generics_err.vv:10:26: notice: un | ~~~ 11 | mut b := false 12 | if f.f != none { -vlib/v/checker/tests/assign_type_mismatch_with_generics_err.vv:13:11: error: unexpected `or` block, the field `f` is not an Option or a Result - 11 | mut b := false - 12 | if f.f != none { - 13 | b = f.f or { panic(err) } - | ~~~~~~~~~~~~~~~~~ - 14 | } else { - 15 | b = true vlib/v/checker/tests/assign_type_mismatch_with_generics_err.vv:13:9: error: cannot assign to `b`: expected `bool`, not `fn (Bar) bool` 11 | mut b := false 12 | if f.f != none { diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index cc2429b94..bf8848b5a 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -1823,6 +1823,28 @@ fn (mut g Gen) call_expr(node ast.CallExpr) { g.write2(line, '*(${g.base_type(ret_typ)}*)${tmp_res}.data') return } + } else if !g.inside_curry_call && node.left is ast.SelectorExpr && node.name == '' { + if node.or_block.kind == .absent { + g.expr(ast.Expr(node.left)) + } else { + ret_typ := node.return_type + + line := g.go_before_last_stmt() + g.empty_line = true + + tmp_res := g.new_tmp_var() + g.write('${g.styp(ret_typ)} ${tmp_res} = ') + + g.last_tmp_call_var << tmp_res + g.expr(ast.Expr(node.left)) + + old_inside_curry_call := g.inside_curry_call + g.inside_curry_call = true + g.expr(node) + g.inside_curry_call = old_inside_curry_call + g.write2(line, '*(${g.base_type(ret_typ)}*)${tmp_res}.data') + return + } } old_inside_call := g.inside_call g.inside_call = true diff --git a/vlib/v/parser/expr.v b/vlib/v/parser/expr.v index 8146a8e3e..69a0b2b8a 100644 --- a/vlib/v/parser/expr.v +++ b/vlib/v/parser/expr.v @@ -661,7 +661,16 @@ fn (mut p Parser) expr_with_left(left ast.Expr, precedence int, is_stmt_ident bo p.process_custom_orm_operators() // Infix - for precedence < p.tok.kind.precedence() { + for { + if p.tok.kind == .lpar && p.tok.line_nr == p.prev_tok.line_nr + && node in [ast.CallExpr, ast.IndexExpr, ast.SelectorExpr] { + node = p.call_expr_with_left(node) + p.is_stmt_ident = is_stmt_ident + continue + } + if precedence >= p.tok.kind.precedence() { + return node + } if p.tok.kind == .dot { // no spaces or line break before dot in map_init if (p.inside_map_init || p.inside_array_lit) @@ -684,38 +693,7 @@ fn (mut p Parser) expr_with_left(left ast.Expr, precedence int, is_stmt_ident bo } else { node = p.index_expr(node, false) } - p.is_stmt_ident = is_stmt_ident - if p.tok.kind == .lpar && p.tok.line_nr == p.prev_tok.line_nr && node is ast.IndexExpr { - p.next() - pos := p.tok.pos() - args := p.call_args() - p.check(.rpar) - or_block := p.gen_or_block() - node = ast.CallExpr{ - left: node - args: args - pos: pos - scope: p.scope - or_block: or_block - is_return_used: p.expecting_value - } - p.is_stmt_ident = is_stmt_ident - if p.tok.kind == .lpar && p.prev_tok.line_nr == p.tok.line_nr { - p.next() - pos2 := p.tok.pos() - args2 := p.call_args() - p.check(.rpar) - or_block2 := p.gen_or_block() - node = ast.CallExpr{ - left: node - args: args2 - pos: pos2 - scope: p.scope - or_block: or_block2 - } - } - } } else if p.tok.kind == .key_as && p.tok.line_nr == p.prev_tok.line_nr { // sum type as cast `x := SumType as Variant` if !p.inside_asm { @@ -819,6 +797,22 @@ fn (mut p Parser) expr_with_left(left ast.Expr, precedence int, is_stmt_ident bo return node } +fn (mut p Parser) call_expr_with_left(left ast.Expr) ast.CallExpr { + p.next() + pos := p.tok.pos() + args := p.call_args() + p.check(.rpar) + or_block := p.gen_or_block() + return ast.CallExpr{ + left: left + args: args + pos: pos + scope: p.scope + or_block: or_block + is_return_used: p.expecting_value + } +} + fn (mut p Parser) gen_or_block() ast.OrExpr { if p.tok.kind == .key_orelse { // `foo() or {}`` diff --git a/vlib/v/tests/fns/optional_fn_selector_call_test.v b/vlib/v/tests/fns/optional_fn_selector_call_test.v new file mode 100644 index 000000000..031aa3ed9 --- /dev/null +++ b/vlib/v/tests/fns/optional_fn_selector_call_test.v @@ -0,0 +1,59 @@ +type GuardedFn = fn (arg Bar) bool + +type GuardedResultFn = fn (arg Bar) !bool + +struct GuardedFoo { + f ?GuardedFn +} + +struct GuardedResultFoo { + f ?GuardedResultFn +} + +@[params] +struct Bar {} + +fn (foo GuardedFoo) call(arg Bar) bool { + if foo.f != none { + // vfmt off + return foo.f or { panic(err) }(arg) + // vfmt on + } + return false +} + +fn (foo GuardedResultFoo) call(arg Bar) bool { + if foo.f != none { + // vfmt off + return foo.f or { panic(err) }(arg) or { return false } + // vfmt on + } + return false +} + +fn test_optional_fn_selector_call_in_if_guard() { + foo := GuardedFoo{ + f: fn (arg Bar) bool { + _ = arg + return true + } + } + assert foo.call(Bar{}) +} + +fn test_optional_fn_selector_call_with_nested_or_blocks() { + ok := GuardedResultFoo{ + f: fn (arg Bar) !bool { + _ = arg + return true + } + } + failing := GuardedResultFoo{ + f: fn (arg Bar) !bool { + _ = arg + return error('boom') + } + } + assert ok.call(Bar{}) + assert !failing.call(Bar{}) +} -- 2.39.5