1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

all: basic implementation of result type (#14140)

This commit is contained in:
Daniel Däschle 2022-04-30 00:59:14 +02:00 committed by GitHub
parent db185e6580
commit 08fd0ce0de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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 {
return 'none'
}

View File

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

View File

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

View File

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

View File

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

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() {
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
}
}

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

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.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',

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 {
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)
}
}

View File

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

View File

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

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)
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() ?

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 |
5 | fn main() {
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 |
5 | fn opt_fn() ?int {
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')
} 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(' !')
}
}
}

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.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

View File

@ -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) {

View File

@ -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()

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 { '' }
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')

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

View File

@ -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) {

View File

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

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.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

View File

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

View File

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

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 {
// 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)
}

View File

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

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
return s.new_token(.not_is, '', 3)
} 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]
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]