Skip to content

Commit

Permalink
all: add $res compile time function to get returned value in defer bl…
Browse files Browse the repository at this point in the history
…ock (#18382)
  • Loading branch information
LouisSchmieder committed Jun 17, 2023
1 parent ac32d2a commit dbd2517
Show file tree
Hide file tree
Showing 19 changed files with 223 additions and 10 deletions.
36 changes: 36 additions & 0 deletions doc/docs.md
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions vlib/v/checker/checker.v
Expand Up @@ -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 {
Expand Down
34 changes: 34 additions & 0 deletions vlib/v/checker/comptime.v
Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions vlib/v/checker/fn.v
Expand Up @@ -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()
Expand Down
@@ -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'
@@ -0,0 +1,6 @@
fn test() (string, string) {
defer {
println($res(2))
}
return 'test', 'test2'
}
@@ -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'
@@ -0,0 +1,6 @@
fn test() (string, string) {
defer {
println($res())
}
return 'test', 'test2'
}
@@ -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 | }
@@ -0,0 +1,5 @@
fn test() {
defer {
println($res())
}
}
@@ -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'
@@ -0,0 +1,6 @@
fn test() !string {
defer {
println($res())
}
return 'test'
}
6 changes: 6 additions & 0 deletions 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 | }
4 changes: 4 additions & 0 deletions vlib/v/checker/tests/res_use_outside_defer.vv
@@ -0,0 +1,4 @@
fn test() string {
println($res())
return 'test'
}
7 changes: 7 additions & 0 deletions vlib/v/fmt/fmt.v
Expand Up @@ -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
Expand Down
18 changes: 10 additions & 8 deletions vlib/v/gen/c/cgen.v
Expand Up @@ -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
Expand Down Expand Up @@ -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}'
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions vlib/v/gen/c/comptime.v
Expand Up @@ -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 := ''
Expand Down
26 changes: 24 additions & 2 deletions vlib/v/parser/comptime.v
Expand Up @@ -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']
)
Expand Down Expand Up @@ -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' {
Expand Down Expand Up @@ -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 {
Expand Down
40 changes: 40 additions & 0 deletions 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
}

0 comments on commit dbd2517

Please sign in to comment.