checker: fix error for 'or expr with nested match expr' (#13658)

pull/13661/head
yuyi 2022-03-05 19:06:08 +08:00 committed by GitHub
parent 0e5ae7126f
commit 8136157f87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 99 additions and 35 deletions

View File

@ -1441,34 +1441,50 @@ 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)
}
fn (mut c Checker) check_or_last_stmt(stmt ast.Stmt, ret_type ast.Type, expr_return_type ast.Type) {
if ret_type != ast.void_type { if ret_type != ast.void_type {
match last_stmt { match stmt {
ast.ExprStmt { ast.ExprStmt {
c.expected_type = ret_type c.expected_type = ret_type
c.expected_or_type = ret_type.clear_flag(.optional) c.expected_or_type = ret_type.clear_flag(.optional)
last_stmt_typ := c.expr(last_stmt.expr) last_stmt_typ := c.expr(stmt.expr)
c.expected_or_type = ast.void_type c.expected_or_type = ast.void_type
type_fits := c.check_types(last_stmt_typ, ret_type) type_fits := c.check_types(last_stmt_typ, ret_type)
&& last_stmt_typ.nr_muls() == ret_type.nr_muls() && last_stmt_typ.nr_muls() == ret_type.nr_muls()
is_noreturn := is_noreturn_callexpr(last_stmt.expr) is_noreturn := is_noreturn_callexpr(stmt.expr)
if type_fits || is_noreturn { if type_fits || is_noreturn {
return return
} }
expected_type_name := c.table.type_to_str(ret_type.clear_flag(.optional)) expected_type_name := c.table.type_to_str(ret_type.clear_flag(.optional))
if last_stmt.typ == ast.void_type { if stmt.typ == ast.void_type {
c.error('`or` block must provide a default value of type `$expected_type_name`, or return/continue/break or call a [noreturn] function like panic(err) or exit(1)', if stmt.expr is ast.IfExpr {
last_stmt.pos) for branch in stmt.expr.branches {
} else { last_stmt := branch.stmts[branch.stmts.len - 1]
type_name := c.table.type_to_str(last_stmt_typ) c.check_or_last_stmt(last_stmt, ret_type, expr_return_type)
c.error('wrong return type `$type_name` in the `or {}` block, expected `$expected_type_name`', }
last_stmt.pos) return
} else if stmt.expr is ast.MatchExpr {
for branch in stmt.expr.branches {
last_stmt := branch.stmts[branch.stmts.len - 1]
c.check_or_last_stmt(last_stmt, ret_type, expr_return_type)
} }
return return
} }
c.error('`or` block must provide a default value of type `$expected_type_name`, or return/continue/break or call a [noreturn] function like panic(err) or exit(1)',
stmt.expr.pos())
} else {
type_name := c.table.type_to_str(last_stmt_typ)
c.error('wrong return type `$type_name` in the `or {}` block, expected `$expected_type_name`',
stmt.expr.pos())
}
}
ast.BranchStmt { ast.BranchStmt {
if last_stmt.kind !in [.key_continue, .key_break] { if stmt.kind !in [.key_continue, .key_break] {
c.error('only break/continue is allowed as a branch statement in the end of an `or {}` block', c.error('only break/continue is allowed as a branch statement in the end of an `or {}` block',
last_stmt.pos) stmt.pos)
return return
} }
} }
@ -1476,27 +1492,42 @@ pub fn (mut c Checker) check_or_expr(node ast.OrExpr, ret_type ast.Type, expr_re
else { else {
expected_type_name := c.table.type_to_str(ret_type.clear_flag(.optional)) expected_type_name := c.table.type_to_str(ret_type.clear_flag(.optional))
c.error('last statement in the `or {}` block should be an expression of type `$expected_type_name` or exit parent scope', c.error('last statement in the `or {}` block should be an expression of type `$expected_type_name` or exit parent scope',
node.pos) stmt.pos)
return
} }
} }
} else { } else {
match last_stmt { match stmt {
ast.ExprStmt { ast.ExprStmt {
if last_stmt.typ == ast.void_type { match stmt.expr {
ast.IfExpr {
for branch in stmt.expr.branches {
last_stmt := branch.stmts[branch.stmts.len - 1]
c.check_or_last_stmt(last_stmt, ret_type, expr_return_type)
}
}
ast.MatchExpr {
for branch in stmt.expr.branches {
last_stmt := branch.stmts[branch.stmts.len - 1]
c.check_or_last_stmt(last_stmt, ret_type, expr_return_type)
}
}
else {
if stmt.typ == ast.void_type {
return return
} }
if is_noreturn_callexpr(last_stmt.expr) { if is_noreturn_callexpr(stmt.expr) {
return return
} }
if c.check_types(last_stmt.typ, expr_return_type) { if c.check_types(stmt.typ, expr_return_type) {
return return
} }
// opt_returning_string() or { ... 123 } // opt_returning_string() or { ... 123 }
type_name := c.table.type_to_str(last_stmt.typ) type_name := c.table.type_to_str(stmt.typ)
expr_return_type_name := c.table.type_to_str(expr_return_type) expr_return_type_name := c.table.type_to_str(expr_return_type)
c.error('the default expression type in the `or` block should be `$expr_return_type_name`, instead you gave a value of type `$type_name`', c.error('the default expression type in the `or` block should be `$expr_return_type_name`, instead you gave a value of type `$type_name`',
last_stmt.expr.pos()) stmt.expr.pos())
}
}
} }
else {} else {}
} }

View File

@ -1,10 +1,10 @@
vlib/v/checker/tests/or_err.vv:4:10: error: last statement in the `or {}` block should be an expression of type `&int` or exit parent scope vlib/v/checker/tests/or_err.vv:5:2: error: last statement in the `or {}` block should be an expression of type `&int` or exit parent scope
2 | return none
3 | } 3 | }
4 | a := f() or { 4 | a := f() or {
| ~~~~
5 | {} 5 | {}
| ^
6 | } 6 | }
7 | _ = f() or {
vlib/v/checker/tests/or_err.vv:11:2: error: wrong return type `rune` in the `or {}` block, expected `&int` vlib/v/checker/tests/or_err.vv:11:2: error: wrong return type `rune` in the `or {}` block, expected `&int`
9 | } 9 | }
10 | _ = f() or { 10 | _ = f() or {

View File

@ -5,10 +5,10 @@ vlib/v/parser/tests/or_default_missing.vv:4:3: error: `or` block must provide a
| ~~~~~~~~~~~~~~~~ | ~~~~~~~~~~~~~~~~
5 | } 5 | }
6 | println(el) 6 | println(el)
vlib/v/parser/tests/or_default_missing.vv:16:16: error: last statement in the `or {}` block should be an expression of type `int` or exit parent scope vlib/v/parser/tests/or_default_missing.vv:17:11: error: last statement in the `or {}` block should be an expression of type `int` or exit parent scope
14 | }
15 | mut testvar := 0 15 | mut testvar := 0
16 | el := m['pp'] or { 16 | el := m['pp'] or {
| ~~~~
17 | testvar = 12 17 | testvar = 12
| ^
18 | } 18 | }
19 | println('$el $testvar')

