| //! Interface with `rustc_pattern_analysis`. |
| |
| use std::cell::LazyCell; |
| use std::fmt; |
| |
| use hir_def::{DefWithBodyId, EnumId, EnumVariantId, HasModule, LocalFieldId, ModuleId, VariantId}; |
| use intern::sym; |
| use rustc_pattern_analysis::{ |
| constructor::{Constructor, ConstructorSet, VariantVisibility}, |
| usefulness::{compute_match_usefulness, PlaceValidity, UsefulnessReport}, |
| Captures, IndexVec, PatCx, PrivateUninhabitedField, |
| }; |
| use smallvec::{smallvec, SmallVec}; |
| use stdx::never; |
| |
| use crate::{ |
| db::HirDatabase, |
| infer::normalize, |
| inhabitedness::{is_enum_variant_uninhabited_from, is_ty_uninhabited_from}, |
| AdtId, Interner, Scalar, Ty, TyExt, TyKind, |
| }; |
| |
| use super::{is_box, FieldPat, Pat, PatKind}; |
| |
| use Constructor::*; |
| |
| // Re-export r-a-specific versions of all these types. |
| pub(crate) type DeconstructedPat<'db> = |
| rustc_pattern_analysis::pat::DeconstructedPat<MatchCheckCtx<'db>>; |
| pub(crate) type MatchArm<'db> = rustc_pattern_analysis::MatchArm<'db, MatchCheckCtx<'db>>; |
| pub(crate) type WitnessPat<'db> = rustc_pattern_analysis::pat::WitnessPat<MatchCheckCtx<'db>>; |
| |
| /// [Constructor] uses this in unimplemented variants. |
| /// It allows porting match expressions from upstream algorithm without losing semantics. |
| #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
| pub(crate) enum Void {} |
| |
| /// An index type for enum variants. This ranges from 0 to `variants.len()`, whereas `EnumVariantId` |
| /// can take arbitrary large values (and hence mustn't be used with `IndexVec`/`BitSet`). |
| #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] |
| pub(crate) struct EnumVariantContiguousIndex(usize); |
| |
| impl EnumVariantContiguousIndex { |
| fn from_enum_variant_id(db: &dyn HirDatabase, target_evid: EnumVariantId) -> Self { |
| // Find the index of this variant in the list of variants. |
| use hir_def::Lookup; |
| let i = target_evid.lookup(db.upcast()).index as usize; |
| EnumVariantContiguousIndex(i) |
| } |
| |
| fn to_enum_variant_id(self, db: &dyn HirDatabase, eid: EnumId) -> EnumVariantId { |
| db.enum_data(eid).variants[self.0].0 |
| } |
| } |
| |
| impl rustc_pattern_analysis::Idx for EnumVariantContiguousIndex { |
| fn new(idx: usize) -> Self { |
| EnumVariantContiguousIndex(idx) |
| } |
| |
| fn index(self) -> usize { |
| self.0 |
| } |
| } |
| |
| #[derive(Clone)] |
| pub(crate) struct MatchCheckCtx<'db> { |
| module: ModuleId, |
| body: DefWithBodyId, |
| pub(crate) db: &'db dyn HirDatabase, |
| exhaustive_patterns: bool, |
| } |
| |
| impl<'db> MatchCheckCtx<'db> { |
| pub(crate) fn new(module: ModuleId, body: DefWithBodyId, db: &'db dyn HirDatabase) -> Self { |
| let def_map = db.crate_def_map(module.krate()); |
| let exhaustive_patterns = def_map.is_unstable_feature_enabled(&sym::exhaustive_patterns); |
| Self { module, body, db, exhaustive_patterns } |
| } |
| |
| pub(crate) fn compute_match_usefulness( |
| &self, |
| arms: &[MatchArm<'db>], |
| scrut_ty: Ty, |
| known_valid_scrutinee: Option<bool>, |
| ) -> Result<UsefulnessReport<'db, Self>, ()> { |
| if scrut_ty.contains_unknown() { |
| return Err(()); |
| } |
| for arm in arms { |
| if arm.pat.ty().contains_unknown() { |
| return Err(()); |
| } |
| } |
| |
| let place_validity = PlaceValidity::from_bool(known_valid_scrutinee.unwrap_or(true)); |
| // Measured to take ~100ms on modern hardware. |
| let complexity_limit = Some(500000); |
| compute_match_usefulness(self, arms, scrut_ty, place_validity, complexity_limit) |
| } |
| |
| fn is_uninhabited(&self, ty: &Ty) -> bool { |
| is_ty_uninhabited_from(self.db, ty, self.module) |
| } |
| |
| /// Returns whether the given ADT is from another crate declared `#[non_exhaustive]`. |
| fn is_foreign_non_exhaustive(&self, adt: hir_def::AdtId) -> bool { |
| let is_local = adt.krate(self.db.upcast()) == self.module.krate(); |
| !is_local && self.db.attrs(adt.into()).by_key(&sym::non_exhaustive).exists() |
| } |
| |
| fn variant_id_for_adt( |
| db: &'db dyn HirDatabase, |
| ctor: &Constructor<Self>, |
| adt: hir_def::AdtId, |
| ) -> Option<VariantId> { |
| match ctor { |
| Variant(id) => { |
| let hir_def::AdtId::EnumId(eid) = adt else { |
| panic!("bad constructor {ctor:?} for adt {adt:?}") |
| }; |
| Some(id.to_enum_variant_id(db, eid).into()) |
| } |
| Struct | UnionField => match adt { |
| hir_def::AdtId::EnumId(_) => None, |
| hir_def::AdtId::StructId(id) => Some(id.into()), |
| hir_def::AdtId::UnionId(id) => Some(id.into()), |
| }, |
| _ => panic!("bad constructor {ctor:?} for adt {adt:?}"), |
| } |
| } |
| |
| // This lists the fields of a variant along with their types. |
| fn list_variant_fields<'a>( |
| &'a self, |
| ty: &'a Ty, |
| variant: VariantId, |
| ) -> impl Iterator<Item = (LocalFieldId, Ty)> + Captures<'a> + Captures<'db> { |
| let (_, substs) = ty.as_adt().unwrap(); |
| |
| let field_tys = self.db.field_types(variant); |
| let fields_len = variant.variant_data(self.db.upcast()).fields().len() as u32; |
| |
| (0..fields_len).map(|idx| LocalFieldId::from_raw(idx.into())).map(move |fid| { |
| let ty = field_tys[fid].clone().substitute(Interner, substs); |
| let ty = normalize(self.db, self.db.trait_environment_for_body(self.body), ty); |
| (fid, ty) |
| }) |
| } |
| |
| pub(crate) fn lower_pat(&self, pat: &Pat) -> DeconstructedPat<'db> { |
| let singleton = |pat: DeconstructedPat<'db>| vec![pat.at_index(0)]; |
| let ctor; |
| let mut fields: Vec<_>; |
| let arity; |
| |
| match pat.kind.as_ref() { |
| PatKind::Binding { subpattern: Some(subpat), .. } => return self.lower_pat(subpat), |
| PatKind::Binding { subpattern: None, .. } | PatKind::Wild => { |
| ctor = Wildcard; |
| fields = Vec::new(); |
| arity = 0; |
| } |
| PatKind::Deref { subpattern } => { |
| ctor = match pat.ty.kind(Interner) { |
| // This is a box pattern. |
| TyKind::Adt(adt, _) if is_box(self.db, adt.0) => Struct, |
| TyKind::Ref(..) => Ref, |
| _ => { |
| never!("pattern has unexpected type: pat: {:?}, ty: {:?}", pat, &pat.ty); |
| Wildcard |
| } |
| }; |
| fields = singleton(self.lower_pat(subpattern)); |
| arity = 1; |
| } |
| PatKind::Leaf { subpatterns } | PatKind::Variant { subpatterns, .. } => { |
| fields = subpatterns |
| .iter() |
| .map(|pat| { |
| let idx: u32 = pat.field.into_raw().into(); |
| self.lower_pat(&pat.pattern).at_index(idx as usize) |
| }) |
| .collect(); |
| match pat.ty.kind(Interner) { |
| TyKind::Tuple(_, substs) => { |
| ctor = Struct; |
| arity = substs.len(Interner); |
| } |
| TyKind::Adt(adt, _) if is_box(self.db, adt.0) => { |
| // The only legal patterns of type `Box` (outside `std`) are `_` and box |
| // patterns. If we're here we can assume this is a box pattern. |
| // FIXME(Nadrieril): A `Box` can in theory be matched either with `Box(_, |
| // _)` or a box pattern. As a hack to avoid an ICE with the former, we |
| // ignore other fields than the first one. This will trigger an error later |
| // anyway. |
| // See https://github.com/rust-lang/rust/issues/82772 , |
| // explanation: https://github.com/rust-lang/rust/pull/82789#issuecomment-796921977 |
| // The problem is that we can't know from the type whether we'll match |
| // normally or through box-patterns. We'll have to figure out a proper |
| // solution when we introduce generalized deref patterns. Also need to |
| // prevent mixing of those two options. |
| fields.retain(|ipat| ipat.idx == 0); |
| ctor = Struct; |
| arity = 1; |
| } |
| &TyKind::Adt(AdtId(adt), _) => { |
| ctor = match pat.kind.as_ref() { |
| PatKind::Leaf { .. } if matches!(adt, hir_def::AdtId::UnionId(_)) => { |
| UnionField |
| } |
| PatKind::Leaf { .. } => Struct, |
| PatKind::Variant { enum_variant, .. } => { |
| Variant(EnumVariantContiguousIndex::from_enum_variant_id( |
| self.db, |
| *enum_variant, |
| )) |
| } |
| _ => { |
| never!(); |
| Wildcard |
| } |
| }; |
| let variant = Self::variant_id_for_adt(self.db, &ctor, adt).unwrap(); |
| arity = variant.variant_data(self.db.upcast()).fields().len(); |
| } |
| _ => { |
| never!("pattern has unexpected type: pat: {:?}, ty: {:?}", pat, &pat.ty); |
| ctor = Wildcard; |
| fields.clear(); |
| arity = 0; |
| } |
| } |
| } |
| &PatKind::LiteralBool { value } => { |
| ctor = Bool(value); |
| fields = Vec::new(); |
| arity = 0; |
| } |
| PatKind::Never => { |
| ctor = Never; |
| fields = Vec::new(); |
| arity = 0; |
| } |
| PatKind::Or { pats } => { |
| ctor = Or; |
| fields = pats |
| .iter() |
| .enumerate() |
| .map(|(i, pat)| self.lower_pat(pat).at_index(i)) |
| .collect(); |
| arity = pats.len(); |
| } |
| } |
| DeconstructedPat::new(ctor, fields, arity, pat.ty.clone(), ()) |
| } |
| |
| pub(crate) fn hoist_witness_pat(&self, pat: &WitnessPat<'db>) -> Pat { |
| let mut subpatterns = pat.iter_fields().map(|p| self.hoist_witness_pat(p)); |
| let kind = match pat.ctor() { |
| &Bool(value) => PatKind::LiteralBool { value }, |
| IntRange(_) => unimplemented!(), |
| Struct | Variant(_) | UnionField => match pat.ty().kind(Interner) { |
| TyKind::Tuple(..) => PatKind::Leaf { |
| subpatterns: subpatterns |
| .zip(0u32..) |
| .map(|(p, i)| FieldPat { |
| field: LocalFieldId::from_raw(i.into()), |
| pattern: p, |
| }) |
| .collect(), |
| }, |
| TyKind::Adt(adt, _) if is_box(self.db, adt.0) => { |
| // Without `box_patterns`, the only legal pattern of type `Box` is `_` (outside |
| // of `std`). So this branch is only reachable when the feature is enabled and |
| // the pattern is a box pattern. |
| PatKind::Deref { subpattern: subpatterns.next().unwrap() } |
| } |
| TyKind::Adt(adt, substs) => { |
| let variant = Self::variant_id_for_adt(self.db, pat.ctor(), adt.0).unwrap(); |
| let subpatterns = self |
| .list_variant_fields(pat.ty(), variant) |
| .zip(subpatterns) |
| .map(|((field, _ty), pattern)| FieldPat { field, pattern }) |
| .collect(); |
| |
| if let VariantId::EnumVariantId(enum_variant) = variant { |
| PatKind::Variant { substs: substs.clone(), enum_variant, subpatterns } |
| } else { |
| PatKind::Leaf { subpatterns } |
| } |
| } |
| _ => { |
| never!("unexpected ctor for type {:?} {:?}", pat.ctor(), pat.ty()); |
| PatKind::Wild |
| } |
| }, |
| // Note: given the expansion of `&str` patterns done in `expand_pattern`, we should |
| // be careful to reconstruct the correct constant pattern here. However a string |
| // literal pattern will never be reported as a non-exhaustiveness witness, so we |
| // ignore this issue. |
| Ref => PatKind::Deref { subpattern: subpatterns.next().unwrap() }, |
| Slice(_) => unimplemented!(), |
| &Str(void) => match void {}, |
| Wildcard | NonExhaustive | Hidden | PrivateUninhabited => PatKind::Wild, |
| Never => PatKind::Never, |
| Missing | F16Range(..) | F32Range(..) | F64Range(..) | F128Range(..) | Opaque(..) |
| | Or => { |
| never!("can't convert to pattern: {:?}", pat.ctor()); |
| PatKind::Wild |
| } |
| }; |
| Pat { ty: pat.ty().clone(), kind: Box::new(kind) } |
| } |
| } |
| |
| impl<'db> PatCx for MatchCheckCtx<'db> { |
| type Error = (); |
| type Ty = Ty; |
| type VariantIdx = EnumVariantContiguousIndex; |
| type StrLit = Void; |
| type ArmData = (); |
| type PatData = (); |
| |
| fn is_exhaustive_patterns_feature_on(&self) -> bool { |
| self.exhaustive_patterns |
| } |
| |
| fn ctor_arity( |
| &self, |
| ctor: &rustc_pattern_analysis::constructor::Constructor<Self>, |
| ty: &Self::Ty, |
| ) -> usize { |
| match ctor { |
| Struct | Variant(_) | UnionField => match *ty.kind(Interner) { |
| TyKind::Tuple(arity, ..) => arity, |
| TyKind::Adt(AdtId(adt), ..) => { |
| if is_box(self.db, adt) { |
| // The only legal patterns of type `Box` (outside `std`) are `_` and box |
| // patterns. If we're here we can assume this is a box pattern. |
| 1 |
| } else { |
| let variant = Self::variant_id_for_adt(self.db, ctor, adt).unwrap(); |
| variant.variant_data(self.db.upcast()).fields().len() |
| } |
| } |
| _ => { |
| never!("Unexpected type for `Single` constructor: {:?}", ty); |
| 0 |
| } |
| }, |
| Ref => 1, |
| Slice(..) => unimplemented!(), |
| Never | Bool(..) | IntRange(..) | F16Range(..) | F32Range(..) | F64Range(..) |
| | F128Range(..) | Str(..) | Opaque(..) | NonExhaustive | PrivateUninhabited |
| | Hidden | Missing | Wildcard => 0, |
| Or => { |
| never!("The `Or` constructor doesn't have a fixed arity"); |
| 0 |
| } |
| } |
| } |
| |
| fn ctor_sub_tys<'a>( |
| &'a self, |
| ctor: &'a rustc_pattern_analysis::constructor::Constructor<Self>, |
| ty: &'a Self::Ty, |
| ) -> impl ExactSizeIterator<Item = (Self::Ty, PrivateUninhabitedField)> + Captures<'a> { |
| let single = |ty| smallvec![(ty, PrivateUninhabitedField(false))]; |
| let tys: SmallVec<[_; 2]> = match ctor { |
| Struct | Variant(_) | UnionField => match ty.kind(Interner) { |
| TyKind::Tuple(_, substs) => { |
| let tys = substs.iter(Interner).map(|ty| ty.assert_ty_ref(Interner)); |
| tys.cloned().map(|ty| (ty, PrivateUninhabitedField(false))).collect() |
| } |
| TyKind::Ref(.., rty) => single(rty.clone()), |
| &TyKind::Adt(AdtId(adt), ref substs) => { |
| if is_box(self.db, adt) { |
| // The only legal patterns of type `Box` (outside `std`) are `_` and box |
| // patterns. If we're here we can assume this is a box pattern. |
| let subst_ty = substs.at(Interner, 0).assert_ty_ref(Interner).clone(); |
| single(subst_ty) |
| } else { |
| let variant = Self::variant_id_for_adt(self.db, ctor, adt).unwrap(); |
| |
| // Whether we must not match the fields of this variant exhaustively. |
| let is_non_exhaustive = |
| LazyCell::new(|| self.is_foreign_non_exhaustive(adt)); |
| let visibilities = LazyCell::new(|| self.db.field_visibilities(variant)); |
| |
| self.list_variant_fields(ty, variant) |
| .map(move |(fid, ty)| { |
| let is_visible = || { |
| matches!(adt, hir_def::AdtId::EnumId(..)) |
| || visibilities[fid] |
| .is_visible_from(self.db.upcast(), self.module) |
| }; |
| let is_uninhabited = self.is_uninhabited(&ty); |
| let private_uninhabited = |
| is_uninhabited && (!is_visible() || *is_non_exhaustive); |
| (ty, PrivateUninhabitedField(private_uninhabited)) |
| }) |
| .collect() |
| } |
| } |
| ty_kind => { |
| never!("Unexpected type for `{:?}` constructor: {:?}", ctor, ty_kind); |
| single(ty.clone()) |
| } |
| }, |
| Ref => match ty.kind(Interner) { |
| TyKind::Ref(.., rty) => single(rty.clone()), |
| ty_kind => { |
| never!("Unexpected type for `{:?}` constructor: {:?}", ctor, ty_kind); |
| single(ty.clone()) |
| } |
| }, |
| Slice(_) => unreachable!("Found a `Slice` constructor in match checking"), |
| Never | Bool(..) | IntRange(..) | F16Range(..) | F32Range(..) | F64Range(..) |
| | F128Range(..) | Str(..) | Opaque(..) | NonExhaustive | PrivateUninhabited |
| | Hidden | Missing | Wildcard => { |
| smallvec![] |
| } |
| Or => { |
| never!("called `Fields::wildcards` on an `Or` ctor"); |
| smallvec![] |
| } |
| }; |
| tys.into_iter() |
| } |
| |
| fn ctors_for_ty( |
| &self, |
| ty: &Self::Ty, |
| ) -> Result<rustc_pattern_analysis::constructor::ConstructorSet<Self>, Self::Error> { |
| let cx = self; |
| |
| // Unhandled types are treated as non-exhaustive. Being explicit here instead of falling |
| // to catchall arm to ease further implementation. |
| let unhandled = || ConstructorSet::Unlistable; |
| |
| // This determines the set of all possible constructors for the type `ty`. For numbers, |
| // arrays and slices we use ranges and variable-length slices when appropriate. |
| // |
| // If the `exhaustive_patterns` feature is enabled, we make sure to omit constructors that |
| // are statically impossible. E.g., for `Option<!>`, we do not include `Some(_)` in the |
| // returned list of constructors. |
| // Invariant: this is empty if and only if the type is uninhabited (as determined by |
| // `cx.is_uninhabited()`). |
| Ok(match ty.kind(Interner) { |
| TyKind::Scalar(Scalar::Bool) => ConstructorSet::Bool, |
| TyKind::Scalar(Scalar::Char) => unhandled(), |
| TyKind::Scalar(Scalar::Int(..) | Scalar::Uint(..)) => unhandled(), |
| TyKind::Array(..) | TyKind::Slice(..) => unhandled(), |
| &TyKind::Adt(AdtId(adt @ hir_def::AdtId::EnumId(enum_id)), ref subst) => { |
| let enum_data = cx.db.enum_data(enum_id); |
| let is_declared_nonexhaustive = cx.is_foreign_non_exhaustive(adt); |
| |
| if enum_data.variants.is_empty() && !is_declared_nonexhaustive { |
| ConstructorSet::NoConstructors |
| } else { |
| let mut variants = IndexVec::with_capacity(enum_data.variants.len()); |
| for &(variant, _) in enum_data.variants.iter() { |
| let is_uninhabited = |
| is_enum_variant_uninhabited_from(cx.db, variant, subst, cx.module); |
| let visibility = if is_uninhabited { |
| VariantVisibility::Empty |
| } else { |
| VariantVisibility::Visible |
| }; |
| variants.push(visibility); |
| } |
| |
| ConstructorSet::Variants { variants, non_exhaustive: is_declared_nonexhaustive } |
| } |
| } |
| TyKind::Adt(AdtId(hir_def::AdtId::UnionId(_)), _) => ConstructorSet::Union, |
| TyKind::Adt(..) | TyKind::Tuple(..) => { |
| ConstructorSet::Struct { empty: cx.is_uninhabited(ty) } |
| } |
| TyKind::Ref(..) => ConstructorSet::Ref, |
| TyKind::Never => ConstructorSet::NoConstructors, |
| // This type is one for which we cannot list constructors, like `str` or `f64`. |
| _ => ConstructorSet::Unlistable, |
| }) |
| } |
| |
| fn write_variant_name( |
| f: &mut fmt::Formatter<'_>, |
| _ctor: &Constructor<Self>, |
| _ty: &Self::Ty, |
| ) -> fmt::Result { |
| write!(f, "<write_variant_name unsupported>") |
| // We lack the database here ... |
| // let variant = ty.as_adt().and_then(|(adt, _)| Self::variant_id_for_adt(db, ctor, adt)); |
| |
| // if let Some(variant) = variant { |
| // match variant { |
| // VariantId::EnumVariantId(v) => { |
| // write!(f, "{}", db.enum_variant_data(v).name.display(db.upcast()))?; |
| // } |
| // VariantId::StructId(s) => { |
| // write!(f, "{}", db.struct_data(s).name.display(db.upcast()))? |
| // } |
| // VariantId::UnionId(u) => { |
| // write!(f, "{}", db.union_data(u).name.display(db.upcast()))? |
| // } |
| // } |
| // } |
| // Ok(()) |
| } |
| |
| fn bug(&self, fmt: fmt::Arguments<'_>) { |
| never!("{}", fmt) |
| } |
| |
| fn complexity_exceeded(&self) -> Result<(), Self::Error> { |
| Err(()) |
| } |
| } |
| |
| impl<'db> fmt::Debug for MatchCheckCtx<'db> { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| f.debug_struct("MatchCheckCtx").finish() |
| } |
| } |