diff options
Diffstat (limited to 'compiler/rustc_pattern_analysis/src/rustc.rs')
| -rw-r--r-- | compiler/rustc_pattern_analysis/src/rustc.rs | 191 |
1 files changed, 100 insertions, 91 deletions
diff --git a/compiler/rustc_pattern_analysis/src/rustc.rs b/compiler/rustc_pattern_analysis/src/rustc.rs index 910dd4c6c1a..10b7968a1a7 100644 --- a/compiler/rustc_pattern_analysis/src/rustc.rs +++ b/compiler/rustc_pattern_analysis/src/rustc.rs @@ -7,7 +7,7 @@ use rustc_hir::HirId; use rustc_index::{Idx, IndexVec}; use rustc_middle::middle::stability::EvalResult; use rustc_middle::mir::{self, Const}; -use rustc_middle::thir::{self, FieldPat, Pat, PatKind, PatRange, PatRangeBoundary}; +use rustc_middle::thir::{self, Pat, PatKind, PatRange, PatRangeBoundary}; use rustc_middle::ty::layout::IntegerExt; use rustc_middle::ty::{ self, FieldDef, OpaqueTypeKey, ScalarInt, Ty, TyCtxt, TypeVisitableExt, VariantDef, @@ -17,15 +17,17 @@ use rustc_session::lint; use rustc_span::{ErrorGuaranteed, Span, DUMMY_SP}; use rustc_target::abi::{FieldIdx, Integer, VariantIdx, FIRST_VARIANT}; +use crate::constructor::Constructor::*; use crate::constructor::{ IntRange, MaybeInfiniteInt, OpaqueId, RangeEnd, Slice, SliceKind, VariantVisibility, }; use crate::lints::lint_nonexhaustive_missing_variants; use crate::pat_column::PatternColumn; +use crate::rustc::print::EnumInfo; use crate::usefulness::{compute_match_usefulness, PlaceValidity}; use crate::{errors, Captures, PatCx, PrivateUninhabitedField}; -use crate::constructor::Constructor::*; +mod print; // Re-export rustc-specific versions of all these types. pub type Constructor<'p, 'tcx> = crate::constructor::Constructor<RustcPatCtxt<'p, 'tcx>>; @@ -236,9 +238,7 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> { let tys = cx.variant_sub_tys(ty, variant).map(|(field, ty)| { let is_visible = adt.is_enum() || field.vis.is_accessible_from(cx.module, cx.tcx); - let is_uninhabited = (cx.tcx.features().exhaustive_patterns - || cx.tcx.features().min_exhaustive_patterns) - && cx.is_uninhabited(*ty); + let is_uninhabited = cx.is_uninhabited(*ty); let skip = is_uninhabited && (!is_visible || is_non_exhaustive); (ty, PrivateUninhabitedField(skip)) }); @@ -744,7 +744,7 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> { /// Note: it is possible to get `isize/usize::MAX+1` here, as explained in the doc for /// [`IntRange::split`]. This cannot be represented as a `Const`, so we represent it with /// `PosInfinity`. - pub(crate) fn hoist_pat_range_bdy( + fn hoist_pat_range_bdy( &self, miint: MaybeInfiniteInt, ty: RevealedTy<'tcx>, @@ -774,8 +774,9 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> { } } - /// Convert back to a `thir::Pat` for diagnostic purposes. - pub(crate) fn hoist_pat_range(&self, range: &IntRange, ty: RevealedTy<'tcx>) -> Pat<'tcx> { + /// Convert to a [`print::Pat`] for diagnostic purposes. + fn hoist_pat_range(&self, range: &IntRange, ty: RevealedTy<'tcx>) -> print::Pat<'tcx> { + use print::{Pat, PatKind}; use MaybeInfiniteInt::*; let cx = self; let kind = if matches!((range.lo, range.hi), (NegInfinity, PosInfinity)) { @@ -809,83 +810,79 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> { PatKind::Range(Box::new(PatRange { lo, hi, end, ty: ty.inner() })) }; - Pat { ty: ty.inner(), span: DUMMY_SP, kind } + Pat { ty: ty.inner(), kind } } - /// Convert back to a `thir::Pat` for diagnostic purposes. This panics for patterns that don't + + /// Prints a [`WitnessPat`] to an owned string, for diagnostic purposes. + pub fn print_witness_pat(&self, pat: &WitnessPat<'p, 'tcx>) -> String { + // This works by converting the witness pattern to a `print::Pat` + // and then printing that, but callers don't need to know that. + self.hoist_witness_pat(pat).to_string() + } + + /// Convert to a [`print::Pat`] for diagnostic purposes. This panics for patterns that don't /// appear in diagnostics, like float ranges. - pub fn hoist_witness_pat(&self, pat: &WitnessPat<'p, 'tcx>) -> Pat<'tcx> { + fn hoist_witness_pat(&self, pat: &WitnessPat<'p, 'tcx>) -> print::Pat<'tcx> { + use print::{FieldPat, Pat, PatKind}; let cx = self; - let is_wildcard = |pat: &Pat<'_>| matches!(pat.kind, PatKind::Wild); - let mut subpatterns = pat.iter_fields().map(|p| Box::new(cx.hoist_witness_pat(p))); + let hoist = |p| Box::new(cx.hoist_witness_pat(p)); let kind = match pat.ctor() { Bool(b) => PatKind::Constant { value: mir::Const::from_bool(cx.tcx, *b) }, IntRange(range) => return self.hoist_pat_range(range, *pat.ty()), - Struct | Variant(_) | UnionField => match pat.ty().kind() { - ty::Tuple(..) => PatKind::Leaf { - subpatterns: subpatterns - .enumerate() - .map(|(i, pattern)| FieldPat { field: FieldIdx::new(i), pattern }) - .collect(), - }, - ty::Adt(adt_def, _) if adt_def.is_box() => { - // 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() } - } - ty::Adt(adt_def, args) => { - let variant_index = RustcPatCtxt::variant_index_for_adt(&pat.ctor(), *adt_def); - let subpatterns = subpatterns - .enumerate() - .map(|(i, pattern)| FieldPat { field: FieldIdx::new(i), pattern }) - .collect(); + Struct if pat.ty().is_box() => { + // Outside of the `alloc` crate, the only way to create a struct pattern + // of type `Box` is to use a `box` pattern via #[feature(box_patterns)]. + PatKind::Box { subpattern: hoist(&pat.fields[0]) } + } + Struct | Variant(_) | UnionField => { + let enum_info = match *pat.ty().kind() { + ty::Adt(adt_def, _) if adt_def.is_enum() => EnumInfo::Enum { + adt_def, + variant_index: RustcPatCtxt::variant_index_for_adt(pat.ctor(), adt_def), + }, + ty::Adt(..) | ty::Tuple(..) => EnumInfo::NotEnum, + _ => bug!("unexpected ctor for type {:?} {:?}", pat.ctor(), *pat.ty()), + }; - if adt_def.is_enum() { - PatKind::Variant { adt_def: *adt_def, args, variant_index, subpatterns } - } else { - PatKind::Leaf { subpatterns } - } - } - _ => bug!("unexpected ctor for type {:?} {:?}", pat.ctor(), *pat.ty()), - }, - // 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() }, + let subpatterns = pat + .iter_fields() + .enumerate() + .map(|(i, pat)| FieldPat { field: FieldIdx::new(i), pattern: hoist(pat) }) + .collect::<Vec<_>>(); + + PatKind::StructLike { enum_info, subpatterns } + } + Ref => PatKind::Deref { subpattern: hoist(&pat.fields[0]) }, Slice(slice) => { - match slice.kind { - SliceKind::FixedLen(_) => PatKind::Slice { - prefix: subpatterns.collect(), - slice: None, - suffix: Box::new([]), - }, - SliceKind::VarLen(prefix, _) => { - let mut subpatterns = subpatterns.peekable(); - let mut prefix: Vec<_> = subpatterns.by_ref().take(prefix).collect(); - if slice.array_len.is_some() { - // Improves diagnostics a bit: if the type is a known-size array, instead - // of reporting `[x, _, .., _, y]`, we prefer to report `[x, .., y]`. - // This is incorrect if the size is not known, since `[_, ..]` captures - // arrays of lengths `>= 1` whereas `[..]` captures any length. - while !prefix.is_empty() && is_wildcard(prefix.last().unwrap()) { - prefix.pop(); - } - while subpatterns.peek().is_some() - && is_wildcard(subpatterns.peek().unwrap()) - { - subpatterns.next(); - } - } - let suffix: Box<[_]> = subpatterns.collect(); - let wild = Pat::wildcard_from_ty(pat.ty().inner()); - PatKind::Slice { - prefix: prefix.into_boxed_slice(), - slice: Some(Box::new(wild)), - suffix, - } + let (prefix_len, has_dot_dot) = match slice.kind { + SliceKind::FixedLen(len) => (len, false), + SliceKind::VarLen(prefix_len, _) => (prefix_len, true), + }; + + let (mut prefix, mut suffix) = pat.fields.split_at(prefix_len); + + // If the pattern contains a `..`, but is applied to values of statically-known + // length (arrays), then we can slightly simplify diagnostics by merging any + // adjacent wildcard patterns into the `..`: `[x, _, .., _, y]` => `[x, .., y]`. + // (This simplification isn't allowed for slice values, because in that case + // `[x, .., y]` would match some slices that `[x, _, .., _, y]` would not.) + if has_dot_dot && slice.array_len.is_some() { + while let [rest @ .., last] = prefix + && would_print_as_wildcard(cx.tcx, last) + { + prefix = rest; + } + while let [first, rest @ ..] = suffix + && would_print_as_wildcard(cx.tcx, first) + { + suffix = rest; } } + + let prefix = prefix.iter().map(hoist).collect(); + let suffix = suffix.iter().map(hoist).collect(); + + PatKind::Slice { prefix, has_dot_dot, suffix } } &Str(value) => PatKind::Constant { value }, Never if self.tcx.features().never_patterns => PatKind::Never, @@ -899,7 +896,23 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> { } }; - Pat { ty: pat.ty().inner(), span: DUMMY_SP, kind } + Pat { ty: pat.ty().inner(), kind } + } +} + +/// Returns `true` if the given pattern would be printed as a wildcard (`_`). +fn would_print_as_wildcard(tcx: TyCtxt<'_>, p: &WitnessPat<'_, '_>) -> bool { + match p.ctor() { + Constructor::IntRange(IntRange { + lo: MaybeInfiniteInt::NegInfinity, + hi: MaybeInfiniteInt::PosInfinity, + }) + | Constructor::Wildcard + | Constructor::NonExhaustive + | Constructor::Hidden + | Constructor::PrivateUninhabited => true, + Constructor::Never if !tcx.features().never_patterns => true, + _ => false, } } @@ -914,9 +927,6 @@ impl<'p, 'tcx: 'p> PatCx for RustcPatCtxt<'p, 'tcx> { fn is_exhaustive_patterns_feature_on(&self) -> bool { self.tcx.features().exhaustive_patterns } - fn is_min_exhaustive_patterns_feature_on(&self) -> bool { - self.tcx.features().min_exhaustive_patterns - } fn ctor_arity(&self, ctor: &crate::constructor::Constructor<Self>, ty: &Self::Ty) -> usize { self.ctor_arity(ctor, *ty) @@ -966,7 +976,7 @@ impl<'p, 'tcx: 'p> PatCx for RustcPatCtxt<'p, 'tcx> { let overlaps: Vec<_> = overlaps_with .iter() .map(|pat| pat.data().span) - .map(|span| errors::Overlap { range: overlap_as_pat.clone(), span }) + .map(|span| errors::Overlap { range: overlap_as_pat.to_string(), span }) .collect(); let pat_span = pat.data().span; self.tcx.emit_node_span_lint( @@ -996,12 +1006,11 @@ impl<'p, 'tcx: 'p> PatCx for RustcPatCtxt<'p, 'tcx> { } // `pat` is an exclusive range like `lo..gap`. `gapped_with` contains ranges that start with // `gap+1`. - let suggested_range: thir::Pat<'_> = { + let suggested_range: String = { // Suggest `lo..=gap` instead. - let mut suggested_range = thir_pat.clone(); - let thir::PatKind::Range(range) = &mut suggested_range.kind else { unreachable!() }; - range.end = rustc_hir::RangeEnd::Included; - suggested_range + let mut suggested_range = PatRange::clone(range); + suggested_range.end = rustc_hir::RangeEnd::Included; + suggested_range.to_string() }; let gap_as_pat = self.hoist_pat_range(&gap, *pat.ty()); if gapped_with.is_empty() { @@ -1014,9 +1023,9 @@ impl<'p, 'tcx: 'p> PatCx for RustcPatCtxt<'p, 'tcx> { // Point at this range. first_range: thir_pat.span, // That's the gap that isn't covered. - max: gap_as_pat.clone(), + max: gap_as_pat.to_string(), // Suggest `lo..=max` instead. - suggestion: suggested_range.to_string(), + suggestion: suggested_range, }, ); } else { @@ -1028,17 +1037,17 @@ impl<'p, 'tcx: 'p> PatCx for RustcPatCtxt<'p, 'tcx> { // Point at this range. first_range: thir_pat.span, // That's the gap that isn't covered. - gap: gap_as_pat.clone(), + gap: gap_as_pat.to_string(), // Suggest `lo..=gap` instead. - suggestion: suggested_range.to_string(), + suggestion: suggested_range, // All these ranges skipped over `gap` which we think is probably a // mistake. gap_with: gapped_with .iter() .map(|pat| errors::GappedRange { span: pat.data().span, - gap: gap_as_pat.clone(), - first_range: thir_pat.clone(), + gap: gap_as_pat.to_string(), + first_range: range.to_string(), }) .collect(), }, |
