From 80022345231e8bd344132e692d9705f5aa109998 Mon Sep 17 00:00:00 2001 From: JMD <56417208+StunxFS@users.noreply.github.com> Date: Sat, 8 Nov 2025 03:03:13 -0400 Subject: [PATCH] checker: error/warn when using `defer(fn)` inside function-scope and `lock` stmts (#25681) --- vlib/v/checker/checker.v | 68 +++++++++++-------- .../tests/defer_fn_inside_fn_scope.out | 6 ++ .../checker/tests/defer_fn_inside_fn_scope.vv | 4 ++ .../tests/defer_fn_inside_lock_stmt_err.out | 7 ++ .../tests/defer_fn_inside_lock_stmt_err.vv | 10 +++ vlib/v/fmt/tests/defer_mode_expected.vv | 1 - vlib/v/parser/parser.v | 4 +- 7 files changed, 68 insertions(+), 32 deletions(-) create mode 100644 vlib/v/checker/tests/defer_fn_inside_fn_scope.out create mode 100644 vlib/v/checker/tests/defer_fn_inside_fn_scope.vv create mode 100644 vlib/v/checker/tests/defer_fn_inside_lock_stmt_err.out create mode 100644 vlib/v/checker/tests/defer_fn_inside_lock_stmt_err.vv diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index b29216012..88f2990e5 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2348,35 +2348,7 @@ fn (mut c Checker) stmt(mut node ast.Stmt) { c.inside_const = false } ast.DeferStmt { - c.inside_defer = true - if node.idx_in_fn < 0 && c.table.cur_fn != unsafe { nil } { - node.idx_in_fn = c.table.cur_fn.defer_stmts.len - c.table.cur_fn.defer_stmts << unsafe { &node } - } - if c.locked_names.len != 0 || c.rlocked_names.len != 0 { - c.error('defers are not allowed in lock statements', node.pos) - } - for i, ident in node.defer_vars { - mut id := ident - if mut id.info is ast.IdentVar { - if id.comptime && (id.tok_kind == .question - || id.name in ast.valid_comptime_not_user_defined) { - node.defer_vars[i] = ast.Ident{ - scope: unsafe { nil } - name: '' - } - continue - } - typ := c.ident(mut id) - if typ == ast.error_type_idx { - continue - } - id.info.typ = typ - node.defer_vars[i] = id - } - } - c.stmts(mut node.stmts) - c.inside_defer = false + c.defer_stmt(mut node) } ast.EnumDecl { c.enum_decl(mut node) @@ -2449,6 +2421,44 @@ fn (mut c Checker) stmt(mut node ast.Stmt) { } } +fn (mut c Checker) defer_stmt(mut node ast.DeferStmt) { + c.inside_defer = true + if node.idx_in_fn < 0 && c.table.cur_fn != unsafe { nil } { + node.idx_in_fn = c.table.cur_fn.defer_stmts.len + c.table.cur_fn.defer_stmts << unsafe { &node } + } + if node.mode == .function { + if !isnil(c.fn_scope) && node.scope == c.fn_scope { + c.warn('`defer` is already in function scope; just use `defer {` instead', + node.pos) + } + if c.locked_names.len != 0 || c.rlocked_names.len != 0 { + c.error('`defer(fn)`s are not allowed in lock statements', node.pos) + } + } + for i, ident in node.defer_vars { + mut id := ident + if mut id.info is ast.IdentVar { + if id.comptime + && (id.tok_kind == .question || id.name in ast.valid_comptime_not_user_defined) { + node.defer_vars[i] = ast.Ident{ + scope: unsafe { nil } + name: '' + } + continue + } + typ := c.ident(mut id) + if typ == ast.error_type_idx { + continue + } + id.info.typ = typ + node.defer_vars[i] = id + } + } + c.stmts(mut node.stmts) + c.inside_defer = false +} + fn (mut c Checker) assert_stmt(mut node ast.AssertStmt) { cur_exp_typ := c.expected_type c.expected_type = ast.bool_type diff --git a/vlib/v/checker/tests/defer_fn_inside_fn_scope.out b/vlib/v/checker/tests/defer_fn_inside_fn_scope.out new file mode 100644 index 000000000..3b1342e57 --- /dev/null +++ b/vlib/v/checker/tests/defer_fn_inside_fn_scope.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/defer_fn_inside_fn_scope.vv:3:2: warning: `defer` is already in function scope; just use `defer {` instead + 1 | fn main() { + 2 | defer {} // ok + 3 | defer(fn) {} // fail + | ~~~~~~~~~~~~ + 4 | } diff --git a/vlib/v/checker/tests/defer_fn_inside_fn_scope.vv b/vlib/v/checker/tests/defer_fn_inside_fn_scope.vv new file mode 100644 index 000000000..827f58abc --- /dev/null +++ b/vlib/v/checker/tests/defer_fn_inside_fn_scope.vv @@ -0,0 +1,4 @@ +fn main() { + defer {} // ok + defer(fn) {} // fail +} diff --git a/vlib/v/checker/tests/defer_fn_inside_lock_stmt_err.out b/vlib/v/checker/tests/defer_fn_inside_lock_stmt_err.out new file mode 100644 index 000000000..04d968a9d --- /dev/null +++ b/vlib/v/checker/tests/defer_fn_inside_lock_stmt_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/defer_fn_inside_lock_stmt_err.vv:7:3: error: `defer(fn)`s are not allowed in lock statements + 5 | lock x { + 6 | defer {} // ok + 7 | defer(fn) {} // fail + | ~~~~~~~~~~~~ + 8 | _ = x + 9 | } diff --git a/vlib/v/checker/tests/defer_fn_inside_lock_stmt_err.vv b/vlib/v/checker/tests/defer_fn_inside_lock_stmt_err.vv new file mode 100644 index 000000000..4102c6c71 --- /dev/null +++ b/vlib/v/checker/tests/defer_fn_inside_lock_stmt_err.vv @@ -0,0 +1,10 @@ +struct St {} + +fn main() { + shared x := St{} + lock x { + defer {} // ok + defer(fn) {} // fail + _ = x + } +} diff --git a/vlib/v/fmt/tests/defer_mode_expected.vv b/vlib/v/fmt/tests/defer_mode_expected.vv index bfdebe6f7..4e11d4b92 100644 --- a/vlib/v/fmt/tests/defer_mode_expected.vv +++ b/vlib/v/fmt/tests/defer_mode_expected.vv @@ -8,7 +8,6 @@ fn main() { } println('exit fn main().scope.1') } - defer(fn) { println('defer(fn)') } diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 3ddec4f75..2950a5573 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -1128,6 +1128,7 @@ fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { } .key_defer { if !p.inside_defer { + spos := p.tok.pos() p.next() mut defer_mode := ast.DeferMode.scoped if p.tok.kind == .lpar { @@ -1145,7 +1146,6 @@ fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { } p.check(.rpar) } - spos := p.tok.pos() p.inside_defer = true p.defer_vars = []ast.Ident{} stmts := p.parse_block() @@ -1155,7 +1155,7 @@ fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { scope: p.scope stmts: stmts defer_vars: p.defer_vars.clone() - pos: spos.extend_with_last_line(p.tok.pos(), p.prev_tok.line_nr) + pos: spos.extend_with_last_line(p.prev_tok.pos(), p.prev_tok.line_nr) } } else { return p.error_with_pos('`defer` blocks cannot be nested', p.tok.pos()) -- 2.39.5