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
}
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 {
match last_stmt {
match stmt {
ast.ExprStmt {
c.expected_type = ret_type
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
type_fits := c.check_types(last_stmt_typ, ret_type)
&& 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 {
return
}
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 {
if stmt.expr is 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)
}
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
}
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)',
last_stmt.pos)
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`',
last_stmt.pos)
stmt.expr.pos())
}
return
}
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',
last_stmt.pos)
stmt.pos)
return
}
}
@ -1476,27 +1492,42 @@ pub fn (mut c Checker) check_or_expr(node ast.OrExpr, ret_type ast.Type, expr_re
else {
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',
node.pos)
return
stmt.pos)
}
}
} else {
match last_stmt {
match stmt {
ast.ExprStmt {
if last_stmt.typ == ast.void_type {
return
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
}
if is_noreturn_callexpr(stmt.expr) {
return
}
if c.check_types(stmt.typ, expr_return_type) {
return
}
// opt_returning_string() or { ... 123 }
type_name := c.table.type_to_str(stmt.typ)
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`',
stmt.expr.pos())
}
}
if is_noreturn_callexpr(last_stmt.expr) {
return
}
if c.check_types(last_stmt.typ, expr_return_type) {
return
}
// opt_returning_string() or { ... 123 }
type_name := c.table.type_to_str(last_stmt.typ)
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`',
last_stmt.expr.pos())
}
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
2 | return none
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
3 | }
4 | a := f() or {
| ~~~~
5 | {}
| ^
6 | }
7 | _ = f() or {
vlib/v/checker/tests/or_err.vv:11:2: error: wrong return type `rune` in the `or {}` block, expected `&int`
9 | }
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 | }
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
14 | }
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
15 | mut testvar := 0
16 | el := m['pp'] or {
| ~~~~
17 | testvar = 12
| ^
18 | }
19 | println('$el $testvar')

View File

@ -13,16 +13,16 @@ vlib/v/parser/tests/prefix_first.vv:27:3: warning: move infix `&` operator befor
28 | }
29 | }
vlib/v/parser/tests/prefix_first.vv:13:6: error: `if` expression requires an expression as the last statement of every branch
11 |
11 |
12 | // later this should compile correctly
13 | _ = if true {
| ~~~~~~~
14 | v = 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
23 | // later this should compile correctly
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
24 | v := 3
25 | _ = opt() or {
| ~~~~
26 | _ = 1
| ^
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'
}