From 12e48c6fe25dfb293706a2b9fc344813458b5ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=C3=A4schle?= Date: Tue, 14 Apr 2020 01:03:31 +0200 Subject: [PATCH] checker: check match for exhaustion --- vlib/v/ast/ast.v | 1 + vlib/v/checker/checker.v | 12 ++++++++++ .../v/checker/tests/inout/match_expr_else.out | 7 ++++++ vlib/v/checker/tests/inout/match_expr_else.vv | 13 ++++++++++ vlib/v/fmt/fmt.v | 2 +- vlib/v/gen/cgen.v | 6 ++--- vlib/v/parser/parser.v | 24 +++++++++++++------ vlib/v/util/errors.v | 4 +++- 8 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 vlib/v/checker/tests/inout/match_expr_else.out create mode 100644 vlib/v/checker/tests/inout/match_expr_else.vv diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 904170faaf..454e9a36a4 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -399,6 +399,7 @@ pub: stmts []Stmt pos token.Position comment Comment // comment above `xxx {` + is_else bool } pub struct CompIf { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 94aa8941d7..642300b190 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1323,6 +1323,18 @@ pub fn (c mut Checker) match_expr(node mut ast.MatchExpr) table.Type { if cond_type == 0 { c.error('match 0 cond type', node.pos) } + // check for exhaustion when match is expr + if node.is_sum_type && node.is_expr && !node.branches[node.branches.len - 1].is_else { + type_sym := c.table.get_type_symbol(cond_type) + info := type_sym.info as table.SumType + mut used_sum_types_count := 0 + for branch in node.branches { + used_sum_types_count += branch.exprs.len + } + if used_sum_types_count < info.variants.len { + c.error('match must be exhaustive', node.pos) + } + } c.expected_type = cond_type mut ret_type := table.void_type for branch in node.branches { diff --git a/vlib/v/checker/tests/inout/match_expr_else.out b/vlib/v/checker/tests/inout/match_expr_else.out new file mode 100644 index 0000000000..3ac1bbf9cc --- /dev/null +++ b/vlib/v/checker/tests/inout/match_expr_else.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/inout/match_expr_else.v:5:9: error: match must be exhaustive + 3| fn main() { + 4| x := A('test') + 5| res := match x { + ~~~~~~~~~ + 6| int { + 7| 'int' \ No newline at end of file diff --git a/vlib/v/checker/tests/inout/match_expr_else.vv b/vlib/v/checker/tests/inout/match_expr_else.vv new file mode 100644 index 0000000000..d32dabd9f2 --- /dev/null +++ b/vlib/v/checker/tests/inout/match_expr_else.vv @@ -0,0 +1,13 @@ +type A = int | string | f64 + +fn main() { + x := A('test') + res := match x { + int { + 'int' + } + string { + 'string' + } + } +} diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index b4469c5c93..44e144b603 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -577,7 +577,7 @@ fn (f mut Fmt) expr(node ast.Expr) { if branch.comment.text != '' { f.comment(branch.comment) } - if i < it.branches.len - 1 { + if !branch.is_else { // normal branch for j, expr in branch.exprs { f.expr(expr) diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 1a93f72128..24a1cfd25e 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -1373,8 +1373,8 @@ fn (g mut Gen) match_expr(node ast.MatchExpr) { // g.writeln(';') // $it.blocks.len') // mut sum_type_str = '' for j, branch in node.branches { - if j == node.branches.len - 1 { - // last block is an `else{}` + is_last := j == node.branches.len - 1 + if branch.is_else || node.is_expr && is_last { if node.branches.len > 1 { if is_expr { // TODO too many branches. maybe separate ?: matches @@ -1447,7 +1447,7 @@ fn (g mut Gen) match_expr(node ast.MatchExpr) { } g.stmts(branch.stmts) if !g.inside_ternary && node.branches.len > 1 { - g.writeln('}') + g.write('}') } } g.inside_ternary = was_inside_ternary diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 7aaeff0876..b16ee45a7d 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -1834,8 +1834,8 @@ fn (p mut Parser) global_decl() ast.GlobalDecl { } fn (p mut Parser) match_expr() ast.MatchExpr { + match_first_pos := p.tok.position() p.check(.key_match) - pos := p.tok.position() is_mut := p.tok.kind == .key_mut mut is_sum_type := false if is_mut { @@ -1844,15 +1844,15 @@ fn (p mut Parser) match_expr() ast.MatchExpr { cond := p.expr(0) p.check(.lcbr) mut branches := []ast.MatchBranch - mut have_final_else := false for { + branch_first_pos := p.tok.position() comment := p.check_comment() // comment before {} mut exprs := []ast.Expr - branch_pos := p.tok.position() p.open_scope() // final else + mut is_else := false if p.tok.kind == .key_else { - have_final_else = true + is_else = true p.next() } else if p.tok.kind == .name && (p.tok.lit in table.builtin_type_names || (p.tok.lit[0].is_capital() && !p.tok.lit.is_upper()) || p.peek_tok.kind == .dot) { @@ -1890,21 +1890,31 @@ fn (p mut Parser) match_expr() ast.MatchExpr { p.check(.comma) } } + branch_last_pos := p.tok.position() // p.warn('match block') stmts := p.parse_block() + pos := token.Position{ + line_nr: match_first_pos.line_nr + pos: match_first_pos.pos + len: branch_last_pos.pos - branch_first_pos.pos + branch_last_pos.len + } branches << ast.MatchBranch{ exprs: exprs stmts: stmts - pos: branch_pos + pos: pos comment: comment + is_else: is_else } p.close_scope() if p.tok.kind == .rcbr { break } } - if !have_final_else { - p.error('match must be exhaustive') + match_last_pos := p.tok.position() + pos := token.Position{ + line_nr: match_first_pos.line_nr + pos: match_first_pos.pos + len: match_last_pos.pos - match_first_pos.pos + match_last_pos.len } p.check(.rcbr) return ast.MatchExpr{ diff --git a/vlib/v/util/errors.v b/vlib/v/util/errors.v index 32c90b4adb..623043dce9 100644 --- a/vlib/v/util/errors.v +++ b/vlib/v/util/errors.v @@ -96,7 +96,9 @@ pub fn formatted_error(kind string /*error or warn*/, emsg string, filepath stri continue } if pos.len > 1 { - underline := '~'.repeat(pos.len) + max_len := sline.len - pointerline.len // rest of the line + len := if pos.len > max_len { max_len } else { pos.len } + underline := '~'.repeat(len) pointerline << if emanager.support_color { term.bold(term.blue(underline)) } else { underline } }else{ pointerline << if emanager.support_color { term.bold(term.blue('^')) } else { '^' }