From 2b01ef39f7ef477f554519df1ac3378168a27c3a Mon Sep 17 00:00:00 2001 From: Edward Wang Date: Thu, 22 Jan 2015 03:19:55 +0800 Subject: [PATCH 1/2] Make the default branch checking more general for match expressions to address comments. --- src/librustc/middle/check_match.rs | 15 +++++++-------- src/librustc_trans/trans/_match.rs | 31 ++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/librustc/middle/check_match.rs b/src/librustc/middle/check_match.rs index a1a90395b3b78..cc1de5e0f3009 100644 --- a/src/librustc/middle/check_match.rs +++ b/src/librustc/middle/check_match.rs @@ -43,7 +43,7 @@ pub const DUMMY_WILD_PAT: &'static Pat = &Pat { span: DUMMY_SP }; -struct Matrix<'a>(Vec>); +pub struct Matrix<'a>(pub Vec>); /// Pretty-printer for matrices of patterns, example: /// ++++++++++++++++++++++++++ @@ -120,14 +120,14 @@ pub enum Constructor { } #[derive(Clone, PartialEq)] -enum Usefulness { +pub enum Usefulness { Useful, UsefulWithWitness(Vec>), NotUseful } #[derive(Copy)] -enum WitnessPreference { +pub enum WitnessPreference { ConstructWitness, LeaveOutWitness } @@ -568,11 +568,10 @@ fn all_constructors(cx: &MatchCheckCtxt, left_ty: Ty, // Note: is_useful doesn't work on empty types, as the paper notes. // So it assumes that v is non-empty. -fn is_useful(cx: &MatchCheckCtxt, - matrix: &Matrix, - v: &[&Pat], - witness: WitnessPreference) - -> Usefulness { +pub fn is_useful(cx: &MatchCheckCtxt, + matrix: &Matrix, + v: &[&Pat], + witness: WitnessPreference) -> Usefulness { let &Matrix(ref rows) = matrix; debug!("{:?}", matrix); if rows.len() == 0u { diff --git a/src/librustc_trans/trans/_match.rs b/src/librustc_trans/trans/_match.rs index 9f3c55d4f687e..e43b1055dd364 100644 --- a/src/librustc_trans/trans/_match.rs +++ b/src/librustc_trans/trans/_match.rs @@ -191,6 +191,8 @@ use self::FailureHandler::*; use back::abi; use llvm::{ValueRef, BasicBlockRef}; use middle::check_match::StaticInliner; +use middle::check_match::Usefulness::NotUseful; +use middle::check_match::WitnessPreference::LeaveOutWitness; use middle::check_match; use middle::const_eval; use middle::def::{self, DefMap}; @@ -1398,14 +1400,27 @@ fn trans_match_inner<'blk, 'tcx>(scope_cx: Block<'blk, 'tcx>, // `compile_submatch` works one column of arm patterns a time and // then peels that column off. So as we progress, it may become - // impossible to tell whether we have a genuine default arm, i.e. - // `_ => foo` or not. Sometimes it is important to know that in order - // to decide whether moving on to the next condition or falling back - // to the default arm. - let has_default = arms.last().map_or(false, |arm| { - arm.pats.len() == 1 - && arm.pats.last().unwrap().node == ast::PatWild(ast::PatWildSingle) - }); + // impossible to tell whether we have a genuine default arm or not. + // Computing such property upfront, however, is straightforward - + // if the last arm of the match expression shadows a `PatWildSingle`, + // then it is a genuine default arm. + // + // Sometimes it is important to know that in order to decide whether + // moving on to the next arm or falling back to the default one. + let is_geniune_default = |&: pats: &Vec>| { + let mcx = check_match::MatchCheckCtxt { + tcx: tcx, + param_env: ty::empty_parameter_environment(tcx), + }; + let ps = pats.iter().map(|p| &**p).collect(); + let matrix = check_match::Matrix(vec![ps]); + let candidate = [check_match::DUMMY_WILD_PAT]; + match check_match::is_useful(&mcx, &matrix, &candidate, LeaveOutWitness) { + NotUseful => true, + _ => false + } + }; + let has_default = arm_pats.iter().last().map_or(false, is_geniune_default); compile_submatch(bcx, &matches[], &[discr_datum.val], &chk, has_default); From d55c3fa887ccb937e606b7f5e06fb1fa9a4bc86e Mon Sep 17 00:00:00 2001 From: Edward Wang Date: Thu, 22 Jan 2015 03:22:22 +0800 Subject: [PATCH 2/2] Fix codegen bug for mixed range and literal patterns If there are both range and literal patterns in the same column of a match expression, rustc may generate incorrect code. This patch fixes it. Closes #18060 --- src/librustc/middle/check_match.rs | 100 +++++++++++----- src/librustc_trans/trans/_match.rs | 183 +++++++++++++++++++---------- src/test/run-pass/issue-18060.rs | 78 ++++++++++++ 3 files changed, 272 insertions(+), 89 deletions(-) create mode 100644 src/test/run-pass/issue-18060.rs diff --git a/src/librustc/middle/check_match.rs b/src/librustc/middle/check_match.rs index cc1de5e0f3009..ccff26eeb4c6a 100644 --- a/src/librustc/middle/check_match.rs +++ b/src/librustc/middle/check_match.rs @@ -102,7 +102,7 @@ pub struct MatchCheckCtxt<'a, 'tcx: 'a> { pub param_env: ParameterEnvironment<'a, 'tcx>, } -#[derive(Clone, PartialEq)] +#[derive(Clone)] pub enum Constructor { /// The constructor of all patterns that don't vary by constructor, /// e.g. struct patterns and fixed-length arrays. @@ -110,15 +110,31 @@ pub enum Constructor { /// Enum variants. Variant(ast::DefId), /// Literal values. - ConstantValue(const_val), + ConstantValue(NodeId, const_val), /// Ranges of literal values (2..5). - ConstantRange(const_val, const_val), + ConstantRange(NodeId, const_val, NodeId, const_val), /// Array patterns of length n. Slice(uint), /// Array patterns with a subslice. SliceWithSubslice(uint, uint) } +impl PartialEq for Constructor { + fn eq(&self, other: &Constructor) -> bool { + match (self, other) { + (&Single, &Single) => true, + (&Variant(ref id), &Variant(ref o_id)) => id == o_id, + (&ConstantValue(_, ref val), &ConstantValue(_, ref o_val)) => val == o_val, + (&ConstantRange(_, ref from, _, ref to), + &ConstantRange(_, ref o_from, _, ref o_to)) => from == o_from && to == o_to, + (&Slice(ref val), &Slice(ref o_val)) => val == o_val, + (&SliceWithSubslice(ref from, ref to), + &SliceWithSubslice(ref o_from, ref o_to)) => from == o_from && to == o_to, + _ => false + } + } +} + #[derive(Clone, PartialEq)] pub enum Usefulness { Useful, @@ -505,7 +521,7 @@ fn construct_witness(cx: &MatchCheckCtxt, ctor: &Constructor, _ => { match *ctor { - ConstantValue(ref v) => ast::PatLit(const_val_to_expr(v)), + ConstantValue(_, ref v) => ast::PatLit(const_val_to_expr(v)), _ => ast::PatWild(ast::PatWildSingle), } } @@ -536,7 +552,9 @@ fn all_constructors(cx: &MatchCheckCtxt, left_ty: Ty, max_slice_length: uint) -> Vec { match left_ty.sty { ty::ty_bool => - [true, false].iter().map(|b| ConstantValue(const_bool(*b))).collect(), + [true, false].iter() + .map(|b| ConstantValue(DUMMY_NODE_ID, const_bool(*b))) + .collect(), ty::ty_rptr(_, ty::mt { ty, .. }) => match ty.sty { ty::ty_vec(_, None) => @@ -655,9 +673,9 @@ fn is_useful_specialized(cx: &MatchCheckCtxt, &Matrix(ref m): &Matrix, witness: WitnessPreference) -> Usefulness { let arity = constructor_arity(cx, &ctor, lty); let matrix = Matrix(m.iter().filter_map(|r| { - specialize(cx, &r[], &ctor, 0u, arity) + specialize(cx, &r[], &ctor, 0u, arity, range_covered_by_constructor) }).collect()); - match specialize(cx, v, &ctor, 0u, arity) { + match specialize(cx, v, &ctor, 0u, arity, range_covered_by_constructor) { Some(v) => is_useful(cx, &matrix, &v[], witness), None => NotUseful } @@ -702,9 +720,11 @@ fn pat_constructors(cx: &MatchCheckCtxt, p: &Pat, _ => vec!(Single) }, ast::PatLit(ref expr) => - vec!(ConstantValue(eval_const_expr(cx.tcx, &**expr))), + vec!(ConstantValue(expr.id, eval_const_expr(cx.tcx, &**expr))), ast::PatRange(ref lo, ref hi) => - vec!(ConstantRange(eval_const_expr(cx.tcx, &**lo), eval_const_expr(cx.tcx, &**hi))), + vec!(ConstantRange( + lo.id, eval_const_expr(cx.tcx, &**lo), + hi.id, eval_const_expr(cx.tcx, &**hi))), ast::PatVec(ref before, ref slice, ref after) => match left_ty.sty { ty::ty_vec(_, Some(_)) => vec!(Single), @@ -737,7 +757,7 @@ pub fn constructor_arity(cx: &MatchCheckCtxt, ctor: &Constructor, ty: Ty) -> uin ty::ty_rptr(_, ty::mt { ty, .. }) => match ty.sty { ty::ty_vec(_, None) => match *ctor { Slice(length) => length, - ConstantValue(_) => 0u, + ConstantValue(..) => 0u, _ => unreachable!() }, ty::ty_str => 0u, @@ -756,17 +776,29 @@ pub fn constructor_arity(cx: &MatchCheckCtxt, ctor: &Constructor, ty: Ty) -> uin } fn range_covered_by_constructor(ctor: &Constructor, - from: &const_val, to: &const_val) -> Option { - let (c_from, c_to) = match *ctor { - ConstantValue(ref value) => (value, value), - ConstantRange(ref from, ref to) => (from, to), - Single => return Some(true), - _ => unreachable!() + pat_ctor: &Constructor) -> Option { + let (r_from, r_to) = match (ctor, pat_ctor) { + (&Single, _) => return Some(true), + (&ConstantValue(_, ref val), + &ConstantValue(_, ref pat_val)) => + return compare_const_vals(val, pat_val).map(|r| r == 0), + + (&ConstantValue(_, ref val), + &ConstantRange(_, ref p_from, _, ref p_to)) => + (compare_const_vals(val, p_from), compare_const_vals(val, p_to)), + + (&ConstantRange(_, ref from, _, ref to), + &ConstantValue(_, ref pat_val)) => + (compare_const_vals(from, pat_val), compare_const_vals(to, pat_val)), + + (&ConstantRange(_, ref from, _, ref to), + &ConstantRange(_, ref p_from, _, ref p_to)) => + (compare_const_vals(from, p_from), compare_const_vals(to, p_to)), + + _ => unreachable!() }; - let cmp_from = compare_const_vals(c_from, from); - let cmp_to = compare_const_vals(c_to, to); - match (cmp_from, cmp_to) { - (Some(val1), Some(val2)) => Some(val1 >= 0 && val2 <= 0), + match (r_from, r_to) { + (Some(r1), Some(r2)) => Some(0 <= r1 && r2 <= 0), _ => None } } @@ -779,8 +811,21 @@ fn range_covered_by_constructor(ctor: &Constructor, /// different patterns. /// Structure patterns with a partial wild pattern (Foo { a: 42, .. }) have their missing /// fields filled with wild patterns. -pub fn specialize<'a>(cx: &MatchCheckCtxt, r: &[&'a Pat], - constructor: &Constructor, col: uint, arity: uint) -> Option> { +/// +/// Both `typeck` and `codegen` use the specialization but in slightly different ways. +/// For typeck's exhaustion and reachability checking, whether a range pattern covers another +/// range or literal pattern is apparently significant. For codegen, however, the semantic of +/// the equality of range and literal patterns depends on the generated LLVM instruction. +/// The `op_range` is to deal with such distinctions. For the concrete examples of some subtle +/// match expressions, check out `run-pass/issue-18060.rs`. +pub fn specialize<'a, F>(cx: &MatchCheckCtxt, + r: &[&'a Pat], + constructor: &Constructor, + col: uint, + arity: uint, + op_range: F) -> Option> where + F: Fn(&Constructor, &Constructor) -> Option +{ let &Pat { id: pat_id, ref node, span: pat_span } = raw_pat(r[col]); @@ -863,8 +908,8 @@ pub fn specialize<'a>(cx: &MatchCheckCtxt, r: &[&'a Pat], Some(vec![&**inner]), ast::PatLit(ref expr) => { - let expr_value = eval_const_expr(cx.tcx, &**expr); - match range_covered_by_constructor(constructor, &expr_value, &expr_value) { + let pat_ctor = ConstantValue(expr.id, eval_const_expr(cx.tcx, &**expr)); + match op_range(constructor, &pat_ctor) { Some(true) => Some(vec![]), Some(false) => None, None => { @@ -875,9 +920,10 @@ pub fn specialize<'a>(cx: &MatchCheckCtxt, r: &[&'a Pat], } ast::PatRange(ref from, ref to) => { - let from_value = eval_const_expr(cx.tcx, &**from); - let to_value = eval_const_expr(cx.tcx, &**to); - match range_covered_by_constructor(constructor, &from_value, &to_value) { + let pat_ctor = ConstantRange( + from.id, eval_const_expr(cx.tcx, &**from), + to.id, eval_const_expr(cx.tcx, &**to)); + match op_range(constructor, &pat_ctor) { Some(true) => Some(vec![]), Some(false) => None, None => { diff --git a/src/librustc_trans/trans/_match.rs b/src/librustc_trans/trans/_match.rs index e43b1055dd364..23d6675a7c141 100644 --- a/src/librustc_trans/trans/_match.rs +++ b/src/librustc_trans/trans/_match.rs @@ -482,6 +482,34 @@ fn enter_default<'a, 'p, 'blk, 'tcx>(bcx: Block<'blk, 'tcx>, }) } +fn range_or_lit_eq(ctor: &check_match::Constructor, + pat_ctor: &check_match::Constructor, + kind: BranchKind) -> Option { + match (ctor, pat_ctor) { + (&check_match::Single, _) => Some(true), + (&check_match::ConstantValue(id, ref val), + &check_match::ConstantValue(pat_id, ref pat_val)) => + if kind == Switch { + const_eval::compare_const_vals(val, pat_val).map(|r| r == 0) + } else { + Some(id == pat_id) + }, + + (&check_match::ConstantRange(id1, _, id2, _), + &check_match::ConstantRange(pat_id1, _, pat_id2, _)) => + // The branch kind can't be LLVM switch since we have range + // pattern here. So it can only equal to itself. + Some(id1 == pat_id1 && id2 == pat_id2), + + (&check_match::ConstantValue(..), &check_match::ConstantRange(..)) | + (&check_match::ConstantRange(..), &check_match::ConstantValue(..)) => + Some(false), + + + _ => unreachable!() + } +} + // nmatsakis: what does enter_opt do? // in trans/match // trans/match.rs is like stumbling around in a dark cave @@ -512,11 +540,11 @@ fn enter_default<'a, 'p, 'blk, 'tcx>(bcx: Block<'blk, 'tcx>, /// the check_match specialization step. fn enter_opt<'a, 'p, 'blk, 'tcx>( bcx: Block<'blk, 'tcx>, - _: ast::NodeId, dm: &DefMap, m: &[Match<'a, 'p, 'blk, 'tcx>], opt: &Opt, col: uint, + kind: BranchKind, variant_size: uint, val: ValueRef) -> Vec> { @@ -530,11 +558,11 @@ fn enter_opt<'a, 'p, 'blk, 'tcx>( let ctor = match opt { &ConstantValue(ConstantExpr(expr)) => check_match::ConstantValue( - const_eval::eval_const_expr(bcx.tcx(), &*expr) + expr.id, const_eval::eval_const_expr(bcx.tcx(), &*expr) ), &ConstantRange(ConstantExpr(lo), ConstantExpr(hi)) => check_match::ConstantRange( - const_eval::eval_const_expr(bcx.tcx(), &*lo), - const_eval::eval_const_expr(bcx.tcx(), &*hi) + lo.id, const_eval::eval_const_expr(bcx.tcx(), &*lo), + hi.id, const_eval::eval_const_expr(bcx.tcx(), &*hi) ), &SliceLengthEqual(n) => check_match::Slice(n), @@ -549,8 +577,11 @@ fn enter_opt<'a, 'p, 'blk, 'tcx>( tcx: bcx.tcx(), param_env: param_env, }; - enter_match(bcx, dm, m, col, val, |pats| - check_match::specialize(&mcx, &pats[], &ctor, col, variant_size) + enter_match( + bcx, dm, m, col, val, + |pats| check_match::specialize( + &mcx, &pats[], &ctor, col, variant_size, + |ctor, pat_ctor| range_or_lit_eq(ctor, pat_ctor, kind)) ) } @@ -558,43 +589,102 @@ fn enter_opt<'a, 'p, 'blk, 'tcx>( // needs to be conditionally matched at runtime; for example, the discriminant // on a set of enum variants or a literal. fn get_branches<'a, 'p, 'blk, 'tcx>(bcx: Block<'blk, 'tcx>, - m: &[Match<'a, 'p, 'blk, 'tcx>], col: uint) - -> Vec> { + m: &[Match<'a, 'p, 'blk, 'tcx>], + col: uint, + left_ty: Ty<'tcx>, + val: ValueRef) + -> (Vec>, BranchKind, ValueRef) { let tcx = bcx.tcx(); - let mut found: Vec = vec![]; - for br in m.iter() { + let match_to_opt = |&: br: &Match<'a, 'p, 'blk, 'tcx>| { let cur = br.pats[col]; - let opt = match cur.node { - ast::PatLit(ref l) => ConstantValue(ConstantExpr(&**l)), + match cur.node { + ast::PatLit(ref l) => Some(ConstantValue(ConstantExpr(&**l))), ast::PatIdent(..) | ast::PatEnum(..) | ast::PatStruct(..) => { // This is either an enum variant or a variable binding. let opt_def = tcx.def_map.borrow().get(&cur.id).cloned(); match opt_def { Some(def::DefVariant(enum_id, var_id, _)) => { let variant = ty::enum_variant_with_id(tcx, enum_id, var_id); - Variant(variant.disr_val, adt::represent_node(bcx, cur.id), var_id) + Some(Variant( + variant.disr_val, + adt::represent_node(bcx, cur.id), var_id)) } - _ => continue + _ => None } } ast::PatRange(ref l1, ref l2) => { - ConstantRange(ConstantExpr(&**l1), ConstantExpr(&**l2)) + Some(ConstantRange(ConstantExpr(&**l1), ConstantExpr(&**l2))) } ast::PatVec(ref before, None, ref after) => { - SliceLengthEqual(before.len() + after.len()) + Some(SliceLengthEqual(before.len() + after.len())) } ast::PatVec(ref before, Some(_), ref after) => { - SliceLengthGreaterOrEqual(before.len(), after.len()) + Some(SliceLengthGreaterOrEqual(before.len(), after.len())) } - _ => continue - }; - - if !found.iter().any(|x| x.eq(&opt, tcx)) { - found.push(opt); + _ => None } - } - found + }; + let compute_branch_kind = |&: opts: &Vec>| { + let mut kind = NoBranch; + let mut test_val = val; + debug!("test_val={}", bcx.val_to_string(test_val)); + if opts.len() > 0u { + match opts[0] { + ConstantValue(_) | ConstantRange(_, _) => { + test_val = load_if_immediate(bcx, val, left_ty); + kind = if ty::type_is_integral(left_ty) { + Switch + } else { + Compare + }; + } + Variant(_, ref repr, _) => { + let (the_kind, val_opt) = adt::trans_switch(bcx, &**repr, val); + kind = the_kind; + for &tval in val_opt.iter() { test_val = tval; } + } + SliceLengthEqual(_) | SliceLengthGreaterOrEqual(_, _) => { + let (_, len) = tvec::get_base_and_len(bcx, val, left_ty); + test_val = len; + kind = Switch; + } + } + } + for o in opts.iter() { + match *o { + ConstantRange(_, _) => { kind = Compare; break }, + SliceLengthGreaterOrEqual(_, _) => { kind = CompareSliceLength; break }, + _ => () + } + } + (kind, test_val) + }; + + let all_opts: Vec = m.iter().filter_map(match_to_opt).collect(); + let (kind, test_val) = compute_branch_kind(&all_opts); + let opts: Vec = if (kind == Switch) | (kind == Single) { + // LLVM switch instruction can't have duplicated switch case, + // so dedup all opts based on their values. + all_opts + .into_iter() + .fold(vec![], + |mut opts, opt| + if !opts.iter().any(|x| x.eq(&opt, tcx)) { + opts.push(opt); opts + } else { + opts + }) + } else { + // Otherwise we can not do the dedup, otherwise, subtle bugs + // such as #18060 may creep in. + // Note, strictly speaking, we can dedup consecutive duplicated + // opts. But because `enter_match` deals with one row of + // matches a time, an following `enter_match` will not be able + // to tell the start and end of one such group of opts. + all_opts + }; + (opts, kind, test_val) } struct ExtractedBlock<'blk, 'tcx: 'blk> { @@ -1025,10 +1115,11 @@ fn compile_submatch_continue<'a, 'p, 'blk, 'tcx>(mut bcx: Block<'blk, 'tcx>, match adt_vals { Some(field_vals) => { - let pats = enter_match(bcx, dm, m, col, val, |pats| - check_match::specialize(&mcx, pats, - &check_match::Single, col, - field_vals.len()) + let pats = enter_match( + bcx, dm, m, col, val, + |pats| check_match::specialize( + &mcx, pats, &check_match::Single, col, field_vals.len(), + |ctor, pat_ctor| range_or_lit_eq(ctor, pat_ctor, Single)) ); let mut vals = field_vals; vals.push_all(vals_left.as_slice()); @@ -1039,40 +1130,8 @@ fn compile_submatch_continue<'a, 'p, 'blk, 'tcx>(mut bcx: Block<'blk, 'tcx>, } // Decide what kind of branch we need - let opts = get_branches(bcx, m, col); + let (opts, kind, test_val) = get_branches(bcx, m, col, left_ty, val); debug!("options={:?}", opts); - let mut kind = NoBranch; - let mut test_val = val; - debug!("test_val={}", bcx.val_to_string(test_val)); - if opts.len() > 0u { - match opts[0] { - ConstantValue(_) | ConstantRange(_, _) => { - test_val = load_if_immediate(bcx, val, left_ty); - kind = if ty::type_is_integral(left_ty) { - Switch - } else { - Compare - }; - } - Variant(_, ref repr, _) => { - let (the_kind, val_opt) = adt::trans_switch(bcx, &**repr, val); - kind = the_kind; - for &tval in val_opt.iter() { test_val = tval; } - } - SliceLengthEqual(_) | SliceLengthGreaterOrEqual(_, _) => { - let (_, len) = tvec::get_base_and_len(bcx, val, left_ty); - test_val = len; - kind = Switch; - } - } - } - for o in opts.iter() { - match *o { - ConstantRange(_, _) => { kind = Compare; break }, - SliceLengthGreaterOrEqual(_, _) => { kind = CompareSliceLength; break }, - _ => () - } - } let else_cx = match kind { NoBranch | Single => bcx, _ => bcx.fcx.new_temp_block("match_else") @@ -1183,7 +1242,7 @@ fn compile_submatch_continue<'a, 'p, 'blk, 'tcx>(mut bcx: Block<'blk, 'tcx>, } ConstantValue(_) | ConstantRange(_, _) => () } - let opt_ms = enter_opt(opt_cx, pat_id, dm, m, opt, col, size, val); + let opt_ms = enter_opt(opt_cx, dm, m, opt, col, kind, size, val); let mut opt_vals = unpacked; opt_vals.push_all(&vals_left[]); compile_submatch(opt_cx, diff --git a/src/test/run-pass/issue-18060.rs b/src/test/run-pass/issue-18060.rs new file mode 100644 index 0000000000000..9d14865726250 --- /dev/null +++ b/src/test/run-pass/issue-18060.rs @@ -0,0 +1,78 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +fn main() { + // Consider the second column of pattern matrix, if two range + // patterns `2...5` are deemed equal, they'll end up in the same + // subtree in pattern matrix reduction and crucially, before + // the second branch. So we will get the wrong answer 3. + let a = match (1, 3) { + (0, 2...5) => 1, + (1, 3) => 2, + (_, 2...5) => 3, + _ => 4us + }; + assert_eq!(a, 2); + + let b = match (1, 3) { + (0, 2...5) => 1, + (1, 3) => 2, + (_, 2...5) => 3, + (_, _) => 4us + }; + assert_eq!(b, 2); + + let c = match (1, 3) { + (0, 2...5) => 1, + (1, 3) => 2, + (_, 3) => 3, + (_, _) => 4us + }; + assert_eq!(c, 2); + + // ditto, the same error will happen if two literal patterns `3` + // are deemed equal. + let d = match (1, 3) { + (0, 3) => 1, + (1, 2...5) => 2, + (_, 3) => 3, + (_, _) => 4us + }; + assert_eq!(d, 2); + + let e = match (1, 3) { + (0, 3) => 1, + (2, 2...5) => 2, + (_, 3) => 3, + (_, _) => 4us + }; + assert_eq!(e, 3); + + let f = match (2, 10) { + (_, 9) => 1, + (1, 10) => 2, + (2, 10) => 3, + (3, 1...9) => 4, + (_, _) => 100 + }; + assert_eq!(f, 3); + + // OTOH, if certain column of pattern matrix consists only of literal + // patterns, an LLVM switch instruction will be generated for such + // column. If we don't group those literal patterns by their value, + // some branches will be lost! + let g = match (1, 3) { + (0, 3) => 0, + (1, 5) => 1, + (1, 3) => 2, + (_, _) => 4 + }; + assert_eq!(g, 2); +}