From dbd251793eaa387936367bf907f366acb3050365 Mon Sep 17 00:00:00 2001 From: Louis Schmieder Date: Sat, 17 Jun 2023 13:08:50 +0200 Subject: [PATCH] all: add $res compile time function to get returned value in defer block (#18382) --- doc/docs.md | 36 +++++++++++++++++ vlib/v/checker/checker.v | 1 + vlib/v/checker/comptime.v | 34 ++++++++++++++++ vlib/v/checker/fn.v | 1 + ..._return_value_with_index_out_of_bounds.out | 7 ++++ ...i_return_value_with_index_out_of_bounds.vv | 6 +++ ...r_use_multi_return_value_without_index.out | 7 ++++ ...er_use_multi_return_value_without_index.vv | 6 +++ ...eturned_value_when_nothing_is_returned.out | 7 ++++ ...returned_value_when_nothing_is_returned.vv | 5 +++ ...returned_value_when_result_is_returned.out | 7 ++++ ..._returned_value_when_result_is_returned.vv | 6 +++ .../v/checker/tests/res_use_outside_defer.out | 6 +++ vlib/v/checker/tests/res_use_outside_defer.vv | 4 ++ vlib/v/fmt/fmt.v | 7 ++++ vlib/v/gen/c/cgen.v | 18 +++++---- vlib/v/gen/c/comptime.v | 9 +++++ vlib/v/parser/comptime.v | 26 +++++++++++- vlib/v/tests/defer_use_returned_value_test.v | 40 +++++++++++++++++++ 19 files changed, 223 insertions(+), 10 deletions(-) create mode 100644 vlib/v/checker/tests/defer_use_multi_return_value_with_index_out_of_bounds.out create mode 100644 vlib/v/checker/tests/defer_use_multi_return_value_with_index_out_of_bounds.vv create mode 100644 vlib/v/checker/tests/defer_use_multi_return_value_without_index.out create mode 100644 vlib/v/checker/tests/defer_use_multi_return_value_without_index.vv create mode 100644 vlib/v/checker/tests/defer_use_returned_value_when_nothing_is_returned.out create mode 100644 vlib/v/checker/tests/defer_use_returned_value_when_nothing_is_returned.vv create mode 100644 vlib/v/checker/tests/defer_use_returned_value_when_result_is_returned.out create mode 100644 vlib/v/checker/tests/defer_use_returned_value_when_result_is_returned.vv create mode 100644 vlib/v/checker/tests/res_use_outside_defer.out create mode 100644 vlib/v/checker/tests/res_use_outside_defer.vv create mode 100644 vlib/v/tests/defer_use_returned_value_test.v diff --git a/doc/docs.md b/doc/docs.md index e1e0fd684f08eb..aee39d81eb6a01 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -2119,6 +2119,42 @@ fn main() { } ``` +To access the result of the function inside a `defer` block the `$res()` expression can be used. +`$res()` is only used when a single value is returned, while on multi-return the `$res(idx)` +is parameterized. + +```v ignore +fn (mut app App) auth_middleware() bool { + defer { + if !$res() { + app.response.status_code = 401 + app.response.body = 'Unauthorized' + } + } + header := app.get_header('Authorization') + if header == '' { + return false + } + return true +} + +fn (mut app App) auth_with_user_middleware() (bool, string) { + defer { + if !$res(0) { + app.response.status_code = 401 + app.response.body = 'Unauthorized' + } else { + app.user = $res(1) + } + } + header := app.get_header('Authorization') + if header == '' { + return false, '' + } + return true, 'TestUser' +} +``` + ### Goto V allows unconditionally jumping to a label with `goto`. The label name must be contained diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 3282f0d1f8780f..7632320ee21b21 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -115,6 +115,7 @@ mut: comptime_call_pos int // needed for correctly checking use before decl for templates goto_labels map[string]ast.GotoLabel // to check for unused goto labels enum_data_type ast.Type + fn_return_type ast.Type } pub fn new_checker(table &ast.Table, pref_ &pref.Preferences) &Checker { diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index 66a78996ad89db..5daa2be59c48ef 100644 --- a/vlib/v/checker/comptime.v +++ b/vlib/v/checker/comptime.v @@ -153,6 +153,40 @@ fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) ast.Type { // assume string for now return ast.string_type } + if node.method_name == 'res' { + if !c.inside_defer { + c.error('`res` can only be used in defer blocks', node.pos) + return ast.void_type + } + + if c.fn_return_type == ast.void_type { + c.error('`res` can only be used in functions that returns something', node.pos) + return ast.void_type + } + + sym := c.table.sym(c.fn_return_type) + + if c.fn_return_type.has_flag(.result) { + c.error('`res` cannot be used in functions that returns a Result', node.pos) + return ast.void_type + } + + if sym.info is ast.MultiReturn { + if node.args_var == '' { + c.error('`res` requires an index of the returned value', node.pos) + return ast.void_type + } + idx := node.args_var.int() + if idx < 0 || idx >= sym.info.types.len { + c.error('index ${idx} out of range of ${sym.info.types.len} return types', + node.pos) + return ast.void_type + } + return sym.info.types[idx] + } + + return c.fn_return_type + } if node.is_vweb { return ast.string_type } diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index b13eeccc385264..bcde8ebeba23d1 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -102,6 +102,7 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { } } } + c.fn_return_type = node.return_type if node.return_type != ast.void_type { if ct_attr_idx := node.attrs.find_comptime_define() { sexpr := node.attrs[ct_attr_idx].ct_expr.str() diff --git a/vlib/v/checker/tests/defer_use_multi_return_value_with_index_out_of_bounds.out b/vlib/v/checker/tests/defer_use_multi_return_value_with_index_out_of_bounds.out new file mode 100644 index 00000000000000..08cdc16849ac93 --- /dev/null +++ b/vlib/v/checker/tests/defer_use_multi_return_value_with_index_out_of_bounds.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/defer_use_multi_return_value_with_index_out_of_bounds.vv:3:11: error: index 2 out of range of 2 return types + 1 | fn test() (string, string) { + 2 | defer { + 3 | println($res(2)) + | ~~~~~~~ + 4 | } + 5 | return 'test', 'test2' diff --git a/vlib/v/checker/tests/defer_use_multi_return_value_with_index_out_of_bounds.vv b/vlib/v/checker/tests/defer_use_multi_return_value_with_index_out_of_bounds.vv new file mode 100644 index 00000000000000..de393db906d9fe --- /dev/null +++ b/vlib/v/checker/tests/defer_use_multi_return_value_with_index_out_of_bounds.vv @@ -0,0 +1,6 @@ +fn test() (string, string) { + defer { + println($res(2)) + } + return 'test', 'test2' +} diff --git a/vlib/v/checker/tests/defer_use_multi_return_value_without_index.out b/vlib/v/checker/tests/defer_use_multi_return_value_without_index.out new file mode 100644 index 00000000000000..70898e96c45513 --- /dev/null +++ b/vlib/v/checker/tests/defer_use_multi_return_value_without_index.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/defer_use_multi_return_value_without_index.vv:3:11: error: `res` requires an index of the returned value + 1 | fn test() (string, string) { + 2 | defer { + 3 | println($res()) + | ~~~~~~ + 4 | } + 5 | return 'test', 'test2' diff --git a/vlib/v/checker/tests/defer_use_multi_return_value_without_index.vv b/vlib/v/checker/tests/defer_use_multi_return_value_without_index.vv new file mode 100644 index 00000000000000..4cd11a5cd0ef83 --- /dev/null +++ b/vlib/v/checker/tests/defer_use_multi_return_value_without_index.vv @@ -0,0 +1,6 @@ +fn test() (string, string) { + defer { + println($res()) + } + return 'test', 'test2' +} diff --git a/vlib/v/checker/tests/defer_use_returned_value_when_nothing_is_returned.out b/vlib/v/checker/tests/defer_use_returned_value_when_nothing_is_returned.out new file mode 100644 index 00000000000000..88094686f58e1a --- /dev/null +++ b/vlib/v/checker/tests/defer_use_returned_value_when_nothing_is_returned.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/defer_use_returned_value_when_nothing_is_returned.vv:3:11: error: `res` can only be used in functions that returns something + 1 | fn test() { + 2 | defer { + 3 | println($res()) + | ~~~~~~ + 4 | } + 5 | } diff --git a/vlib/v/checker/tests/defer_use_returned_value_when_nothing_is_returned.vv b/vlib/v/checker/tests/defer_use_returned_value_when_nothing_is_returned.vv new file mode 100644 index 00000000000000..b57a83556aad8e --- /dev/null +++ b/vlib/v/checker/tests/defer_use_returned_value_when_nothing_is_returned.vv @@ -0,0 +1,5 @@ +fn test() { + defer { + println($res()) + } +} diff --git a/vlib/v/checker/tests/defer_use_returned_value_when_result_is_returned.out b/vlib/v/checker/tests/defer_use_returned_value_when_result_is_returned.out new file mode 100644 index 00000000000000..ca8995cdc5461a --- /dev/null +++ b/vlib/v/checker/tests/defer_use_returned_value_when_result_is_returned.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/defer_use_returned_value_when_result_is_returned.vv:3:11: error: `res` cannot be used in functions that returns a Result + 1 | fn test() !string { + 2 | defer { + 3 | println($res()) + | ~~~~~~ + 4 | } + 5 | return 'test' diff --git a/vlib/v/checker/tests/defer_use_returned_value_when_result_is_returned.vv b/vlib/v/checker/tests/defer_use_returned_value_when_result_is_returned.vv new file mode 100644 index 00000000000000..606e412a90e757 --- /dev/null +++ b/vlib/v/checker/tests/defer_use_returned_value_when_result_is_returned.vv @@ -0,0 +1,6 @@ +fn test() !string { + defer { + println($res()) + } + return 'test' +} diff --git a/vlib/v/checker/tests/res_use_outside_defer.out b/vlib/v/checker/tests/res_use_outside_defer.out new file mode 100644 index 00000000000000..54122a0db095e3 --- /dev/null +++ b/vlib/v/checker/tests/res_use_outside_defer.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/res_use_outside_defer.vv:2:10: error: `res` can only be used in defer blocks + 1 | fn test() string { + 2 | println($res()) + | ~~~~~~ + 3 | return 'test' + 4 | } diff --git a/vlib/v/checker/tests/res_use_outside_defer.vv b/vlib/v/checker/tests/res_use_outside_defer.vv new file mode 100644 index 00000000000000..1adca1923f52fe --- /dev/null +++ b/vlib/v/checker/tests/res_use_outside_defer.vv @@ -0,0 +1,4 @@ +fn test() string { + println($res()) + return 'test' +} diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 5ce35d56996d11..f6912f3ef29e5a 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -1969,6 +1969,13 @@ pub fn (mut f Fmt) comptime_call(node ast.ComptimeCall) { node.method_name in ['compile_error', 'compile_warn'] { f.write("\$${node.method_name}('${node.args_var}')") } + node.method_name == 'res' { + if node.args_var != '' { + f.write('\$res(${node.args_var})') + } else { + f.write('\$res()') + } + } else { inner_args := if node.args_var != '' { node.args_var diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index bd2af478d24556..b5f9e6665a63f0 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -245,8 +245,9 @@ mut: out_fn_start_pos []int // for generating multiple .c files, stores locations of all fn positions in `out` string builder static_modifier string // for parallel_cc - has_reflection bool - reflection_strings &map[string]int + has_reflection bool + reflection_strings &map[string]int + defer_return_tmp_var string } // global or const variable definition string @@ -4699,6 +4700,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { return } tmpvar := g.new_tmp_var() + g.defer_return_tmp_var = tmpvar mut ret_typ := g.typ(g.unwrap_generic(fn_ret_type)) if fn_ret_type.has_flag(.generic) && fn_return_is_fixed_array { ret_typ = '_v_${ret_typ}' @@ -4742,7 +4744,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { } } g.write_defer_stmts_when_needed() - g.writeln('return ${tmpvar};') + g.writeln('return ${tmpvar}; //test') } return } @@ -4770,7 +4772,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { g.writeln(';') if use_tmp_var { g.write_defer_stmts_when_needed() - g.writeln('return ${tmpvar};') + g.writeln('return ${tmpvar}; //test1') } return } @@ -4878,7 +4880,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { g.writeln(';') } g.write_defer_stmts_when_needed() - g.writeln('return ${tmpvar};') + g.writeln('return ${tmpvar}; //test2') has_semicolon = true } } else if node.exprs.len >= 1 { @@ -4917,7 +4919,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { g.writeln(' }, (${c.option_name}*)(&${tmpvar}), sizeof(${styp}));') g.write_defer_stmts_when_needed() g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) - g.writeln('return ${tmpvar};') + g.writeln('return ${tmpvar}; //test4') return } expr_type_is_result := match expr0 { @@ -4950,7 +4952,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { g.writeln(' }, (${c.result_name}*)(&${tmpvar}), sizeof(${styp}));') g.write_defer_stmts_when_needed() g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) - g.writeln('return ${tmpvar};') + g.writeln('return ${tmpvar}; //test 4') return } // autofree before `return` @@ -5028,7 +5030,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { if !g.is_builtin_mod { g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) } - g.write('return ${tmpvar}') + g.write('return ${tmpvar} /* test5 */') has_semicolon = false } } else { diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index 7a81bec0d45bf8..306ebd6c8f9956 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -80,6 +80,15 @@ fn (mut g Gen) comptime_call(mut node ast.ComptimeCall) { g.write('_SLIT("${val}")') return } + if node.method_name == 'res' { + if node.args_var != '' { + g.write('${g.defer_return_tmp_var}.arg${node.args_var}') + return + } + + g.write('${g.defer_return_tmp_var}') + return + } if node.is_vweb { is_html := node.method_name == 'html' mut cur_line := '' diff --git a/vlib/v/parser/comptime.v b/vlib/v/parser/comptime.v index 76bc4fa2d72f4e..861a429f9493ea 100644 --- a/vlib/v/parser/comptime.v +++ b/vlib/v/parser/comptime.v @@ -10,7 +10,7 @@ import v.token const ( supported_comptime_calls = ['html', 'tmpl', 'env', 'embed_file', 'pkgconfig', 'compile_error', - 'compile_warn'] + 'compile_warn', 'res'] comptime_types = ['map', 'array', 'int', 'float', 'struct', 'interface', 'enum', 'sumtype', 'alias', 'function', 'option'] ) @@ -97,7 +97,7 @@ fn (mut p Parser) comptime_call() ast.ComptimeCall { } start_pos := p.tok.pos() p.check(.dollar) - error_msg := 'only `\$tmpl()`, `\$env()`, `\$embed_file()`, `\$pkgconfig()`, `\$vweb.html()`, `\$compile_error()` and `\$compile_warn()` comptime functions are supported right now' + error_msg := 'only `\$tmpl()`, `\$env()`, `\$embed_file()`, `\$pkgconfig()`, `\$vweb.html()`, `\$compile_error()`, `\$compile_warn()` and `\$res()` comptime functions are supported right now' if p.peek_tok.kind == .dot { name := p.check_name() // skip `vweb.html()` TODO if name != 'vweb' { @@ -129,6 +129,28 @@ fn (mut p Parser) comptime_call() ast.ComptimeCall { env_pos: start_pos pos: start_pos.extend(p.prev_tok.pos()) } + } else if method_name == 'res' { + mut has_args := false + mut type_index := '' + if p.tok.kind == .number { + has_args = true + type_index = p.tok.lit + p.check(.number) + } + p.check(.rpar) + if has_args { + return ast.ComptimeCall{ + scope: 0 + method_name: method_name + args_var: type_index + pos: start_pos.extend(p.prev_tok.pos()) + } + } + return ast.ComptimeCall{ + scope: 0 + method_name: method_name + pos: start_pos.extend(p.prev_tok.pos()) + } } mut literal_string_param := if is_html { '' } else { p.tok.lit } if p.tok.kind == .name { diff --git a/vlib/v/tests/defer_use_returned_value_test.v b/vlib/v/tests/defer_use_returned_value_test.v new file mode 100644 index 00000000000000..a8ea9df4e878fe --- /dev/null +++ b/vlib/v/tests/defer_use_returned_value_test.v @@ -0,0 +1,40 @@ +struct Test { +mut: + a int + b string +} + +fn (mut test Test) with_single_return() int { + defer { + test.a = $res() + } + return 41 +} + +fn (mut test Test) with_multi_return() (int, string) { + defer { + test.a = $res(0) + test.b = $res(1) + } + return 41, 'foo' +} + +fn test_with_single_return() { + mut test := Test{ + a: 0 + } + assert test.with_single_return() == 41 + assert test.a == 41 +} + +fn test_with_multi_return() { + mut test := Test{ + a: 0 + b: '' + } + a, b := test.with_multi_return() + assert a == 41 + assert b == 'foo' + assert test.a == a + assert test.b == b +}