compiler: propagate optional
parent
5037d9de37
commit
801bca1ef2
|
@ -720,11 +720,17 @@ pub mut:
|
|||
expr_type table.Type
|
||||
}
|
||||
|
||||
pub enum OrKind {
|
||||
absent
|
||||
block
|
||||
propagate
|
||||
}
|
||||
|
||||
// `or { ... }`
|
||||
pub struct OrExpr {
|
||||
pub:
|
||||
stmts []Stmt
|
||||
is_used bool // if the or{} block is written down or left out
|
||||
kind OrKind
|
||||
}
|
||||
|
||||
pub struct Assoc {
|
||||
|
|
|
@ -24,7 +24,7 @@ pub mut:
|
|||
warnings []errors.Warning
|
||||
error_lines []int // to avoid printing multiple errors for the same line
|
||||
expected_type table.Type
|
||||
fn_return_type table.Type // current function's return type
|
||||
cur_fn &ast.FnDecl // current function
|
||||
const_decl string
|
||||
const_deps []string
|
||||
const_names []string
|
||||
|
@ -44,6 +44,7 @@ pub fn new_checker(table &table.Table, pref &pref.Preferences) Checker {
|
|||
return Checker{
|
||||
table: table
|
||||
pref: pref
|
||||
cur_fn: 0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1014,24 +1015,34 @@ fn (mut c Checker) type_implements(typ, inter_typ table.Type, pos token.Position
|
|||
}
|
||||
}
|
||||
|
||||
pub fn (mut c Checker) check_expr_opt_call(expr ast.Expr, xtype table.Type, is_return_used bool) {
|
||||
pub fn (mut c Checker) check_expr_opt_call(expr ast.Expr, ret_type table.Type, is_return_used bool) {
|
||||
if expr is ast.CallExpr {
|
||||
call_expr := expr as ast.CallExpr
|
||||
if call_expr.return_type.flag_is(.optional) {
|
||||
c.check_or_block(call_expr, xtype, is_return_used)
|
||||
} else if call_expr.or_block.is_used {
|
||||
c.check_or_block(call_expr, ret_type, is_return_used)
|
||||
} else if call_expr.or_block.kind == .block {
|
||||
c.error('unexpected `or` block, the function `$call_expr.name` does not return an optional',
|
||||
call_expr.pos)
|
||||
} else if call_expr.or_block.kind == .propagate {
|
||||
c.error('unexpected `?`, the function `$call_expr.name`, does not return an optional',
|
||||
call_expr.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut c Checker) check_or_block(mut call_expr ast.CallExpr, ret_type table.Type, is_ret_used bool) {
|
||||
if !call_expr.or_block.is_used {
|
||||
if call_expr.or_block.kind == .absent {
|
||||
c.error('${call_expr.name}() returns an option, but you missed to add an `or {}` block to it',
|
||||
call_expr.pos)
|
||||
return
|
||||
}
|
||||
if call_expr.or_block.kind == .propagate {
|
||||
if !c.cur_fn.return_type.flag_is(.optional) && c.cur_fn.name != 'main' {
|
||||
c.error('to propagate the optional call, `${c.cur_fn.name}` must itself return an optional',
|
||||
call_expr.pos)
|
||||
}
|
||||
return
|
||||
}
|
||||
stmts_len := call_expr.or_block.stmts.len
|
||||
if stmts_len == 0 {
|
||||
if is_ret_used {
|
||||
|
@ -1044,7 +1055,7 @@ pub fn (mut c Checker) check_or_block(mut call_expr ast.CallExpr, ret_type table
|
|||
}
|
||||
last_stmt := call_expr.or_block.stmts[stmts_len - 1]
|
||||
if is_ret_used {
|
||||
if !c.is_last_or_block_stmt_valid(last_stmt) {
|
||||
if !(last_stmt is ast.Return || last_stmt is ast.BranchStmt || last_stmt is ast.ExprStmt) {
|
||||
expected_type_name := c.table.get_type_symbol(ret_type).name
|
||||
c.error('last statement in the `or {}` block should return `$expected_type_name`',
|
||||
call_expr.pos)
|
||||
|
@ -1084,16 +1095,6 @@ fn is_expr_panic_or_exit(expr ast.Expr) bool {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: merge to check_or_block when v can handle it
|
||||
pub fn (mut c Checker) is_last_or_block_stmt_valid(stmt ast.Stmt) bool {
|
||||
return match stmt {
|
||||
ast.Return { true }
|
||||
ast.BranchStmt { true }
|
||||
ast.ExprStmt { true }
|
||||
else { false }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut c Checker) selector_expr(mut selector_expr ast.SelectorExpr) table.Type {
|
||||
typ := c.expr(selector_expr.expr)
|
||||
if typ == table.void_type_idx {
|
||||
|
@ -1126,19 +1127,19 @@ pub fn (mut c Checker) selector_expr(mut selector_expr ast.SelectorExpr) table.T
|
|||
|
||||
// TODO: non deferred
|
||||
pub fn (mut c Checker) return_stmt(mut return_stmt ast.Return) {
|
||||
c.expected_type = c.fn_return_type
|
||||
if return_stmt.exprs.len > 0 && c.fn_return_type == table.void_type {
|
||||
c.expected_type = c.cur_fn.return_type
|
||||
if return_stmt.exprs.len > 0 && c.expected_type == table.void_type {
|
||||
c.error('too many arguments to return, current function does not return anything',
|
||||
return_stmt.pos)
|
||||
return
|
||||
} else if return_stmt.exprs.len == 0 && c.fn_return_type != table.void_type {
|
||||
} else if return_stmt.exprs.len == 0 && c.expected_type != table.void_type {
|
||||
c.error('too few arguments to return', return_stmt.pos)
|
||||
return
|
||||
}
|
||||
if return_stmt.exprs.len == 0 {
|
||||
return
|
||||
}
|
||||
expected_type := c.fn_return_type
|
||||
expected_type := c.expected_type
|
||||
expected_type_sym := c.table.get_type_symbol(expected_type)
|
||||
exp_is_optional := expected_type.flag_is(.optional)
|
||||
mut expected_types := [expected_type]
|
||||
|
@ -1646,10 +1647,10 @@ fn (mut c Checker) stmts(stmts []ast.Stmt) {
|
|||
pub fn (mut c Checker) expr(node ast.Expr) table.Type {
|
||||
match mut node {
|
||||
ast.AnonFn {
|
||||
keep_ret_type := c.fn_return_type
|
||||
c.fn_return_type = it.decl.return_type
|
||||
keep_fn := c.cur_fn
|
||||
c.cur_fn = &it.decl
|
||||
c.stmts(it.decl.stmts)
|
||||
c.fn_return_type = keep_ret_type
|
||||
c.cur_fn = keep_fn
|
||||
return it.typ
|
||||
}
|
||||
ast.ArrayInit {
|
||||
|
@ -2361,7 +2362,7 @@ fn (mut c Checker) fn_decl(it ast.FnDecl) {
|
|||
}
|
||||
}
|
||||
c.expected_type = table.void_type
|
||||
c.fn_return_type = it.return_type
|
||||
c.cur_fn = &it
|
||||
c.stmts(it.stmts)
|
||||
if it.language == .v && !it.no_body && it.return_type != table.void_type && !c.returns &&
|
||||
it.name !in ['panic', 'exit'] {
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
vlib/v/checker/tests/unexpected_or.v:6:7: error: unexpected `or` block, the function `ret_zero` does not return an optional
|
||||
4 |
|
||||
5 | fn main() {
|
||||
6 | _ := ret_zero() or { 1 }
|
||||
| ~~~~~~~~~~
|
||||
7 | }
|
|
@ -0,0 +1,7 @@
|
|||
fn ret_zero() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ := ret_zero() or { 1 }
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
vlib/v/checker/tests/unexpected_or_propagate.v:6:7: error: unexpected `?`, the function `ret_zero`, does not return an optional
|
||||
4 |
|
||||
5 | fn opt_fn() ?int {
|
||||
6 | a := ret_zero()?
|
||||
| ~~~~~~~~~~
|
||||
7 | return a
|
||||
8 | }
|
|
@ -0,0 +1,12 @@
|
|||
fn ret_zero() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
fn opt_fn() ?int {
|
||||
a := ret_zero()?
|
||||
return a
|
||||
}
|
||||
|
||||
fn main() {
|
||||
opt_fn() or {}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
vlib/v/checker/tests/wrong_propagate_ret_type.v:6:7: error: to propagate the optional call, `opt_call` must itself return an optional
|
||||
4 |
|
||||
5 | fn opt_call() int {
|
||||
6 | a := ret_none()?
|
||||
| ~~~~~~~~~~
|
||||
7 | return a
|
||||
8 | }
|
|
@ -0,0 +1,8 @@
|
|||
fn ret_none() ?int {
|
||||
return none
|
||||
}
|
||||
|
||||
fn opt_call() int {
|
||||
a := ret_none()?
|
||||
return a
|
||||
}
|
|
@ -725,11 +725,17 @@ pub fn (mut f Fmt) call_args(args []ast.CallArg) {
|
|||
}
|
||||
|
||||
pub fn (mut f Fmt) or_expr(or_block ast.OrExpr) {
|
||||
if or_block.is_used {
|
||||
match or_block.kind {
|
||||
.absent {}
|
||||
.block {
|
||||
f.writeln(' or {')
|
||||
f.stmts(or_block.stmts)
|
||||
f.write('}')
|
||||
}
|
||||
.propagate {
|
||||
f.write('?')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut f Fmt) comment(node ast.Comment) {
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
fn opt_propagate() ?int {
|
||||
eventual_wrong_int()?
|
||||
}
|
|
@ -95,6 +95,7 @@ mut:
|
|||
is_builtin_mod bool
|
||||
hotcode_fn_names []string
|
||||
fn_main &ast.FnDecl // the FnDecl of the main function. Needed in order to generate the main function code *last*
|
||||
cur_fn &ast.FnDecl
|
||||
cur_generic_type table.Type // `int`, `string`, etc in `foo<T>()`
|
||||
}
|
||||
|
||||
|
@ -124,6 +125,7 @@ pub fn cgen(files []ast.File, table &table.Table, pref &pref.Preferences) string
|
|||
pref: pref
|
||||
fn_decl: 0
|
||||
fn_main: 0
|
||||
cur_fn: 0
|
||||
autofree: true
|
||||
indent: -1
|
||||
module_built: pref.path.after('vlib/')
|
||||
|
@ -895,7 +897,6 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
|
|||
1 {
|
||||
// multi return
|
||||
// TODO Handle in if_expr
|
||||
mut or_stmts := []ast.Stmt{}
|
||||
is_optional := return_type.flag_is(.optional)
|
||||
mr_var_name := 'mr_$assign_stmt.pos.pos'
|
||||
mr_styp := g.typ(return_type)
|
||||
|
@ -905,9 +906,8 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
|
|||
g.is_assign_rhs = false
|
||||
if is_optional && assign_stmt.right[0] is ast.CallExpr {
|
||||
val := assign_stmt.right[0] as ast.CallExpr
|
||||
or_stmts = val.or_block.stmts
|
||||
return_type = val.return_type
|
||||
g.or_block(mr_var_name, or_stmts, return_type)
|
||||
g.or_block(mr_var_name, val.or_block, return_type)
|
||||
}
|
||||
g.writeln(';')
|
||||
for i, ident in assign_stmt.left {
|
||||
|
@ -936,12 +936,10 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
|
|||
ident_var_info := ident.var_info()
|
||||
styp := g.typ(ident_var_info.typ)
|
||||
mut is_call := false
|
||||
mut or_stmts := []ast.Stmt{}
|
||||
blank_assign := ident.kind == .blank_ident
|
||||
match val {
|
||||
ast.CallExpr {
|
||||
is_call = true
|
||||
or_stmts = it.or_block.stmts
|
||||
return_type = it.return_type
|
||||
}
|
||||
// TODO: no buffer fiddling
|
||||
|
@ -1454,12 +1452,12 @@ fn (mut g Gen) enum_expr(node ast.Expr) {
|
|||
fn (mut g Gen) assign_expr(node ast.AssignExpr) {
|
||||
// g.write('/*assign_expr*/')
|
||||
mut is_call := false
|
||||
mut or_stmts := []ast.Stmt{}
|
||||
mut or_block := ast.OrExpr{}
|
||||
mut return_type := table.void_type
|
||||
match node.val {
|
||||
ast.CallExpr {
|
||||
is_call = true
|
||||
or_stmts = it.or_block.stmts
|
||||
or_block = it.or_block
|
||||
return_type = it.return_type
|
||||
}
|
||||
else {}
|
||||
|
@ -1538,7 +1536,7 @@ fn (mut g Gen) assign_expr(node ast.AssignExpr) {
|
|||
}
|
||||
if gen_or {
|
||||
// g.write('/*777 $tmp_opt*/')
|
||||
g.or_block(tmp_opt, or_stmts, return_type)
|
||||
g.or_block(tmp_opt, or_block, return_type)
|
||||
unwrapped_type_str := g.typ(return_type.set_flag(.unset))
|
||||
ident := node.left as ast.Ident
|
||||
if ident.kind != .blank_ident && ident.info is ast.IdentVar {
|
||||
|
@ -2936,15 +2934,17 @@ fn (mut g Gen) insert_before_stmt(s string) {
|
|||
// to access its fields (`.ok`, `.error` etc)
|
||||
// `os.cp(...)` => `Option bool tmp = os__cp(...); if (!tmp.ok) { ... }`
|
||||
// Returns the type of the last stmt
|
||||
fn (mut g Gen) or_block(var_name string, stmts []ast.Stmt, return_type table.Type) {
|
||||
fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type table.Type) {
|
||||
cvar_name := c_name(var_name)
|
||||
mr_styp := g.base_type(return_type)
|
||||
g.writeln(';') // or')
|
||||
g.writeln('if (!${cvar_name}.ok) {')
|
||||
if or_block.kind == .block {
|
||||
g.writeln('\tstring err = ${cvar_name}.v_error;')
|
||||
g.writeln('\tint errcode = ${cvar_name}.ecode;')
|
||||
if stmts.len > 0 && stmts[stmts.len - 1] is ast.ExprStmt && (stmts[stmts.len - 1] as ast.ExprStmt).typ !=
|
||||
table.void_type {
|
||||
stmts := or_block.stmts
|
||||
if stmts.len > 0 && stmts[or_block.stmts.len - 1] is ast.ExprStmt && (stmts[stmts.len -
|
||||
1] as ast.ExprStmt).typ != table.void_type {
|
||||
g.indent++
|
||||
for i, stmt in stmts {
|
||||
if i == stmts.len - 1 {
|
||||
|
@ -2971,6 +2971,13 @@ fn (mut g Gen) or_block(var_name string, stmts []ast.Stmt, return_type table.Typ
|
|||
} else {
|
||||
g.stmts(stmts)
|
||||
}
|
||||
} else if or_block.kind == .propagate {
|
||||
if g.file.mod.name == 'main' && g.cur_fn.name == 'main' {
|
||||
g.writeln('\tv_panic(${cvar_name}.v_error);')
|
||||
} else {
|
||||
g.writeln('\treturn $cvar_name;')
|
||||
}
|
||||
}
|
||||
g.write('}')
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,11 @@ fn (mut g Gen) gen_fn_decl(it ast.FnDecl) {
|
|||
// || it.no_body {
|
||||
return
|
||||
}
|
||||
former_cur_fn := g.cur_fn
|
||||
g.cur_fn = &it
|
||||
defer {
|
||||
g.cur_fn = former_cur_fn
|
||||
}
|
||||
is_main := it.name == 'main'
|
||||
if it.is_generic && g.cur_generic_type == 0 { // need the cur_generic_type check to avoid inf. recursion
|
||||
// loop thru each generic type and generate a function
|
||||
|
@ -284,7 +289,7 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
|
|||
if node.should_be_skipped {
|
||||
return
|
||||
}
|
||||
gen_or := node.or_block.stmts.len > 0
|
||||
gen_or := node.or_block.kind != .absent
|
||||
cur_line := if gen_or && g.is_assign_rhs {
|
||||
line := g.go_before_stmt(0)
|
||||
g.out.write(tabs[g.indent])
|
||||
|
@ -303,7 +308,7 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
|
|||
g.fn_call(node)
|
||||
}
|
||||
if gen_or {
|
||||
g.or_block(tmp_opt, node.or_block.stmts, node.return_type)
|
||||
g.or_block(tmp_opt, node.or_block, node.return_type)
|
||||
g.write('\n${cur_line}${tmp_opt}')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,11 +19,11 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp
|
|||
} else {
|
||||
p.check_name()
|
||||
}
|
||||
mut is_or_block_used := false
|
||||
mut or_kind := ast.OrKind.absent
|
||||
if fn_name == 'json.decode' {
|
||||
p.expecting_type = true // Makes name_expr() parse the type `User` in `json.decode(User, txt)`
|
||||
p.expr_mod = ''
|
||||
is_or_block_used = true
|
||||
or_kind = .block
|
||||
}
|
||||
mut generic_type := table.void_type
|
||||
if p.tok.kind == .lt {
|
||||
|
@ -42,9 +42,9 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp
|
|||
pos: first_pos.pos
|
||||
len: last_pos.pos - first_pos.pos + last_pos.len
|
||||
}
|
||||
// `foo() or {}``
|
||||
mut or_stmts := []ast.Stmt{}
|
||||
if p.tok.kind == .key_orelse {
|
||||
// `foo() or {}``
|
||||
was_inside_or_expr := p.inside_or_expr
|
||||
p.inside_or_expr = true
|
||||
p.next()
|
||||
|
@ -61,7 +61,7 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp
|
|||
pos: p.tok.position()
|
||||
is_used: true
|
||||
})
|
||||
is_or_block_used = true
|
||||
or_kind = .block
|
||||
or_stmts = p.parse_block_no_scope()
|
||||
p.close_scope()
|
||||
p.inside_or_expr = was_inside_or_expr
|
||||
|
@ -69,10 +69,7 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp
|
|||
if p.tok.kind == .question {
|
||||
// `foo()?`
|
||||
p.next()
|
||||
is_or_block_used = true
|
||||
// mut s := ast.Stmt{}
|
||||
// s = ast.ReturnStmt{}
|
||||
or_stmts << ast.Return{}
|
||||
or_kind = .propagate
|
||||
}
|
||||
node := ast.CallExpr{
|
||||
name: fn_name
|
||||
|
@ -82,7 +79,7 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp
|
|||
language: language
|
||||
or_block: ast.OrExpr{
|
||||
stmts: or_stmts
|
||||
is_used: is_or_block_used
|
||||
kind: or_kind
|
||||
}
|
||||
generic_type: generic_type
|
||||
}
|
||||
|
|
|
@ -986,7 +986,7 @@ fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr {
|
|||
}
|
||||
p.check(.rpar)
|
||||
mut or_stmts := []ast.Stmt{}
|
||||
mut is_or_block_used := false
|
||||
mut or_kind := ast.OrKind.absent
|
||||
if p.tok.kind == .key_orelse {
|
||||
p.next()
|
||||
p.open_scope()
|
||||
|
@ -1002,10 +1002,15 @@ fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr {
|
|||
pos: p.tok.position()
|
||||
is_used: true
|
||||
})
|
||||
is_or_block_used = true
|
||||
or_kind = .block
|
||||
or_stmts = p.parse_block_no_scope()
|
||||
p.close_scope()
|
||||
}
|
||||
if p.tok.kind == .question {
|
||||
// `foo()?`
|
||||
p.next()
|
||||
or_kind = .propagate
|
||||
}
|
||||
end_pos := p.tok.position()
|
||||
pos := token.Position{
|
||||
line_nr: name_pos.line_nr
|
||||
|
@ -1020,7 +1025,7 @@ fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr {
|
|||
is_method: true
|
||||
or_block: ast.OrExpr{
|
||||
stmts: or_stmts
|
||||
is_used: is_or_block_used
|
||||
kind: or_kind
|
||||
}
|
||||
}
|
||||
if is_filter {
|
||||
|
|
|
@ -93,6 +93,33 @@ fn foo_str() ?string {
|
|||
return 'something'
|
||||
}
|
||||
|
||||
fn propagate_optional(b bool) ?int {
|
||||
a := err_call(b)?
|
||||
return a
|
||||
}
|
||||
|
||||
fn propagate_different_type(b bool) ?bool {
|
||||
err_call(b)?
|
||||
return true
|
||||
}
|
||||
|
||||
fn test_propagation() {
|
||||
a := propagate_optional(true) or {
|
||||
0
|
||||
}
|
||||
assert a == 42
|
||||
if _ := propagate_optional(false) {
|
||||
assert false
|
||||
}
|
||||
b := propagate_different_type(true) or {
|
||||
false
|
||||
}
|
||||
assert b == true
|
||||
if _ := propagate_different_type(false) {
|
||||
assert false
|
||||
}
|
||||
}
|
||||
|
||||
fn test_q() {
|
||||
// assert foo_ok()? == true
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue