Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v: add $res compile time function to get returned value in defer block #18382

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions doc/docs.md
Original file line number Diff line number Diff line change
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
spytheman marked this conversation as resolved.
Show resolved Hide resolved
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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'
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fn test() (string, string) {
defer {
println($res(2))
}
return 'test', 'test2'
}
Original file line number Diff line number Diff line change
@@ -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'
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fn test() (string, string) {
defer {
println($res())
}
return 'test', 'test2'
}
Original file line number Diff line number Diff line change
@@ -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 | }
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fn test() {
defer {
println($res())
}
}
Original file line number Diff line number Diff line change
@@ -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'
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fn test() string {
println($res())
return 'test'
}
7 changes: 7 additions & 0 deletions vlib/v/fmt/fmt.v
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 @@ -4695,6 +4696,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 @@ -4738,7 +4740,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 @@ -4766,7 +4768,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 @@ -4874,7 +4876,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 @@ -4913,7 +4915,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 @@ -4946,7 +4948,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 @@ -5024,7 +5026,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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
}