diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index 51f33691bb7dd..b938b679948a9 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -717,23 +717,6 @@ impl<'tcx> Pat<'tcx> { true }) } - - /// Whether this a never pattern. - pub fn is_never_pattern(&self) -> bool { - let mut is_never_pattern = false; - self.walk(|pat| match &pat.kind { - PatKind::Never => { - is_never_pattern = true; - false - } - PatKind::Or { pats } => { - is_never_pattern = pats.iter().all(|p| p.is_never_pattern()); - false - } - _ => true, - }); - is_never_pattern - } } #[derive(Clone, Debug, HashStable, TypeVisitable)] diff --git a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs index e2d00238e2d59..072a26cb5e380 100644 --- a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs +++ b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs @@ -1,10 +1,11 @@ use std::sync::Arc; use rustc_abi::FieldIdx; -use rustc_middle::mir::*; +use rustc_middle::mir::{Pinnedness, Place, PlaceElem, ProjectionElem}; use rustc_middle::span_bug; -use rustc_middle::thir::*; +use rustc_middle::thir::{Ascription, DerefPatBorrowMode, FieldPat, Pat, PatKind}; use rustc_middle::ty::{self, Ty, TypeVisitableExt}; +use rustc_span::Span; use crate::builder::Builder; use crate::builder::expr::as_place::{PlaceBase, PlaceBuilder}; @@ -66,16 +67,158 @@ fn prefix_slice_suffix<'a, 'tcx>( output_pairs } -impl<'tcx> MatchPairTree<'tcx> { - /// Recursively builds a match pair tree for the given pattern and its - /// subpatterns. - pub(super) fn for_pattern( - mut place_builder: PlaceBuilder<'tcx>, +impl<'tcx> FlatPat<'tcx> { + /// Creates a `FlatPat` containing a simplified [`MatchPairTree`] list/forest + /// for the given pattern. + pub(crate) fn new( + place: PlaceBuilder<'tcx>, pattern: &Pat<'tcx>, cx: &mut Builder<'_, 'tcx>, - match_pairs: &mut Vec, // Newly-created nodes are added to this vector - extra_data: &mut PatternExtraData<'tcx>, // Bindings/ascriptions are added here - ) { + ) -> Self { + // Recursively lower the THIR pattern into an intermediate form, + // then flatten into a `FlatPat`. + let inter_pat = InterPat::lower_thir_pat(cx, place, pattern); + FlatPat::from_inter_pat(inter_pat) + } + + fn from_inter_pat(inter_pat: InterPat<'tcx>) -> Self { + let mut match_pairs = vec![]; + let mut extra_data = PatternExtraData { + span: inter_pat.pattern_span, + bindings: vec![], + ascriptions: vec![], + is_never: inter_pat.is_never, + }; + squash_inter_pat(inter_pat, &mut match_pairs, &mut extra_data); + + FlatPat { match_pairs, extra_data } + } +} + +/// Recursively squashes an [`InterPat`] into a forest of refutable [`MatchPairTree`] +/// nodes, while accumulating ascriptions and bindings. +fn squash_inter_pat<'tcx>( + inter_pat: InterPat<'tcx>, + match_pairs: &mut Vec>, // Newly-created nodes are added to this vector + extra_data: &mut PatternExtraData<'tcx>, // Bindings/ascriptions are added here +) { + // Destructure exhaustively to make sure we don't miss any fields. + let InterPat { + place, + testable_case, + subpats, + or_subpats, + ascriptions, + binding, + pattern_span, + is_never: _, // Not needed by `MatchPairTree` forests. + } = inter_pat; + + // Type ascriptions can appear regardless of whether the node is an or-pattern. + extra_data.ascriptions.extend(ascriptions); + + // Or and non-or patterns have very different handling. + if let Some(or_subpats) = or_subpats { + // We're dealing with an or-pattern node. + assert!(testable_case.is_none()); + assert!(subpats.is_empty()); + assert!(binding.is_none()); + + let or_subpats = or_subpats + .into_iter() + .map(|subpat| FlatPat::from_inter_pat(subpat)) + .collect::>(); + + if !or_subpats[0].extra_data.bindings.is_empty() { + // Hold a place for any bindings established in (possibly-nested) or-patterns. + // By only holding a place when bindings are present, we skip over any + // or-patterns that will be simplified by `merge_trivial_subcandidates`. In + // other words, we can assume this expands into subcandidates. + // FIXME(@dianne): this needs updating/removing if we always merge or-patterns + extra_data.bindings.push(super::SubpatternBindings::FromOrPattern); + } + + match_pairs.push(MatchPairTree { + // Or-patterns never need a place during MIR building. + place: None, + testable_case: TestableCase::Or { pats: or_subpats }, + subpairs: vec![], + pattern_span, + }); + } else { + // We're dealing with a node that isn't an or-pattern. + + // Recursively squash any subpatterns into refutable `MatchPairTree` forests. + // This must happen _before_ pushing the binding, as described by the binding step. + let mut subpairs = vec![]; + for subpat in subpats { + squash_inter_pat(subpat, &mut subpairs, extra_data); + } + + if let Some(testable_case) = testable_case { + // This pattern is refutable, so push a new match-pair node. + // + // If this match is inside a closure, it's essential that the place + // we're testing was actually captured! Be sure to keep `ExprUseVisitor` + // in sync with the refutability checks in this module. + assert!(place.is_some()); + assert!(!matches!(testable_case, TestableCase::Or { .. })); + match_pairs.push(MatchPairTree { place, testable_case, subpairs, pattern_span }); + } else { + // This pattern is irrefutable, so it doesn't need its own match-pair node. + // Just push its refutable subpatterns instead, if any. + match_pairs.extend(subpairs); + } + + // If present, the binding must be pushed _after_ traversing subpatterns. + // This is so that when lowering something like `x @ NonCopy { copy_field }`, + // the binding to `copy_field` will occur before the binding for `x`. + // See for more background. + if let Some(binding) = binding { + extra_data.bindings.push(super::SubpatternBindings::One(binding)); + } + } +} + +/// "Intermediate pattern", a partly-lowered THIR [`Pat`] that has not yet been +/// squashed into a forest of refutable [`MatchPairTree`] nodes. +struct InterPat<'tcx> { + /// Place that this pattern node will test. + /// + /// If `None`, we're in a closure that didn't capture the relevant place, + /// because it won't actually be tested. + place: Option>, + /// Testable condition to compare the place to (e.g. "is 3" or "is Some"). + /// + /// If `None`, this pattern node is irrefutable or an or-pattern, + /// though it might have refutable descendants. + testable_case: Option>, + + /// Immediate subpatterns of a node that is *not* an or-pattern. + subpats: Vec>, + /// Immediate subpatterns of an or-pattern node. + /// + /// Invariant: If this is Some, then fields `subpats`, `testable_case`, + /// and `binding` must all be empty. + or_subpats: Option]>>, + + ascriptions: Vec>, + /// Binding to establish for a [`PatKind::Binding`] node. + binding: Option>, + + /// Span field of the THIR pattern this node was created from. + pattern_span: Span, + /// True if this pattern can never match, because all of its alternatives + /// contain a `!` pattern. + is_never: bool, +} + +impl<'tcx> InterPat<'tcx> { + fn lower_thir_pat( + cx: &mut Builder<'_, 'tcx>, + mut place_builder: PlaceBuilder<'tcx>, + pattern: &Pat<'tcx>, + ) -> Self { // Force the place type to the pattern's type. // FIXME(oli-obk): can we use this to simplify slice/array pattern hacks? if let Some(resolved) = place_builder.resolve_upvar(cx) { @@ -99,14 +242,19 @@ impl<'tcx> MatchPairTree<'tcx> { } } + // Variables that will become `InterPat` fields: let place = place_builder.try_to_place(cx); + let mut subpats = vec![]; + let mut or_subpats = None; + let mut ascriptions = vec![]; + let mut binding = None; // Apply any type ascriptions to the value at `match_pair.place`. if let Some(place) = place && let Some(extra) = &pattern.extra { for &Ascription { ref annotation, variance } in &extra.ascriptions { - extra_data.ascriptions.push(super::Ascription { + ascriptions.push(super::Ascription { source: place, annotation: annotation.clone(), variance, @@ -114,22 +262,16 @@ impl<'tcx> MatchPairTree<'tcx> { } } - let mut subpairs = Vec::new(); let testable_case = match pattern.kind { PatKind::Missing | PatKind::Wild | PatKind::Error(_) => None, PatKind::Or { ref pats } => { - let pats: Box<[FlatPat<'tcx>]> = - pats.iter().map(|pat| FlatPat::new(place_builder.clone(), pat, cx)).collect(); - if !pats[0].extra_data.bindings.is_empty() { - // Hold a place for any bindings established in (possibly-nested) or-patterns. - // By only holding a place when bindings are present, we skip over any - // or-patterns that will be simplified by `merge_trivial_subcandidates`. In - // other words, we can assume this expands into subcandidates. - // FIXME(@dianne): this needs updating/removing if we always merge or-patterns - extra_data.bindings.push(super::SubpatternBindings::FromOrPattern); - } - Some(TestableCase::Or { pats }) + or_subpats = Some( + pats.iter() + .map(|subpat| InterPat::lower_thir_pat(cx, place_builder.clone(), subpat)) + .collect::>(), + ); + None } PatKind::Range(ref range) => { @@ -165,48 +307,22 @@ impl<'tcx> MatchPairTree<'tcx> { } PatKind::Binding { mode, var, is_shorthand, ref subpattern, .. } => { - // In order to please the borrow checker, when lowering a pattern - // like `x @ subpat` we must establish any bindings in `subpat` - // before establishing the binding for `x`. - // - // For example (from #69971): - // - // ```ignore (illustrative) - // struct NonCopyStruct { - // copy_field: u32, - // } - // - // fn foo1(x: NonCopyStruct) { - // let y @ NonCopyStruct { copy_field: z } = x; - // // the above should turn into - // let z = x.copy_field; - // let y = x; - // } - // ``` - // First, recurse into the subpattern, if any. if let Some(subpattern) = subpattern.as_ref() { // this is the `x @ P` case; have to keep matching against `P` now - MatchPairTree::for_pattern( - place_builder, - subpattern, - cx, - &mut subpairs, - extra_data, - ); + subpats.push(InterPat::lower_thir_pat(cx, place_builder, subpattern)); } // Then push this binding, after any bindings in the subpattern. - if let Some(source) = place { - extra_data.bindings.push(super::SubpatternBindings::One(super::Binding { + if let Some(place) = place { + binding = Some(super::Binding { span: pattern.span, - source, + source: place, var_id: var, binding_mode: mode, is_shorthand, - })); + }); } - None } @@ -223,7 +339,7 @@ impl<'tcx> MatchPairTree<'tcx> { for (subplace, subpat) in prefix_slice_suffix(&place_builder, Some(array_len), prefix, slice, suffix) { - MatchPairTree::for_pattern(subplace, subpat, cx, &mut subpairs, extra_data); + subpats.push(InterPat::lower_thir_pat(cx, subplace, subpat)); } } else { // If the array length couldn't be determined, ignore the @@ -243,12 +359,12 @@ impl<'tcx> MatchPairTree<'tcx> { for (subplace, subpat) in prefix_slice_suffix(&place_builder, None, prefix, slice, suffix) { - MatchPairTree::for_pattern(subplace, subpat, cx, &mut subpairs, extra_data); + subpats.push(InterPat::lower_thir_pat(cx, subplace, subpat)); } if prefix.is_empty() && slice.is_some() && suffix.is_empty() { - // This pattern is shaped like `[..]`. It can match a slice - // of any length, so no length test is needed. + // A slice pattern shaped like `[..]` is irrefutable. + // It can match a slice of any length, so no length test is needed. None } else { // Any other shape of slice pattern requires a length test. @@ -269,7 +385,7 @@ impl<'tcx> MatchPairTree<'tcx> { let downcast_place = place_builder.downcast(adt_def, variant_index); // `(x as Variant)` for &FieldPat { field, pattern: ref subpat } in subpatterns { let subplace = downcast_place.clone_project(PlaceElem::Field(field, subpat.ty)); - MatchPairTree::for_pattern(subplace, subpat, cx, &mut subpairs, extra_data); + subpats.push(InterPat::lower_thir_pat(cx, subplace, subpat)); } // We treat non-exhaustive enums the same independent of the crate they are @@ -286,7 +402,7 @@ impl<'tcx> MatchPairTree<'tcx> { PatKind::Leaf { ref subpatterns } => { for &FieldPat { field, pattern: ref subpat } in subpatterns { let subplace = place_builder.clone_project(PlaceElem::Field(field, subpat.ty)); - MatchPairTree::for_pattern(subplace, subpat, cx, &mut subpairs, extra_data); + subpats.push(InterPat::lower_thir_pat(cx, subplace, subpat)); } None } @@ -296,27 +412,19 @@ impl<'tcx> MatchPairTree<'tcx> { Some(p_ty) if p_ty.is_ref() => p_ty, _ => span_bug!(pattern.span, "bad type for pinned deref: {:?}", pattern.ty), }; - MatchPairTree::for_pattern( + subpats.push(InterPat::lower_thir_pat( + cx, // Project into the `Pin(_)` struct, then deref the inner `&` or `&mut`. place_builder.field(FieldIdx::ZERO, pinned_ref_ty).deref(), subpattern, - cx, - &mut subpairs, - extra_data, - ); + )); None } PatKind::Deref { pin: Pinnedness::Not, ref subpattern } | PatKind::DerefPattern { ref subpattern, borrow: DerefPatBorrowMode::Box } => { - MatchPairTree::for_pattern( - place_builder.deref(), - subpattern, - cx, - &mut subpairs, - extra_data, - ); + subpats.push(InterPat::lower_thir_pat(cx, place_builder.deref(), subpattern)); None } @@ -330,13 +438,11 @@ impl<'tcx> MatchPairTree<'tcx> { Ty::new_ref(cx.tcx, cx.tcx.lifetimes.re_erased, subpattern.ty, mutability), pattern.span, ); - MatchPairTree::for_pattern( + subpats.push(InterPat::lower_thir_pat( + cx, PlaceBuilder::from(temp).deref(), subpattern, - cx, - &mut subpairs, - extra_data, - ); + )); Some(TestableCase::Deref { temp, mutability }) } @@ -348,24 +454,25 @@ impl<'tcx> MatchPairTree<'tcx> { PatKind::Never => Some(TestableCase::Never), }; - if let Some(testable_case) = testable_case { - // This pattern is refutable, so push a new match-pair node. - // - // Note: unless test_case is TestCase::Or, place must not be None. - // This means that the closure capture analysis in - // rustc_hir_typeck::upvar, and in particular the pattern handling - // code of ExprUseVisitor, must capture all of the places we'll use. - // Make sure to keep these two parts in sync! - match_pairs.push(MatchPairTree { - place, - testable_case, - subpairs, - pattern_span: pattern.span, - }) - } else { - // This pattern is irrefutable, so it doesn't need its own match-pair node. - // Just push its refutable subpatterns instead, if any. - match_pairs.extend(subpairs); + // A pattern node is guaranteed to never match if one of these is true: + // - The node itself is a never pattern (`!`). + // - It is not an or-pattern, and one of its subpatterns will never match. + // - It is an or-pattern, and _all_ of its or-subpatterns will never match. + let is_never = matches!(pattern.kind, PatKind::Never) + || subpats.iter().any(|subpat| subpat.is_never) + || or_subpats + .as_ref() + .is_some_and(|or_subpats| or_subpats.iter().all(|subpat| subpat.is_never)); + + InterPat { + place, + testable_case, + subpats, + or_subpats, + ascriptions, + binding, + pattern_span: pattern.span, + is_never, } } } diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index 5604e86e06722..6b1085b4876ea 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -999,24 +999,6 @@ struct FlatPat<'tcx> { extra_data: PatternExtraData<'tcx>, } -impl<'tcx> FlatPat<'tcx> { - /// Creates a `FlatPat` containing a simplified [`MatchPairTree`] list/forest - /// for the given pattern. - fn new(place: PlaceBuilder<'tcx>, pattern: &Pat<'tcx>, cx: &mut Builder<'_, 'tcx>) -> Self { - // Recursively build a tree of match pairs for the given pattern. - let mut match_pairs = vec![]; - let mut extra_data = PatternExtraData { - span: pattern.span, - bindings: Vec::new(), - ascriptions: Vec::new(), - is_never: pattern.is_never_pattern(), - }; - MatchPairTree::for_pattern(place, pattern, cx, &mut match_pairs, &mut extra_data); - - Self { match_pairs, extra_data } - } -} - /// Candidates are a generalization of (a) top-level match arms, and /// (b) sub-branches of or-patterns, allowing the match-lowering process to handle /// them both in a mostly-uniform way. For example, the list of candidates passed @@ -1226,7 +1208,7 @@ struct Ascription<'tcx> { /// and helps [`TestKind::Switch`] and [`TestKind::SwitchInt`] know what target /// values to use. /// -/// Created by [`MatchPairTree::for_pattern`], and then inspected primarily by: +/// Created by [`MatchPairTree`], and then inspected primarily by: /// - [`Builder::pick_test_for_match_pair`] (to choose a test) /// - [`Builder::choose_bucket_for_candidate`] (to see how the test interacts with a match pair) ///