diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 2d1e78ead8..de16370255 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -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 {} } diff --git a/vlib/v/checker/tests/or_err.out b/vlib/v/checker/tests/or_err.out index 5aaf5f24f1..b36fbf003d 100644 --- a/vlib/v/checker/tests/or_err.out +++ b/vlib/v/checker/tests/or_err.out @@ -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 { diff --git a/vlib/v/parser/tests/or_default_missing.out b/vlib/v/parser/tests/or_default_missing.out index 82cfa59535..00cb8c5396 100644 --- a/vlib/v/parser/tests/or_default_missing.out +++ b/vlib/v/parser/tests/or_default_missing.out @@ -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') diff --git a/vlib/v/parser/tests/prefix_first.out b/vlib/v/parser/tests/prefix_first.out index 3a4adc679d..d723fa55d6 100644 --- a/vlib/v/parser/tests/prefix_first.out +++ b/vlib/v/parser/tests/prefix_first.out @@ -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 | } diff --git a/vlib/v/tests/or_expr_with_nested_match_expr_test.v b/vlib/v/tests/or_expr_with_nested_match_expr_test.v new file mode 100644 index 0000000000..b28f386d65 --- /dev/null +++ b/vlib/v/tests/or_expr_with_nested_match_expr_test.v @@ -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' +}