Fix a subtle regression

Before, the SwitchInt cases were computed in two passes: if the first
pass accepted e.g. 0..=5 and then 1, the second pass would not accept
0..=5 anymore because 1 would be listed in the SwitchInt options.

Now there's a single pass, so if we sort 0..=5 we must take care to not
sort a subsequent 1.
This commit is contained in:
Nadrieril 2024-03-02 02:49:33 +01:00
parent edea739292
commit d46ff6415c
3 changed files with 44 additions and 3 deletions
compiler/rustc_mir_build/src/build/matches
tests/ui/pattern/usefulness/integer-ranges

@ -1091,6 +1091,12 @@ enum TestCase<'pat, 'tcx> {
Or { pats: Box<[FlatPat<'pat, 'tcx>]> }, Or { pats: Box<[FlatPat<'pat, 'tcx>]> },
} }
impl<'pat, 'tcx> TestCase<'pat, 'tcx> {
fn as_range(&self) -> Option<&'pat PatRange<'tcx>> {
if let Self::Range(v) = self { Some(*v) } else { None }
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct MatchPair<'pat, 'tcx> { pub(crate) struct MatchPair<'pat, 'tcx> {
/// This place... /// This place...

@ -510,9 +510,30 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
(TestKind::SwitchInt, &TestCase::Constant { value }) (TestKind::SwitchInt, &TestCase::Constant { value })
if is_switch_ty(match_pair.pattern.ty) => if is_switch_ty(match_pair.pattern.ty) =>
{ {
fully_matched = true; // Beware: there might be some ranges sorted into the failure case; we must not add
let bits = value.eval_bits(self.tcx, self.param_env); // a success case that could be matched by one of these ranges.
Some(TestBranch::Constant(value, bits)) let is_covering_range = |test_case: &TestCase<'_, 'tcx>| {
test_case.as_range().is_some_and(|range| {
matches!(range.contains(value, self.tcx, self.param_env), None | Some(true))
})
};
let is_conflicting_candidate = |candidate: &&mut Candidate<'_, 'tcx>| {
candidate
.match_pairs
.iter()
.any(|mp| mp.place == *test_place && is_covering_range(&mp.test_case))
};
if sorted_candidates
.get(&TestBranch::Failure)
.is_some_and(|candidates| candidates.iter().any(is_conflicting_candidate))
{
fully_matched = false;
None
} else {
fully_matched = true;
let bits = value.eval_bits(self.tcx, self.param_env);
Some(TestBranch::Constant(value, bits))
}
} }
(TestKind::SwitchInt, TestCase::Range(range)) => { (TestKind::SwitchInt, TestCase::Range(range)) => {
fully_matched = false; fully_matched = false;

@ -0,0 +1,14 @@
//@ run-pass
//
// Regression test for match lowering to MIR: when gathering candidates, by the time we get to the
// range we know the range will only match on the failure case of the switchint. Hence we mustn't
// add the `1` to the switchint or the range would be incorrectly sorted.
#![allow(unreachable_patterns)]
fn main() {
match 1 {
10 => unreachable!(),
0..=5 => {}
1 => unreachable!(),
_ => unreachable!(),
}
}