all: basic implementation of result type (#14140)

Daniel Däschle 2022-04-30 00:59:14 +02:00 committed by Jef Roosens
parent 3e4834aebf
commit 85cb0a8c85
Signed by: Jef Roosens
GPG Key ID: B75D4F293C7052DB
39 changed files with 478 additions and 79 deletions

View File

@ -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 { pub fn (_ none) str() string {
return 'none' return 'none'
} }

View File

@ -1487,7 +1487,8 @@ pub mut:
pub enum OrKind { pub enum OrKind {
absent absent
block block
propagate propagate_option
propagate_result
} }
// `or { ... }` // `or { ... }`

View File

@ -210,7 +210,8 @@ pub fn (lit &StringInterLiteral) get_fspec_braces(i int) (string, bool) {
} }
CallExpr { CallExpr {
if sub_expr.args.len != 0 || sub_expr.concrete_types.len != 0 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 needs_braces = true
} else if sub_expr.left is CallExpr { } else if sub_expr.left is CallExpr {
sub_expr = sub_expr.left sub_expr = sub_expr.left
@ -302,7 +303,7 @@ pub fn (x Expr) str() string {
} }
CallExpr { CallExpr {
sargs := args2str(x.args) 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 { if x.is_method {
return '${x.left.str()}.${x.name}($sargs)$propagate_suffix' return '${x.left.str()}.${x.name}($sargs)$propagate_suffix'
} }

View File

@ -232,7 +232,7 @@ pub fn (t &Table) fn_type_signature(f &Fn) string {
return sig 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 { pub fn (t &Table) fn_type_source_signature(f &Fn) string {
mut sig := '(' mut sig := '('
for i, arg in f.params { for i, arg in f.params {
@ -252,10 +252,14 @@ pub fn (t &Table) fn_type_source_signature(f &Fn) string {
sig += ')' sig += ')'
if f.return_type == ovoid_type { if f.return_type == ovoid_type {
sig += ' ?' sig += ' ?'
} else if f.return_type == rvoid_type {
sig += ' !'
} else if f.return_type != void_type { } else if f.return_type != void_type {
return_type_sym := t.sym(f.return_type) return_type_sym := t.sym(f.return_type)
if f.return_type.has_flag(.optional) { if f.return_type.has_flag(.optional) {
sig += ' ?$return_type_sym.name' sig += ' ?$return_type_sym.name'
} else if f.return_type.has_flag(.result) {
sig += ' !$return_type_sym.name'
} else { } else {
sig += ' $return_type_sym.name' sig += ' $return_type_sym.name'
} }

View File

@ -98,6 +98,7 @@ pub mut:
// max of 8 // max of 8
pub enum TypeFlag { pub enum TypeFlag {
optional optional
result
variadic variadic
generic generic
shared_f shared_f
@ -472,6 +473,7 @@ pub const (
pub const ( pub const (
void_type = new_type(void_type_idx) void_type = new_type(void_type_idx)
ovoid_type = new_type(void_type_idx).set_flag(.optional) // the return type of `fn () ?` 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) voidptr_type = new_type(voidptr_type_idx)
byteptr_type = new_type(byteptr_type_idx) byteptr_type = new_type(byteptr_type_idx)
charptr_type = new_type(charptr_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) { if typ.has_flag(.optional) {
return '?' return '?'
} }
if typ.has_flag(.result) {
return '!'
}
return 'void' return 'void'
} }
.thread { .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) { if typ.has_flag(.optional) {
res = '?$res' res = '?$res'
} }
if typ.has_flag(.result) {
res = '!$res'
}
return res return res
} }

View File

@ -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() { if expected == ast.charptr_type && got == ast.char_type.ref() {
return true return true
} }
if expected.has_flag(.optional) { if expected.has_flag(.optional) || expected.has_flag(.result) {
sym := c.table.sym(got) 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 // IErorr
return true 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 return false
} }
} }

View File

@ -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 // 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 { 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 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 expr.or_block.kind == .absent {
if c.inside_defer { 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) expr.pos)
} else { } 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) expr.pos)
} }
} else { } 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) return ret_type.clear_flag(.optional)
} else if expr.or_block.kind == .block { } 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) 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', c.error('unexpected `?`, the function `$expr.name` does not return an optional',
expr.or_block.pos) expr.or_block.pos)
} }
} else if expr is ast.IndexExpr { } else if expr is ast.IndexExpr {
if expr.or_expr.kind != .absent { 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 return ret_type
} }
pub fn (mut c Checker) check_or_expr(node ast.OrExpr, ret_type ast.Type, expr_return_type ast.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' if !c.table.cur_fn.return_type.has_flag(.optional) && c.table.cur_fn.name != 'main.main'
&& !c.inside_const { && !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) node.pos)
} }
return return
@ -1535,7 +1557,7 @@ pub fn (mut c Checker) check_or_expr(node ast.OrExpr, ret_type ast.Type, expr_re
return return
} }
last_stmt := node.stmts[stmts_len - 1] 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) { 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 { ast.CallExpr {
mut ret_type := c.call_expr(mut node) 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 { 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) node.or_block.pos)
} else if node.or_block.kind == .propagate { } else if node.or_block.kind == .propagate_option {
c.error('unexpected `?`, the function `$node.name` does not return an optional', c.error('unexpected `?`, the function `$node.name` does neither return an optional nor a result',
node.or_block.pos) node.or_block.pos)
} }
} }
if ret_type.has_flag(.optional) && node.or_block.kind != .absent { if node.or_block.kind != .absent {
ret_type = ret_type.clear_flag(.optional) 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 return ret_type
} }

