From ce86b2ae962f3d6643153533d7bf722741d9e01f Mon Sep 17 00:00:00 2001 From: Zalathar Date: Tue, 9 Jul 2024 14:45:47 +1000 Subject: [PATCH 1/2] Move `MatchPair` tree creation to its own module This makes it easier to see that `MatchPair::new` has only one non-recursive caller, because the recursive callers are all in this module. --- .../src/build/matches/match_pair.rs | 245 ++++++++++++++++++ .../rustc_mir_build/src/build/matches/mod.rs | 1 + .../rustc_mir_build/src/build/matches/util.rs | 242 +---------------- 3 files changed, 248 insertions(+), 240 deletions(-) create mode 100644 compiler/rustc_mir_build/src/build/matches/match_pair.rs diff --git a/compiler/rustc_mir_build/src/build/matches/match_pair.rs b/compiler/rustc_mir_build/src/build/matches/match_pair.rs new file mode 100644 index 0000000000000..a26b6d49aed7a --- /dev/null +++ b/compiler/rustc_mir_build/src/build/matches/match_pair.rs @@ -0,0 +1,245 @@ +use rustc_middle::mir::*; +use rustc_middle::thir::{self, *}; +use rustc_middle::ty::{self, Ty, TypeVisitableExt}; + +use crate::build::expr::as_place::{PlaceBase, PlaceBuilder}; +use crate::build::matches::{FlatPat, MatchPair, TestCase}; +use crate::build::Builder; + +impl<'a, 'tcx> Builder<'a, 'tcx> { + fn field_match_pairs<'pat>( + &mut self, + place: PlaceBuilder<'tcx>, + subpatterns: &'pat [FieldPat<'tcx>], + ) -> Vec> { + subpatterns + .iter() + .map(|fieldpat| { + let place = + place.clone_project(PlaceElem::Field(fieldpat.field, fieldpat.pattern.ty)); + MatchPair::new(place, &fieldpat.pattern, self) + }) + .collect() + } + + fn prefix_slice_suffix<'pat>( + &mut self, + match_pairs: &mut Vec>, + place: &PlaceBuilder<'tcx>, + prefix: &'pat [Box>], + opt_slice: &'pat Option>>, + suffix: &'pat [Box>], + ) { + let tcx = self.tcx; + let (min_length, exact_size) = if let Some(place_resolved) = place.try_to_place(self) { + match place_resolved.ty(&self.local_decls, tcx).ty.kind() { + ty::Array(_, length) => (length.eval_target_usize(tcx, self.param_env), true), + _ => ((prefix.len() + suffix.len()).try_into().unwrap(), false), + } + } else { + ((prefix.len() + suffix.len()).try_into().unwrap(), false) + }; + + match_pairs.extend(prefix.iter().enumerate().map(|(idx, subpattern)| { + let elem = + ProjectionElem::ConstantIndex { offset: idx as u64, min_length, from_end: false }; + MatchPair::new(place.clone_project(elem), subpattern, self) + })); + + if let Some(subslice_pat) = opt_slice { + let suffix_len = suffix.len() as u64; + let subslice = place.clone_project(PlaceElem::Subslice { + from: prefix.len() as u64, + to: if exact_size { min_length - suffix_len } else { suffix_len }, + from_end: !exact_size, + }); + match_pairs.push(MatchPair::new(subslice, subslice_pat, self)); + } + + match_pairs.extend(suffix.iter().rev().enumerate().map(|(idx, subpattern)| { + let end_offset = (idx + 1) as u64; + let elem = ProjectionElem::ConstantIndex { + offset: if exact_size { min_length - end_offset } else { end_offset }, + min_length, + from_end: !exact_size, + }; + let place = place.clone_project(elem); + MatchPair::new(place, subpattern, self) + })); + } +} + +impl<'pat, 'tcx> MatchPair<'pat, 'tcx> { + /// Recursively builds a `MatchPair` tree for the given pattern and its + /// subpatterns. + pub(in crate::build) fn new( + mut place_builder: PlaceBuilder<'tcx>, + pattern: &'pat Pat<'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? + if let Some(resolved) = place_builder.resolve_upvar(cx) { + place_builder = resolved; + } + + // Only add the OpaqueCast projection if the given place is an opaque type and the + // expected type from the pattern is not. + let may_need_cast = match place_builder.base() { + PlaceBase::Local(local) => { + let ty = + Place::ty_from(local, place_builder.projection(), &cx.local_decls, cx.tcx).ty; + ty != pattern.ty && ty.has_opaque_types() + } + _ => true, + }; + if may_need_cast { + place_builder = place_builder.project(ProjectionElem::OpaqueCast(pattern.ty)); + } + + let place = place_builder.try_to_place(cx); + let default_irrefutable = || TestCase::Irrefutable { binding: None, ascription: None }; + let mut subpairs = Vec::new(); + let test_case = match pattern.kind { + PatKind::Wild | PatKind::Error(_) => default_irrefutable(), + + PatKind::Or { ref pats } => TestCase::Or { + pats: pats.iter().map(|pat| FlatPat::new(place_builder.clone(), pat, cx)).collect(), + }, + + PatKind::Range(ref range) => { + if range.is_full_range(cx.tcx) == Some(true) { + default_irrefutable() + } else { + TestCase::Range(range) + } + } + + PatKind::Constant { value } => TestCase::Constant { value }, + + PatKind::AscribeUserType { + ascription: thir::Ascription { ref annotation, variance }, + ref subpattern, + .. + } => { + // Apply the type ascription to the value at `match_pair.place` + let ascription = place.map(|source| super::Ascription { + annotation: annotation.clone(), + source, + variance, + }); + + subpairs.push(MatchPair::new(place_builder, subpattern, cx)); + TestCase::Irrefutable { ascription, binding: None } + } + + PatKind::Binding { mode, var, ref subpattern, .. } => { + let binding = place.map(|source| super::Binding { + span: 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 + subpairs.push(MatchPair::new(place_builder, subpattern, cx)); + } + TestCase::Irrefutable { ascription: None, binding } + } + + PatKind::InlineConstant { subpattern: ref pattern, def, .. } => { + // Apply a type ascription for the inline constant to the value at `match_pair.place` + let ascription = place.map(|source| { + let span = pattern.span; + let parent_id = cx.tcx.typeck_root_def_id(cx.def_id.to_def_id()); + let args = ty::InlineConstArgs::new( + cx.tcx, + ty::InlineConstArgsParts { + parent_args: ty::GenericArgs::identity_for_item(cx.tcx, parent_id), + ty: cx.infcx.next_ty_var(span), + }, + ) + .args; + let user_ty = cx.infcx.canonicalize_user_type_annotation(ty::UserType::TypeOf( + def.to_def_id(), + ty::UserArgs { args, user_self_ty: None }, + )); + let annotation = ty::CanonicalUserTypeAnnotation { + inferred_ty: pattern.ty, + span, + user_ty: Box::new(user_ty), + }; + super::Ascription { annotation, source, variance: ty::Contravariant } + }); + + subpairs.push(MatchPair::new(place_builder, pattern, cx)); + TestCase::Irrefutable { ascription, binding: None } + } + + PatKind::Array { ref prefix, ref slice, ref suffix } => { + cx.prefix_slice_suffix(&mut subpairs, &place_builder, prefix, slice, suffix); + default_irrefutable() + } + PatKind::Slice { ref prefix, ref slice, ref suffix } => { + cx.prefix_slice_suffix(&mut subpairs, &place_builder, prefix, slice, suffix); + + if prefix.is_empty() && slice.is_some() && suffix.is_empty() { + default_irrefutable() + } else { + TestCase::Slice { + len: prefix.len() + suffix.len(), + variable_length: slice.is_some(), + } + } + } + + PatKind::Variant { adt_def, variant_index, args, ref subpatterns } => { + let downcast_place = place_builder.downcast(adt_def, variant_index); // `(x as Variant)` + subpairs = cx.field_match_pairs(downcast_place, subpatterns); + + let irrefutable = adt_def.variants().iter_enumerated().all(|(i, v)| { + i == variant_index || { + (cx.tcx.features().exhaustive_patterns + || cx.tcx.features().min_exhaustive_patterns) + && !v + .inhabited_predicate(cx.tcx, adt_def) + .instantiate(cx.tcx, args) + .apply_ignore_module(cx.tcx, cx.param_env) + } + }) && (adt_def.did().is_local() + || !adt_def.is_variant_list_non_exhaustive()); + if irrefutable { + default_irrefutable() + } else { + TestCase::Variant { adt_def, variant_index } + } + } + + PatKind::Leaf { ref subpatterns } => { + subpairs = cx.field_match_pairs(place_builder, subpatterns); + default_irrefutable() + } + + PatKind::Deref { ref subpattern } => { + subpairs.push(MatchPair::new(place_builder.deref(), subpattern, cx)); + default_irrefutable() + } + + PatKind::DerefPattern { ref subpattern, mutability } => { + // Create a new temporary for each deref pattern. + // FIXME(deref_patterns): dedup temporaries to avoid multiple `deref()` calls? + let temp = cx.temp( + Ty::new_ref(cx.tcx, cx.tcx.lifetimes.re_erased, subpattern.ty, mutability), + pattern.span, + ); + subpairs.push(MatchPair::new(PlaceBuilder::from(temp).deref(), subpattern, cx)); + TestCase::Deref { temp, mutability } + } + + PatKind::Never => TestCase::Never, + }; + + MatchPair { place, test_case, subpairs, pattern } + } +} diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs index 841ef2719c99d..7c655ecde023a 100644 --- a/compiler/rustc_mir_build/src/build/matches/mod.rs +++ b/compiler/rustc_mir_build/src/build/matches/mod.rs @@ -24,6 +24,7 @@ use tracing::{debug, instrument}; use util::visit_bindings; // helper functions, broken out by category: +mod match_pair; mod simplify; mod test; mod util; diff --git a/compiler/rustc_mir_build/src/build/matches/util.rs b/compiler/rustc_mir_build/src/build/matches/util.rs index 3bec154e1df5d..e67fc843285e6 100644 --- a/compiler/rustc_mir_build/src/build/matches/util.rs +++ b/compiler/rustc_mir_build/src/build/matches/util.rs @@ -1,78 +1,15 @@ use std::marker::PhantomData; -use crate::build::expr::as_place::{PlaceBase, PlaceBuilder}; +use crate::build::expr::as_place::PlaceBase; use crate::build::matches::{Binding, Candidate, FlatPat, MatchPair, TestCase}; use crate::build::Builder; use rustc_data_structures::fx::FxIndexMap; use rustc_middle::mir::*; -use rustc_middle::thir::{self, *}; -use rustc_middle::ty::TypeVisitableExt; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::Ty; use rustc_span::Span; use tracing::debug; impl<'a, 'tcx> Builder<'a, 'tcx> { - pub(crate) fn field_match_pairs<'pat>( - &mut self, - place: PlaceBuilder<'tcx>, - subpatterns: &'pat [FieldPat<'tcx>], - ) -> Vec> { - subpatterns - .iter() - .map(|fieldpat| { - let place = - place.clone_project(PlaceElem::Field(fieldpat.field, fieldpat.pattern.ty)); - MatchPair::new(place, &fieldpat.pattern, self) - }) - .collect() - } - - pub(crate) fn prefix_slice_suffix<'pat>( - &mut self, - match_pairs: &mut Vec>, - place: &PlaceBuilder<'tcx>, - prefix: &'pat [Box>], - opt_slice: &'pat Option>>, - suffix: &'pat [Box>], - ) { - let tcx = self.tcx; - let (min_length, exact_size) = if let Some(place_resolved) = place.try_to_place(self) { - match place_resolved.ty(&self.local_decls, tcx).ty.kind() { - ty::Array(_, length) => (length.eval_target_usize(tcx, self.param_env), true), - _ => ((prefix.len() + suffix.len()).try_into().unwrap(), false), - } - } else { - ((prefix.len() + suffix.len()).try_into().unwrap(), false) - }; - - match_pairs.extend(prefix.iter().enumerate().map(|(idx, subpattern)| { - let elem = - ProjectionElem::ConstantIndex { offset: idx as u64, min_length, from_end: false }; - MatchPair::new(place.clone_project(elem), subpattern, self) - })); - - if let Some(subslice_pat) = opt_slice { - let suffix_len = suffix.len() as u64; - let subslice = place.clone_project(PlaceElem::Subslice { - from: prefix.len() as u64, - to: if exact_size { min_length - suffix_len } else { suffix_len }, - from_end: !exact_size, - }); - match_pairs.push(MatchPair::new(subslice, subslice_pat, self)); - } - - match_pairs.extend(suffix.iter().rev().enumerate().map(|(idx, subpattern)| { - let end_offset = (idx + 1) as u64; - let elem = ProjectionElem::ConstantIndex { - offset: if exact_size { min_length - end_offset } else { end_offset }, - min_length, - from_end: !exact_size, - }; - let place = place.clone_project(elem); - MatchPair::new(place, subpattern, self) - })); - } - /// Creates a false edge to `imaginary_target` and a real edge to /// real_target. If `imaginary_target` is none, or is the same as the real /// target, a Goto is generated instead to simplify the generated MIR. @@ -96,181 +33,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } } -impl<'pat, 'tcx> MatchPair<'pat, 'tcx> { - /// Recursively builds a `MatchPair` tree for the given pattern and its - /// subpatterns. - pub(in crate::build) fn new( - mut place_builder: PlaceBuilder<'tcx>, - pattern: &'pat Pat<'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? - if let Some(resolved) = place_builder.resolve_upvar(cx) { - place_builder = resolved; - } - - // Only add the OpaqueCast projection if the given place is an opaque type and the - // expected type from the pattern is not. - let may_need_cast = match place_builder.base() { - PlaceBase::Local(local) => { - let ty = - Place::ty_from(local, place_builder.projection(), &cx.local_decls, cx.tcx).ty; - ty != pattern.ty && ty.has_opaque_types() - } - _ => true, - }; - if may_need_cast { - place_builder = place_builder.project(ProjectionElem::OpaqueCast(pattern.ty)); - } - - let place = place_builder.try_to_place(cx); - let default_irrefutable = || TestCase::Irrefutable { binding: None, ascription: None }; - let mut subpairs = Vec::new(); - let test_case = match pattern.kind { - PatKind::Wild | PatKind::Error(_) => default_irrefutable(), - - PatKind::Or { ref pats } => TestCase::Or { - pats: pats.iter().map(|pat| FlatPat::new(place_builder.clone(), pat, cx)).collect(), - }, - - PatKind::Range(ref range) => { - if range.is_full_range(cx.tcx) == Some(true) { - default_irrefutable() - } else { - TestCase::Range(range) - } - } - - PatKind::Constant { value } => TestCase::Constant { value }, - - PatKind::AscribeUserType { - ascription: thir::Ascription { ref annotation, variance }, - ref subpattern, - .. - } => { - // Apply the type ascription to the value at `match_pair.place` - let ascription = place.map(|source| super::Ascription { - annotation: annotation.clone(), - source, - variance, - }); - - subpairs.push(MatchPair::new(place_builder, subpattern, cx)); - TestCase::Irrefutable { ascription, binding: None } - } - - PatKind::Binding { mode, var, ref subpattern, .. } => { - let binding = place.map(|source| super::Binding { - span: 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 - subpairs.push(MatchPair::new(place_builder, subpattern, cx)); - } - TestCase::Irrefutable { ascription: None, binding } - } - - PatKind::InlineConstant { subpattern: ref pattern, def, .. } => { - // Apply a type ascription for the inline constant to the value at `match_pair.place` - let ascription = place.map(|source| { - let span = pattern.span; - let parent_id = cx.tcx.typeck_root_def_id(cx.def_id.to_def_id()); - let args = ty::InlineConstArgs::new( - cx.tcx, - ty::InlineConstArgsParts { - parent_args: ty::GenericArgs::identity_for_item(cx.tcx, parent_id), - ty: cx.infcx.next_ty_var(span), - }, - ) - .args; - let user_ty = cx.infcx.canonicalize_user_type_annotation(ty::UserType::TypeOf( - def.to_def_id(), - ty::UserArgs { args, user_self_ty: None }, - )); - let annotation = ty::CanonicalUserTypeAnnotation { - inferred_ty: pattern.ty, - span, - user_ty: Box::new(user_ty), - }; - super::Ascription { annotation, source, variance: ty::Contravariant } - }); - - subpairs.push(MatchPair::new(place_builder, pattern, cx)); - TestCase::Irrefutable { ascription, binding: None } - } - - PatKind::Array { ref prefix, ref slice, ref suffix } => { - cx.prefix_slice_suffix(&mut subpairs, &place_builder, prefix, slice, suffix); - default_irrefutable() - } - PatKind::Slice { ref prefix, ref slice, ref suffix } => { - cx.prefix_slice_suffix(&mut subpairs, &place_builder, prefix, slice, suffix); - - if prefix.is_empty() && slice.is_some() && suffix.is_empty() { - default_irrefutable() - } else { - TestCase::Slice { - len: prefix.len() + suffix.len(), - variable_length: slice.is_some(), - } - } - } - - PatKind::Variant { adt_def, variant_index, args, ref subpatterns } => { - let downcast_place = place_builder.downcast(adt_def, variant_index); // `(x as Variant)` - subpairs = cx.field_match_pairs(downcast_place, subpatterns); - - let irrefutable = adt_def.variants().iter_enumerated().all(|(i, v)| { - i == variant_index || { - (cx.tcx.features().exhaustive_patterns - || cx.tcx.features().min_exhaustive_patterns) - && !v - .inhabited_predicate(cx.tcx, adt_def) - .instantiate(cx.tcx, args) - .apply_ignore_module(cx.tcx, cx.param_env) - } - }) && (adt_def.did().is_local() - || !adt_def.is_variant_list_non_exhaustive()); - if irrefutable { - default_irrefutable() - } else { - TestCase::Variant { adt_def, variant_index } - } - } - - PatKind::Leaf { ref subpatterns } => { - subpairs = cx.field_match_pairs(place_builder, subpatterns); - default_irrefutable() - } - - PatKind::Deref { ref subpattern } => { - subpairs.push(MatchPair::new(place_builder.deref(), subpattern, cx)); - default_irrefutable() - } - - PatKind::DerefPattern { ref subpattern, mutability } => { - // Create a new temporary for each deref pattern. - // FIXME(deref_patterns): dedup temporaries to avoid multiple `deref()` calls? - let temp = cx.temp( - Ty::new_ref(cx.tcx, cx.tcx.lifetimes.re_erased, subpattern.ty, mutability), - pattern.span, - ); - subpairs.push(MatchPair::new(PlaceBuilder::from(temp).deref(), subpattern, cx)); - TestCase::Deref { temp, mutability } - } - - PatKind::Never => TestCase::Never, - }; - - MatchPair { place, test_case, subpairs, pattern } - } -} - /// Determine the set of places that have to be stable across match guards. /// /// Returns a list of places that need a fake borrow along with a local to store it. From f7508f881676a3e123245bcf99f9f0d21d4d6b49 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Fri, 12 Jul 2024 12:31:39 +1000 Subject: [PATCH 2/2] Improve internal docs for `MatchPair` --- .../src/build/matches/match_pair.rs | 9 +++++++++ .../rustc_mir_build/src/build/matches/mod.rs | 20 ++++++++++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_mir_build/src/build/matches/match_pair.rs b/compiler/rustc_mir_build/src/build/matches/match_pair.rs index a26b6d49aed7a..2f540478674d7 100644 --- a/compiler/rustc_mir_build/src/build/matches/match_pair.rs +++ b/compiler/rustc_mir_build/src/build/matches/match_pair.rs @@ -7,6 +7,11 @@ use crate::build::matches::{FlatPat, MatchPair, TestCase}; use crate::build::Builder; impl<'a, 'tcx> Builder<'a, 'tcx> { + /// Builds and returns [`MatchPair`] trees, one for each pattern in + /// `subpatterns`, representing the fields of a [`PatKind::Variant`] or + /// [`PatKind::Leaf`]. + /// + /// Used internally by [`MatchPair::new`]. fn field_match_pairs<'pat>( &mut self, place: PlaceBuilder<'tcx>, @@ -22,6 +27,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { .collect() } + /// Builds [`MatchPair`] trees for the prefix/middle/suffix parts of an + /// array pattern or slice pattern, and adds those trees to `match_pairs`. + /// + /// Used internally by [`MatchPair::new`]. fn prefix_slice_suffix<'pat>( &mut self, match_pairs: &mut Vec>, diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs index 7c655ecde023a..98de4df3ce39a 100644 --- a/compiler/rustc_mir_build/src/build/matches/mod.rs +++ b/compiler/rustc_mir_build/src/build/matches/mod.rs @@ -1196,17 +1196,27 @@ impl<'pat, 'tcx> TestCase<'pat, 'tcx> { } } +/// Node in a tree of "match pairs", where each pair consists of a place to be +/// tested, and a test to perform on that place. +/// +/// Each node also has a list of subpairs (possibly empty) that must also match, +/// and a reference to the THIR pattern it represents. #[derive(Debug, Clone)] pub(crate) struct MatchPair<'pat, 'tcx> { /// This place... - // This can be `None` if it referred to a non-captured place in a closure. - // Invariant: place.is_none() => test_case is Irrefutable - // In other words this must be `Some(_)` after simplification. + /// + /// --- + /// This can be `None` if it referred to a non-captured place in a closure. + /// + /// Invariant: Can only be `None` when `test_case` is `Irrefutable`. + /// Therefore this must be `Some(_)` after simplification. place: Option>, /// ... must pass this test... - // Invariant: after creation and simplification in `Candidate::new()`, this must not be - // `Irrefutable`. + /// + /// --- + /// Invariant: after creation and simplification in [`FlatPat::new`], + /// this must not be [`TestCase::Irrefutable`]. test_case: TestCase<'pat, 'tcx>, /// ... and these subpairs must match.