From 85cb0a8c85dd3df7fc0701b82b4ba1a879e113ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=C3=A4schle?= Date: Sat, 30 Apr 2022 00:59:14 +0200 Subject: [PATCH] all: basic implementation of result type (#14140) --- vlib/builtin/option.v | 16 ++ vlib/v/ast/ast.v | 3 +- vlib/v/ast/str.v | 5 +- vlib/v/ast/table.v | 6 +- vlib/v/ast/types.v | 8 + vlib/v/checker/check_types.v | 9 +- vlib/v/checker/checker.v | 59 ++++-- vlib/v/checker/fn.v | 12 +- vlib/v/checker/return.v | 2 +- vlib/v/checker/str.v | 2 +- vlib/v/checker/tests/fn_return_or_err.out | 4 +- vlib/v/checker/tests/go_wait_or.out | 8 +- .../propagate_option_with_result_err.out | 7 + .../tests/propagate_option_with_result_err.vv | 10 + .../tests/propagate_result_with_option.out | 7 + .../tests/propagate_result_with_option.vv | 10 + vlib/v/checker/tests/unexpected_or.out | 2 +- .../checker/tests/unexpected_or_propagate.out | 2 +- vlib/v/fmt/fmt.v | 7 +- vlib/v/fmt/tests/result_return_type_keep.vv | 3 + vlib/v/gen/c/assert.v | 2 +- vlib/v/gen/c/auto_str_methods.v | 42 ++++- vlib/v/gen/c/cgen.v | 176 +++++++++++++++++- vlib/v/gen/c/fn.v | 5 +- vlib/v/gen/c/str_intp.v | 3 +- vlib/v/gen/js/auto_str_methods.v | 2 + vlib/v/gen/js/fn.v | 10 +- vlib/v/gen/js/js.v | 2 +- vlib/v/parser/expr.v | 4 +- vlib/v/parser/fn.v | 9 +- vlib/v/parser/parse_type.v | 23 ++- vlib/v/parser/parser.v | 12 +- vlib/v/parser/tests/option_result_err.out | 5 + vlib/v/parser/tests/option_result_err.vv | 3 + vlib/v/parser/tests/result_option_err.out | 5 + vlib/v/parser/tests/result_option_err.vv | 3 + vlib/v/scanner/scanner.v | 2 +- vlib/v/tests/results_test.v | 65 +++++++ vlib/v/token/token.v | 2 +- 39 files changed, 478 insertions(+), 79 deletions(-) create mode 100644 vlib/v/checker/tests/propagate_option_with_result_err.out create mode 100644 vlib/v/checker/tests/propagate_option_with_result_err.vv create mode 100644 vlib/v/checker/tests/propagate_result_with_option.out create mode 100644 vlib/v/checker/tests/propagate_result_with_option.vv create mode 100644 vlib/v/fmt/tests/result_return_type_keep.vv create mode 100644 vlib/v/parser/tests/option_result_err.out create mode 100644 vlib/v/parser/tests/option_result_err.vv create mode 100644 vlib/v/parser/tests/result_option_err.out create mode 100644 vlib/v/parser/tests/result_option_err.vv create mode 100644 vlib/v/tests/results_test.v diff --git a/vlib/builtin/option.v b/vlib/builtin/option.v index 2cfcc55388..d487595d8b 100644 --- a/vlib/builtin/option.v +++ b/vlib/builtin/option.v @@ -118,6 +118,22 @@ fn opt_ok(data voidptr, mut option Option, size int) { } } +struct result { + is_error bool + err IError = none__ + // Data is trailing after err + // and is not included in here but in the + // derived Result_xxx types +} + +fn result_ok(data voidptr, mut res result, size int) { + unsafe { + *res = result{} + // use err to get the end of ResultBase and then memcpy into it + vmemcpy(&u8(&res.err) + sizeof(IError), data, size) + } +} + pub fn (_ none) str() string { return 'none' } diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index b2244306c8..06fb9a1bf7 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -1487,7 +1487,8 @@ pub mut: pub enum OrKind { absent block - propagate + propagate_option + propagate_result } // `or { ... }` diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index f80a877379..9ef7494d07 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -210,7 +210,8 @@ pub fn (lit &StringInterLiteral) get_fspec_braces(i int) (string, bool) { } CallExpr { if sub_expr.args.len != 0 || sub_expr.concrete_types.len != 0 - || sub_expr.or_block.kind == .propagate || sub_expr.or_block.stmts.len > 0 { + || sub_expr.or_block.kind == .propagate_option + || sub_expr.or_block.stmts.len > 0 { needs_braces = true } else if sub_expr.left is CallExpr { sub_expr = sub_expr.left @@ -302,7 +303,7 @@ pub fn (x Expr) str() string { } CallExpr { sargs := args2str(x.args) - propagate_suffix := if x.or_block.kind == .propagate { ' ?' } else { '' } + propagate_suffix := if x.or_block.kind == .propagate_option { ' ?' } else { '' } if x.is_method { return '${x.left.str()}.${x.name}($sargs)$propagate_suffix' } diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 79c5a95d3d..20d1951934 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -232,7 +232,7 @@ pub fn (t &Table) fn_type_signature(f &Fn) string { return sig } -// source_signature generates the signature of a function which looks like in the V source +// fn_type_source_signature generates the signature of a function which looks like in the V source pub fn (t &Table) fn_type_source_signature(f &Fn) string { mut sig := '(' for i, arg in f.params { @@ -252,10 +252,14 @@ pub fn (t &Table) fn_type_source_signature(f &Fn) string { sig += ')' if f.return_type == ovoid_type { sig += ' ?' + } else if f.return_type == rvoid_type { + sig += ' !' } else if f.return_type != void_type { return_type_sym := t.sym(f.return_type) if f.return_type.has_flag(.optional) { sig += ' ?$return_type_sym.name' + } else if f.return_type.has_flag(.result) { + sig += ' !$return_type_sym.name' } else { sig += ' $return_type_sym.name' } diff --git a/vlib/v/ast/types.v b/vlib/v/ast/types.v index 97935bafac..515f179e81 100644 --- a/vlib/v/ast/types.v +++ b/vlib/v/ast/types.v @@ -98,6 +98,7 @@ pub mut: // max of 8 pub enum TypeFlag { optional + result variadic generic shared_f @@ -472,6 +473,7 @@ pub const ( pub const ( void_type = new_type(void_type_idx) ovoid_type = new_type(void_type_idx).set_flag(.optional) // the return type of `fn () ?` + rvoid_type = new_type(void_type_idx).set_flag(.result) // the return type of `fn () !` voidptr_type = new_type(voidptr_type_idx) byteptr_type = new_type(byteptr_type_idx) charptr_type = new_type(charptr_type_idx) @@ -1227,6 +1229,9 @@ pub fn (t &Table) type_to_str_using_aliases(typ Type, import_aliases map[string] if typ.has_flag(.optional) { return '?' } + if typ.has_flag(.result) { + return '!' + } return 'void' } .thread { @@ -1262,6 +1267,9 @@ pub fn (t &Table) type_to_str_using_aliases(typ Type, import_aliases map[string] if typ.has_flag(.optional) { res = '?$res' } + if typ.has_flag(.result) { + res = '!$res' + } return res } diff --git a/vlib/v/checker/check_types.v b/vlib/v/checker/check_types.v index 6c846a1db0..9d2bb65a50 100644 --- a/vlib/v/checker/check_types.v +++ b/vlib/v/checker/check_types.v @@ -89,12 +89,15 @@ pub fn (mut c Checker) check_types(got ast.Type, expected ast.Type) bool { if expected == ast.charptr_type && got == ast.char_type.ref() { return true } - if expected.has_flag(.optional) { + if expected.has_flag(.optional) || expected.has_flag(.result) { sym := c.table.sym(got) - if sym.idx == ast.error_type_idx || got in [ast.none_type, ast.error_type] { + if ((sym.idx == ast.error_type_idx || got in [ast.none_type, ast.error_type]) + && expected.has_flag(.optional)) + || ((sym.idx == ast.error_type_idx || got == ast.error_type) + && expected.has_flag(.result)) { // IErorr return true - } else if !c.check_basic(got, expected.clear_flag(.optional)) { + } else if !c.check_basic(got, expected.clear_flag(.optional).clear_flag(.result)) { return false } } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 4627e0d123..3bdbf50278 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1488,39 +1488,61 @@ fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos to // return the actual type of the expression, once the optional is handled pub fn (mut c Checker) check_expr_opt_call(expr ast.Expr, ret_type ast.Type) ast.Type { if expr is ast.CallExpr { - if expr.return_type.has_flag(.optional) { + if expr.return_type.has_flag(.optional) || expr.return_type.has_flag(.result) { + return_modifier_kind := if expr.return_type.has_flag(.optional) { + 'an option' + } else { + 'a result' + } + return_modifier := if expr.return_type.has_flag(.optional) { '?' } else { '!' } if expr.or_block.kind == .absent { if c.inside_defer { - c.error('${expr.name}() returns an option, so it should have an `or {}` block at the end', + c.error('${expr.name}() returns $return_modifier_kind, so it should have an `or {}` block at the end', expr.pos) } else { - c.error('${expr.name}() returns an option, so it should have either an `or {}` block, or `?` at the end', + c.error('${expr.name}() returns $return_modifier_kind, so it should have either an `or {}` block, or `$return_modifier` at the end', expr.pos) } } else { - c.check_or_expr(expr.or_block, ret_type, expr.return_type.clear_flag(.optional)) + c.check_or_expr(expr.or_block, ret_type, expr.return_type) } return ret_type.clear_flag(.optional) } else if expr.or_block.kind == .block { - c.error('unexpected `or` block, the function `$expr.name` does not return an optional', + c.error('unexpected `or` block, the function `$expr.name` does neither return an optional nor a result', expr.or_block.pos) - } else if expr.or_block.kind == .propagate { + } else if expr.or_block.kind == .propagate_option { c.error('unexpected `?`, the function `$expr.name` does not return an optional', expr.or_block.pos) } } else if expr is ast.IndexExpr { if expr.or_expr.kind != .absent { - c.check_or_expr(expr.or_expr, ret_type, ret_type) + c.check_or_expr(expr.or_expr, ret_type, ret_type.set_flag(.optional)) } } return ret_type } pub fn (mut c Checker) check_or_expr(node ast.OrExpr, ret_type ast.Type, expr_return_type ast.Type) { - if node.kind == .propagate { + if node.kind == .propagate_option { if !c.table.cur_fn.return_type.has_flag(.optional) && c.table.cur_fn.name != 'main.main' && !c.inside_const { - c.error('to propagate the optional call, `$c.table.cur_fn.name` must return an optional', + c.error('to propagate the call, `$c.table.cur_fn.name` must return an optional type', + node.pos) + } + if !expr_return_type.has_flag(.optional) { + c.error('to propagate an option, the call must also return an optional type', + node.pos) + } + return + } + if node.kind == .propagate_result { + if !c.table.cur_fn.return_type.has_flag(.result) && c.table.cur_fn.name != 'main.main' + && !c.inside_const { + c.error('to propagate the call, `$c.table.cur_fn.name` must return an result type', + node.pos) + } + if !expr_return_type.has_flag(.result) { + c.error('to propagate a result, the call must also return a result type', node.pos) } return @@ -1535,7 +1557,7 @@ pub fn (mut c Checker) check_or_expr(node ast.OrExpr, ret_type ast.Type, expr_re return } last_stmt := node.stmts[stmts_len - 1] - c.check_or_last_stmt(last_stmt, ret_type, expr_return_type) + c.check_or_last_stmt(last_stmt, ret_type, expr_return_type.clear_flag(.optional).clear_flag(.result)) } fn (mut c Checker) check_or_last_stmt(stmt ast.Stmt, ret_type ast.Type, expr_return_type ast.Type) { @@ -2600,17 +2622,22 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { } ast.CallExpr { mut ret_type := c.call_expr(mut node) - if !ret_type.has_flag(.optional) { + if !ret_type.has_flag(.optional) && !ret_type.has_flag(.result) { if node.or_block.kind == .block { - c.error('unexpected `or` block, the function `$node.name` does not return an optional', + c.error('unexpected `or` block, the function `$node.name` does neither return an optional nor a result', node.or_block.pos) - } else if node.or_block.kind == .propagate { - c.error('unexpected `?`, the function `$node.name` does not return an optional', + } else if node.or_block.kind == .propagate_option { + c.error('unexpected `?`, the function `$node.name` does neither return an optional nor a result', node.or_block.pos) } } - if ret_type.has_flag(.optional) && node.or_block.kind != .absent { - ret_type = ret_type.clear_flag(.optional) + if node.or_block.kind != .absent { + if ret_type.has_flag(.optional) { + ret_type = ret_type.clear_flag(.optional) + } + if ret_type.has_flag(.result) { + ret_type = ret_type.clear_flag(.result) + } } return ret_type } diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 1c8e2ea042..d1072743c8 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -310,6 +310,16 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { } } } + // same for result `fn (...) ! { ... }` + if node.return_type != ast.void_type && node.return_type.has_flag(.result) + && (node.stmts.len == 0 || node.stmts.last() !is ast.Return) { + sym := c.table.sym(node.return_type) + if sym.kind == .void { + node.stmts << ast.Return{ + pos: node.pos + } + } + } c.fn_scope = node.scope c.stmts(node.stmts) node_has_top_return := has_top_return(node.stmts) @@ -411,7 +421,7 @@ pub fn (mut c Checker) call_expr(mut node ast.CallExpr) ast.Type { c.expected_or_type = node.return_type.clear_flag(.optional) c.stmts_ending_with_expression(node.or_block.stmts) c.expected_or_type = ast.void_type - if node.or_block.kind == .propagate && !c.table.cur_fn.return_type.has_flag(.optional) + if node.or_block.kind == .propagate_option && !c.table.cur_fn.return_type.has_flag(.optional) && !c.inside_const { if !c.table.cur_fn.is_main { c.error('to propagate the optional call, `$c.table.cur_fn.name` must return an optional', diff --git a/vlib/v/checker/return.v b/vlib/v/checker/return.v index f442826d95..08fce331e0 100644 --- a/vlib/v/checker/return.v +++ b/vlib/v/checker/return.v @@ -158,7 +158,7 @@ pub fn (mut c Checker) return_stmt(mut node ast.Return) { if exp_is_optional && node.exprs.len > 0 { expr0 := node.exprs[0] if expr0 is ast.CallExpr { - if expr0.or_block.kind == .propagate && node.exprs.len == 1 { + if expr0.or_block.kind == .propagate_option && node.exprs.len == 1 { c.error('`?` is not needed, use `return ${expr0.name}()`', expr0.pos) } } diff --git a/vlib/v/checker/str.v b/vlib/v/checker/str.v index 8f3ed9c223..fa1575e00b 100644 --- a/vlib/v/checker/str.v +++ b/vlib/v/checker/str.v @@ -7,7 +7,7 @@ import v.ast import v.token pub fn (mut c Checker) get_default_fmt(ftyp ast.Type, typ ast.Type) u8 { - if ftyp.has_flag(.optional) { + if ftyp.has_flag(.optional) || ftyp.has_flag(.result) { return `s` } else if typ.is_float() { return `g` diff --git a/vlib/v/checker/tests/fn_return_or_err.out b/vlib/v/checker/tests/fn_return_or_err.out index ebb4494d27..3da8179470 100644 --- a/vlib/v/checker/tests/fn_return_or_err.out +++ b/vlib/v/checker/tests/fn_return_or_err.out @@ -1,5 +1,5 @@ -vlib/v/checker/tests/fn_return_or_err.vv:6:17: error: unexpected `or` block, the function `pop` does not return an optional - 4 | +vlib/v/checker/tests/fn_return_or_err.vv:6:17: error: unexpected `or` block, the function `pop` does neither return an optional nor a result + 4 | 5 | pub fn next(mut v []Typ) Typ { 6 | return v.pop() or { Typ{} } | ~~~~~~~~~~~~ diff --git a/vlib/v/checker/tests/go_wait_or.out b/vlib/v/checker/tests/go_wait_or.out index 146f4fbbf4..d00dbd2209 100644 --- a/vlib/v/checker/tests/go_wait_or.out +++ b/vlib/v/checker/tests/go_wait_or.out @@ -1,25 +1,25 @@ -vlib/v/checker/tests/go_wait_or.vv:11:17: error: unexpected `?`, the function `wait` does not return an optional +vlib/v/checker/tests/go_wait_or.vv:11:17: error: unexpected `?`, the function `wait` does neither return an optional nor a result 9 | go d(1) 10 | ] 11 | r := tg.wait() ? | ^ 12 | println(r) 13 | s := tg[0].wait() or { panic('problem') } -vlib/v/checker/tests/go_wait_or.vv:13:20: error: unexpected `or` block, the function `wait` does not return an optional +vlib/v/checker/tests/go_wait_or.vv:13:20: error: unexpected `or` block, the function `wait` does neither return an optional nor a result 11 | r := tg.wait() ? 12 | println(r) 13 | s := tg[0].wait() or { panic('problem') } | ~~~~~~~~~~~~~~~~~~~~~~~ 14 | println(s) 15 | tg2 := [ -vlib/v/checker/tests/go_wait_or.vv:19:13: error: unexpected `or` block, the function `wait` does not return an optional +vlib/v/checker/tests/go_wait_or.vv:19:13: error: unexpected `or` block, the function `wait` does neither return an optional nor a result 17 | go e(1) 18 | ] 19 | tg2.wait() or { panic('problem') } | ~~~~~~~~~~~~~~~~~~~~~~~ 20 | tg2[0].wait() ? 21 | tg3 := [ -vlib/v/checker/tests/go_wait_or.vv:20:16: error: unexpected `?`, the function `wait` does not return an optional +vlib/v/checker/tests/go_wait_or.vv:20:16: error: unexpected `?`, the function `wait` does neither return an optional nor a result 18 | ] 19 | tg2.wait() or { panic('problem') } 20 | tg2[0].wait() ? diff --git a/vlib/v/checker/tests/propagate_option_with_result_err.out b/vlib/v/checker/tests/propagate_option_with_result_err.out new file mode 100644 index 0000000000..edbb23304c --- /dev/null +++ b/vlib/v/checker/tests/propagate_option_with_result_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/propagate_option_with_result_err.vv:6:8: error: to propagate an option, the call must also return an optional type + 4 | + 5 | fn bar() ?string { + 6 | foo() ? + | ^ + 7 | return '' + 8 | } diff --git a/vlib/v/checker/tests/propagate_option_with_result_err.vv b/vlib/v/checker/tests/propagate_option_with_result_err.vv new file mode 100644 index 0000000000..0b57053fbb --- /dev/null +++ b/vlib/v/checker/tests/propagate_option_with_result_err.vv @@ -0,0 +1,10 @@ +fn foo() !string { + return '' +} + +fn bar() ?string { + foo() ? + return '' +} + +fn main() {} diff --git a/vlib/v/checker/tests/propagate_result_with_option.out b/vlib/v/checker/tests/propagate_result_with_option.out new file mode 100644 index 0000000000..d54af05115 --- /dev/null +++ b/vlib/v/checker/tests/propagate_result_with_option.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/propagate_result_with_option.vv:6:8: error: to propagate a result, the call must also return a result type + 4 | + 5 | fn bar() !string { + 6 | foo() ! + | ^ + 7 | return '' + 8 | } diff --git a/vlib/v/checker/tests/propagate_result_with_option.vv b/vlib/v/checker/tests/propagate_result_with_option.vv new file mode 100644 index 0000000000..8025c9f638 --- /dev/null +++ b/vlib/v/checker/tests/propagate_result_with_option.vv @@ -0,0 +1,10 @@ +fn foo() ?string { + return '' +} + +fn bar() !string { + foo() ! + return '' +} + +fn main() {} diff --git a/vlib/v/checker/tests/unexpected_or.out b/vlib/v/checker/tests/unexpected_or.out index ab1e077c69..03eeaea331 100644 --- a/vlib/v/checker/tests/unexpected_or.out +++ b/vlib/v/checker/tests/unexpected_or.out @@ -1,4 +1,4 @@ -vlib/v/checker/tests/unexpected_or.vv:6:17: error: unexpected `or` block, the function `ret_zero` does not return an optional +vlib/v/checker/tests/unexpected_or.vv:6:17: error: unexpected `or` block, the function `ret_zero` does neither return an optional nor a result 4 | 5 | fn main() { 6 | _ = ret_zero() or { 1 } diff --git a/vlib/v/checker/tests/unexpected_or_propagate.out b/vlib/v/checker/tests/unexpected_or_propagate.out index 411748d155..7470c57b5d 100644 --- a/vlib/v/checker/tests/unexpected_or_propagate.out +++ b/vlib/v/checker/tests/unexpected_or_propagate.out @@ -1,4 +1,4 @@ -vlib/v/checker/tests/unexpected_or_propagate.vv:6:17: error: unexpected `?`, the function `ret_zero` does not return an optional +vlib/v/checker/tests/unexpected_or_propagate.vv:6:17: error: unexpected `?`, the function `ret_zero` does neither return an optional nor a result 4 | 5 | fn opt_fn() ?int { 6 | a := ret_zero()? diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index fe35d39191..edc5f07bf3 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -1335,6 +1335,8 @@ pub fn (mut f Fmt) fn_type_decl(node ast.FnTypeDecl) { f.write(' $ret_str') } else if fn_info.return_type.has_flag(.optional) { f.write(' ?') + } else if fn_info.return_type.has_flag(.result) { + f.write(' !') } f.comments(node.comments, has_nl: false) @@ -2315,9 +2317,12 @@ pub fn (mut f Fmt) or_expr(node ast.OrExpr) { f.stmts(node.stmts) f.write('}') } - .propagate { + .propagate_option { f.write(' ?') } + .propagate_result { + f.write(' !') + } } } diff --git a/vlib/v/fmt/tests/result_return_type_keep.vv b/vlib/v/fmt/tests/result_return_type_keep.vv new file mode 100644 index 0000000000..4a88ca2289 --- /dev/null +++ b/vlib/v/fmt/tests/result_return_type_keep.vv @@ -0,0 +1,3 @@ +fn foo() !int { + return 1 +} diff --git a/vlib/v/gen/c/assert.v b/vlib/v/gen/c/assert.v index 0d236f5807..a87d1deb2a 100644 --- a/vlib/v/gen/c/assert.v +++ b/vlib/v/gen/c/assert.v @@ -159,7 +159,7 @@ fn (mut g Gen) gen_assert_single_expr(expr ast.Expr, typ ast.Type) { if expr is ast.CTempVar { if expr.orig is ast.CallExpr { should_clone = false - if expr.orig.or_block.kind == .propagate { + if expr.orig.or_block.kind == .propagate_option { should_clone = true } if expr.orig.is_method && expr.orig.args.len == 0 diff --git a/vlib/v/gen/c/auto_str_methods.v b/vlib/v/gen/c/auto_str_methods.v index dae5ae955b..62b551e496 100644 --- a/vlib/v/gen/c/auto_str_methods.v +++ b/vlib/v/gen/c/auto_str_methods.v @@ -169,7 +169,8 @@ fn (mut g Gen) final_gen_str(typ StrType) { } g.generated_str_fns << typ sym := g.table.sym(typ.typ) - if sym.has_method_with_generic_parent('str') && !typ.typ.has_flag(.optional) { + if sym.has_method_with_generic_parent('str') && !(typ.typ.has_flag(.optional) + || typ.typ.has_flag(.result)) { return } styp := typ.styp @@ -178,6 +179,10 @@ fn (mut g Gen) final_gen_str(typ StrType) { g.gen_str_for_option(typ.typ, styp, str_fn_name) return } + if typ.typ.has_flag(.result) { + g.gen_str_for_result(typ.typ, styp, str_fn_name) + return + } match sym.info { ast.Alias { if sym.info.is_import { @@ -258,6 +263,39 @@ fn (mut g Gen) gen_str_for_option(typ ast.Type, styp string, str_fn_name string) g.auto_str_funcs.writeln('}') } +fn (mut g Gen) gen_str_for_result(typ ast.Type, styp string, str_fn_name string) { + $if trace_autostr ? { + eprintln('> gen_str_for_result: $typ.debug() | $styp | $str_fn_name') + } + parent_type := typ.clear_flag(.result) + sym := g.table.sym(parent_type) + sym_has_str_method, _, _ := sym.str_method_info() + parent_str_fn_name := g.get_str_fn(parent_type) + + g.definitions.writeln('string ${str_fn_name}($styp it); // auto') + g.auto_str_funcs.writeln('string ${str_fn_name}($styp it) { return indent_${str_fn_name}(it, 0); }') + g.definitions.writeln('string indent_${str_fn_name}($styp it, int indent_count); // auto') + g.auto_str_funcs.writeln('string indent_${str_fn_name}($styp it, int indent_count) {') + g.auto_str_funcs.writeln('\tstring res;') + g.auto_str_funcs.writeln('\tif (!it.is_error) {') + if sym.kind == .string { + tmp_res := '${parent_str_fn_name}(*($sym.cname*)it.data)' + g.auto_str_funcs.writeln('\t\tres = ${str_intp_sq(tmp_res)};') + } else if should_use_indent_func(sym.kind) && !sym_has_str_method { + g.auto_str_funcs.writeln('\t\tres = indent_${parent_str_fn_name}(*($sym.cname*)it.data, indent_count);') + } else { + g.auto_str_funcs.writeln('\t\tres = ${parent_str_fn_name}(*($sym.cname*)it.data);') + } + g.auto_str_funcs.writeln('\t} else {') + + tmp_str := str_intp_sub('error: %%', 'IError_str(it.err)') + g.auto_str_funcs.writeln('\t\tres = $tmp_str;') + g.auto_str_funcs.writeln('\t}') + + g.auto_str_funcs.writeln('\treturn ${str_intp_sub('result(%%)', 'res')};') + g.auto_str_funcs.writeln('}') +} + fn (mut g Gen) gen_str_for_alias(info ast.Alias, styp string, str_fn_name string) { parent_str_fn_name := g.get_str_fn(info.parent_type) $if trace_autostr ? { @@ -506,6 +544,8 @@ fn (mut g Gen) fn_decl_str(info ast.FnType) string { fn_str += ')' if info.func.return_type == ast.ovoid_type { fn_str += ' ?' + } else if info.func.return_type == ast.rvoid_type { + fn_str += ' !' } else if info.func.return_type != ast.void_type { x := util.strip_main_name(g.table.get_type_name(g.unwrap_generic(info.func.return_type))) if info.func.return_type.has_flag(.optional) { diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index a4b20fe684..a03406716c 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -73,7 +73,8 @@ mut: embedded_data strings.Builder // data to embed in the executable/binary shared_types strings.Builder // shared/lock types shared_functions strings.Builder // shared constructors - options strings.Builder // `Option_xxxx` types + options strings.Builder // `option_xxxx` types + out_results strings.Builder // `result_xxxx` types json_forward_decls strings.Builder // json type forward decls sql_buf strings.Builder // for writing exprs to args via `sqlite3_bind_int()` etc file &ast.File @@ -100,6 +101,7 @@ mut: is_cc_msvc bool // g.pref.ccompiler == 'msvc' vlines_path string // set to the proper path for generating #line directives optionals map[string]string // to avoid duplicates + results map[string]string // to avoid duplicates done_optionals shared []string // to avoid duplicates chan_pop_optionals map[string]string // types for `x := <-ch or {...}` chan_push_optionals map[string]string // types for `ch <- x or {...}` @@ -246,6 +248,7 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { pcs_declarations: strings.new_builder(100) embedded_data: strings.new_builder(1000) options: strings.new_builder(100) + out_results: strings.new_builder(100) shared_types: strings.new_builder(100) shared_functions: strings.new_builder(100) json_forward_decls: strings.new_builder(100) @@ -319,6 +322,9 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { for k, v in g.optionals { global_g.optionals[k] = v } + for k, v in g.results { + global_g.results[k] = v + } for k, v in g.as_cast_type_names { global_g.as_cast_type_names[k] = v } @@ -373,6 +379,7 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { global_g.gen_jsons() global_g.write_optionals() + global_g.write_results() global_g.dump_expr_definitions() // this uses global_g.get_str_fn, so it has to go before the below for loop for i := 0; i < global_g.str_types.len; i++ { global_g.final_gen_str(global_g.str_types[i]) @@ -441,6 +448,8 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { b.write_string(g.shared_types.str()) b.writeln('\n// V Option_xxx definitions:') b.write_string(g.options.str()) + b.writeln('\n// V result_xxx definitions:') + b.write_string(g.out_results.str()) b.writeln('\n// V json forward decls:') b.write_string(g.json_forward_decls.str()) b.writeln('\n// V definitions:') @@ -535,6 +544,7 @@ fn cgen_process_one_file_cb(p &pool.PoolProcessor, idx int, wid int) &Gen { hotcode_definitions: strings.new_builder(100) embedded_data: strings.new_builder(1000) options: strings.new_builder(100) + out_results: strings.new_builder(100) shared_types: strings.new_builder(100) shared_functions: strings.new_builder(100) channel_definitions: strings.new_builder(100) @@ -595,6 +605,7 @@ pub fn (mut g Gen) free_builders() { g.shared_functions.free() g.channel_definitions.free() g.options.free() + g.out_results.free() g.json_forward_decls.free() g.enum_typedefs.free() g.sql_buf.free() @@ -884,6 +895,8 @@ fn (mut g Gen) typ(t ast.Type) string { if t.has_flag(.optional) { // Register an optional if it's not registered yet return g.register_optional(t) + } else if t.has_flag(.result) { + return g.register_result(t) } else { return g.base_type(t) } @@ -965,6 +978,15 @@ fn (mut g Gen) optional_type_name(t ast.Type) (string, string) { return styp, base } +fn (mut g Gen) result_type_name(t ast.Type) (string, string) { + base := g.base_type(t) + mut styp := 'result_$base' + if t.is_ptr() { + styp = styp.replace('*', '_ptr') + } + return styp, base +} + fn (g Gen) optional_type_text(styp string, base string) string { // replace void with something else size := if base == 'void' { @@ -982,12 +1004,35 @@ fn (g Gen) optional_type_text(styp string, base string) string { return ret } +fn (g Gen) result_type_text(styp string, base string) string { + // replace void with something else + size := if base == 'void' { + 'u8' + } else if base.starts_with('anon_fn') { + 'void*' + } else { + base + } + ret := 'struct $styp { + bool is_error; + IError err; + byte data[sizeof($size) > 0 ? sizeof($size) : 1]; +}' + return ret +} + fn (mut g Gen) register_optional(t ast.Type) string { styp, base := g.optional_type_name(t) g.optionals[base] = styp return styp } +fn (mut g Gen) register_result(t ast.Type) string { + styp, base := g.result_type_name(t) + g.results[base] = styp + return styp +} + fn (mut g Gen) write_optionals() { mut done := []string{} rlock g.done_optionals { @@ -1003,6 +1048,18 @@ fn (mut g Gen) write_optionals() { } } +fn (mut g Gen) write_results() { + mut done := []string{} + for base, styp in g.results { + if base in done { + continue + } + done << base + g.typedefs.writeln('typedef struct $styp $styp;') + g.out_results.write_string(g.result_type_text(styp, base) + ';\n\n') + } +} + fn (mut g Gen) find_or_register_shared(t ast.Type, base string) string { g.shareds[t.idx()] = base return '__shared__$base' @@ -1881,6 +1938,10 @@ fn (mut g Gen) stmt(node ast.Stmt) { // Register an optional if it's not registered yet g.register_optional(method.return_type) } + if method.return_type.has_flag(.result) { + // Register a result if it's not registered yet + g.register_result(method.return_type) + } } } } @@ -3893,6 +3954,14 @@ fn (g &Gen) expr_is_multi_return_call(expr ast.Expr) bool { return false } +fn (mut g Gen) gen_result_error(target_type ast.Type, expr ast.Expr) { + styp := g.typ(target_type) + g.write('($styp){ .is_error=true, .err=') + g.expr(expr) + g.write(', .data={EMPTY_STRUCT_INITIALIZATION} }') +} + +// NB: remove this when optional has no errors anymore fn (mut g Gen) gen_optional_error(target_type ast.Type, expr ast.Expr) { styp := g.typ(target_type) g.write('($styp){ .state=2, .err=') @@ -3921,10 +3990,11 @@ fn (mut g Gen) return_stmt(node ast.Return) { sym := g.table.sym(g.fn_decl.return_type) fn_return_is_multi := sym.kind == .multi_return fn_return_is_optional := g.fn_decl.return_type.has_flag(.optional) + fn_return_is_result := g.fn_decl.return_type.has_flag(.result) mut has_semicolon := false if node.exprs.len == 0 { g.write_defer_stmts_when_needed() - if fn_return_is_optional { + if fn_return_is_optional || fn_return_is_result { styp := g.typ(g.fn_decl.return_type) g.writeln('return ($styp){0};') } else { @@ -3969,6 +4039,34 @@ fn (mut g Gen) return_stmt(node ast.Return) { return } } + // handle promoting error/function returning 'result' + if fn_return_is_result { + ftyp := g.typ(node.types[0]) + mut is_regular_result := ftyp == 'result' + if is_regular_result || node.types[0] == ast.error_type_idx { + if !isnil(g.fn_decl) && g.fn_decl.is_test { + test_error_var := g.new_tmp_var() + g.write('$ret_typ $test_error_var = ') + g.gen_result_error(g.fn_decl.return_type, node.exprs[0]) + g.writeln(';') + g.write_defer_stmts_when_needed() + g.gen_failing_return_error_for_test_fn(node, test_error_var) + return + } + if use_tmp_var { + g.write('$ret_typ $tmpvar = ') + } else { + g.write('return ') + } + g.gen_result_error(g.fn_decl.return_type, node.exprs[0]) + g.writeln(';') + if use_tmp_var { + g.write_defer_stmts_when_needed() + g.writeln('return $tmpvar;') + } + return + } + } // regular cases if fn_return_is_multi && node.exprs.len > 0 && !g.expr_is_multi_return_call(node.exprs[0]) { if node.exprs.len == 1 && (node.exprs[0] is ast.IfExpr || node.exprs[0] is ast.MatchExpr) { @@ -3983,7 +4081,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { typ_sym := g.table.sym(g.fn_decl.return_type) mr_info := typ_sym.info as ast.MultiReturn mut styp := '' - if fn_return_is_optional { + if fn_return_is_optional || fn_return_is_result { g.writeln('$ret_typ $tmpvar;') styp = g.base_type(g.fn_decl.return_type) g.write('opt_ok(&($styp/*X*/[]) { ') @@ -4054,7 +4152,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { } } g.write('}') - if fn_return_is_optional { + if fn_return_is_optional || fn_return_is_result { g.writeln(' }, (Option*)(&$tmpvar), sizeof($styp));') g.write_defer_stmts_when_needed() g.write('return $tmpvar') @@ -4063,7 +4161,7 @@ fn (mut g Gen) return_stmt(node ast.Return) { if multi_unpack.len > 0 { g.insert_before_stmt(multi_unpack) } - if use_tmp_var && !fn_return_is_optional { + if use_tmp_var && !fn_return_is_optional && !fn_return_is_result { if !has_semicolon { g.writeln(';') } @@ -4109,6 +4207,35 @@ fn (mut g Gen) return_stmt(node ast.Return) { g.writeln('return $tmpvar;') return } + expr_type_is_result := match expr0 { + ast.CallExpr { + expr0.return_type.has_flag(.result) && expr0.or_block.kind == .absent + } + else { + node.types[0].has_flag(.result) + } + } + if fn_return_is_result && !expr_type_is_result && return_sym.name != 'result' { + styp := g.base_type(g.fn_decl.return_type) + g.writeln('$ret_typ $tmpvar;') + g.write('result_ok(&($styp[]) { ') + if !g.fn_decl.return_type.is_ptr() && node.types[0].is_ptr() { + if !(node.exprs[0] is ast.Ident && !g.is_amp) { + g.write('*') + } + } + for i, expr in node.exprs { + g.expr_with_cast(expr, node.types[i], g.fn_decl.return_type.clear_flag(.result)) + if i < node.exprs.len - 1 { + g.write(', ') + } + } + g.writeln(' }, (result*)(&$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;') + return + } // autofree before `return` // set free_parent_scopes to true, since all variables defined in parent // scopes need to be freed before the return @@ -4596,7 +4723,7 @@ fn (mut g Gen) write_init_function() { } const ( - builtins = ['string', 'array', 'DenseArray', 'map', 'Error', 'IError', 'Option'] + builtins = ['string', 'array', 'DenseArray', 'map', 'Error', 'IError', 'Option', 'result'] ) fn (mut g Gen) write_builtin_types() { @@ -4933,7 +5060,11 @@ fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type ast.Ty if return_type != 0 && g.table.sym(return_type).kind == .function { mr_styp = 'voidptr' } - g.writeln('if (${cvar_name}.state != 0) { /*or block*/ ') + if return_type.has_flag(.result) { + g.writeln('if (${cvar_name}.is_error) { /*or block*/ ') + } else { + g.writeln('if (${cvar_name}.state != 0) { /*or block*/ ') + } } if or_block.kind == .block { g.or_expr_return_type = return_type.clear_flag(.optional) @@ -4975,7 +5106,7 @@ fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type ast.Ty } } g.or_expr_return_type = ast.void_type - } else if or_block.kind == .propagate { + } else if or_block.kind == .propagate_option { if g.file.mod.name == 'main' && (isnil(g.fn_decl) || g.fn_decl.is_main) { // In main(), an `opt()?` call is sugar for `opt() or { panic(err) }` err_msg := 'IError_name_table[${cvar_name}.err._typ]._method_msg(${cvar_name}.err._object)' @@ -5004,6 +5135,35 @@ fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type ast.Ty g.writeln('\treturn $err_obj;') } } + } else if or_block.kind == .propagate_result { + if g.file.mod.name == 'main' && (isnil(g.fn_decl) || g.fn_decl.is_main) { + // In main(), an `opt()!` call is sugar for `opt() or { panic(err) }` + err_msg := 'IError_name_table[${cvar_name}.err._typ]._method_msg(${cvar_name}.err._object)' + if g.pref.is_debug { + paline, pafile, pamod, pafn := g.panic_debug_info(or_block.pos) + g.writeln('panic_debug($paline, tos3("$pafile"), tos3("$pamod"), tos3("$pafn"), $err_msg);') + } else { + g.writeln('\tpanic_result_not_set($err_msg);') + } + } else if !isnil(g.fn_decl) && g.fn_decl.is_test { + g.gen_failing_error_propagation_for_test_fn(or_block, cvar_name) + } else { + // In ordinary functions, `opt()!` call is sugar for: + // `opt() or { return err }` + // Since we *do* return, first we have to ensure that + // the defered statements are generated. + g.write_defer_stmts() + // Now that option types are distinct we need a cast here + if g.fn_decl.return_type == ast.void_type { + g.writeln('\treturn;') + } else { + styp := g.typ(g.fn_decl.return_type) + err_obj := g.new_tmp_var() + g.writeln('\t$styp $err_obj;') + g.writeln('\tmemcpy(&$err_obj, &$cvar_name, sizeof(result));') + g.writeln('\treturn $err_obj;') + } + } } g.writeln('}') g.set_current_pos_as_last_stmt_pos() diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 5656307564..ffb66eeaa8 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -671,9 +671,6 @@ fn (mut g Gen) call_expr(node ast.CallExpr) { tmp_opt := if gen_or || gen_keep_alive { g.new_tmp_var() } else { '' } if gen_or || gen_keep_alive { mut ret_typ := node.return_type - if gen_or { - ret_typ = ret_typ.set_flag(.optional) - } styp := g.typ(ret_typ) if gen_or && !is_gen_or_and_assign_rhs { cur_line = g.go_before_stmt(0) @@ -695,7 +692,7 @@ fn (mut g Gen) call_expr(node ast.CallExpr) { // if !g.is_autofree { g.or_block(tmp_opt, node.or_block, node.return_type) //} - unwrapped_typ := node.return_type.clear_flag(.optional) + unwrapped_typ := node.return_type.clear_flag(.optional).clear_flag(.result) unwrapped_styp := g.typ(unwrapped_typ) if unwrapped_typ == ast.void_type { g.write('\n $cur_line') diff --git a/vlib/v/gen/c/str_intp.v b/vlib/v/gen/c/str_intp.v index 663fe05bd1..9544a56ec4 100644 --- a/vlib/v/gen/c/str_intp.v +++ b/vlib/v/gen/c/str_intp.v @@ -17,13 +17,14 @@ fn (mut g Gen) str_format(node ast.StringInterLiteral, i int) (u64, string) { mut upper_case := false // set upercase for the result string mut typ := g.unwrap_generic(node.expr_types[i]) sym := g.table.sym(typ) + if sym.kind == .alias { typ = (sym.info as ast.Alias).parent_type } mut remove_tail_zeros := false fspec := node.fmts[i] mut fmt_type := StrIntpType{} - + g.write('/*$fspec $sym*/') // upper cases if (fspec - `A`) <= (`Z` - `A`) { upper_case = true diff --git a/vlib/v/gen/js/auto_str_methods.v b/vlib/v/gen/js/auto_str_methods.v index 2374936e8c..f90b24ed4a 100644 --- a/vlib/v/gen/js/auto_str_methods.v +++ b/vlib/v/gen/js/auto_str_methods.v @@ -413,6 +413,8 @@ fn (mut g JsGen) fn_decl_str(info ast.FnType) string { fn_str += ')' if info.func.return_type == ast.ovoid_type { fn_str += ' ?' + } else if info.func.return_type == ast.rvoid_type { + fn_str += ' !' } else if info.func.return_type != ast.void_type { x := util.strip_main_name(g.table.get_type_name(g.unwrap_generic(info.func.return_type))) if info.func.return_type.has_flag(.optional) { diff --git a/vlib/v/gen/js/fn.v b/vlib/v/gen/js/fn.v index e82724ebb7..0fde781856 100644 --- a/vlib/v/gen/js/fn.v +++ b/vlib/v/gen/js/fn.v @@ -116,7 +116,7 @@ fn (mut g JsGen) js_call(node ast.CallExpr) { // g.write('return ') g.stmt(it.or_block.stmts.last()) } - .propagate { + .propagate_option { panicstr := '`optional not set (\${err + ""})`' if g.file.mod.name == 'main' && g.fn_decl.name == 'main.main' { g.writeln('return builtin__panic($panicstr)') @@ -176,12 +176,12 @@ fn (mut g JsGen) js_method_call(node ast.CallExpr) { // g.write('return ') g.stmt(it.or_block.stmts.last()) } - .propagate { + .propagate_option { panicstr := '`optional not set (\${err + ""})`' if g.file.mod.name == 'main' && g.fn_decl.name == 'main.main' { g.writeln('return builtin__panic($panicstr)') } else { - g.writeln('throw new Option({ state: new u8(2), err: error(new string($panicstr)) });') + g.writeln('throw new option({ state: new u8(2), err: error(new string($panicstr)) });') } } else {} @@ -367,7 +367,7 @@ fn (mut g JsGen) method_call(node ast.CallExpr) { // g.write('return ') g.stmt(it.or_block.stmts.last()) } - .propagate { + .propagate_option { panicstr := '`optional not set (\${err.valueOf().msg})`' if g.file.mod.name == 'main' && g.fn_decl.name == 'main.main' { g.writeln('return builtin__panic($panicstr)') @@ -468,7 +468,7 @@ fn (mut g JsGen) gen_call_expr(it ast.CallExpr) { // g.write('return ') g.stmt(it.or_block.stmts.last()) } - .propagate { + .propagate_option { panicstr := '`optional not set (\${err.valueOf().msg})`' if g.file.mod.name == 'main' && g.fn_decl.name == 'main.main' { g.writeln('return builtin__panic($panicstr)') diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 364181c206..02e660cd86 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -1183,7 +1183,7 @@ fn (mut g JsGen) gen_assert_single_expr(expr ast.Expr, typ ast.Type) { if expr is ast.CTempVar { if expr.orig is ast.CallExpr { should_clone = false - if expr.orig.or_block.kind == .propagate { + if expr.orig.or_block.kind == .propagate_option { should_clone = true } if expr.orig.is_method && expr.orig.args.len == 0 diff --git a/vlib/v/parser/expr.v b/vlib/v/parser/expr.v index 41cac7f6d1..fc18f0cb5c 100644 --- a/vlib/v/parser/expr.v +++ b/vlib/v/parser/expr.v @@ -535,7 +535,7 @@ fn (mut p Parser) infix_expr(left ast.Expr) ast.Expr { } if p.tok.kind == .question { p.next() - or_kind = .propagate + or_kind = .propagate_option } p.or_is_handled = false } @@ -627,7 +627,7 @@ fn (mut p Parser) prefix_expr() ast.Expr { } if p.tok.kind == .question { p.next() - or_kind = .propagate + or_kind = .propagate_option } p.or_is_handled = false } diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 5b36633900..09b68a1bd5 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -45,10 +45,6 @@ pub fn (mut p Parser) call_expr(language ast.Language, mod string) ast.CallExpr args := p.call_args() last_pos := p.tok.pos() p.check(.rpar) - // ! in mutable methods - if p.tok.kind == .not { - p.next() - } 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() @@ -70,13 +66,14 @@ pub fn (mut p Parser) call_expr(language ast.Language, mod string) ast.CallExpr p.close_scope() p.inside_or_expr = was_inside_or_expr } - if p.tok.kind == .question { + if p.tok.kind in [.question, .not] { + is_not := p.tok.kind == .not // `foo()?` p.next() if p.inside_defer { p.error_with_pos('error propagation not allowed inside `defer` blocks', p.prev_tok.pos()) } - or_kind = .propagate + or_kind = if is_not { .propagate_result } else { .propagate_option } } if fn_name in p.imported_symbols { fn_name = p.imported_symbols[fn_name] diff --git a/vlib/v/parser/parse_type.v b/vlib/v/parser/parse_type.v index ce29321e1c..5076a67736 100644 --- a/vlib/v/parser/parse_type.v +++ b/vlib/v/parser/parse_type.v @@ -375,18 +375,24 @@ pub fn (mut p Parser) parse_sum_type_variants() []ast.TypeNode { pub fn (mut p Parser) parse_type() ast.Type { // optional mut is_optional := false + mut is_result := false + line_nr := p.tok.line_nr optional_pos := p.tok.pos() if p.tok.kind == .question { - line_nr := p.tok.line_nr p.next() is_optional = true - if p.tok.line_nr > line_nr { - mut typ := ast.void_type - if is_optional { - typ = typ.set_flag(.optional) - } - return typ + } else if p.tok.kind == .not { + p.next() + is_result = true + } + if (is_optional || is_result) && p.tok.line_nr > line_nr { + mut typ := ast.void_type + if is_optional { + typ = typ.set_flag(.optional) + } else if is_result { + typ = typ.set_flag(.result) } + return typ } is_shared := p.tok.kind == .key_shared is_atomic := p.tok.kind == .key_atomic @@ -441,6 +447,9 @@ pub fn (mut p Parser) parse_type() ast.Type { if is_optional { typ = typ.set_flag(.optional) } + if is_result { + typ = typ.set_flag(.result) + } if is_shared { typ = typ.set_flag(.shared_f) } diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index f0fade2121..d07b67d9ac 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -2478,7 +2478,7 @@ fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr { // `a[start..end] ?` if p.tok.kind == .question { or_pos_high = p.tok.pos() - or_kind_high = .propagate + or_kind_high = .propagate_option p.next() } } @@ -2552,7 +2552,7 @@ fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr { // `a[start..end] ?` if p.tok.kind == .question { or_pos_low = p.tok.pos() - or_kind_low = .propagate + or_kind_low = .propagate_option p.next() } } @@ -2609,7 +2609,7 @@ fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr { // `a[i] ?` if p.tok.kind == .question { or_pos = p.tok.pos() - or_kind = .propagate + or_kind = .propagate_option p.next() } } @@ -2711,15 +2711,15 @@ fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr { p.inside_or_expr = was_inside_or_expr } // `foo()?` - if p.tok.kind == .question { + if p.tok.kind in [.question, .not] { + is_not := p.tok.kind == .not p.next() if p.inside_defer { p.error_with_pos('error propagation not allowed inside `defer` blocks', p.prev_tok.pos()) } - or_kind = .propagate + or_kind = if is_not { .propagate_result } else { .propagate_option } } - // end_pos := p.prev_tok.pos() pos := name_pos.extend(end_pos) comments := p.eat_comments(same_line: true) diff --git a/vlib/v/parser/tests/option_result_err.out b/vlib/v/parser/tests/option_result_err.out new file mode 100644 index 0000000000..7d83b67384 --- /dev/null +++ b/vlib/v/parser/tests/option_result_err.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/option_result_err.vv:2:2: error: invalid expression: unexpected keyword `return` + 1 | fn abc() ?!string { + 2 | return '' + | ~~~~~~ + 3 | } diff --git a/vlib/v/parser/tests/option_result_err.vv b/vlib/v/parser/tests/option_result_err.vv new file mode 100644 index 0000000000..e36c0b19c5 --- /dev/null +++ b/vlib/v/parser/tests/option_result_err.vv @@ -0,0 +1,3 @@ +fn abc() ?!string { + return '' +} diff --git a/vlib/v/parser/tests/result_option_err.out b/vlib/v/parser/tests/result_option_err.out new file mode 100644 index 0000000000..4375d046a3 --- /dev/null +++ b/vlib/v/parser/tests/result_option_err.out @@ -0,0 +1,5 @@ +vlib/v/parser/tests/result_option_err.vv:2:2: error: invalid expression: unexpected keyword `return` + 1 | fn abc() ?!string { + 2 | return '' + | ~~~~~~ + 3 | } diff --git a/vlib/v/parser/tests/result_option_err.vv b/vlib/v/parser/tests/result_option_err.vv new file mode 100644 index 0000000000..e36c0b19c5 --- /dev/null +++ b/vlib/v/parser/tests/result_option_err.vv @@ -0,0 +1,3 @@ +fn abc() ?!string { + return '' +} diff --git a/vlib/v/scanner/scanner.v b/vlib/v/scanner/scanner.v index 2f764b1840..36733e8962 100644 --- a/vlib/v/scanner/scanner.v +++ b/vlib/v/scanner/scanner.v @@ -1023,7 +1023,7 @@ fn (mut s Scanner) text_scan() token.Token { s.pos += 2 return s.new_token(.not_is, '', 3) } else { - return s.new_token(.not, '', 1) + return s.new_token(.not, '!', 1) } } `~` { diff --git a/vlib/v/tests/results_test.v b/vlib/v/tests/results_test.v new file mode 100644 index 0000000000..ab4c047987 --- /dev/null +++ b/vlib/v/tests/results_test.v @@ -0,0 +1,65 @@ +fn foo() !int { + return 1 +} + +fn test_return_int() { + x := foo() or { 0 } + assert x == 1 +} + +fn foo_err() !int { + return error('throw') +} + +fn test_return_err() { + x := foo_err() or { 0 } + assert x == 0 +} + +fn test_return_err_var() { + foo_err() or { assert err.msg() == 'throw' } +} + +fn test_str() { + assert '$foo()' == 'result(1)' +} + +fn result_void(err bool) ! { + if err { + return error('throw') + } +} + +fn test_result_void() { + result_void(false) or { assert false } +} + +fn test_result_void_err() { + mut or_block := false + result_void(true) or { + assert err.msg() == 'throw' + or_block = true + } + assert or_block +} + +fn propagate() ! { + result_void(false) ! +} + +fn test_propagation() { + propagate() or { assert false } +} + +fn function_that_can_return_error() !int { + return error('abc') +} + +fn util_error_propagation() ! { + function_that_can_return_error() ! + assert false +} + +fn test_return_on_error_propagation() { + util_error_propagation() or { assert err.msg() == 'abc' } +} diff --git a/vlib/v/token/token.v b/vlib/v/token/token.v index 2325f0b9aa..dda124058e 100644 --- a/vlib/v/token/token.v +++ b/vlib/v/token/token.v @@ -479,7 +479,7 @@ pub fn (tok Kind) is_relational() bool { [inline] pub fn (k Kind) is_start_of_type() bool { - return k in [.name, .lpar, .amp, .lsbr, .question, .key_shared] + return k in [.name, .lpar, .amp, .lsbr, .question, .key_shared, .not] } [inline]