about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_mir_build/src/build/matches/mod.rs54
-rw-r--r--compiler/rustc_mir_build/src/build/matches/simplify.rs228
-rw-r--r--compiler/rustc_mir_build/src/build/matches/test.rs137
-rw-r--r--compiler/rustc_mir_build/src/build/matches/util.rs52
-rw-r--r--tests/mir-opt/exponential_or.match_tuple.SimplifyCfg-initial.after.mir8
5 files changed, 219 insertions, 260 deletions
diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs
index 35f5a6bfac5..309fd3bf09e 100644
--- a/compiler/rustc_mir_build/src/build/matches/mod.rs
+++ b/compiler/rustc_mir_build/src/build/matches/mod.rs
@@ -22,8 +22,6 @@ use rustc_middle::ty::{self, CanonicalUserTypeAnnotation, Ty};
 use rustc_span::symbol::Symbol;
 use rustc_span::{BytePos, Pos, Span};
 use rustc_target::abi::VariantIdx;
-use smallvec::{smallvec, SmallVec};
-
 // helper functions, broken out by category:
 mod simplify;
 mod test;
@@ -949,12 +947,16 @@ struct Candidate<'pat, 'tcx> {
     has_guard: bool,
 
     /// All of these must be satisfied...
-    match_pairs: SmallVec<[MatchPair<'pat, 'tcx>; 1]>,
+    // Invariant: all the `MatchPair`s are recursively simplified.
+    // Invariant: or-patterns must be sorted at the end.
+    match_pairs: Vec<MatchPair<'pat, 'tcx>>,
 
     /// ...these bindings established...
+    // Invariant: not mutated outside `Candidate::new()`.
     bindings: Vec<Binding<'tcx>>,
 
     /// ...and these types asserted...
+    // Invariant: not mutated outside `Candidate::new()`.
     ascriptions: Vec<Ascription<'tcx>>,
 
     /// ...and if this is non-empty, one of these subcandidates also has to match...
@@ -974,19 +976,27 @@ impl<'tcx, 'pat> Candidate<'pat, 'tcx> {
         place: PlaceBuilder<'tcx>,
         pattern: &'pat Pat<'tcx>,
         has_guard: bool,
-        cx: &Builder<'_, 'tcx>,
+        cx: &mut Builder<'_, 'tcx>,
     ) -> Self {
-        Candidate {
+        let mut candidate = Candidate {
             span: pattern.span,
             has_guard,
-            match_pairs: smallvec![MatchPair::new(place, pattern, cx)],
+            match_pairs: vec![MatchPair::new(place, pattern, cx)],
             bindings: Vec::new(),
             ascriptions: Vec::new(),
             subcandidates: Vec::new(),
             otherwise_block: None,
             pre_binding_block: None,
             next_candidate_pre_binding_block: None,
-        }
+        };
+
+        cx.simplify_match_pairs(
+            &mut candidate.match_pairs,
+            &mut candidate.bindings,
+            &mut candidate.ascriptions,
+        );
+
+        candidate
     }
 
     /// Visit the leaf candidates (those with no subcandidates) contained in
@@ -1042,13 +1052,18 @@ struct Ascription<'tcx> {
     variance: ty::Variance,
 }
 
-#[derive(Clone, Debug)]
+#[derive(Debug)]
 pub(crate) struct MatchPair<'pat, 'tcx> {
-    // this place...
+    // This place...
     place: PlaceBuilder<'tcx>,
 
     // ... must match this pattern.
+    // Invariant: after creation and simplification in `Candidate::new()`, all match pairs must be
+    // simplified, i.e. require a test.
     pattern: &'pat Pat<'tcx>,
+
+    /// Precomputed sub-match pairs of `pattern`.
+    subpairs: Vec<Self>,
 }
 
 /// See [`Test`] for more.