View File

@ -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.fn_scope = node.scope
c.stmts(node.stmts) c.stmts(node.stmts)
node_has_top_return := has_top_return(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.expected_or_type = node.return_type.clear_flag(.optional)
c.stmts_ending_with_expression(node.or_block.stmts) c.stmts_ending_with_expression(node.or_block.stmts)
c.expected_or_type = ast.void_type 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 { && !c.inside_const {
if !c.table.cur_fn.is_main { if !c.table.cur_fn.is_main {
c.error('to propagate the optional call, `$c.table.cur_fn.name` must return an optional', c.error('to propagate the optional call, `$c.table.cur_fn.name` must return an optional',

View File

@ -158,7 +158,7 @@ pub fn (mut c Checker) return_stmt(mut node ast.Return) {
if exp_is_optional && node.exprs.len > 0 { if exp_is_optional && node.exprs.len > 0 {
expr0 := node.exprs[0] expr0 := node.exprs[0]
if expr0 is ast.CallExpr { 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) c.error('`?` is not needed, use `return ${expr0.name}()`', expr0.pos)
} }
} }

View File

@ -7,7 +7,7 @@ import v.ast
import v.token import v.token
pub fn (mut c Checker) get_default_fmt(ftyp ast.Type, typ ast.Type) u8 { 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` return `s`
} else if typ.is_float() { } else if typ.is_float() {
return `g` return `g`

View File

@ -1,4 +1,4 @@
vlib/v/checker/tests/fn_return_or_err.vv:6:17: error: unexpected `or` block, the function `pop` does not return an optional 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 | 4 |
5 | pub fn next(mut v []Typ) Typ { 5 | pub fn next(mut v []Typ) Typ {
6 | return v.pop() or { Typ{} } 6 | return v.pop() or { Typ{} }

View File

@ -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) 9 | go d(1)
10 | ] 10 | ]
11 | r := tg.wait() ? 11 | r := tg.wait() ?
| ^ | ^
12 | println(r) 12 | println(r)
13 | s := tg[0].wait() or { panic('problem') } 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() ? 11 | r := tg.wait() ?
12 | println(r) 12 | println(r)
13 | s := tg[0].wait() or { panic('problem') } 13 | s := tg[0].wait() or { panic('problem') }
| ~~~~~~~~~~~~~~~~~~~~~~~ | ~~~~~~~~~~~~~~~~~~~~~~~
14 | println(s) 14 | println(s)
15 | tg2 := [ 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) 17 | go e(1)
18 | ] 18 | ]
19 | tg2.wait() or { panic('problem') } 19 | tg2.wait() or { panic('problem') }
| ~~~~~~~~~~~~~~~~~~~~~~~ | ~~~~~~~~~~~~~~~~~~~~~~~
20 | tg2[0].wait() ? 20 | tg2[0].wait() ?
21 | tg3 := [ 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 | ] 18 | ]
19 | tg2.wait() or { panic('problem') } 19 | tg2.wait() or { panic('problem') }
20 | tg2[0].wait() ? 20 | tg2[0].wait() ?

View File

@ -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 | }

View File

@ -0,0 +1,10 @@
fn foo() !string {
return ''
}
fn bar() ?string {
foo() ?
return ''
}
fn main() {}

View File

@ -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 | }

View File

@ -0,0 +1,10 @@
fn foo() ?string {
return ''
}
fn bar() !string {
foo() !
return ''
}
fn main() {}

View File

@ -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 | 4 |
5 | fn main() { 5 | fn main() {
6 | _ = ret_zero() or { 1 } 6 | _ = ret_zero() or { 1 }

View File

@ -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 | 4 |
5 | fn opt_fn() ?int { 5 | fn opt_fn() ?int {
6 | a := ret_zero()? 6 | a := ret_zero()?

View File

@ -1335,6 +1335,8 @@ pub fn (mut f Fmt) fn_type_decl(node ast.FnTypeDecl) {
f.write(' $ret_str') f.write(' $ret_str')
} else if fn_info.return_type.has_flag(.optional) { } else if fn_info.return_type.has_flag(.optional) {
f.write(' ?') f.write(' ?')
} else if fn_info.return_type.has_flag(.result) {
f.write(' !')
} }
f.comments(node.comments, has_nl: false) 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.stmts(node.stmts)
f.write('}') f.write('}')
} }
.propagate { .propagate_option {
f.write(' ?') f.write(' ?')
} }
.propagate_result {
f.write(' !')
}
} }
} }

View File

@ -0,0 +1,3 @@
fn foo() !int {
return 1
}

View File

@ -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 is ast.CTempVar {
if expr.orig is ast.CallExpr { if expr.orig is ast.CallExpr {
should_clone = false should_clone = false
if expr.orig.or_block.kind == .propagate { if expr.orig.or_block.kind == .propagate_option {
should_clone = true should_clone = true
} }
if expr.orig.is_method && expr.orig.args.len == 0 if expr.orig.is_method && expr.orig.args.len == 0

View File

@ -169,7 +169,8 @@ fn (mut g Gen) final_gen_str(typ StrType) {
} }
g.generated_str_fns << typ g.generated_str_fns << typ
sym := g.table.sym(typ.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 return
} }
styp := typ.styp 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) g.gen_str_for_option(typ.typ, styp, str_fn_name)
return return
} }
if typ.typ.has_flag(.result) {
g.gen_str_for_result(typ.typ, styp, str_fn_name)
return
}
match sym.info { match sym.info {
ast.Alias { ast.Alias {
if sym.info.is_import { 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('}') 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) { 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) parent_str_fn_name := g.get_str_fn(info.parent_type)
$if trace_autostr ? { $if trace_autostr ? {
@ -506,6 +544,8 @@ fn (mut g Gen) fn_decl_str(info ast.FnType) string {
fn_str += ')' fn_str += ')'
if info.func.return_type == ast.ovoid_type { if info.func.return_type == ast.ovoid_type {
fn_str += ' ?' fn_str += ' ?'
} else if info.func.return_type == ast.rvoid_type {
fn_str += ' !'
} else if info.func.return_type != ast.void_type { } 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))) 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) { if info.func.return_type.has_flag(.optional) {

View File

@ -73,7 +73,8 @@ mut:
embedded_data strings.Builder // data to embed in the executable/binary embedded_data strings.Builder // data to embed in the executable/binary
shared_types strings.Builder // shared/lock types shared_types strings.Builder // shared/lock types
shared_functions strings.Builder // shared constructors 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 json_forward_decls strings.Builder // json type forward decls
sql_buf strings.Builder // for writing exprs to args via `sqlite3_bind_int()` etc sql_buf strings.Builder // for writing exprs to args via `sqlite3_bind_int()` etc
file &ast.File file &ast.File
@ -100,6 +101,7 @@ mut:
is_cc_msvc bool // g.pref.ccompiler == 'msvc' is_cc_msvc bool // g.pref.ccompiler == 'msvc'
vlines_path string // set to the proper path for generating #line directives vlines_path string // set to the proper path for generating #line directives
optionals map[string]string // to avoid duplicates optionals map[string]string // to avoid duplicates
results map[string]string // to avoid duplicates
done_optionals shared []string // to avoid duplicates done_optionals shared []string // to avoid duplicates
chan_pop_optionals map[string]string // types for `x := <-ch or {...}` chan_pop_optionals map[string]string // types for `x := <-ch or {...}`
chan_push_optionals map[string]string // types for `ch <- x 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) pcs_declarations: strings.new_builder(100)
embedded_data: strings.new_builder(1000) embedded_data: strings.new_builder(1000)
options: strings.new_builder(100) options: strings.new_builder(100)
out_results: strings.new_builder(100)
shared_types: strings.new_builder(100) shared_types: strings.new_builder(100)
shared_functions: strings.new_builder(100) shared_functions: strings.new_builder(100)
json_forward_decls: 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 { for k, v in g.optionals {
global_g.optionals[k] = v 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 { for k, v in g.as_cast_type_names {
global_g.as_cast_type_names[k] = v 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.gen_jsons()
global_g.write_optionals() 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 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++ { for i := 0; i < global_g.str_types.len; i++ {
global_g.final_gen_str(global_g.str_types[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.write_string(g.shared_types.str())
b.writeln('\n// V Option_xxx definitions:') b.writeln('\n// V Option_xxx definitions:')
b.write_string(g.options.str()) 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.writeln('\n// V json forward decls:')
b.write_string(g.json_forward_decls.str()) b.write_string(g.json_forward_decls.str())
b.writeln('\n// V definitions:') 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) hotcode_definitions: strings.new_builder(100)
embedded_data: strings.new_builder(1000) embedded_data: strings.new_builder(1000)
options: strings.new_builder(100) options: strings.new_builder(100)
out_results: strings.new_builder(100)
shared_types: strings.new_builder(100) shared_types: strings.new_builder(100)
shared_functions: strings.new_builder(100) shared_functions: strings.new_builder(100)
channel_definitions: 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.shared_functions.free()
g.channel_definitions.free() g.channel_definitions.free()
g.options.free() g.options.free()
g.out_results.free()
g.json_forward_decls.free() g.json_forward_decls.free()
g.enum_typedefs.free() g.enum_typedefs.free()
g.sql_buf.free() g.sql_buf.free()
@ -884,6 +895,8 @@ fn (mut g Gen) typ(t ast.Type) string {
if t.has_flag(.optional) { if t.has_flag(.optional) {
// Register an optional if it's not registered yet // Register an optional if it's not registered yet
return g.register_optional(t) return g.register_optional(t)
} else if t.has_flag(.result) {
return g.register_result(t)
} else { } else {
return g.base_type(t) return g.base_type(t)
} }
@ -965,6 +978,15 @@ fn (mut g Gen) optional_type_name(t ast.Type) (string, string) {
return styp, base 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 { fn (g Gen) optional_type_text(styp string, base string) string {
// replace void with something else // replace void with something else
size := if base == 'void' { size := if base == 'void' {
@ -982,12 +1004,35 @@ fn (g Gen) optional_type_text(styp string, base string) string {
return ret 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 { fn (mut g Gen) register_optional(t ast.Type) string {
styp, base := g.optional_type_name(t) styp, base := g.optional_type_name(t)
g.optionals[base] = styp g.optionals[base] = styp
return 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() { fn (mut g Gen) write_optionals() {
mut done := []string{} mut done := []string{}
rlock g.done_optionals { 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 { fn (mut g Gen) find_or_register_shared(t ast.Type, base string) string {
g.shareds[t.idx()] = base g.shareds[t.idx()] = base
return '__shared__$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 // Register an optional if it's not registered yet
g.register_optional(method.return_type) 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 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) { fn (mut g Gen) gen_optional_error(target_type ast.Type, expr ast.Expr) {
styp := g.typ(target_type) styp := g.typ(target_type)
g.write('($styp){ .state=2, .err=') 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) sym := g.table.sym(g.fn_decl.return_type)
fn_return_is_multi := sym.kind == .multi_return fn_return_is_multi := sym.kind == .multi_return
fn_return_is_optional := g.fn_decl.return_type.has_flag(.optional) 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 mut has_semicolon := false
if node.exprs.len == 0 { if node.exprs.len == 0 {
g.write_defer_stmts_when_needed() 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) styp := g.typ(g.fn_decl.return_type)
g.writeln('return ($styp){0};') g.writeln('return ($styp){0};')
} else { } else {
@ -3969,6 +4039,34 @@ fn (mut g Gen) return_stmt(node ast.Return) {
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 // regular cases
if fn_return_is_multi && node.exprs.len > 0 && !g.expr_is_multi_return_call(node.exprs[0]) { 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) { 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) typ_sym := g.table.sym(g.fn_decl.return_type)
mr_info := typ_sym.info as ast.MultiReturn mr_info := typ_sym.info as ast.MultiReturn
mut styp := '' mut styp := ''
if fn_return_is_optional { if fn_return_is_optional || fn_return_is_result {
g.writeln('$ret_typ $tmpvar;') g.writeln('$ret_typ $tmpvar;')
styp = g.base_type(g.fn_decl.return_type) styp = g.base_type(g.fn_decl.return_type)
g.write('opt_ok(&($styp/*X*/[]) { ') g.write('opt_ok(&($styp/*X*/[]) { ')
@ -4054,7 +4152,7 @@ fn (mut g Gen) return_stmt(node ast.Return) {
} }
} }
g.write('}') g.write('}')
if fn_return_is_optional { if fn_return_is_optional || fn_return_is_result {
g.writeln(' }, (Option*)(&$tmpvar), sizeof($styp));') g.writeln(' }, (Option*)(&$tmpvar), sizeof($styp));')
g.write_defer_stmts_when_needed() g.write_defer_stmts_when_needed()
g.write('return $tmpvar') g.write('return $tmpvar')
@ -4063,7 +4161,7 @@ fn (mut g Gen) return_stmt(node ast.Return) {
if multi_unpack.len > 0 { if multi_unpack.len > 0 {
g.insert_before_stmt(multi_unpack) 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 { if !has_semicolon {
g.writeln(';') g.writeln(';')
} }
@ -4109,6 +4207,35 @@ fn (mut g Gen) return_stmt(node ast.Return) {
g.writeln('return $tmpvar;') g.writeln('return $tmpvar;')
return 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` // autofree before `return`
// set free_parent_scopes to true, since all variables defined in parent // set free_parent_scopes to true, since all variables defined in parent
// scopes need to be freed before the return // scopes need to be freed before the return
@ -4596,7 +4723,7 @@ fn (mut g Gen) write_init_function() {
} }
const ( 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() { 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 { if return_type != 0 && g.table.sym(return_type).kind == .function {
mr_styp = 'voidptr' 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 { if or_block.kind == .block {
g.or_expr_return_type = return_type.clear_flag(.optional) 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 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) { 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) }` // 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)' 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;') 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.writeln('}')
g.set_current_pos_as_last_stmt_pos() g.set_current_pos_as_last_stmt_pos()

View File

@ -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 { '' } tmp_opt := if gen_or || gen_keep_alive { g.new_tmp_var() } else { '' }
if gen_or || gen_keep_alive { if gen_or || gen_keep_alive {
mut ret_typ := node.return_type mut ret_typ := node.return_type
if gen_or {
ret_typ = ret_typ.set_flag(.optional)
}
styp := g.typ(ret_typ) styp := g.typ(ret_typ)
if gen_or && !is_gen_or_and_assign_rhs { if gen_or && !is_gen_or_and_assign_rhs {
cur_line = g.go_before_stmt(0) cur_line = g.go_before_stmt(0)
@ -695,7 +692,7 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
// if !g.is_autofree { // if !g.is_autofree {
g.or_block(tmp_opt, node.or_block, node.return_type) 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) unwrapped_styp := g.typ(unwrapped_typ)
if unwrapped_typ == ast.void_type { if unwrapped_typ == ast.void_type {
g.write('\n $cur_line') g.write('\n $cur_line')

View File

@ -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 upper_case := false // set upercase for the result string
mut typ := g.unwrap_generic(node.expr_types[i]) mut typ := g.unwrap_generic(node.expr_types[i])
sym := g.table.sym(typ) sym := g.table.sym(typ)
if sym.kind == .alias { if sym.kind == .alias {
typ = (sym.info as ast.Alias).parent_type typ = (sym.info as ast.Alias).parent_type
} }
mut remove_tail_zeros := false mut remove_tail_zeros := false
fspec := node.fmts[i] fspec := node.fmts[i]
mut fmt_type := StrIntpType{} mut fmt_type := StrIntpType{}
g.write('/*$fspec $sym*/')
// upper cases // upper cases
if (fspec - `A`) <= (`Z` - `A`) { if (fspec - `A`) <= (`Z` - `A`) {
upper_case = true upper_case = true

View File

@ -413,6 +413,8 @@ fn (mut g JsGen) fn_decl_str(info ast.FnType) string {
fn_str += ')' fn_str += ')'
if info.func.return_type == ast.ovoid_type { if info.func.return_type == ast.ovoid_type {
fn_str += ' ?' fn_str += ' ?'
} else if info.func.return_type == ast.rvoid_type {
fn_str += ' !'
} else if info.func.return_type != ast.void_type { } 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))) 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) { if info.func.return_type.has_flag(.optional) {

View File

@ -116,7 +116,7 @@ fn (mut g JsGen) js_call(node ast.CallExpr) {
// g.write('return ') // g.write('return ')
g.stmt(it.or_block.stmts.last()) g.stmt(it.or_block.stmts.last())
} }
.propagate { .propagate_option {
panicstr := '`optional not set (\${err + ""})`' panicstr := '`optional not set (\${err + ""})`'
if g.file.mod.name == 'main' && g.fn_decl.name == 'main.main' { if g.file.mod.name == 'main' && g.fn_decl.name == 'main.main' {
g.writeln('return builtin__panic($panicstr)') g.writeln('return builtin__panic($panicstr)')
@ -176,12 +176,12 @@ fn (mut g JsGen) js_method_call(node ast.CallExpr) {
// g.write('return ') // g.write('return ')
g.stmt(it.or_block.stmts.last()) g.stmt(it.or_block.stmts.last())
} }
.propagate { .propagate_option {
panicstr := '`optional not set (\${err + ""})`' panicstr := '`optional not set (\${err + ""})`'
if g.file.mod.name == 'main' && g.fn_decl.name == 'main.main' { if g.file.mod.name == 'main' && g.fn_decl.name == 'main.main' {
g.writeln('return builtin__panic($panicstr)') g.writeln('return builtin__panic($panicstr)')
} else { } 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 {} else {}
@ -367,7 +367,7 @@ fn (mut g JsGen) method_call(node ast.CallExpr) {
// g.write('return ') // g.write('return ')
g.stmt(it.or_block.stmts.last()) g.stmt(it.or_block.stmts.last())
} }
.propagate { .propagate_option {
panicstr := '`optional not set (\${err.valueOf().msg})`' panicstr := '`optional not set (\${err.valueOf().msg})`'
if g.file.mod.name == 'main' && g.fn_decl.name == 'main.main' { if g.file.mod.name == 'main' && g.fn_decl.name == 'main.main' {
g.writeln('return builtin__panic($panicstr)') g.writeln('return builtin__panic($panicstr)')
@ -468,7 +468,7 @@ fn (mut g JsGen) gen_call_expr(it ast.CallExpr) {
// g.write('return ') // g.write('return ')
g.stmt(it.or_block.stmts.last()) g.stmt(it.or_block.stmts.last())
} }
.propagate { .propagate_option {
panicstr := '`optional not set (\${err.valueOf().msg})`' panicstr := '`optional not set (\${err.valueOf().msg})`'
if g.file.mod.name == 'main' && g.fn_decl.name == 'main.main' { if g.file.mod.name == 'main' && g.fn_decl.name == 'main.main' {
g.writeln('return builtin__panic($panicstr)') g.writeln('return builtin__panic($panicstr)')

View File

@ -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 is ast.CTempVar {
if expr.orig is ast.CallExpr { if expr.orig is ast.CallExpr {
should_clone = false should_clone = false
if expr.orig.or_block.kind == .propagate { if expr.orig.or_block.kind == .propagate_option {
should_clone = true should_clone = true
} }
if expr.orig.is_method && expr.orig.args.len == 0 if expr.orig.is_method && expr.orig.args.len == 0

View File

@ -535,7 +535,7 @@ fn (mut p Parser) infix_expr(left ast.Expr) ast.Expr {
} }
if p.tok.kind == .question { if p.tok.kind == .question {
p.next() p.next()
or_kind = .propagate or_kind = .propagate_option
} }
p.or_is_handled = false p.or_is_handled = false
} }
@ -627,7 +627,7 @@ fn (mut p Parser) prefix_expr() ast.Expr {
} }
if p.tok.kind == .question { if p.tok.kind == .question {
p.next() p.next()
or_kind = .propagate or_kind = .propagate_option
} }
p.or_is_handled = false p.or_is_handled = false
} }

View File

@ -45,10 +45,6 @@ pub fn (mut p Parser) call_expr(language ast.Language, mod string) ast.CallExpr
args := p.call_args() args := p.call_args()
last_pos := p.tok.pos() last_pos := p.tok.pos()
p.check(.rpar) p.check(.rpar)
// ! in mutable methods
if p.tok.kind == .not {
p.next()
}
mut pos := first_pos.extend(last_pos) mut pos := first_pos.extend(last_pos)
mut or_stmts := []ast.Stmt{} // TODO remove unnecessary allocations by just using .absent mut or_stmts := []ast.Stmt{} // TODO remove unnecessary allocations by just using .absent
mut or_pos := p.tok.pos() 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.close_scope()
p.inside_or_expr = was_inside_or_expr 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()?` // `foo()?`
p.next() p.next()
if p.inside_defer { if p.inside_defer {
p.error_with_pos('error propagation not allowed inside `defer` blocks', p.prev_tok.pos()) 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 { if fn_name in p.imported_symbols {
fn_name = p.imported_symbols[fn_name] fn_name = p.imported_symbols[fn_name]

View File

@ -375,18 +375,24 @@ pub fn (mut p Parser) parse_sum_type_variants() []ast.TypeNode {
pub fn (mut p Parser) parse_type() ast.Type { pub fn (mut p Parser) parse_type() ast.Type {
// optional // optional
mut is_optional := false mut is_optional := false
mut is_result := false
line_nr := p.tok.line_nr
optional_pos := p.tok.pos() optional_pos := p.tok.pos()
if p.tok.kind == .question { if p.tok.kind == .question {
line_nr := p.tok.line_nr
p.next() p.next()
is_optional = true is_optional = true
if p.tok.line_nr > line_nr { } else if p.tok.kind == .not {
mut typ := ast.void_type p.next()
if is_optional { is_result = true
typ = typ.set_flag(.optional) }
} if (is_optional || is_result) && p.tok.line_nr > line_nr {
return typ 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_shared := p.tok.kind == .key_shared
is_atomic := p.tok.kind == .key_atomic is_atomic := p.tok.kind == .key_atomic
@ -441,6 +447,9 @@ pub fn (mut p Parser) parse_type() ast.Type {
if is_optional { if is_optional {
typ = typ.set_flag(.optional) typ = typ.set_flag(.optional)
} }
if is_result {
typ = typ.set_flag(.result)
}
if is_shared { if is_shared {
typ = typ.set_flag(.shared_f) typ = typ.set_flag(.shared_f)
} }

View File

@ -2478,7 +2478,7 @@ fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr {
// `a[start..end] ?` // `a[start..end] ?`
if p.tok.kind == .question { if p.tok.kind == .question {
or_pos_high = p.tok.pos() or_pos_high = p.tok.pos()
or_kind_high = .propagate or_kind_high = .propagate_option
p.next() p.next()
} }
} }
@ -2552,7 +2552,7 @@ fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr {
// `a[start..end] ?` // `a[start..end] ?`
if p.tok.kind == .question { if p.tok.kind == .question {
or_pos_low = p.tok.pos() or_pos_low = p.tok.pos()
or_kind_low = .propagate or_kind_low = .propagate_option
p.next() p.next()
} }
} }
@ -2609,7 +2609,7 @@ fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr {
// `a[i] ?` // `a[i] ?`
if p.tok.kind == .question { if p.tok.kind == .question {
or_pos = p.tok.pos() or_pos = p.tok.pos()
or_kind = .propagate or_kind = .propagate_option
p.next() 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 p.inside_or_expr = was_inside_or_expr
} }
// `foo()?` // `foo()?`
if p.tok.kind == .question { if p.tok.kind in [.question, .not] {
is_not := p.tok.kind == .not
p.next() p.next()
if p.inside_defer { if p.inside_defer {
p.error_with_pos('error propagation not allowed inside `defer` blocks', p.error_with_pos('error propagation not allowed inside `defer` blocks',
p.prev_tok.pos()) p.prev_tok.pos())
} }
or_kind = .propagate or_kind = if is_not { .propagate_result } else { .propagate_option }
} }
//
end_pos := p.prev_tok.pos() end_pos := p.prev_tok.pos()
pos := name_pos.extend(end_pos) pos := name_pos.extend(end_pos)
comments := p.eat_comments(same_line: true) comments := p.eat_comments(same_line: true)

View File

@ -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 | }

View File

@ -0,0 +1,3 @@
fn abc() ?!string {
return ''
}

View File

@ -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 | }

View File

@ -0,0 +1,3 @@
fn abc() ?!string {
return ''
}

View File

@ -1023,7 +1023,7 @@ fn (mut s Scanner) text_scan() token.Token {
s.pos += 2 s.pos += 2
return s.new_token(.not_is, '', 3) return s.new_token(.not_is, '', 3)
} else { } else {
return s.new_token(.not, '', 1) return s.new_token(.not, '!', 1)
} }
} }
`~` { `~` {

View File

@ -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' }
}

View File

@ -479,7 +479,7 @@ pub fn (tok Kind) is_relational() bool {
[inline] [inline]
pub fn (k Kind) is_start_of_type() bool { 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] [inline]