View File

@ -19,10 +19,10 @@ vlib/v/parser/tests/prefix_first.vv:13:6: error: `if` expression requires an exp
| ~~~~~~~ | ~~~~~~~
14 | v = 1 14 | v = 1
15 | -1 15 | -1
vlib/v/parser/tests/prefix_first.vv:25:12: error: last statement in the `or {}` block should be an expression of type `&int` or exit parent scope vlib/v/parser/tests/prefix_first.vv:26:5: error: last statement in the `or {}` block should be an expression of type `&int` or exit parent scope
23 | // later this should compile correctly
24 | v := 3 24 | v := 3
25 | _ = opt() or { 25 | _ = opt() or {
| ~~~~
26 | _ = 1 26 | _ = 1
| ^
27 | &v 27 | &v
28 | }

View File

@ -0,0 +1,33 @@
struct API_error {
pub mut:
errors []string
}
fn delete_secret_v1() API_error {
response := req_do() or {
match err.msg {
'dial_tcp failed' {
return API_error{
errors: ['Vault server not started']
}
}
else {
return API_error{
errors: [err.msg]
}
}
}
}
println(response)
}
fn req_do() ?string {
return error('dial_tcp failed')
}
fn test_or_expr_with_nested_match_expr() {
err := delete_secret_v1()
println(err)
assert err.errors.len == 1
assert err.errors[0] == 'Vault server not started'
}