diff --git a/CHANGELOG.md b/CHANGELOG.md index 31852cfa3b..8be633e313 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ *Not yet released* - `go foo()` has been replaced with `spawn foo()` (launches an OS thread, `go` will be used for upcoming coroutines instead). -- vfmt now supports `// vfmt off` and `// vfmt on` for turning off the formatting locally for *short* snippets of code. Useful for keeping your carefully arranged matrices in tact. +- vfmt now supports `// vfmt off` and `// vfmt on` for turning off the formatting locally for *short* snippets of code. Useful for keeping your carefully arranged matrices intact. +- support match branch range expressions with consts: `match x { const1...const2 {} }` ## V 0.3.2 *31 Oct 2022* diff --git a/doc/docs.md b/doc/docs.md index 6ffde1293f..a2910ff7ab 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -1676,6 +1676,26 @@ Note that the ranges use `...` (three dots) rather than `..` (two dots). This is because the range is *inclusive* of the last element, rather than exclusive (as `..` ranges are). Using `..` in a match branch will throw an error. +```v +const start = 1 + +const end = 10 + +c := 2 +num := match c { + start...end { + 1000 + } + else { + 0 + } +} +println(num) +// 1000 +``` + +Constants can also be used in the range branch expressions. + Note: `match` as an expression is not usable in `for` loop and `if` statements. ### In operator diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 057de0d751..36480c383a 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -1325,6 +1325,7 @@ pub: pub mut: low Expr high Expr + typ Type // filled in by checker; the type of `0...1` is `int` for example, while `a`...`z` is `rune` etc } [minify] diff --git a/vlib/v/checker/check_types.v b/vlib/v/checker/check_types.v index 1ad9dbca76..d5239fdefc 100644 --- a/vlib/v/checker/check_types.v +++ b/vlib/v/checker/check_types.v @@ -12,8 +12,19 @@ fn (mut c Checker) check_types(got ast.Type, expected ast.Type) bool { } got_is_ptr := got.is_ptr() exp_is_ptr := expected.is_ptr() + got_is_int := got.is_int() + exp_is_int := expected.is_int() + + exp_is_pure_int := expected.is_pure_int() + got_is_pure_int := got.is_pure_int() + // allow int literals where any kind of real integers are expected: + if (exp_is_pure_int && got == ast.int_literal_type) + || (got_is_pure_int && expected == ast.int_literal_type) { + return true + } + if c.pref.translated { - if expected.is_int() && got.is_int() { + if exp_is_int && got_is_int { return true } if expected == ast.byteptr_type { @@ -22,8 +33,8 @@ fn (mut c Checker) check_types(got ast.Type, expected ast.Type) bool { if expected == ast.voidptr_type || expected == ast.nil_type { return true } - if (expected == ast.bool_type && (got.is_any_kind_of_pointer() || got.is_int())) - || ((expected.is_any_kind_of_pointer() || expected.is_int()) && got == ast.bool_type) { + if (expected == ast.bool_type && (got.is_any_kind_of_pointer() || got_is_int)) + || ((expected.is_any_kind_of_pointer() || exp_is_int) && got == ast.bool_type) { return true } @@ -38,8 +49,7 @@ fn (mut c Checker) check_types(got ast.Type, expected ast.Type) bool { } // allow rune -> any int and vice versa - if (expected == ast.rune_type && got.is_int()) - || (got == ast.rune_type && expected.is_int()) { + if (expected == ast.rune_type && got_is_int) || (got == ast.rune_type && exp_is_int) { return true } got_sym := c.table.sym(got) @@ -546,6 +556,9 @@ fn (mut c Checker) promote_keeping_aliases(left_type ast.Type, right_type ast.Ty } fn (mut c Checker) promote(left_type ast.Type, right_type ast.Type) ast.Type { + if left_type == right_type { + return left_type // strings, self defined operators + } if left_type.is_any_kind_of_pointer() { if right_type.is_int() || c.pref.translated { return left_type @@ -559,9 +572,6 @@ fn (mut c Checker) promote(left_type ast.Type, right_type ast.Type) ast.Type { return ast.void_type } } - if left_type == right_type { - return left_type // strings, self defined operators - } if right_type.is_number() && left_type.is_number() { return c.promote_num(left_type, right_type) } else if left_type.has_flag(.optional) != right_type.has_flag(.optional) { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 003134d562..faa1ae4f40 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2452,8 +2452,20 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { return c.expr(node.expr) } ast.RangeExpr { - // never happens - return ast.void_type + // branch range expression of `match x { a...b {} }`, or: `a#[x..y]`: + ltyp := c.expr(node.low) + htyp := c.expr(node.high) + if !c.check_types(ltyp, htyp) { + lstype := c.table.type_to_str(ltyp) + hstype := c.table.type_to_str(htyp) + c.add_error_detail('') + c.add_error_detail(' low part type: ${lstype}') + c.add_error_detail('high part type: ${hstype}') + c.error('the low and high parts of a range expression, should have matching types', + node.pos) + } + node.typ = c.promote(ltyp, htyp) + return ltyp } ast.SelectExpr { return c.select_expr(mut node) diff --git a/vlib/v/checker/match.v b/vlib/v/checker/match.v index 3c6da9aef8..d7ea150597 100644 --- a/vlib/v/checker/match.v +++ b/vlib/v/checker/match.v @@ -139,66 +139,98 @@ fn (mut c Checker) check_match_branch_last_stmt(last_stmt ast.ExprStmt, ret_type } } +fn (mut c Checker) get_comptime_number_value(mut expr ast.Expr) ?i64 { + if mut expr is ast.CharLiteral { + return expr.val[0] + } + if mut expr is ast.IntegerLiteral { + return expr.val.i64() + } + if mut expr is ast.Ident { + if mut obj := c.table.global_scope.find_const(expr.name) { + if obj.typ == 0 { + obj.typ = c.expr(obj.expr) + } + return c.get_comptime_number_value(mut obj.expr) + } + } + return none +} + fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSymbol) { + c.expected_type = node.expected_type + cond_sym := c.table.sym(node.cond_type) // branch_exprs is a histogram of how many times // an expr was used in the match mut branch_exprs := map[string]int{} for branch_i, _ in node.branches { mut branch := node.branches[branch_i] mut expr_types := []ast.TypeNode{} - for k, expr in branch.exprs { + for k, mut expr in branch.exprs { mut key := '' - if expr is ast.RangeExpr { + // TODO: investigate why enums are different here: + if expr !is ast.EnumVal { + // ensure that the sub expressions of the branch are actually checked, before anything else: + _ := c.expr(expr) + } + if mut expr is ast.RangeExpr { + // Allow for `match enum_value { 4..5 { } }`, even though usually int and enum values, + // are considered incompatible outside unsafe{}, and are not allowed to be compared directly + if cond_sym.kind != .enum_ && !c.check_types(expr.typ, node.cond_type) { + mcstype := c.table.type_to_str(node.cond_type) + brstype := c.table.type_to_str(expr.typ) + c.add_error_detail('') + c.add_error_detail('match condition type: ${mcstype}') + c.add_error_detail(' range type: ${brstype}') + c.error('the range type and the match condition type should match', + expr.pos) + } + mut low_value_higher_than_high_value := false mut low := i64(0) mut high := i64(0) - c.expected_type = node.expected_type - low_expr := expr.low - high_expr := expr.high - final_cond_sym := c.table.final_sym(node.cond_type) - if low_expr is ast.IntegerLiteral { - if high_expr is ast.IntegerLiteral - && (final_cond_sym.is_int() || final_cond_sym.info is ast.Enum) { - low = low_expr.val.i64() - high = high_expr.val.i64() - if low > high { - c.error('start value is higher than end value', branch.pos) + mut both_low_and_high_are_known := false + if low_value := c.get_comptime_number_value(mut expr.low) { + low = low_value + if high_value := c.get_comptime_number_value(mut expr.high) { + high = high_value + both_low_and_high_are_known = true + if low_value > high_value { + low_value_higher_than_high_value = true } } else { - c.error('mismatched range types - ${expr.low} is an integer, but ${expr.high} is not', - low_expr.pos) - } - } else if low_expr is ast.CharLiteral { - if high_expr is ast.CharLiteral && final_cond_sym.kind in [.u8, .char, .rune] { - low = low_expr.val[0] - high = high_expr.val[0] - if low > high { - c.error('start value is higher than end value', branch.pos) + if expr.high !is ast.EnumVal { + c.error('match branch range expressions need the end value to be known at compile time (only enums, const or literals are supported)', + expr.high.pos()) } - } else { - typ := c.table.type_to_str(c.expr(node.cond)) - c.error('mismatched range types - trying to match `${node.cond}`, which has type `${typ}`, against a range of `rune`', - low_expr.pos) } } else { - typ := c.table.type_to_str(c.expr(expr.low)) - c.error('cannot use type `${typ}` in match range', branch.pos) + if expr.low !is ast.EnumVal { + c.error('match branch range expressions need the start value to be known at compile time (only enums, const or literals are supported)', + expr.low.pos()) + } } - high_low_cutoff := 1000 - if high - low > high_low_cutoff { - c.warn('more than ${high_low_cutoff} possibilities (${low} ... ${high}) in match range', + if low_value_higher_than_high_value { + c.error('the start value `${low}` should be lower than the end value `${high}`', branch.pos) } - for i in low .. high + 1 { - key = i.str() - val := if key in branch_exprs { branch_exprs[key] } else { 0 } - if val == 1 { - c.error('match case `${key}` is handled more than once', branch.pos) + if both_low_and_high_are_known { + high_low_cutoff := 1000 + if high - low > high_low_cutoff { + c.warn('more than ${high_low_cutoff} possibilities (${low} ... ${high}) in match range', + branch.pos) + } + for i in low .. high + 1 { + key = i.str() + val := if key in branch_exprs { branch_exprs[key] } else { 0 } + if val == 1 { + c.error('match case `${key}` is handled more than once', branch.pos) + } + branch_exprs[key] = val + 1 } - branch_exprs[key] = val + 1 } continue } - match expr { + match mut expr { ast.TypeNode { key = c.table.type_to_str(expr.typ) expr_types << expr @@ -207,7 +239,7 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym key = expr.val } else { - key = expr.str() + key = (*expr).str() } } val := if key in branch_exprs { branch_exprs[key] } else { 0 } diff --git a/vlib/v/checker/tests/const_expr_match_range_invalid_err.out b/vlib/v/checker/tests/const_expr_match_range_invalid_err.out new file mode 100644 index 0000000000..21d35d878d --- /dev/null +++ b/vlib/v/checker/tests/const_expr_match_range_invalid_err.out @@ -0,0 +1,20 @@ +vlib/v/checker/tests/const_expr_match_range_invalid_err.vv:6:2: error: the range type and the match condition type should match + 4 | + 5 | match 5 { + 6 | start...`1` { + | ~~~~~~~~~~~ + 7 | println(start) + 8 | } +Details: +match condition type: int + range type: rune +vlib/v/checker/tests/const_expr_match_range_invalid_err.vv:9:2: error: the low and high parts of a range expression, should have matching types + 7 | println(start) + 8 | } + 9 | 'str'...end { + | ~~~~~~~~~~~ + 10 | println(end) + 11 | } +Details: + low part type: string +high part type: int literal diff --git a/vlib/v/checker/tests/const_expr_match_range_invalid_err.vv b/vlib/v/checker/tests/const_expr_match_range_invalid_err.vv new file mode 100644 index 0000000000..17901625d5 --- /dev/null +++ b/vlib/v/checker/tests/const_expr_match_range_invalid_err.vv @@ -0,0 +1,13 @@ +const start = 1 + +const end = 20 + +match 5 { + start...`1` { + println(start) + } + 'str'...end { + println(end) + } + else {} +} diff --git a/vlib/v/checker/tests/const_match_invalid_type_range_err.out b/vlib/v/checker/tests/const_match_invalid_type_range_err.out new file mode 100644 index 0000000000..792fbcdd36 --- /dev/null +++ b/vlib/v/checker/tests/const_match_invalid_type_range_err.out @@ -0,0 +1,20 @@ +vlib/v/checker/tests/const_match_invalid_type_range_err.vv:6:2: error: the range type and the match condition type should match + 4 | + 5 | match 5 { + 6 | start...end { + | ~~~~~~~~~~~ + 7 | println(start) + 8 | } +Details: +match condition type: int + range type: rune +vlib/v/checker/tests/const_match_invalid_type_range_err.vv:13:2: error: the range type and the match condition type should match + 11 | + 12 | b := match 5 { + 13 | start...end { + | ~~~~~~~~~~~ + 14 | 3 + 15 | } +Details: +match condition type: int + range type: rune diff --git a/vlib/v/checker/tests/const_match_invalid_type_range_err.vv b/vlib/v/checker/tests/const_match_invalid_type_range_err.vv new file mode 100644 index 0000000000..fdb2a8a25f --- /dev/null +++ b/vlib/v/checker/tests/const_match_invalid_type_range_err.vv @@ -0,0 +1,20 @@ +const start = `a` + +const end = 2 + +match 5 { + start...end { + println(start) + } + else {} +} + +b := match 5 { + start...end { + 3 + } + else { + 2 + } +} +println(b) diff --git a/vlib/v/checker/tests/const_match_mismatch_end_range_err.out b/vlib/v/checker/tests/const_match_mismatch_end_range_err.out new file mode 100644 index 0000000000..b654b77ffb --- /dev/null +++ b/vlib/v/checker/tests/const_match_mismatch_end_range_err.out @@ -0,0 +1,40 @@ +vlib/v/checker/tests/const_match_mismatch_end_range_err.vv:6:2: error: the range type and the match condition type should match + 4 | + 5 | match 5 { + 6 | 0...end { + | ~~~~~~~ + 7 | println(start) + 8 | } +Details: +match condition type: int + range type: rune +vlib/v/checker/tests/const_match_mismatch_end_range_err.vv:9:2: error: the range type and the match condition type should match + 7 | println(start) + 8 | } + 9 | `a`...start { + | ~~~~~~~~~~~ + 10 | println(end) + 11 | } +Details: +match condition type: int + range type: rune +vlib/v/checker/tests/const_match_mismatch_end_range_err.vv:16:2: error: the range type and the match condition type should match + 14 | + 15 | a := match 5 { + 16 | 0...end { + | ~~~~~~~ + 17 | 1 + 18 | } +Details: +match condition type: int + range type: rune +vlib/v/checker/tests/const_match_mismatch_end_range_err.vv:19:2: error: the range type and the match condition type should match + 17 | 1 + 18 | } + 19 | `a`...start { + | ~~~~~~~~~~~ + 20 | `a` + 21 | } +Details: +match condition type: int + range type: rune diff --git a/vlib/v/checker/tests/const_match_mismatch_end_range_err.vv b/vlib/v/checker/tests/const_match_mismatch_end_range_err.vv new file mode 100644 index 0000000000..228df3f55b --- /dev/null +++ b/vlib/v/checker/tests/const_match_mismatch_end_range_err.vv @@ -0,0 +1,26 @@ +const start = 12 + +const end = `a` + +match 5 { + 0...end { + println(start) + } + `a`...start { + println(end) + } + else {} +} + +a := match 5 { + 0...end { + 1 + } + `a`...start { + `a` + } + else { + 2 + } +} +println(a) diff --git a/vlib/v/checker/tests/const_match_range_duplicate_case_err.out b/vlib/v/checker/tests/const_match_range_duplicate_case_err.out new file mode 100644 index 0000000000..b23875977d --- /dev/null +++ b/vlib/v/checker/tests/const_match_range_duplicate_case_err.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/const_match_range_duplicate_case_err.vv:9:2: error: match case `2` is handled more than once + 7 | println(1) + 8 | } + 9 | start...end { + | ~~~~~~~~~~~ + 10 | println(3) + 11 | } +vlib/v/checker/tests/const_match_range_duplicate_case_err.vv:21:2: error: match case `2` is handled more than once + 19 | 1 + 20 | } + 21 | start...end { + | ~~~~~~~~~~~ + 22 | 3 + 23 | } diff --git a/vlib/v/checker/tests/const_match_range_duplicate_case_err.vv b/vlib/v/checker/tests/const_match_range_duplicate_case_err.vv new file mode 100644 index 0000000000..1b32c48ece --- /dev/null +++ b/vlib/v/checker/tests/const_match_range_duplicate_case_err.vv @@ -0,0 +1,28 @@ +const start = 2 + +const end = 12 + +match 5 { + start...end { + println(1) + } + start...end { + println(3) + } + else { + println(2) + } +} + +a := match 5 { + start...end { + 1 + } + start...end { + 3 + } + else { + 2 + } +} +println(a) diff --git a/vlib/v/checker/tests/const_match_range_invalid_err.out b/vlib/v/checker/tests/const_match_range_invalid_err.out new file mode 100644 index 0000000000..e56ea775d9 --- /dev/null +++ b/vlib/v/checker/tests/const_match_range_invalid_err.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/const_match_range_invalid_err.vv:6:2: error: the start value `10` should be lower than the end value `1` + 4 | + 5 | match 5 { + 6 | end...start { + | ~~~~~~~~~~~ + 7 | println(start) + 8 | } +vlib/v/checker/tests/const_match_range_invalid_err.vv:13:2: error: the start value `10` should be lower than the end value `1` + 11 | + 12 | a := match 5 { + 13 | end...start { + | ~~~~~~~~~~~ + 14 | 4 + 15 | } diff --git a/vlib/v/checker/tests/const_match_range_invalid_err.vv b/vlib/v/checker/tests/const_match_range_invalid_err.vv new file mode 100644 index 0000000000..49674a70ee --- /dev/null +++ b/vlib/v/checker/tests/const_match_range_invalid_err.vv @@ -0,0 +1,20 @@ +const end = 10 + +const start = 1 + +match 5 { + end...start { + println(start) + } + else {} +} + +a := match 5 { + end...start { + 4 + } + else { + 3 + } +} +println(a) diff --git a/vlib/v/checker/tests/const_match_type_mismatch_range_err.out b/vlib/v/checker/tests/const_match_type_mismatch_range_err.out new file mode 100644 index 0000000000..768c2a4124 --- /dev/null +++ b/vlib/v/checker/tests/const_match_type_mismatch_range_err.out @@ -0,0 +1,20 @@ +vlib/v/checker/tests/const_match_type_mismatch_range_err.vv:6:2: error: the low and high parts of a range expression, should have matching types + 4 | + 5 | match 5 { + 6 | start...end { + | ~~~~~~~~~~~ + 7 | println(start) + 8 | } +Details: + low part type: string +high part type: int literal +vlib/v/checker/tests/const_match_type_mismatch_range_err.vv:13:2: error: the low and high parts of a range expression, should have matching types + 11 | + 12 | z := match 5 { + 13 | start...end { + | ~~~~~~~~~~~ + 14 | 2 + 15 | } +Details: + low part type: string +high part type: int literal diff --git a/vlib/v/checker/tests/const_match_type_mismatch_range_err.vv b/vlib/v/checker/tests/const_match_type_mismatch_range_err.vv new file mode 100644 index 0000000000..77d5805241 --- /dev/null +++ b/vlib/v/checker/tests/const_match_type_mismatch_range_err.vv @@ -0,0 +1,20 @@ +const end = 10 + +const start = 'str' + +match 5 { + start...end { + println(start) + } + else {} +} + +z := match 5 { + start...end { + 2 + } + else { + 5 + } +} +println(z) diff --git a/vlib/v/checker/tests/invalid_const_expr_match_range_err.out b/vlib/v/checker/tests/invalid_const_expr_match_range_err.out new file mode 100644 index 0000000000..de4d723783 --- /dev/null +++ b/vlib/v/checker/tests/invalid_const_expr_match_range_err.out @@ -0,0 +1,28 @@ +vlib/v/checker/tests/invalid_const_expr_match_range_err.vv:5:2: error: match branch range expressions need the start value to be known at compile time (only enums, const or literals are supported) + 3 | + 4 | match 5 { + 5 | start...10 { + | ~~~~~ + 6 | println(start) + 7 | } +vlib/v/checker/tests/invalid_const_expr_match_range_err.vv:8:7: error: match branch range expressions need the end value to be known at compile time (only enums, const or literals are supported) + 6 | println(start) + 7 | } + 8 | 10...end { + | ~~~ + 9 | println(end) + 10 | } +vlib/v/checker/tests/invalid_const_expr_match_range_err.vv:15:2: error: match branch range expressions need the start value to be known at compile time (only enums, const or literals are supported) + 13 | + 14 | a := match 5 { + 15 | start...10 { + | ~~~~~ + 16 | 2 + 17 | } +vlib/v/checker/tests/invalid_const_expr_match_range_err.vv:18:7: error: match branch range expressions need the end value to be known at compile time (only enums, const or literals are supported) + 16 | 2 + 17 | } + 18 | 10...end { + | ~~~ + 19 | 3 + 20 | } diff --git a/vlib/v/checker/tests/invalid_const_expr_match_range_err.vv b/vlib/v/checker/tests/invalid_const_expr_match_range_err.vv new file mode 100644 index 0000000000..a589f368b7 --- /dev/null +++ b/vlib/v/checker/tests/invalid_const_expr_match_range_err.vv @@ -0,0 +1,25 @@ +start := 1 +end := 20 + +match 5 { + start...10 { + println(start) + } + 10...end { + println(end) + } + else {} +} + +a := match 5 { + start...10 { + 2 + } + 10...end { + 3 + } + else { + 1 + } +} +println(a) diff --git a/vlib/v/checker/tests/match_expr_range_low_higher_than_high.out b/vlib/v/checker/tests/match_expr_range_low_higher_than_high.out index 85228d2227..d441d03d40 100644 --- a/vlib/v/checker/tests/match_expr_range_low_higher_than_high.out +++ b/vlib/v/checker/tests/match_expr_range_low_higher_than_high.out @@ -1,4 +1,4 @@ -vlib/v/checker/tests/match_expr_range_low_higher_than_high.vv:5:3: error: start value is higher than end value +vlib/v/checker/tests/match_expr_range_low_higher_than_high.vv:5:3: error: the start value `20` should be lower than the end value `10` 3 | value := 10 4 | match value { 5 | 20...10 {} diff --git a/vlib/v/checker/tests/match_range_mismatch_type_err.out b/vlib/v/checker/tests/match_range_mismatch_type_err.out index 8df8a72b59..5efa36924f 100644 --- a/vlib/v/checker/tests/match_range_mismatch_type_err.out +++ b/vlib/v/checker/tests/match_range_mismatch_type_err.out @@ -1,14 +1,20 @@ -vlib/v/checker/tests/match_range_mismatch_type_err.vv:4:3: error: mismatched range types - trying to match `x`, which has type `string`, against a range of `rune` +vlib/v/checker/tests/match_range_mismatch_type_err.vv:4:3: error: the range type and the match condition type should match 2 | x := '1' 3 | match x { 4 | `0`...`9` { - | ~~~ + | ~~~~~~~~~ 5 | println('0-9') 6 | } -vlib/v/checker/tests/match_range_mismatch_type_err.vv:16:3: error: mismatched range types - 0 is an integer, but `9` is not +Details: +match condition type: string + range type: rune +vlib/v/checker/tests/match_range_mismatch_type_err.vv:16:3: error: the range type and the match condition type should match 14 | x := 1 15 | match x { 16 | 0...`9` {} - | ^ + | ~~~~~~~ 17 | else {} 18 | } +Details: +match condition type: int + range type: rune diff --git a/vlib/v/checker/tests/non_const_match_range_err.out b/vlib/v/checker/tests/non_const_match_range_err.out new file mode 100644 index 0000000000..c9afe0c2e7 --- /dev/null +++ b/vlib/v/checker/tests/non_const_match_range_err.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/non_const_match_range_err.vv:6:2: error: match branch range expressions need the start value to be known at compile time (only enums, const or literals are supported) + 4 | + 5 | match 5 { + 6 | start...end { + | ~~~~~ + 7 | println(start) + 8 | } +vlib/v/checker/tests/non_const_match_range_err.vv:13:2: error: match branch range expressions need the start value to be known at compile time (only enums, const or literals are supported) + 11 | + 12 | a := match 5 { + 13 | start...end { + | ~~~~~ + 14 | 0 + 15 | } diff --git a/vlib/v/checker/tests/non_const_match_range_err.vv b/vlib/v/checker/tests/non_const_match_range_err.vv new file mode 100644 index 0000000000..cc081856f8 --- /dev/null +++ b/vlib/v/checker/tests/non_const_match_range_err.vv @@ -0,0 +1,20 @@ +const end = 10 + +start := 1 + +match 5 { + start...end { + println(start) + } + else {} +} + +a := match 5 { + start...end { + 0 + } + else { + 1 + } +} +println(a) diff --git a/vlib/v/gen/c/index.v b/vlib/v/gen/c/index.v index cf10d77601..5758a6c343 100644 --- a/vlib/v/gen/c/index.v +++ b/vlib/v/gen/c/index.v @@ -8,7 +8,7 @@ import v.util fn (mut g Gen) index_expr(node ast.IndexExpr) { if node.index is ast.RangeExpr { - g.range_expr(node, node.index) + g.index_range_expr(node, node.index) } else { sym := g.table.final_sym(g.unwrap_generic(node.left_type)) if sym.kind == .array { @@ -57,7 +57,7 @@ fn (mut g Gen) index_expr(node ast.IndexExpr) { } } -fn (mut g Gen) range_expr(node ast.IndexExpr, range ast.RangeExpr) { +fn (mut g Gen) index_range_expr(node ast.IndexExpr, range ast.RangeExpr) { sym := g.table.final_sym(node.left_type) mut tmp_opt := '' mut cur_line := '' diff --git a/vlib/v/gen/c/match.v b/vlib/v/gen/c/match.v index d29bfd0f3b..4122972640 100644 --- a/vlib/v/gen/c/match.v +++ b/vlib/v/gen/c/match.v @@ -252,15 +252,8 @@ fn (mut g Gen) match_expr_switch(node ast.MatchExpr, is_expr bool, cond_var stri g.write(' || ') } if expr is ast.RangeExpr { - // if type is unsigned and low is 0, check is unneeded - mut skip_low := false - if expr.low is ast.IntegerLiteral { - if node_cond_type_unsigned && expr.low.val == '0' { - skip_low = true - } - } g.write('(') - if !skip_low { + if g.should_check_low_bound_in_range_expr(expr, node_cond_type_unsigned) { g.write('${cond_var} >= ') g.expr(expr.low) g.write(' && ') @@ -324,15 +317,8 @@ fn (mut g Gen) match_expr_switch(node ast.MatchExpr, is_expr bool, cond_var stri g.write(' || ') } if expr is ast.RangeExpr { - // if type is unsigned and low is 0, check is unneeded - mut skip_low := false - if expr.low is ast.IntegerLiteral { - if node_cond_type_unsigned && expr.low.val == '0' { - skip_low = true - } - } g.write('(') - if !skip_low { + if g.should_check_low_bound_in_range_expr(expr, node_cond_type_unsigned) { g.write('${cond_var} >= ') g.expr(expr.low) g.write(' && ') @@ -359,6 +345,28 @@ fn (mut g Gen) match_expr_switch(node ast.MatchExpr, is_expr bool, cond_var stri g.writeln('}') } +fn (mut g Gen) should_check_low_bound_in_range_expr(expr ast.RangeExpr, node_cond_type_unsigned bool) bool { + // if the type is unsigned, and the low bound of the range expression is 0, + // checking it at runtime is not needed: + mut should_check_low_bound := true + if node_cond_type_unsigned { + if expr.low is ast.IntegerLiteral { + if expr.low.val == '0' { + should_check_low_bound = false + } + } else if expr.low is ast.Ident { + if mut obj := g.table.global_scope.find_const(expr.low.name) { + if mut obj.expr is ast.IntegerLiteral { + if obj.expr.val == '0' { + should_check_low_bound = false + } + } + } + } + } + return should_check_low_bound +} + fn (mut g Gen) match_expr_classic(node ast.MatchExpr, is_expr bool, cond_var string, tmp_var string) { node_cond_type_unsigned := node.cond_type in [ast.u16_type, ast.u32_type, ast.u64_type] type_sym := g.table.sym(node.cond_type) @@ -431,15 +439,8 @@ fn (mut g Gen) match_expr_classic(node ast.MatchExpr, is_expr bool, cond_var str } else { if expr is ast.RangeExpr { - // if type is unsigned and low is 0, check is unneeded - mut skip_low := false - if expr.low is ast.IntegerLiteral { - if node_cond_type_unsigned && expr.low.val == '0' { - skip_low = true - } - } g.write('(') - if !skip_low { + if g.should_check_low_bound_in_range_expr(expr, node_cond_type_unsigned) { g.write('${cond_var} >= ') g.expr(expr.low) g.write(' && ') diff --git a/vlib/v/parser/if_match.v b/vlib/v/parser/if_match.v index 371c725bf8..a2a49c016c 100644 --- a/vlib/v/parser/if_match.v +++ b/vlib/v/parser/if_match.v @@ -267,6 +267,7 @@ fn (mut p Parser) match_expr() ast.MatchExpr { // Expression match for { p.inside_match_case = true + mut range_pos := p.tok.pos() expr := p.expr(0) ecmnts << p.eat_comments() p.inside_match_case = false @@ -276,13 +277,15 @@ fn (mut p Parser) match_expr() ast.MatchExpr { return ast.MatchExpr{} } else if p.tok.kind == .ellipsis { p.next() + p.inside_match_case = true expr2 := p.expr(0) + p.inside_match_case = false exprs << ast.RangeExpr{ low: expr high: expr2 has_low: true has_high: true - pos: p.tok.pos() + pos: range_pos.extend(p.prev_tok.pos()) } } else { exprs << expr diff --git a/vlib/v/tests/match_const_range_test.v b/vlib/v/tests/match_const_range_test.v new file mode 100644 index 0000000000..45b193f689 --- /dev/null +++ b/vlib/v/tests/match_const_range_test.v @@ -0,0 +1,65 @@ +const ( + start = 1 + start_2 = 4 + end = 3 + end_2 = 8 + // + start_rune = `a` + start_2_rune = `d` + end_rune = `c` + end_2_rune = `i` +) + +fn test_match_int_const_ranges() { + mut results := []int{} + for x in 0 .. 10 { + match x { + start...end { results << 1 } + start_2...5 { results << 2 } + 6...end_2 { results << 3 } + else { results << 4 } + } + } + assert results == [4, 1, 1, 1, 2, 2, 3, 3, 3, 4] +} + +fn test_match_rune_const_ranges() { + mut results := []int{} + for x in `a` .. `l` { + match x { + start_rune...end_rune { results << 1 } + start_2_rune...`e` { results << 2 } + `f`...end_2_rune { results << 3 } + else { results << 4 } + } + } + assert results == [1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4] +} + +fn test_match_expr_int_const_ranges() { + mut results := []int{} + for x in 0 .. 10 { + result := match x { + start...end { 1 } + start_2...5 { 2 } + 6...end_2 { 3 } + else { 4 } + } + results << result + } + assert results == [4, 1, 1, 1, 2, 2, 3, 3, 3, 4] +} + +fn test_match_expr_rune_const_ranges() { + mut results := []int{} + for x in `a` .. `l` { + result := match x { + start_rune...end_rune { 1 } + start_2_rune...`e` { 2 } + `f`...end_2_rune { 3 } + else { 4 } + } + results << result + } + assert results == [1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4] +}