@@ -1165,12 +1180,25 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         candidates: &mut [&mut Candidate<'pat, 'tcx>],
         fake_borrows: &mut Option<FxIndexSet<Place<'tcx>>>,
     ) {
-        // Start by simplifying candidates. Once this process is complete, all
-        // the match pairs which remain require some form of test, whether it
-        // be a switch or pattern comparison.
         let mut split_or_candidate = false;
         for candidate in &mut *candidates {
-            split_or_candidate |= self.simplify_candidate(candidate);
+            if let [MatchPair { pattern: Pat { kind: PatKind::Or { pats }, .. }, place, .. }] =
+                &*candidate.match_pairs
+            {
+                // Split a candidate in which the only match-pair is an or-pattern into multiple
+                // candidates. This is so that
+                //
+                // match x {
+                //     0 | 1 => { ... },
+                //     2 | 3 => { ... },
+                // }
+                //
+                // only generates a single switch.
+                candidate.subcandidates =
+                    self.create_or_subcandidates(place, pats, candidate.has_guard);
+                candidate.match_pairs.pop();
+                split_or_candidate = true;
+            }
         }
 
         ensure_sufficient_stack(|| {
diff --git a/compiler/rustc_mir_build/src/build/matches/simplify.rs b/compiler/rustc_mir_build/src/build/matches/simplify.rs
index dba8e8b6499..83922dce327 100644
--- a/compiler/rustc_mir_build/src/build/matches/simplify.rs
+++ b/compiler/rustc_mir_build/src/build/matches/simplify.rs
@@ -6,7 +6,7 @@
 //! - `place @ (P1, P2)` can be simplified to `[place.0 @ P1, place.1 @ P2]`
 //! - `place @ x` can be simplified to `[]` by binding `x` to `place`
 //!
-//! The `simplify_candidate` routine just repeatedly applies these
+//! The `simplify_match_pairs` routine just repeatedly applies these
 //! sort of simplifications until there is nothing left to
 //! simplify. Match pairs cannot be simplified if they require some
 //! sort of test: for example, testing which variant an enum is, or
@@ -22,25 +22,15 @@ use rustc_middle::ty;
 use std::mem;
 
 impl<'a, 'tcx> Builder<'a, 'tcx> {
-    /// Simplify a candidate so that all match pairs require a test.
-    ///
-    /// This method will also split a candidate, in which the only
-    /// match-pair is an or-pattern, into multiple candidates.
-    /// This is so that
-    ///
-    /// match x {
-    ///     0 | 1 => { ... },
-    ///     2 | 3 => { ... },
-    /// }
-    ///
-    /// only generates a single switch. If this happens this method returns
-    /// `true`.
-    #[instrument(skip(self, candidate), level = "debug")]
-    pub(super) fn simplify_candidate<'pat>(
+    /// Simplify a list of match pairs so they all require a test. Stores relevant bindings and
+    /// ascriptions in the provided `Vec`s.
+    #[instrument(skip(self), level = "debug")]
+    pub(super) fn simplify_match_pairs<'pat>(
         &mut self,
-        candidate: &mut Candidate<'pat, 'tcx>,
-    ) -> bool {
-        debug!("{candidate:#?}");
+        match_pairs: &mut Vec<MatchPair<'pat, 'tcx>>,
+        candidate_bindings: &mut Vec<Binding<'tcx>>,
+        candidate_ascriptions: &mut Vec<Ascription<'tcx>>,
+    ) {
         // In order to please the borrow checker, in a pattern like `x @ pat` we must lower the
         // bindings in `pat` before `x`. E.g. (#69971):
         //
@@ -68,105 +58,96 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         // bindings in iter 2: [6, 7]
         //
         // final bindings: [6, 7, 4, 5, 1, 2, 3]
-        let mut accumulated_bindings = mem::take(&mut candidate.bindings);
-        // Repeatedly simplify match pairs until fixed point is reached
+        let mut accumulated_bindings = mem::take(candidate_bindings);
+        let mut simplified_match_pairs = Vec::new();
+        // Repeatedly simplify match pairs until we're left with only unsimplifiable ones.
         loop {
-            let mut changed = false;
-            for match_pair in mem::take(&mut candidate.match_pairs) {
-                match self.simplify_match_pair(match_pair, candidate) {
-                    Ok(()) => {
-                        changed = true;
-                    }
-                    Err(match_pair) => {
-                        candidate.match_pairs.push(match_pair);
-                    }
+            for match_pair in mem::take(match_pairs) {
+                if let Err(match_pair) = self.simplify_match_pair(
+                    match_pair,
+                    candidate_bindings,
+                    candidate_ascriptions,
+                    match_pairs,
+                ) {
+                    simplified_match_pairs.push(match_pair);
                 }
             }
 
             // This does: accumulated_bindings = candidate.bindings.take() ++ accumulated_bindings
-            candidate.bindings.extend_from_slice(&accumulated_bindings);
-            mem::swap(&mut candidate.bindings, &mut accumulated_bindings);
-            candidate.bindings.clear();
+            candidate_bindings.extend_from_slice(&accumulated_bindings);
+            mem::swap(candidate_bindings, &mut accumulated_bindings);
+            candidate_bindings.clear();
 
-            if !changed {
-                // If we were not able to simplify anymore, done.
+            if match_pairs.is_empty() {
                 break;
             }
         }
 
-        // Store computed bindings back in `candidate`.
-        mem::swap(&mut candidate.bindings, &mut accumulated_bindings);
-
-        let did_expand_or =
-            if let [MatchPair { pattern: Pat { kind: PatKind::Or { pats }, .. }, place }] =
-                &*candidate.match_pairs
-            {
-                candidate.subcandidates = self.create_or_subcandidates(candidate, place, pats);
-                candidate.match_pairs.clear();
-                true
-            } else {
-                false
-            };
+        // Store computed bindings back in `candidate_bindings`.
+        mem::swap(candidate_bindings, &mut accumulated_bindings);
+        // Store simplified match pairs back in `match_pairs`.
+        mem::swap(match_pairs, &mut simplified_match_pairs);
 
         // Move or-patterns to the end, because they can result in us
         // creating additional candidates, so we want to test them as
         // late as possible.
-        candidate.match_pairs.sort_by_key(|pair| matches!(pair.pattern.kind, PatKind::Or { .. }));
-        debug!(simplified = ?candidate, "simplify_candidate");
-
-        did_expand_or
+        match_pairs.sort_by_key(|pair| matches!(pair.pattern.kind, PatKind::Or { .. }));
+        debug!(simplified = ?match_pairs, "simplify_match_pairs");
     }
 
-    /// Given `candidate` that has a single or-pattern for its match-pairs,
-    /// creates a fresh candidate for each of its input subpatterns passed via
-    /// `pats`.
-    fn create_or_subcandidates<'pat>(
+    /// Create a new candidate for each pattern in `pats`, and recursively simplify tje
+    /// single-or-pattern case.
+    pub(super) fn create_or_subcandidates<'pat>(
         &mut self,
-        candidate: &Candidate<'pat, 'tcx>,
         place: &PlaceBuilder<'tcx>,
         pats: &'pat [Box<Pat<'tcx>>],
+        has_guard: bool,
     ) -> Vec<Candidate<'pat, 'tcx>> {
         pats.iter()
             .map(|box pat| {
-                let mut candidate = Candidate::new(place.clone(), pat, candidate.has_guard, self);
-                self.simplify_candidate(&mut candidate);
+                let mut candidate = Candidate::new(place.clone(), pat, has_guard, self);
+                if let [MatchPair { pattern: Pat { kind: PatKind::Or { pats }, .. }, place, .. }] =
+                    &*candidate.match_pairs
+                {
+                    candidate.subcandidates =
+                        self.create_or_subcandidates(place, pats, candidate.has_guard);
+                    candidate.match_pairs.pop();
+                }
                 candidate
             })
             .collect()
     }
 
-    /// Tries to simplify `match_pair`, returning `Ok(())` if
-    /// successful. If successful, new match pairs and bindings will
-    /// have been pushed into the candidate. If no simplification is
-    /// possible, `Err` is returned and no changes are made to
-    /// candidate.
+    /// Tries to simplify `match_pair`, returning `Ok(())` if successful. If successful, new match
+    /// pairs and bindings will have been pushed into the respective `Vec`s. If no simplification is
+    /// possible, `Err` is returned.
     fn simplify_match_pair<'pat>(
         &mut self,
-        match_pair: MatchPair<'pat, 'tcx>,
-        candidate: &mut Candidate<'pat, 'tcx>,
+        mut match_pair: MatchPair<'pat, 'tcx>,
+        bindings: &mut Vec<Binding<'tcx>>,
+        ascriptions: &mut Vec<Ascription<'tcx>>,
+        match_pairs: &mut Vec<MatchPair<'pat, 'tcx>>,
     ) -> Result<(), MatchPair<'pat, 'tcx>> {
         match match_pair.pattern.kind {
+            PatKind::Leaf { .. }
+            | PatKind::Deref { .. }
+            | PatKind::Array { .. }
+            | PatKind::Never
+            | PatKind::Wild
+            | PatKind::Error(_) => {}
+
             PatKind::AscribeUserType {
-                ref subpattern,
                 ascription: thir::Ascription { ref annotation, variance },
+                ..
             } => {
                 // Apply the type ascription to the value at `match_pair.place`
                 if let Some(source) = match_pair.place.try_to_place(self) {
-                    candidate.ascriptions.push(Ascription {
+                    ascriptions.push(Ascription {
                         annotation: annotation.clone(),
                         source,
                         variance,
                     });
                 }
-
-                candidate.match_pairs.push(MatchPair::new(match_pair.place, subpattern, self));
-
-                Ok(())
-            }
-
-            PatKind::Wild | PatKind::Error(_) => {
-                // nothing left to do
-                Ok(())
             }
 
             PatKind::Binding {
@@ -175,35 +156,17 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 mode,
                 var,
                 ty: _,
-                ref subpattern,
+                subpattern: _,
                 is_primary: _,
             } => {
                 if let Some(source) = match_pair.place.try_to_place(self) {
-                    candidate.bindings.push(Binding {
+                    bindings.push(Binding {
                         span: match_pair.pattern.span,
                         source,
                         var_id: var,
                         binding_mode: mode,
                     });
                 }
-
-                if let Some(subpattern) = subpattern.as_ref() {
-                    // this is the `x @ P` case; have to keep matching against `P` now
-                    candidate.match_pairs.push(MatchPair::new(match_pair.place, subpattern, self));
-                }
-
-                Ok(())
-            }
-
-            PatKind::Never => {
-                // A never pattern acts like a load from the place.
-                // FIXME(never_patterns): load from the place
-                Ok(())
-            }
-
-            PatKind::Constant { .. } => {
-                // FIXME normalize patterns when possible
-                Err(match_pair)
             }
 
             PatKind::InlineConstant { subpattern: ref pattern, def } => {
@@ -232,42 +195,33 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                         span,
                         user_ty: Box::new(user_ty),
                     };
-                    candidate.ascriptions.push(Ascription {
+                    ascriptions.push(Ascription {
                         annotation,
                         source,
                         variance: ty::Contravariant,
                     });
                 }
-                candidate.match_pairs.push(MatchPair::new(match_pair.place, pattern, self));
+            }
 
-                Ok(())
+            PatKind::Constant { .. } => {
+                // FIXME normalize patterns when possible
+                return Err(match_pair);
             }
 
             PatKind::Range(ref range) => {
-                if let Some(true) = range.is_full_range(self.tcx) {
-                    // Irrefutable pattern match.
-                    return Ok(());
+                if range.is_full_range(self.tcx) != Some(true) {
+                    return Err(match_pair);
                 }
-                Err(match_pair)
             }
 
             PatKind::Slice { ref prefix, ref slice, ref suffix } => {
-                if prefix.is_empty() && slice.is_some() && suffix.is_empty() {
-                    // irrefutable
-                    self.prefix_slice_suffix(
-                        &mut candidate.match_pairs,
-                        &match_pair.place,
-                        prefix,
-                        slice,
-                        suffix,
-                    );
-                    Ok(())
-                } else {
-                    Err(match_pair)
+                if !(prefix.is_empty() && slice.is_some() && suffix.is_empty()) {
+                    self.simplify_match_pairs(&mut match_pair.subpairs, bindings, ascriptions);
+                    return Err(match_pair);
                 }
             }
 
-            PatKind::Variant { adt_def, args, variant_index, ref subpatterns } => {
+            PatKind::Variant { adt_def, args, variant_index, subpatterns: _ } => {
                 let irrefutable = adt_def.variants().iter_enumerated().all(|(i, v)| {
                     i == variant_index || {
                         (self.tcx.features().exhaustive_patterns
@@ -279,41 +233,17 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                     }
                 }) && (adt_def.did().is_local()
                     || !adt_def.is_variant_list_non_exhaustive());
-                if irrefutable {
-                    let place_builder = match_pair.place.downcast(adt_def, variant_index);
-                    candidate
-                        .match_pairs
-                        .extend(self.field_match_pairs(place_builder, subpatterns));
-                    Ok(())
-                } else {
-                    Err(match_pair)
+                if !irrefutable {
+                    self.simplify_match_pairs(&mut match_pair.subpairs, bindings, ascriptions);
+                    return Err(match_pair);
                 }
             }
 
-            PatKind::Array { ref prefix, ref slice, ref suffix } => {
-                self.prefix_slice_suffix(
-                    &mut candidate.match_pairs,
-                    &match_pair.place,
-                    prefix,
-                    slice,
-                    suffix,
-                );
-                Ok(())
-            }
-
-            PatKind::Leaf { ref subpatterns } => {
-                // tuple struct, match subpats (if any)
-                candidate.match_pairs.extend(self.field_match_pairs(match_pair.place, subpatterns));
-                Ok(())
-            }
-
-            PatKind::Deref { ref subpattern } => {
-                let place_builder = match_pair.place.deref();
-                candidate.match_pairs.push(MatchPair::new(place_builder, subpattern, self));
-                Ok(())
-            }
-
-            PatKind::Or { .. } => Err(match_pair),
+            PatKind::Or { .. } => return Err(match_pair),
         }
+
+        // Simplifiable pattern; we replace it with its subpairs.
+        match_pairs.append(&mut match_pair.subpairs);
+        Ok(())
     }
 }
diff --git a/compiler/rustc_mir_build/src/build/matches/test.rs b/compiler/rustc_mir_build/src/build/matches/test.rs
index 990be30b2d6..ae9ebe7170b 100644
--- a/compiler/rustc_mir_build/src/build/matches/test.rs
+++ b/compiler/rustc_mir_build/src/build/matches/test.rs
@@ -590,25 +590,22 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         let (match_pair_index, match_pair) =
             candidate.match_pairs.iter().enumerate().find(|&(_, mp)| mp.place == *test_place)?;
 
-        match (&test.kind, &match_pair.pattern.kind) {
+        let fully_matched;
+        let ret = match (&test.kind, &match_pair.pattern.kind) {
             // If we are performing a variant switch, then this
             // informs variant patterns, but nothing else.
             (
                 &TestKind::Switch { adt_def: tested_adt_def, .. },
-                &PatKind::Variant { adt_def, variant_index, ref subpatterns, .. },
+                &PatKind::Variant { adt_def, variant_index, .. },
             ) => {
                 assert_eq!(adt_def, tested_adt_def);
-                self.candidate_after_variant_switch(
-                    match_pair_index,
-                    adt_def,
-                    variant_index,
-                    subpatterns,
-                    candidate,
-                );
+                fully_matched = true;
                 Some(variant_index.as_usize())
             }
-
-            (&TestKind::Switch { .. }, _) => None,
+            (&TestKind::Switch { .. }, _) => {
+                fully_matched = false;
+                None
+            }
 
             // If we are performing a switch over integers, then this informs integer
             // equality, but nothing else.
@@ -618,12 +615,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
             (TestKind::SwitchInt { switch_ty: _, options }, PatKind::Constant { value })
                 if is_switch_ty(match_pair.pattern.ty) =>
             {
+                fully_matched = true;
                 let index = options.get_index_of(value).unwrap();
-                self.candidate_without_match_pair(match_pair_index, candidate);
                 Some(index)
             }
-
             (TestKind::SwitchInt { switch_ty: _, options }, PatKind::Range(range)) => {
+                fully_matched = false;
                 let not_contained =
                     self.values_not_contained_in_range(&*range, options).unwrap_or(false);
 
@@ -633,8 +630,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                     options.len()
                 })
             }
-
-            (&TestKind::SwitchInt { .. }, _) => None,
+            (&TestKind::SwitchInt { .. }, _) => {
+                fully_matched = false;
+                None
+            }
 
             (
                 &TestKind::Len { len: test_len, op: BinOp::Eq },
@@ -645,34 +644,30 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                     (Ordering::Equal, &None) => {
                         // on true, min_len = len = $actual_length,
                         // on false, len != $actual_length
-                        self.candidate_after_slice_test(
-                            match_pair_index,
-                            candidate,
-                            prefix,
-                            slice,
-                            suffix,
-                        );
+                        fully_matched = true;
                         Some(0)
                     }
                     (Ordering::Less, _) => {
                         // test_len < pat_len. If $actual_len = test_len,
                         // then $actual_len < pat_len and we don't have
                         // enough elements.
+                        fully_matched = false;
                         Some(1)
                     }
                     (Ordering::Equal | Ordering::Greater, &Some(_)) => {
                         // This can match both if $actual_len = test_len >= pat_len,
                         // and if $actual_len > test_len. We can't advance.
+                        fully_matched = false;
                         None
                     }
                     (Ordering::Greater, &None) => {
                         // test_len != pat_len, so if $actual_len = test_len, then
                         // $actual_len != pat_len.
+                        fully_matched = false;
                         Some(1)
                     }
                 }
             }
-
             (
                 &TestKind::Len { len: test_len, op: BinOp::Ge },
                 PatKind::Slice { prefix, slice, suffix },
@@ -683,29 +678,26 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                     (Ordering::Equal, &Some(_)) => {
                         // $actual_len >= test_len = pat_len,
                         // so we can match.
-                        self.candidate_after_slice_test(
-                            match_pair_index,
-                            candidate,
-                            prefix,
-                            slice,
-                            suffix,
-                        );
+                        fully_matched = true;
                         Some(0)
                     }
                     (Ordering::Less, _) | (Ordering::Equal, &None) => {
                         // test_len <= pat_len. If $actual_len < test_len,
                         // then it is also < pat_len, so the test passing is
                         // necessary (but insufficient).
+                        fully_matched = false;
                         Some(0)
                     }
                     (Ordering::Greater, &None) => {
                         // test_len > pat_len. If $actual_len >= test_len > pat_len,
                         // then we know we won't have a match.
+                        fully_matched = false;
                         Some(1)
                     }
                     (Ordering::Greater, &Some(_)) => {
                         // test_len < pat_len, and is therefore less
                         // strict. This can still go both ways.
+                        fully_matched = false;
                         None
                     }
                 }
@@ -713,16 +705,17 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
 
             (TestKind::Range(test), PatKind::Range(pat)) => {
                 if test == pat {
-                    self.candidate_without_match_pair(match_pair_index, candidate);
-                    return Some(0);
+                    fully_matched = true;
+                    Some(0)
+                } else {
+                    fully_matched = false;
+                    // If the testing range does not overlap with pattern range,
+                    // the pattern can be matched only if this test fails.
+                    if !test.overlaps(pat, self.tcx, self.param_env)? { Some(1) } else { None }
                 }
-
-                // If the testing range does not overlap with pattern range,
-                // the pattern can be matched only if this test fails.
-                if !test.overlaps(pat, self.tcx, self.param_env)? { Some(1) } else { None }
             }
-
             (TestKind::Range(range), &PatKind::Constant { value }) => {
+                fully_matched = false;
                 if !range.contains(value, self.tcx, self.param_env)? {
                     // `value` is not contained in the testing range,
                     // so `value` can be matched only if this test fails.
@@ -731,8 +724,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                     None
                 }
             }
-
-            (&TestKind::Range { .. }, _) => None,
+            (&TestKind::Range { .. }, _) => {
+                fully_matched = false;
+                None
+            }
 
             (&TestKind::Eq { .. } | &TestKind::Len { .. }, _) => {
                 // The call to `self.test(&match_pair)` below is not actually used to generate any
@@ -751,64 +746,26 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 // FIXME(#29623) we can be more clever here
                 let pattern_test = self.test(match_pair);
                 if pattern_test.kind == test.kind {
-                    self.candidate_without_match_pair(match_pair_index, candidate);
+                    fully_matched = true;
                     Some(0)
                 } else {
+                    fully_matched = false;
                     None
                 }
             }
-        }
-    }
-
-    fn candidate_without_match_pair(
-        &mut self,
-        match_pair_index: usize,
-        candidate: &mut Candidate<'_, 'tcx>,
-    ) {
-        candidate.match_pairs.remove(match_pair_index);
-    }
+        };
 
-    fn candidate_after_slice_test<'pat>(
-        &mut self,
-        match_pair_index: usize,
-        candidate: &mut Candidate<'pat, 'tcx>,
-        prefix: &'pat [Box<Pat<'tcx>>],
-        opt_slice: &'pat Option<Box<Pat<'tcx>>>,
-        suffix: &'pat [Box<Pat<'tcx>>],
-    ) {
-        let removed_place = candidate.match_pairs.remove(match_pair_index).place;
-        self.prefix_slice_suffix(
-            &mut candidate.match_pairs,
-            &removed_place,
-            prefix,
-            opt_slice,
-            suffix,
-        );
-    }
+        if fully_matched {
+            // Replace the match pair by its sub-pairs.
+            let match_pair = candidate.match_pairs.remove(match_pair_index);
+            candidate.match_pairs.extend(match_pair.subpairs);
+            // Move or-patterns to the end.
+            candidate
+                .match_pairs
+                .sort_by_key(|pair| matches!(pair.pattern.kind, PatKind::Or { .. }));
+        }
 
-    fn candidate_after_variant_switch<'pat>(
-        &mut self,
-        match_pair_index: usize,
-        adt_def: ty::AdtDef<'tcx>,
-        variant_index: VariantIdx,
-        subpatterns: &'pat [FieldPat<'tcx>],
-        candidate: &mut Candidate<'pat, 'tcx>,
-    ) {
-        let match_pair = candidate.match_pairs.remove(match_pair_index);
-
-        // So, if we have a match-pattern like `x @ Enum::Variant(P1, P2)`,
-        // we want to create a set of derived match-patterns like
-        // `(x as Variant).0 @ P1` and `(x as Variant).1 @ P1`.
-        let downcast_place = match_pair.place.downcast(adt_def, variant_index); // `(x as Variant)`
-        let consequent_match_pairs = subpatterns.iter().map(|subpattern| {
-            // e.g., `(x as Variant).0`
-            let place = downcast_place
-                .clone_project(PlaceElem::Field(subpattern.field, subpattern.pattern.ty));
-            // e.g., `(x as Variant).0 @ P1`
-            MatchPair::new(place, &subpattern.pattern, self)
-        });
-
-        candidate.match_pairs.extend(consequent_match_pairs);
+        ret
     }
 
     fn error_simplifiable<'pat>(&mut self, match_pair: &MatchPair<'pat, 'tcx>) -> ! {
diff --git a/compiler/rustc_mir_build/src/build/matches/util.rs b/compiler/rustc_mir_build/src/build/matches/util.rs
index c34105174ef..e42d764147c 100644
--- a/compiler/rustc_mir_build/src/build/matches/util.rs
+++ b/compiler/rustc_mir_build/src/build/matches/util.rs
@@ -6,7 +6,6 @@ use rustc_middle::mir::*;
 use rustc_middle::thir::*;
 use rustc_middle::ty;
 use rustc_middle::ty::TypeVisitableExt;
-use smallvec::SmallVec;
 
 impl<'a, 'tcx> Builder<'a, 'tcx> {
     pub(crate) fn field_match_pairs<'pat>(
@@ -26,7 +25,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
 
     pub(crate) fn prefix_slice_suffix<'pat>(
         &mut self,
-        match_pairs: &mut SmallVec<[MatchPair<'pat, 'tcx>; 1]>,
+        match_pairs: &mut Vec<MatchPair<'pat, 'tcx>>,
         place: &PlaceBuilder<'tcx>,
         prefix: &'pat [Box<Pat<'tcx>>],
         opt_slice: &'pat Option<Box<Pat<'tcx>>>,
@@ -97,7 +96,7 @@ impl<'pat, 'tcx> MatchPair<'pat, 'tcx> {
     pub(in crate::build) fn new(
         mut place: PlaceBuilder<'tcx>,
         pattern: &'pat Pat<'tcx>,
-        cx: &Builder<'_, 'tcx>,
+        cx: &mut Builder<'_, 'tcx>,
     ) -> MatchPair<'pat, 'tcx> {
         // Force the place type to the pattern's type.
         // FIXME(oli-obk): can we use this to simplify slice/array pattern hacks?
@@ -117,6 +116,51 @@ impl<'pat, 'tcx> MatchPair<'pat, 'tcx> {
         if may_need_cast {
             place = place.project(ProjectionElem::OpaqueCast(pattern.ty));
         }
-        MatchPair { place, pattern }
+
+        let mut subpairs = Vec::new();
+        match pattern.kind {
+            PatKind::Constant { .. }
+            | PatKind::Range(_)
+            | PatKind::Or { .. }
+            | PatKind::Never
+            | PatKind::Wild
+            | PatKind::Error(_) => {}
+
+            PatKind::AscribeUserType { ref subpattern, .. } => {
+                subpairs.push(MatchPair::new(place.clone(), subpattern, cx));
+            }
+
+            PatKind::Binding { ref subpattern, .. } => {
+                if let Some(subpattern) = subpattern.as_ref() {
+                    // this is the `x @ P` case; have to keep matching against `P` now
+                    subpairs.push(MatchPair::new(place.clone(), subpattern, cx));
+                }
+            }
+
+            PatKind::InlineConstant { subpattern: ref pattern, .. } => {
+                subpairs.push(MatchPair::new(place.clone(), pattern, cx));
+            }
+
+            PatKind::Slice { ref prefix, ref slice, ref suffix }
+            | PatKind::Array { ref prefix, ref slice, ref suffix } => {
+                cx.prefix_slice_suffix(&mut subpairs, &place, prefix, slice, suffix);
+            }
+
+            PatKind::Variant { adt_def, variant_index, ref subpatterns, .. } => {
+                let downcast_place = place.clone().downcast(adt_def, variant_index); // `(x as Variant)`
+                subpairs = cx.field_match_pairs(downcast_place, subpatterns);
+            }
+
+            PatKind::Leaf { ref subpatterns } => {
+                subpairs = cx.field_match_pairs(place.clone(), subpatterns);
+            }
+
+            PatKind::Deref { ref subpattern } => {
+                let place_builder = place.clone().deref();
+                subpairs.push(MatchPair::new(place_builder, subpattern, cx));
+            }
+        }
+
+        MatchPair { place, pattern, subpairs }
     }
 }
diff --git a/tests/mir-opt/exponential_or.match_tuple.SimplifyCfg-initial.after.mir b/tests/mir-opt/exponential_or.match_tuple.SimplifyCfg-initial.after.mir
index 596dcef85fd..ea5cd55b560 100644
--- a/tests/mir-opt/exponential_or.match_tuple.SimplifyCfg-initial.after.mir
+++ b/tests/mir-opt/exponential_or.match_tuple.SimplifyCfg-initial.after.mir
@@ -19,7 +19,8 @@ fn match_tuple(_1: (u32, bool, Option<i32>, u32)) -> u32 {
 
     bb0: {
         PlaceMention(_1);
-        switchInt((_1.0: u32)) -> [1: bb2, 4: bb2, otherwise: bb1];
+        _2 = discriminant((_1.2: std::option::Option<i32>));
+        switchInt(move _2) -> [0: bb3, 1: bb2, otherwise: bb1];
     }
 
     bb1: {
@@ -28,12 +29,11 @@ fn match_tuple(_1: (u32, bool, Option<i32>, u32)) -> u32 {
     }
 
     bb2: {
-        _2 = discriminant((_1.2: std::option::Option<i32>));
-        switchInt(move _2) -> [0: bb4, 1: bb3, otherwise: bb1];
+        switchInt((((_1.2: std::option::Option<i32>) as Some).0: i32)) -> [1: bb3, 8: bb3, otherwise: bb1];
     }
 
     bb3: {
-        switchInt((((_1.2: std::option::Option<i32>) as Some).0: i32)) -> [1: bb4, 8: bb4, otherwise: bb1];
+        switchInt((_1.0: u32)) -> [1: bb4, 4: bb4, otherwise: bb1];
     }
 
     bb4: {