From 2ad83399239548c11fb3f61c2b6309f7d101f6f5 Mon Sep 17 00:00:00 2001 From: JMD <56417208+StunxFS@users.noreply.github.com> Date: Mon, 3 Nov 2025 06:14:51 -0400 Subject: [PATCH] all: implement scoped defer (part 1) (#25639) --- vlib/v/ast/ast.v | 13 +++- vlib/v/ast/scope.v | 9 +++ vlib/v/checker/fn.v | 6 +- vlib/v/checker/lambda_expr.v | 1 + .../checker/tests/invalid_defer_mode_err.out | 6 ++ .../v/checker/tests/invalid_defer_mode_err.vv | 5 ++ vlib/v/fmt/fmt.v | 11 +-- vlib/v/fmt/tests/defer_mode_expected.vv | 16 +++++ vlib/v/fmt/tests/defer_mode_input.vv | 17 +++++ vlib/v/gen/c/cgen.v | 70 ++++++++----------- vlib/v/gen/c/cmain.v | 2 + vlib/v/gen/c/defer.v | 70 +++++++++++++++++++ vlib/v/gen/c/fn.v | 21 ++---- vlib/v/gen/c/for.v | 4 +- vlib/v/gen/c/if.v | 1 + vlib/v/gen/c/match.v | 4 ++ vlib/v/parser/comptime.v | 3 +- vlib/v/parser/expr.v | 16 +++-- vlib/v/parser/fn.v | 3 +- vlib/v/parser/if_match.v | 2 + vlib/v/parser/orm.v | 3 +- vlib/v/parser/parser.v | 56 ++++++++++++--- vlib/v/pref/pref.v | 6 ++ vlib/v/tests/defer/defer_static_test.v | 2 +- vlib/v/tests/defer/scoped_defer_test.v | 22 ++++++ .../v/tests/options/option_free_method_test.v | 4 +- vlib/v/transformer/transformer.v | 1 + 27 files changed, 285 insertions(+), 89 deletions(-) create mode 100644 vlib/v/checker/tests/invalid_defer_mode_err.out create mode 100644 vlib/v/checker/tests/invalid_defer_mode_err.vv create mode 100644 vlib/v/fmt/tests/defer_mode_expected.vv create mode 100644 vlib/v/fmt/tests/defer_mode_input.vv create mode 100644 vlib/v/gen/c/defer.v create mode 100644 vlib/v/tests/defer/scoped_defer_test.v diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 74fedfe6b..cbb0077ec 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -221,6 +221,7 @@ pub struct Block { pub: is_unsafe bool pos token.Pos + scope &Scope pub mut: stmts []Stmt } @@ -808,6 +809,7 @@ pub struct BranchStmt { pub: kind token.Kind label string + scope &Scope pos token.Pos } @@ -887,6 +889,7 @@ pub mut: // function return statement pub struct Return { pub: + scope &Scope pos token.Pos comments []Comment pub mut: @@ -1326,6 +1329,7 @@ pub: is_else bool is_timeout bool post_comments []Comment + scope &Scope pub mut: stmt Stmt // `a := <-ch` or `ch <- a` stmts []Stmt // right side @@ -1542,13 +1546,20 @@ pub: is_markused bool } +pub enum DeferMode { + scoped // default + function +} + // TODO: handle this differently // v1 excludes non current os ifdefs so // the defer's never get added in the first place @[minify] pub struct DeferStmt { pub: - pos token.Pos + pos token.Pos + scope &Scope + mode DeferMode pub mut: stmts []Stmt defer_vars []Ident diff --git a/vlib/v/ast/scope.v b/vlib/v/ast/scope.v index 23f011bc3..e975046c7 100644 --- a/vlib/v/ast/scope.v +++ b/vlib/v/ast/scope.v @@ -3,6 +3,10 @@ // that can be found in the LICENSE file. module ast +pub const empty_scope = &Scope{ + parent: unsafe { nil } +} + @[heap] pub struct Scope { pub mut: @@ -251,6 +255,11 @@ pub fn (s &Scope) contains(pos int) bool { return pos >= s.start_pos && pos <= s.end_pos } +@[inline] +pub fn (s &Scope) == (o &Scope) bool { + return s.start_pos == o.start_pos && s.end_pos == o.end_pos +} + pub fn (s &Scope) has_inherited_vars() bool { for _, obj in s.objects { if obj is Var { diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 5640cbdc5..0fc49e79a 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -502,7 +502,8 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { node.stmts.last().pos } node.stmts << ast.Return{ - pos: return_pos // node.pos + scope: node.scope + pos: return_pos // node.pos } } } @@ -512,7 +513,8 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { sym := c.table.sym(node.return_type) if sym.kind == .void { node.stmts << ast.Return{ - pos: node.pos + scope: node.scope + pos: node.pos } } } diff --git a/vlib/v/checker/lambda_expr.v b/vlib/v/checker/lambda_expr.v index 464e546ab..58eb46639 100644 --- a/vlib/v/checker/lambda_expr.v +++ b/vlib/v/checker/lambda_expr.v @@ -70,6 +70,7 @@ pub fn (mut c Checker) lambda_expr(mut node ast.LambdaExpr, exp_typ ast.Type) as } } else { stmts << ast.Return{ + scope: node.scope pos: node.pos exprs: [node.expr] } diff --git a/vlib/v/checker/tests/invalid_defer_mode_err.out b/vlib/v/checker/tests/invalid_defer_mode_err.out new file mode 100644 index 000000000..c58aa0edd --- /dev/null +++ b/vlib/v/checker/tests/invalid_defer_mode_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/invalid_defer_mode_err.vv:4:8: error: unknown `defer` mode: `fn1` + 2 | defer {} // ok + 3 | defer(fn) {} // ok + 4 | defer(fn1) {} // fail + | ~~~ + 5 | } diff --git a/vlib/v/checker/tests/invalid_defer_mode_err.vv b/vlib/v/checker/tests/invalid_defer_mode_err.vv new file mode 100644 index 000000000..09b9f436d --- /dev/null +++ b/vlib/v/checker/tests/invalid_defer_mode_err.vv @@ -0,0 +1,5 @@ +fn main() { + defer {} // ok + defer(fn) {} // ok + defer(fn1) {} // fail +} diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 7acc9c99e..927c9ed82 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -981,12 +981,15 @@ pub fn (mut f Fmt) const_decl(node ast.ConstDecl) { } fn (mut f Fmt) defer_stmt(node ast.DeferStmt) { - f.write('defer ') + f.write('defer') + if node.mode == .function { + f.write('(fn)') + } if node.stmts.len == 0 { - f.writeln('{}') + f.writeln(' {}') } else if node.stmts.len == 1 && node.pos.line_nr == node.pos.last_line && stmt_is_single_line(node.stmts[0]) { - f.write('{ ') + f.write(' { ') // the control stmts (return/break/continue...) print a newline inside them, // so, since this'll all be on one line, trim any possible whitespace str := f.node_str(node.stmts[0]).trim_space() @@ -1000,7 +1003,7 @@ fn (mut f Fmt) defer_stmt(node ast.DeferStmt) { // f.stmt(node.stmts[0]) f.writeln(' }') } else { - f.writeln('{') + f.writeln(' {') f.stmts(node.stmts) f.writeln('}') } diff --git a/vlib/v/fmt/tests/defer_mode_expected.vv b/vlib/v/fmt/tests/defer_mode_expected.vv new file mode 100644 index 000000000..bfdebe6f7 --- /dev/null +++ b/vlib/v/fmt/tests/defer_mode_expected.vv @@ -0,0 +1,16 @@ +fn main() { + defer(fn) { println('${@FN} - 0 - defer') } + { + defer { println('${@FN} - 1 - defer') } + { + defer { println('${@FN} - 2 - defer') } + println('exit fn main().scope.2') + } + println('exit fn main().scope.1') + } + + defer(fn) { + println('defer(fn)') + } + println('exit fn main().scope.0') +} diff --git a/vlib/v/fmt/tests/defer_mode_input.vv b/vlib/v/fmt/tests/defer_mode_input.vv new file mode 100644 index 000000000..f19df3490 --- /dev/null +++ b/vlib/v/fmt/tests/defer_mode_input.vv @@ -0,0 +1,17 @@ +fn main() { + defer (fn) { println('${@FN} - 0 - defer') } + { + defer { println('${@FN} - 1 - defer') } + { + defer { println('${@FN} - 2 - defer') } + println('exit fn main().scope.2') + } + println('exit fn main().scope.1') + } + defer + (fn) + { + println('defer(fn)') + } + println('exit fn main().scope.0') +} diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 7b15104cc..f0643d170 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -2549,6 +2549,7 @@ fn (mut g Gen) stmt(node ast.Stmt) { } } g.stmts(node.stmts) + g.write_defer_stmts(node.scope, false, node.pos) g.writeln('}') if node.is_unsafe { g.unsafe_level-- @@ -2655,7 +2656,9 @@ fn (mut g Gen) stmt(node ast.Stmt) { ast.DeferStmt { mut defer_stmt := node defer_stmt.ifdef = g.defer_ifdef - g.writeln('${g.defer_flag_var(defer_stmt)} = true;') + if defer_stmt.mode == .function || !g.pref.scoped_defer { + g.writeln('${g.defer_flag_var(defer_stmt)} = true;') + } g.defer_stmts << defer_stmt } ast.EnumDecl { @@ -2762,30 +2765,6 @@ fn (mut g Gen) stmt(node ast.Stmt) { // `foo('a' + 'b')` => `tmp := 'a' + 'b'; foo(tmp); string_free(&tmp);` } -fn (mut g Gen) write_defer_stmts() { - for i := g.defer_stmts.len - 1; i >= 0; i-- { - defer_stmt := g.defer_stmts[i] - if !g.pref.is_prod { - g.writeln('// Defer begin') - } - g.writeln('if (${g.defer_flag_var(defer_stmt)}) {') - - // g.indent++ - if defer_stmt.ifdef.len > 0 { - g.writeln(defer_stmt.ifdef) - g.stmts(defer_stmt.stmts) - g.writeln2('', '#endif') - } else { - g.stmts(defer_stmt.stmts) - } - // g.indent-- - g.writeln('}') - if !g.pref.is_prod { - g.writeln('// Defer end') - } - } -} - struct SumtypeCastingFn { fn_name string got ast.Type @@ -5036,6 +5015,7 @@ fn (mut g Gen) lock_expr(node ast.LockExpr) { if node.is_expr { g.writeln(';') } + g.write_defer_stmts(node.scope, false, node.pos) g.writeln('}') g.unlock_locks() if node.is_expr { @@ -5282,6 +5262,7 @@ fn (mut g Gen) select_expr(node ast.SelectExpr) { g.writeln('builtin__array_free(&${chan_array});') mut i := 0 for j in 0 .. node.branches.len { + branch := node.branches[j] if j > 0 { g.write('} else ') } @@ -5297,7 +5278,8 @@ fn (mut g Gen) select_expr(node ast.SelectExpr) { } i++ } - g.stmts(node.branches[j].stmts) + g.stmts(branch.stmts) + g.write_defer_stmts(branch.scope, false, branch.pos) } g.writeln('}') if is_expr { @@ -6029,6 +6011,7 @@ fn (mut g Gen) branch_stmt(node ast.BranchStmt) { } else {} } + g.write_defer_stmts(node.scope, false, node.pos) // continue or break if g.is_autofree && !g.is_builtin_mod { g.trace_autofree('// free before continue/break') @@ -6081,7 +6064,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { && !fn_ret_type.has_option_or_result() mut has_semicolon := false if exprs_len == 0 { - g.write_defer_stmts_when_needed() + g.write_defer_stmts_when_needed(node.scope, true, node.pos) if fn_return_is_option || fn_return_is_result { styp := g.styp(fn_ret_type) if g.is_autofree { @@ -6109,10 +6092,10 @@ fn (mut g Gen) return_stmt(node ast.Return) { g.write('${ret_typ} ${tmpvar} = ') g.expr(expr0) g.writeln(';') - g.write_defer_stmts_when_needed() + g.write_defer_stmts_when_needed(node.scope, true, node.pos) g.writeln('return ${tmpvar};') } else { - g.write_defer_stmts_when_needed() + g.write_defer_stmts_when_needed(node.scope, true, node.pos) g.write('return ') g.expr(expr0) g.writeln(';') @@ -6135,7 +6118,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { g.write('${ret_typ} ${test_error_var} = ') g.gen_option_error(fn_ret_type, expr0) g.writeln(';') - g.write_defer_stmts_when_needed() + g.write_defer_stmts_when_needed(node.scope, true, node.pos) g.gen_failing_return_error_for_test_fn(node, test_error_var) return } @@ -6158,7 +6141,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { } } } - g.write_defer_stmts_when_needed() + g.write_defer_stmts_when_needed(node.scope, true, node.pos) g.writeln('return ${tmpvar};') } return @@ -6174,7 +6157,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { g.write('${ret_typ} ${test_error_var} = ') g.gen_result_error(fn_ret_type, expr0) g.writeln(';') - g.write_defer_stmts_when_needed() + g.write_defer_stmts_when_needed(node.scope, true, node.pos) g.gen_failing_return_error_for_test_fn(node, test_error_var) return } @@ -6186,7 +6169,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { g.gen_result_error(fn_ret_type, expr0) g.writeln(';') if use_tmp_var { - g.write_defer_stmts_when_needed() + g.write_defer_stmts_when_needed(node.scope, true, node.pos) g.writeln('return ${tmpvar};') } return @@ -6199,7 +6182,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { g.write('${ret_typ} ${tmpvar} = ') g.expr(expr0) g.writeln(';') - g.write_defer_stmts_when_needed() + g.write_defer_stmts_when_needed(node.scope, true, node.pos) g.writeln('return ${tmpvar};') return } @@ -6301,10 +6284,10 @@ fn (mut g Gen) return_stmt(node ast.Return) { g.write('}') if fn_return_is_option { g.writeln(' }, (${option_name}*)(&${tmpvar}), sizeof(${styp}));') - g.write_defer_stmts_when_needed() + g.write_defer_stmts_when_needed(node.scope, true, node.pos) } else if fn_return_is_result { g.writeln(' }, (${result_name}*)(&${tmpvar}), sizeof(${styp}));') - g.write_defer_stmts_when_needed() + g.write_defer_stmts_when_needed(node.scope, true, node.pos) } // Make sure to add our unpacks if multi_unpack != '' { @@ -6318,7 +6301,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { if !has_semicolon { g.writeln(';') } - g.write_defer_stmts_when_needed() + g.write_defer_stmts_when_needed(node.scope, true, node.pos) g.writeln('return ${tmpvar};') has_semicolon = true } else if fn_return_is_option || fn_return_is_result { @@ -6381,7 +6364,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { } g.writeln(' }, (${option_name}*)(&${tmpvar}), sizeof(${styp}));') } - g.write_defer_stmts_when_needed() + g.write_defer_stmts_when_needed(node.scope, true, node.pos) if g.is_autofree { g.detect_used_var_on_return(expr0) } @@ -6429,7 +6412,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { } g.writeln(' }, (${result_name}*)(&${tmpvar}), sizeof(${styp}));') } - g.write_defer_stmts_when_needed() + g.write_defer_stmts_when_needed(node.scope, true, node.pos) if g.is_autofree { g.detect_used_var_on_return(expr0) } @@ -6539,7 +6522,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { if use_tmp_var { g.writeln(';') has_semicolon = true - g.write_defer_stmts_when_needed() + g.write_defer_stmts_when_needed(node.scope, true, node.pos) if !g.is_builtin_mod { g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) } @@ -7399,12 +7382,14 @@ fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type ast.Ty if stmts.len > 0 && stmts.last() is ast.ExprStmt { g.writeln(';') } + g.write_defer_stmts(or_block.scope, false, or_block.pos) } g.or_expr_return_type = ast.void_type } else if or_block.kind == .propagate_result || (or_block.kind == .propagate_option && return_type.has_flag(.result)) { if g.file.mod.name == 'main' && (g.fn_decl == unsafe { nil } || g.fn_decl.is_main) { // In main(), an `opt()!` call is sugar for `opt() or { panic(err) }` + g.write_defer_stmts(or_block.scope, true, or_block.pos) err_msg := 'IError_name_table[${cvar_name}${tmp_op}err._typ]._method_msg(${cvar_name}${tmp_op}err._object)' if g.pref.is_debug { paline, pafile, pamod, pafn := g.panic_debug_info(or_block.pos) @@ -7419,7 +7404,7 @@ fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type ast.Ty // `opt() or { return err }` // Since we *do* return, first we have to ensure that // the deferred statements are generated. - g.write_defer_stmts() + g.write_defer_stmts(or_block.scope, true, or_block.pos) // Now that option types are distinct we need a cast here if g.fn_decl == unsafe { nil } || g.fn_decl.return_type == ast.void_type { g.writeln('\treturn;') @@ -7439,6 +7424,7 @@ fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type ast.Ty } else if or_block.kind == .propagate_option { if g.file.mod.name == 'main' && (g.fn_decl == unsafe { nil } || g.fn_decl.is_main) { // In main(), an `opt()?` call is sugar for `opt() or { panic(err) }` + g.write_defer_stmts(or_block.scope, true, or_block.pos) err_msg := 'IError_name_table[${cvar_name}${tmp_op}err._typ]._method_msg(${cvar_name}${tmp_op}err._object)' if g.pref.is_debug { paline, pafile, pamod, pafn := g.panic_debug_info(or_block.pos) @@ -7453,7 +7439,7 @@ fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type ast.Ty // `opt() or { return err }` // Since we *do* return, first we have to ensure that // the deferred statements are generated. - g.write_defer_stmts() + g.write_defer_stmts(or_block.scope, true, or_block.pos) // Now that option types are distinct we need a cast here if g.fn_decl == unsafe { nil } || g.fn_decl.return_type == ast.void_type { g.writeln('\treturn;') diff --git a/vlib/v/gen/c/cmain.v b/vlib/v/gen/c/cmain.v index 3a33b8238..0d18704c0 100644 --- a/vlib/v/gen/c/cmain.v +++ b/vlib/v/gen/c/cmain.v @@ -245,6 +245,7 @@ pub fn (mut g Gen) gen_failing_error_propagation_for_test_fn(or_block ast.OrExpr // in test_() functions, an `opt()?` call is sugar for // `or { cb_propagate_test_error(@LINE, @FILE, @MOD, @FN, err.msg() ) }` // and the test is considered failed + g.write_defer_stmts_when_needed(or_block.scope, true, or_block.pos) paline, pafile, pamod, pafn := g.panic_debug_info(or_block.pos) dot_or_ptr := if cvar_name in g.tmp_var_ptr { '->' } else { '.' } err_msg := 'IError_name_table[${cvar_name}${dot_or_ptr}err._typ]._method_msg(${cvar_name}${dot_or_ptr}err._object)' @@ -256,6 +257,7 @@ pub fn (mut g Gen) gen_failing_return_error_for_test_fn(return_stmt ast.Return, // in test_() functions, a `return error('something')` is sugar for // `or { err := error('something') cb_propagate_test_error(@LINE, @FILE, @MOD, @FN, err.msg() ) return err }` // and the test is considered failed + g.write_defer_stmts_when_needed(return_stmt.scope, true, return_stmt.pos) paline, pafile, pamod, pafn := g.panic_debug_info(return_stmt.pos) dot_or_ptr := if cvar_name in g.tmp_var_ptr { '->' } else { '.' } err_msg := 'IError_name_table[${cvar_name}${dot_or_ptr}err._typ]._method_msg(${cvar_name}${dot_or_ptr}err._object)' diff --git a/vlib/v/gen/c/defer.v b/vlib/v/gen/c/defer.v new file mode 100644 index 000000000..255383234 --- /dev/null +++ b/vlib/v/gen/c/defer.v @@ -0,0 +1,70 @@ +// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module c + +import v.ast +import v.token + +fn (g &Gen) defer_flag_var(stmt &ast.DeferStmt) string { + return '${g.last_fn_c_name}_defer_${stmt.idx_in_fn}' +} + +// this function is called at the end of each block (`for`, `if` branches, +// `match` branches, etc.) +fn (mut g Gen) write_defer_stmts(scope &ast.Scope, lookup bool, pos token.Pos) { + if scope == unsafe { nil } { + // this should never happen + g.error('Gen.write_defer_stmts() has received a scope that is nil', pos) + } + + g.indent++ + for i := g.defer_stmts.len - 1; i >= 0; i-- { + defer_stmt := g.defer_stmts[i] + if defer_stmt.scope == unsafe { nil } { + // this should never happen + g.error('Gen.write_defer_stmts(): defer_stmt.scope is nil', pos) + } + + is_scoped := g.pref.scoped_defer && defer_stmt.mode == .scoped + if is_scoped { + if !((lookup && defer_stmt.scope.start_pos < scope.start_pos + && defer_stmt.scope.end_pos > scope.end_pos) + || defer_stmt.scope == scope) { + // generate only `defer`s of the current scope (and previous ones if necessary) + continue + } + g.writeln('{ // defer begin') + } else { + if scope != g.cur_fn.scope && !lookup { + continue + } + g.writeln('if (${g.defer_flag_var(defer_stmt)}) { // defer begin') + } + + if defer_stmt.ifdef.len > 0 { + g.writeln(defer_stmt.ifdef) + g.stmts(defer_stmt.stmts) + g.writeln2('', '#endif') + } else { + g.stmts(defer_stmt.stmts) + } + g.writeln('} // defer end') + } + g.indent-- +} + +// this function is called when returning with `return`, or when the end of a function +// is reached. +fn (mut g Gen) write_defer_stmts_when_needed(scope &ast.Scope, lookup bool, pos token.Pos) { + // unlock all mutexes, in case we are in a lock statement. defers are not + // allowed in lock statements. + g.unlock_locks() + if g.defer_stmts.len > 0 { + g.write_defer_stmts(scope, lookup, pos) + } + if g.defer_profile_code.len > 0 { + g.writeln2('', '\t// defer_profile_code') + g.writeln2(g.defer_profile_code, '') + } +} diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index da0b9ac0b..06724c0df 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -454,6 +454,9 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) { } g.indent++ for defer_stmt in node.defer_stmts { + if defer_stmt.mode != .function && g.pref.scoped_defer { + continue + } g.writeln('bool ${g.defer_flag_var(defer_stmt)} = false;') for var in defer_stmt.defer_vars { if var.name in fargs || var.kind == .constant { @@ -534,7 +537,7 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) { // clear g.fn_mut_arg_names if !node.has_return { - g.write_defer_stmts_when_needed() + g.write_defer_stmts_when_needed(node.scope, false, node.name_pos) } if node.is_anon { g.defer_stmts = prev_defer_stmts @@ -754,22 +757,6 @@ fn (mut g Gen) gen_anon_fn_decl(mut node ast.AnonFn) { } } -fn (g &Gen) defer_flag_var(stmt &ast.DeferStmt) string { - return '${g.last_fn_c_name}_defer_${stmt.idx_in_fn}' -} - -fn (mut g Gen) write_defer_stmts_when_needed() { - // unlock all mutexes, in case we are in a lock statement. defers are not allowed in lock statements - g.unlock_locks() - if g.defer_stmts.len > 0 { - g.write_defer_stmts() - } - if g.defer_profile_code.len > 0 { - g.writeln2('', '\t// defer_profile_code') - g.writeln2(g.defer_profile_code, '') - } -} - fn (mut g Gen) fn_decl_params(params []ast.Param, scope &ast.Scope, is_variadic bool, is_c_variadic bool) ([]string, []string, []bool) { mut fparams := []string{} mut fparamtypes := []string{} diff --git a/vlib/v/gen/c/for.v b/vlib/v/gen/c/for.v index e89b5db46..98326f817 100644 --- a/vlib/v/gen/c/for.v +++ b/vlib/v/gen/c/for.v @@ -101,6 +101,7 @@ fn (mut g Gen) for_c_stmt(node ast.ForCStmt) { if node.label.len > 0 { g.writeln('${node.label}__continue: {}') } + g.write_defer_stmts(node.scope, false, node.pos) g.writeln('}') if node.label.len > 0 { g.writeln('${node.label}__break: {}') @@ -129,6 +130,7 @@ fn (mut g Gen) for_stmt(node ast.ForStmt) { if node.label.len > 0 { g.writeln('\t${node.label}__continue: {}') } + g.write_defer_stmts(node.scope, false, node.pos) g.writeln('}') if node.label.len > 0 { g.writeln('${node.label}__break: {}') @@ -597,7 +599,7 @@ fn (mut g Gen) for_in_stmt(node_ ast.ForInStmt) { // g.writeln('\t$map_len = $cond_var${arw_or_pt}key_values.len;') // g.writeln('}') } - + g.write_defer_stmts(node.scope, false, node.pos) g.writeln('}') if node.label.len > 0 { g.writeln('\t${node.label}__break: {}') diff --git a/vlib/v/gen/c/if.v b/vlib/v/gen/c/if.v index 0ae7d7164..d1f873cc0 100644 --- a/vlib/v/gen/c/if.v +++ b/vlib/v/gen/c/if.v @@ -495,6 +495,7 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { g.stmts(branch.stmts) g.stmt_path_pos << stmt_pos } + g.write_defer_stmts(branch.scope, false, node.pos) } if node.branches.len > 0 { g.writeln('}') diff --git a/vlib/v/gen/c/match.v b/vlib/v/gen/c/match.v index a08d84f43..a4499e821 100644 --- a/vlib/v/gen/c/match.v +++ b/vlib/v/gen/c/match.v @@ -263,6 +263,7 @@ fn (mut g Gen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var str } } g.stmts_with_tmp_var(branch.stmts, tmp_var) + g.write_defer_stmts(branch.scope, false, node.pos) g.inside_interface_deref = inside_interface_deref_old g.expected_cast_type = 0 if g.inside_ternary == 0 { @@ -357,6 +358,7 @@ fn (mut g Gen) match_expr_switch(node ast.MatchExpr, is_expr bool, cond_var stri } ends_with_return := g.stmts_with_tmp_var(branch.stmts, tmp_var) g.expected_cast_type = 0 + g.write_defer_stmts(branch.scope, false, node.pos) if !ends_with_return { g.writeln('\tbreak;') } @@ -393,6 +395,7 @@ fn (mut g Gen) match_expr_switch(node ast.MatchExpr, is_expr bool, cond_var stri if !ends_with_return { g.writeln('\tbreak;') } + g.write_defer_stmts(range_branch.scope, false, node.pos) g.writeln('}') } } @@ -563,6 +566,7 @@ fn (mut g Gen) match_expr_classic(node ast.MatchExpr, is_expr bool, cond_var str g.expected_cast_type = node.return_type } g.stmts_with_tmp_var(branch.stmts, tmp_var) + g.write_defer_stmts(branch.scope, false, node.pos) g.expected_cast_type = 0 if g.inside_ternary == 0 && node.branches.len >= 1 { if reset_if { diff --git a/vlib/v/parser/comptime.v b/vlib/v/parser/comptime.v index e8589520c..ba9f1cdc4 100644 --- a/vlib/v/parser/comptime.v +++ b/vlib/v/parser/comptime.v @@ -544,7 +544,7 @@ fn (mut p Parser) comptime_selector(left ast.Expr) ast.Expr { mut or_kind := ast.OrKind.absent mut or_pos := p.tok.pos() mut or_stmts := []ast.Stmt{} - mut or_scope := &ast.Scope(unsafe { nil }) + mut or_scope := ast.empty_scope if p.tok.kind == .key_orelse { // `$method() or {}`` or_kind = .block @@ -586,6 +586,7 @@ fn (mut p Parser) comptime_selector(left ast.Expr) ast.Expr { or_block: ast.OrExpr{ stmts: []ast.Stmt{} kind: if p.tok.kind == .question { .propagate_option } else { .absent } + scope: p.scope pos: p.tok.pos() } } diff --git a/vlib/v/parser/expr.v b/vlib/v/parser/expr.v index 197403e52..7eeb663a9 100644 --- a/vlib/v/parser/expr.v +++ b/vlib/v/parser/expr.v @@ -746,13 +746,15 @@ fn (mut p Parser) gen_or_block() ast.OrExpr { p.error_with_pos('error propagation not allowed inside `defer` blocks', p.prev_tok.pos()) } return ast.OrExpr{ - kind: if is_not { ast.OrKind.propagate_result } else { ast.OrKind.propagate_option } - pos: or_pos + kind: if is_not { ast.OrKind.propagate_result } else { ast.OrKind.propagate_option } + scope: p.scope + pos: or_pos } } else { return ast.OrExpr{ - kind: ast.OrKind.absent - pos: p.tok.pos() + kind: ast.OrKind.absent + scope: ast.empty_scope + pos: p.tok.pos() } } } @@ -824,7 +826,7 @@ fn (mut p Parser) infix_expr(left ast.Expr) ast.Expr { mut or_stmts := []ast.Stmt{} mut or_kind := ast.OrKind.absent mut or_pos := p.tok.pos() - mut or_scope := &ast.Scope(unsafe { nil }) + mut or_scope := ast.empty_scope // allow `x := <-ch or {...}` to handle closed channel if op == .arrow { if mut right is ast.SelectorExpr { @@ -845,6 +847,7 @@ fn (mut p Parser) infix_expr(left ast.Expr) ast.Expr { if p.tok.kind == .question { p.next() or_kind = .propagate_option + or_scope = p.scope } p.or_is_handled = false } @@ -928,7 +931,7 @@ fn (mut p Parser) prefix_expr() ast.Expr { mut or_stmts := []ast.Stmt{} mut or_kind := ast.OrKind.absent mut or_pos := p.tok.pos() - mut or_scope := &ast.Scope(unsafe { nil }) + mut or_scope := ast.empty_scope // allow `x := <-ch or {...}` to handle closed channel if op == .arrow { if mut right is ast.SelectorExpr { @@ -941,6 +944,7 @@ fn (mut p Parser) prefix_expr() ast.Expr { } else if p.tok.kind == .question { p.next() or_kind = .propagate_option + or_scope = p.scope } p.or_is_handled = false } diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 961320cf7..580531e34 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -76,7 +76,7 @@ fn (mut p Parser) call_expr(language ast.Language, mod string) ast.CallExpr { mut pos := first_pos.extend(last_pos) mut or_stmts := []ast.Stmt{} // TODO: remove unnecessary allocations by just using .absent mut or_pos := p.tok.pos() - mut or_scope := &ast.Scope(unsafe { nil }) + mut or_scope := ast.empty_scope if p.tok.kind == .key_orelse { // `foo() or {}`` or_kind = .block @@ -90,6 +90,7 @@ fn (mut p Parser) call_expr(language ast.Language, mod string) ast.CallExpr { p.error_with_pos('error propagation not allowed inside `defer` blocks', p.prev_tok.pos()) } or_kind = if is_not { .propagate_result } else { .propagate_option } + or_scope = p.scope } if p.is_imported_symbol(fn_name) { check := !p.imported_symbols_used[fn_name] diff --git a/vlib/v/parser/if_match.v b/vlib/v/parser/if_match.v index c5e008c98..1dad04891 100644 --- a/vlib/v/parser/if_match.v +++ b/vlib/v/parser/if_match.v @@ -637,6 +637,7 @@ fn (mut p Parser) select_expr() ast.SelectExpr { p.inside_match_body = true p.inside_for = false stmts := p.parse_block_no_scope(false) + branch_scope := p.scope p.close_scope() p.inside_match_body = false mut pos := token.Pos{ @@ -654,6 +655,7 @@ fn (mut p Parser) select_expr() ast.SelectExpr { stmt: stmt stmts: stmts pos: pos + scope: branch_scope comment: comment is_else: is_else is_timeout: is_timeout diff --git a/vlib/v/parser/orm.v b/vlib/v/parser/orm.v index 5ef475144..809665225 100644 --- a/vlib/v/parser/orm.v +++ b/vlib/v/parser/orm.v @@ -185,13 +185,14 @@ fn (mut p Parser) parse_sql_or_block() ast.OrExpr { mut stmts := []ast.Stmt{} mut kind := ast.OrKind.absent mut pos := p.tok.pos() - mut or_scope := &ast.Scope(unsafe { nil }) + mut or_scope := ast.empty_scope if p.tok.kind == .key_orelse { kind = .block stmts, pos, or_scope = p.or_block(.with_err_var) } else if p.tok.kind == .not { kind = .propagate_result + or_scope = p.scope p.next() } diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index bcf409a96..0eaa5665e 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -969,6 +969,7 @@ fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { pos.update_last_line(p.prev_tok.line_nr) return ast.Block{ stmts: stmts + scope: p.scope.children.last() pos: pos } } @@ -1093,6 +1094,7 @@ fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { return ast.BranchStmt{ kind: tok.kind label: label + scope: p.scope pos: tok.pos() } } @@ -1127,12 +1129,30 @@ fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { .key_defer { if !p.inside_defer { p.next() + mut defer_mode := ast.DeferMode.scoped + if p.tok.kind == .lpar { + p.next() + mode_pos := p.tok.pos() + mode := p.check_name() + match mode { + 'fn' { + defer_mode = .function + } + else { + return p.error_with_pos('unknown `defer` mode: `${mode}`', + mode_pos) + } + } + p.check(.rpar) + } spos := p.tok.pos() p.inside_defer = true p.defer_vars = []ast.Ident{} stmts := p.parse_block() p.inside_defer = false return ast.DeferStmt{ + mode: defer_mode + 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) @@ -1341,10 +1361,11 @@ fn (mut p Parser) ident(language ast.Language) ast.Ident { mut or_kind := ast.OrKind.absent mut or_stmts := []ast.Stmt{} mut or_pos := token.Pos{} - mut or_scope := &ast.Scope(unsafe { nil }) + mut or_scope := ast.empty_scope if allowed_cases && p.tok.kind == .question && p.peek_tok.kind != .lpar { // var?, not var?( or_kind = ast.OrKind.propagate_option + or_scope = p.scope p.check(.question) } else if allowed_cases && p.tok.kind == .key_orelse { or_kind = ast.OrKind.block @@ -1928,7 +1949,7 @@ fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr { mut or_kind_high := ast.OrKind.absent mut or_stmts_high := []ast.Stmt{} mut or_pos_high := token.Pos{} - mut or_scope := &ast.Scope(unsafe { nil }) + mut or_scope := ast.empty_scope if !p.or_is_handled { // a[..end] or {...} @@ -1957,6 +1978,7 @@ fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr { if p.tok.kind == .not { or_pos_high = p.tok.pos() or_kind_high = .propagate_result + or_scope = p.scope p.next() } else if p.tok.kind == .question { p.error_with_pos('`?` for propagating errors from index expressions is no longer supported, use `!` instead of `?`', @@ -1977,6 +1999,7 @@ fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr { or_expr: ast.OrExpr{ kind: or_kind_high stmts: or_stmts_high + scope: or_scope pos: or_pos_high } is_gated: is_gated @@ -1998,7 +2021,7 @@ fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr { mut or_kind_low := ast.OrKind.absent mut or_stmts_low := []ast.Stmt{} mut or_pos_low := token.Pos{} - mut or_scope := &ast.Scope(unsafe { nil }) + mut or_scope := ast.empty_scope if !p.or_is_handled { // a[start..end] or {...} if p.tok.kind == .key_orelse { @@ -2027,6 +2050,7 @@ fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr { if p.tok.kind == .not { or_pos_low = p.tok.pos() or_kind_low = .propagate_result + or_scope = p.scope p.next() } else if p.tok.kind == .question { p.error_with_pos('`?` for propagating errors from index expressions is no longer supported, use `!` instead of `?`', @@ -2048,6 +2072,7 @@ fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr { or_expr: ast.OrExpr{ kind: or_kind_low stmts: or_stmts_low + scope: or_scope pos: or_pos_low } is_gated: is_gated @@ -2059,7 +2084,7 @@ fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr { mut or_kind := ast.OrKind.absent mut or_stmts := []ast.Stmt{} mut or_pos := token.Pos{} - mut or_scope := &ast.Scope(unsafe { nil }) + mut or_scope := ast.empty_scope if !p.or_is_handled { // a[i] or { ... } if p.tok.kind == .key_orelse { @@ -2081,6 +2106,7 @@ fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr { if p.tok.kind == .not { or_pos = p.tok.pos() or_kind = .propagate_result + or_scope = p.scope p.next() } else if p.tok.kind == .question { p.error_with_pos('`?` for propagating errors from index expressions is no longer supported, use `!` instead of `?`', @@ -2094,6 +2120,7 @@ fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr { or_expr: ast.OrExpr{ kind: or_kind stmts: or_stmts + scope: or_scope pos: or_pos } is_gated: is_gated @@ -2221,17 +2248,19 @@ fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr { mut or_kind := ast.OrKind.absent mut or_stmts := []ast.Stmt{} mut or_pos := token.Pos{} - mut or_scope := &ast.Scope(unsafe { nil }) + mut or_scope := ast.empty_scope if p.tok.kind == .key_orelse { or_kind = .block or_stmts, or_pos, or_scope = p.or_block(.with_err_var) } else if p.tok.kind == .not { or_kind = .propagate_result or_pos = p.tok.pos() + or_scope = p.scope p.next() } else if p.tok.kind == .question { or_kind = .propagate_option or_pos = p.tok.pos() + or_scope = p.scope p.next() } sel_expr := ast.SelectorExpr{ @@ -2636,6 +2665,7 @@ fn (mut p Parser) return_stmt() ast.Return { mut comments := p.eat_comments() if p.tok.kind == .rcbr || (p.tok.kind == .name && p.peek_tok.kind == .colon) { return ast.Return{ + scope: p.scope comments: comments pos: first_pos } @@ -2647,6 +2677,7 @@ fn (mut p Parser) return_stmt() ast.Return { p.inside_assign_rhs = old_assign_rhs end_pos := exprs.last().pos() return ast.Return{ + scope: p.scope exprs: exprs comments: comments pos: first_pos.extend(end_pos) @@ -3117,21 +3148,23 @@ fn (mut p Parser) unsafe_stmt() ast.Stmt { if p.inside_unsafe && !p.inside_defer { return p.error_with_pos('already inside `unsafe` block', pos) } + p.inside_unsafe = true + p.open_scope() // needed in case of `unsafe {stmt}` + sc := p.scope + defer { + p.inside_unsafe = false + p.close_scope() + } if p.tok.kind == .rcbr { // `unsafe {}` pos.update_last_line(p.tok.line_nr) p.next() return ast.Block{ + scope: sc is_unsafe: true pos: pos } } - p.inside_unsafe = true - p.open_scope() // needed in case of `unsafe {stmt}` - defer { - p.inside_unsafe = false - p.close_scope() - } stmt := p.stmt(false) if p.tok.kind == .rcbr { if stmt is ast.ExprStmt { @@ -3161,6 +3194,7 @@ fn (mut p Parser) unsafe_stmt() ast.Stmt { pos.update_last_line(p.tok.line_nr) return ast.Block{ stmts: stmts + scope: sc is_unsafe: true pos: pos } diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index 641ab921a..dad3729dc 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -260,6 +260,8 @@ pub mut: subsystem Subsystem // the type of the window app, that is going to be generated; has no effect on !windows is_vls bool json_errors bool // -json-errors, for VLS and other tools + + scoped_defer bool // experimental support, is activated with `-scoped-defer` } pub fn parse_args(known_external_commands []string, args []string) (&Preferences, string) { @@ -762,6 +764,10 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin '-experimental' { res.experimental = true } + '-scoped-defer' { + // experimental, remove once defer is scoped by default + res.scoped_defer = true + } '-usecache' { res.use_cache = true } diff --git a/vlib/v/tests/defer/defer_static_test.v b/vlib/v/tests/defer/defer_static_test.v index 0837de5fd..d2cdedb7a 100644 --- a/vlib/v/tests/defer/defer_static_test.v +++ b/vlib/v/tests/defer/defer_static_test.v @@ -23,8 +23,8 @@ fn f(depth int) { assert levels == 3 } println('levels: ${levels} | depth: ${depth}') + f(depth - 1) } - f(depth - 1) } fn test_main() { diff --git a/vlib/v/tests/defer/scoped_defer_test.v b/vlib/v/tests/defer/scoped_defer_test.v new file mode 100644 index 000000000..222d260e5 --- /dev/null +++ b/vlib/v/tests/defer/scoped_defer_test.v @@ -0,0 +1,22 @@ +fn test_scoped_defer() { + mut res := 0 + + defer(fn) { + res++ + assert res == 5 + } + { + res++ + defer { + res++ + assert res == 4 + } + { + res += 1 + defer { + res++ + assert res == 3 + } + } // <- Block 2 ends. Defer 3 executes. res = 3. + } // <- Block 1 ends. Defer 2 executes. res = 4. +} // <- 'test_scoped_defer' ends. Defer 1 executes. res = 5. diff --git a/vlib/v/tests/options/option_free_method_test.v b/vlib/v/tests/options/option_free_method_test.v index 0fe4ad81c..727dc40c3 100644 --- a/vlib/v/tests/options/option_free_method_test.v +++ b/vlib/v/tests/options/option_free_method_test.v @@ -30,7 +30,9 @@ fn test_main() { defer { unsafe { - t2?.free() + if t2 != none { + t2.free() + } } } } diff --git a/vlib/v/transformer/transformer.v b/vlib/v/transformer/transformer.v index 36ef31f18..b0d187304 100644 --- a/vlib/v/transformer/transformer.v +++ b/vlib/v/transformer/transformer.v @@ -1252,6 +1252,7 @@ pub fn (mut t Transformer) simplify_nested_interpolation_in_sb(mut onode ast.Stm // calls << ast.node unsafe { *onode = ast.Stmt(ast.Block{ + scope: ast.empty_scope stmts: calls }) } -- 2.39.5