diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 6e9dc880a7dee..d6ad54c3fa66b 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -3801,8 +3801,10 @@ declare_lint! { } declare_lint! { - /// The `non_exhaustive_omitted_patterns` lint detects when a wildcard (`_` or `..`) in a - /// pattern for a `#[non_exhaustive]` struct or enum is reachable. + /// The `non_exhaustive_omitted_patterns` lint detects when some variants or fields of a + /// `#[non_exhaustive]` struct or enum are not mentioned explicitly in a pattern. This allows + /// downstream crates to be warned when new variants or fields are added to the upstream struct + /// or enum. /// /// ### Example /// @@ -3817,9 +3819,9 @@ declare_lint! { /// // in crate B /// #![feature(non_exhaustive_omitted_patterns_lint)] /// + /// #[warn(non_exhaustive_omitted_patterns)] /// match Bar::A { /// Bar::A => {}, - /// #[warn(non_exhaustive_omitted_patterns)] /// _ => {}, /// } /// ``` @@ -3830,8 +3832,8 @@ declare_lint! { /// warning: reachable patterns not covered of non exhaustive enum /// --> $DIR/reachable-patterns.rs:70:9 /// | - /// LL | _ => {} - /// | ^ pattern `B` not covered + /// LL | match Bar::A { + /// | ^ pattern `Bar::B` not covered /// | /// note: the lint level is defined here /// --> $DIR/reachable-patterns.rs:69:16 @@ -3844,12 +3846,11 @@ declare_lint! { /// /// ### Explanation /// - /// Structs and enums tagged with `#[non_exhaustive]` force the user to add a - /// (potentially redundant) wildcard when pattern-matching, to allow for future - /// addition of fields or variants. The `non_exhaustive_omitted_patterns` lint - /// detects when such a wildcard happens to actually catch some fields/variants. - /// In other words, when the match without the wildcard would not be exhaustive. - /// This lets the user be informed if new fields/variants were added. + /// Structs and enums tagged with `#[non_exhaustive]` force the user to add a (potentially + /// redundant) wildcard when pattern-matching, to allow for future addition of fields or + /// variants. The `non_exhaustive_omitted_patterns` lint detects when such a wildcard happens to + /// actually catch some fields/variants. This lets the user be informed if new fields/variants + /// were added. pub NON_EXHAUSTIVE_OMITTED_PATTERNS, Allow, "detect when patterns of types marked `non_exhaustive` are missed", diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index ca25f83e6437e..4049bb7b65797 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -254,7 +254,7 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> { let scrut = &self.thir[scrut]; let scrut_ty = scrut.ty; - let report = compute_match_usefulness(&cx, &tarms, self.lint_level, scrut_ty); + let report = compute_match_usefulness(&cx, &tarms, self.lint_level, scrut_ty, scrut.span); match source { // Don't report arm reachability of desugared `match $iter.into_iter() { iter => .. }` @@ -416,7 +416,8 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> { let pattern = self.lower_pattern(&mut cx, pat); let pattern_ty = pattern.ty(); let arm = MatchArm { pat: pattern, hir_id: self.lint_level, has_guard: false }; - let report = compute_match_usefulness(&cx, &[arm], self.lint_level, pattern_ty); + let report = + compute_match_usefulness(&cx, &[arm], self.lint_level, pattern_ty, pattern.span()); // Note: we ignore whether the pattern is unreachable (i.e. whether the type is empty). We // only care about exhaustiveness here. @@ -584,7 +585,7 @@ fn is_let_irrefutable<'p, 'tcx>( pat: &'p DeconstructedPat<'p, 'tcx>, ) -> bool { let arms = [MatchArm { pat, hir_id: pat_id, has_guard: false }]; - let report = compute_match_usefulness(&cx, &arms, pat_id, pat.ty()); + let report = compute_match_usefulness(&cx, &arms, pat_id, pat.ty(), pat.span()); // Report if the pattern is unreachable, which can only occur when the type is uninhabited. // This also reports unreachable sub-patterns though, so we can't just replace it with an diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 6a77146138bb5..0cd3ee1d0bfd9 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -37,20 +37,21 @@ //! skipping some constructors as long as it doesn't change whether the resulting list of witnesses //! is empty of not. We use this in the wildcard `_` case. //! -//! Splitting is implemented in the [`Constructor::split`] function. We don't do splitting for +//! Splitting is implemented in the `Constructor::split` function. We don't do splitting for //! or-patterns; instead we just try the alternatives one-by-one. For details on splitting -//! wildcards, see [`SplitWildcard`]; for integer ranges, see [`SplitIntRange`]; for slices, see +//! wildcards, see `SplitWildcard`; for integer ranges, see [`SplitIntRange`]; for slices, see //! [`SplitVarLenSlice`]. use std::cell::Cell; -use std::cmp::{self, max, min, Ordering}; +use std::cmp::{self, Ordering}; use std::fmt; use std::iter::once; use std::ops::RangeInclusive; -use smallvec::{smallvec, SmallVec}; +use smallvec::SmallVec; use rustc_data_structures::captures::Captures; +use rustc_data_structures::fx::FxHashSet; use rustc_hir::{HirId, RangeEnd}; use rustc_index::Idx; use rustc_middle::mir; @@ -99,19 +100,15 @@ fn expand_or_pat<'p, 'tcx>(pat: &'p Pat<'tcx>) -> Vec<&'p Pat<'tcx>> { #[derive(Clone, PartialEq, Eq)] pub(crate) struct IntRange { range: RangeInclusive, - /// Keeps the bias used for encoding the range. It depends on the type of the range and - /// possibly the pointer size of the current architecture. The algorithm ensures we never - /// compare `IntRange`s with different types/architectures. - bias: u128, } impl IntRange { #[inline] - fn is_integral(ty: Ty<'_>) -> bool { + pub(super) fn is_integral(ty: Ty<'_>) -> bool { matches!(ty.kind(), ty::Char | ty::Int(_) | ty::Uint(_) | ty::Bool) } - fn is_singleton(&self) -> bool { + pub(super) fn is_singleton(&self) -> bool { self.range.start() == self.range.end() } @@ -140,28 +137,25 @@ impl IntRange { value: mir::ConstantKind<'tcx>, ) -> Option { let ty = value.ty(); - if let Some((target_size, bias)) = Self::integral_size_and_signed_bias(tcx, ty) { - let val = if let mir::ConstantKind::Val(ConstValue::Scalar(scalar), _) = value { - // For this specific pattern we can skip a lot of effort and go - // straight to the result, after doing a bit of checking. (We - // could remove this branch and just fall through, which - // is more general but much slower.) - scalar.to_bits_or_ptr_internal(target_size).unwrap().left()? - } else { - if let mir::ConstantKind::Ty(c) = value + let (target_size, bias) = Self::integral_size_and_signed_bias(tcx, ty)?; + let val = if let mir::ConstantKind::Val(ConstValue::Scalar(scalar), _) = value { + // For this specific pattern we can skip a lot of effort and go + // straight to the result, after doing a bit of checking. (We + // could remove this branch and just fall through, which + // is more general but much slower.) + scalar.to_bits_or_ptr_internal(target_size).unwrap().left()? + } else { + if let mir::ConstantKind::Ty(c) = value && let ty::ConstKind::Value(_) = c.kind() { bug!("encountered ConstValue in mir::ConstantKind::Ty, whereas this is expected to be in ConstantKind::Val"); } - // This is a more general form of the previous case. - value.try_eval_bits(tcx, param_env, ty)? - }; - let val = val ^ bias; - Some(IntRange { range: val..=val, bias }) - } else { - None - } + // This is a more general form of the previous case. + value.try_eval_bits(tcx, param_env, ty)? + }; + let val = val ^ bias; + Some(IntRange { range: val..=val }) } #[inline] @@ -182,7 +176,7 @@ impl IntRange { // This should have been caught earlier by E0030. bug!("malformed range pattern: {}..={}", lo, (hi - offset)); } - IntRange { range: lo..=(hi - offset), bias } + IntRange { range: lo..=(hi - offset) } }) } @@ -201,38 +195,11 @@ impl IntRange { other.range.start() <= self.range.start() && self.range.end() <= other.range.end() } - fn intersection(&self, other: &Self) -> Option { - let (lo, hi) = self.boundaries(); - let (other_lo, other_hi) = other.boundaries(); - if lo <= other_hi && other_lo <= hi { - Some(IntRange { range: max(lo, other_lo)..=min(hi, other_hi), bias: self.bias }) - } else { - None - } - } - - fn suspicious_intersection(&self, other: &Self) -> bool { - // `false` in the following cases: - // 1 ---- // 1 ---------- // 1 ---- // 1 ---- - // 2 ---------- // 2 ---- // 2 ---- // 2 ---- - // - // The following are currently `false`, but could be `true` in the future (#64007): - // 1 --------- // 1 --------- - // 2 ---------- // 2 ---------- - // - // `true` in the following cases: - // 1 ------- // 1 ------- - // 2 -------- // 2 ------- - let (lo, hi) = self.boundaries(); - let (other_lo, other_hi) = other.boundaries(); - (lo == other_hi || hi == other_lo) && !self.is_singleton() && !other.is_singleton() - } - /// Only used for displaying the range properly. fn to_pat<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Pat<'tcx> { let (lo, hi) = self.boundaries(); - let bias = self.bias; + let bias = IntRange::signed_bias(tcx, ty); let (lo, hi) = (lo ^ bias, hi ^ bias); let env = ty::ParamEnv::empty().and(ty); @@ -256,82 +223,96 @@ impl IntRange { pub(super) fn lint_overlapping_range_endpoints<'a, 'p: 'a, 'tcx: 'a>( &self, pcx: &PatCtxt<'_, 'p, 'tcx>, - pats: impl Iterator>, + pats: impl Iterator, bool, HirId)>, column_count: usize, - lint_root: HirId, ) { - if self.is_singleton() { - return; - } - - if column_count != 1 { - // FIXME: for now, only check for overlapping ranges on simple range - // patterns. Otherwise with the current logic the following is detected - // as overlapping: - // ``` - // match (0u8, true) { - // (0 ..= 125, false) => {} - // (125 ..= 255, true) => {} - // _ => {} - // } - // ``` + // FIXME: for now, only check for overlapping ranges on non-nested range patterns. Otherwise + // with the current logic the following is detected as overlapping: + // ``` + // match (0u8, true) { + // (0 ..= 125, false) => {} + // (125 ..= 255, true) => {} + // _ => {} + // } + // ``` + if !self.is_singleton() || column_count != 1 { return; } - let overlap: Vec<_> = pats - .filter_map(|pat| Some((pat.ctor().as_int_range()?, pat.span()))) - .filter(|(range, _)| self.suspicious_intersection(range)) - .map(|(range, span)| Overlap { - range: self.intersection(&range).unwrap().to_pat(pcx.cx.tcx, pcx.ty), - span, + let overlap: u128 = self.boundaries().0; + // Spans of ranges that start or end with the overlap. + let mut prefixes: SmallVec<[_; 1]> = Default::default(); + let mut suffixes: SmallVec<[_; 1]> = Default::default(); + // Iterate on rows that contained `overlap`. + for (range, this_span, is_under_guard, arm_hir_id) in + pats.filter_map(|(pat, under_guard, arm_hir_id)| { + Some((pat.ctor().as_int_range()?, pat.span(), under_guard, arm_hir_id)) }) - .collect(); - - if !overlap.is_empty() { - pcx.cx.tcx.emit_spanned_lint( - lint::builtin::OVERLAPPING_RANGE_ENDPOINTS, - lint_root, - pcx.span, - OverlappingRangeEndpoints { overlap, range: pcx.span }, - ); - } - } - - /// See `Constructor::is_covered_by` - fn is_covered_by(&self, other: &Self) -> bool { - if self.intersection(other).is_some() { - // Constructor splitting should ensure that all intersections we encounter are actually - // inclusions. - assert!(self.is_subrange(other)); - true - } else { - false + { + if range.is_singleton() { + continue; + } + let mut overlaps: SmallVec<[_; 1]> = Default::default(); + if *range.range.start() == overlap { + if !prefixes.is_empty() { + overlaps = prefixes.clone(); + } + if !is_under_guard { + suffixes.push(this_span) + } + } else if *range.range.end() == overlap { + if !suffixes.is_empty() { + overlaps = suffixes.clone(); + } + if !is_under_guard { + prefixes.push(this_span) + } + } + if !overlaps.is_empty() { + let overlap_as_pat = + (IntRange { range: overlap..=overlap }).to_pat(pcx.cx.tcx, pcx.ty); + let overlaps: Vec<_> = overlaps + .into_iter() + .map(|span| Overlap { range: overlap_as_pat.clone(), span }) + .collect(); + pcx.cx.tcx.emit_spanned_lint( + lint::builtin::OVERLAPPING_RANGE_ENDPOINTS, + arm_hir_id, + this_span, + OverlappingRangeEndpoints { overlap: overlaps, range: this_span }, + ); + } } } } /// Note: this is often not what we want: e.g. `false` is converted into the range `0..=0` and -/// would be displayed as such. To render properly, convert to a pattern first. +/// `-128..=127i8` is encoded as `0..=255`. To render properly, convert to a pattern first. impl fmt::Debug for IntRange { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (lo, hi) = self.boundaries(); - let bias = self.bias; - let (lo, hi) = (lo ^ bias, hi ^ bias); write!(f, "{}", lo)?; write!(f, "{}", RangeEnd::Included)?; write!(f, "{}", hi) } } -/// Represents a border between 2 integers. Because the intervals spanning borders must be able to -/// cover every integer, we need to be able to represent 2^128 + 1 such borders. +/// Represents a boundary between 2 integers. Because the intervals spanning bdys must be able to +/// cover every integer, we need to be able to represent 2^128 + 1 such bdys. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -enum IntBorder { +enum IntBoundary { JustBefore(u128), AfterMax, } -/// A range of integers that is partitioned into disjoint subranges. This does constructor +/// Whether we have seen a constructor in the matrix or not. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +enum Presence { + Unseen, + Seen, +} + +/// A range of integers that is to be partitioned into disjoint subranges. This does constructor /// splitting for integer ranges as explained at the top of the file. /// /// This is fed multiple ranges, and returns an output that covers the input, but is split so that @@ -349,22 +330,16 @@ enum IntBorder { /// ||---|--||-|---|---|---|--| /// ``` #[derive(Debug, Clone)] -struct SplitIntRange { - /// The range we are splitting - range: IntRange, - /// The borders of ranges we have seen. They are all contained within `range`. This is kept - /// sorted. - borders: Vec, +enum SplitIntRange { + Single(IntRange), + // For chars + Double([IntRange; 2]), } impl SplitIntRange { - fn new(range: IntRange) -> Self { - SplitIntRange { range, borders: Vec::new() } - } - /// Internal use - fn to_borders(r: IntRange) -> [IntBorder; 2] { - use IntBorder::*; + fn into_boundaries(r: IntRange) -> [IntBoundary; 2] { + use IntBoundary::*; let (lo, hi) = r.boundaries(); let lo = JustBefore(lo); let hi = match hi.checked_add(1) { @@ -374,46 +349,78 @@ impl SplitIntRange { [lo, hi] } - /// Add ranges relative to which we split. - fn split(&mut self, ranges: impl Iterator) { - let this_range = &self.range; - let included_ranges = ranges.filter_map(|r| this_range.intersection(&r)); - let included_borders = included_ranges.flat_map(|r| { - let borders = Self::to_borders(r); - once(borders[0]).chain(once(borders[1])) - }); - self.borders.extend(included_borders); - self.borders.sort_unstable(); - } - - /// Iterate over the contained ranges. - fn iter(&self) -> impl Iterator + Captures<'_> { - use IntBorder::*; - - let self_range = Self::to_borders(self.range.clone()); - // Start with the start of the range. - let mut prev_border = self_range[0]; - self.borders - .iter() - .copied() - // End with the end of the range. - .chain(once(self_range[1])) - // List pairs of adjacent borders. - .map(move |border| { - let ret = (prev_border, border); - prev_border = border; + /// Iterate over the split ranges. + /// We have in `self` one or two base ranges of valid values. + /// We have in `ranges` a set of ranges that count as "seen". + /// We partition the `self` ranges into subranges such that: + /// - each output subrange is either included or disjoint with each output subrange + /// - we track whether a given output subrange is included in some input range or not + /// + /// We do this with a parenthesis matching algorithm. We go through all the range boundaries and + /// keep track in parallel of 1/ whether we are within some base range and 2/ whether we are + /// within some seen range. + fn split( + self, + ranges: impl Iterator, + ) -> impl Iterator { + use IntBoundary::*; + use Presence::*; + + // We do a double parenthesis matching. We have two counters. The first is for `self` + // ranges, the second for `ranges` ranges. + // A boundary counts as +1 if it starts a range and -1 if it ends it. When the count is > 0 + // between two boundaries, we are within a range. + let mut bdys: Vec<(IntBoundary, isize, isize)> = ranges + .map(|r| Self::into_boundaries(r)) + .flat_map(|[lo, hi]| [(lo, 0, 1), (hi, 0, -1)]) + .collect(); + match self { + Self::Single(self_range) => { + let [lo, hi] = Self::into_boundaries(self_range); + bdys.push((lo, 1, 0)); + bdys.push((hi, -1, 0)); + } + Self::Double([range_1, range_2]) => { + let [lo, hi] = Self::into_boundaries(range_1); + bdys.push((lo, 1, 0)); + bdys.push((hi, -1, 0)); + let [lo, hi] = Self::into_boundaries(range_2); + bdys.push((lo, 1, 0)); + bdys.push((hi, -1, 0)); + } + } + bdys.sort_unstable(); + + let mut base_counter = 0isize; + let mut seen_counter = 0isize; + let mut iter = bdys + .into_iter() + // Accumulate deltas. + .map(move |(bdy, base_delta, seen_delta)| { + base_counter += base_delta; + seen_counter += seen_delta; + (bdy, base_counter, seen_counter) + }); + let mut prev_bdy = iter.next().unwrap(); + iter + // List pairs of adjacent bdys. + .map(move |bdy| { + let ret = (prev_bdy, bdy); + prev_bdy = bdy; ret }) - // Skip duplicates. - .filter(|(prev_border, border)| prev_border != border) + // Skip duplicate boundaries and boundaries not within base ranges. + .filter(|&((prev_bdy, base_count, _), (bdy, ..))| prev_bdy != bdy && base_count > 0) // Finally, convert to ranges. - .map(move |(prev_border, border)| { - let range = match (prev_border, border) { - (JustBefore(n), JustBefore(m)) if n < m => n..=(m - 1), + .map(move |((prev_bdy, _, seen_count), (bdy, ..))| { + let presence = if seen_count > 0 { Seen } else { Unseen }; + let range = match (prev_bdy, bdy) { + (JustBefore(n), JustBefore(m)) if n < m => n..=m - 1, (JustBefore(n), AfterMax) => n..=u128::MAX, _ => unreachable!(), // Ruled out by the sorting and filtering we did }; - IntRange { range, bias: self.range.bias } + let range = IntRange { range }; + (presence, range) }) } } @@ -543,73 +550,80 @@ impl Slice { /// /// `max_slice` below will be made to have arity `L`. #[derive(Debug)] -struct SplitVarLenSlice { - /// If the type is an array, this is its size. - array_len: Option, - /// The arity of the input slice. - arity: usize, - /// The smallest slice bigger than any slice seen. `max_slice.arity()` is the length `L` - /// described above. - max_slice: SliceKind, -} +struct SplitVarLenSlice; impl SplitVarLenSlice { - fn new(prefix: usize, suffix: usize, array_len: Option) -> Self { - SplitVarLenSlice { array_len, arity: prefix + suffix, max_slice: VarLen(prefix, suffix) } - } - - /// Pass a set of slices relative to which to split this one. - fn split(&mut self, slices: impl Iterator) { - let VarLen(max_prefix_len, max_suffix_len) = &mut self.max_slice else { - // No need to split - return; - }; - // We grow `self.max_slice` to be larger than all slices encountered, as described above. - // For diagnostics, we keep the prefix and suffix lengths separate, but grow them so that - // `L = max_prefix_len + max_suffix_len`. + /// Split the base `[..]` slice relative to `slices`. + fn split( + array_len: Option, + slices: impl Iterator + Clone, + ) -> impl Iterator { + let mut max_prefix_len = 0; + let mut max_suffix_len = 0; let mut max_fixed_len = 0; - for slice in slices { + let mut min_var_len = usize::MAX; + let mut seen_fixed_lens = FxHashSet::default(); + for slice in slices.clone() { match slice { FixedLen(len) => { max_fixed_len = cmp::max(max_fixed_len, len); + seen_fixed_lens.insert(len); } VarLen(prefix, suffix) => { - *max_prefix_len = cmp::max(*max_prefix_len, prefix); - *max_suffix_len = cmp::max(*max_suffix_len, suffix); + max_prefix_len = cmp::max(max_prefix_len, prefix); + max_suffix_len = cmp::max(max_suffix_len, suffix); + min_var_len = cmp::min(min_var_len, prefix + suffix); } } } - // We want `L = max(L, max_fixed_len + 1)`, modulo the fact that we keep prefix and - // suffix separate. - if max_fixed_len + 1 >= *max_prefix_len + *max_suffix_len { - // The subtraction can't overflow thanks to the above check. + // We want to compute `L` that is larger than all slices encountered, as described above. We + // take `L = max_prefix_len + max_suffix_len`, then ensure `L > max_fixed_len`. + // The subtlety with prefix vs suffix is for diagnostics purposes. + if max_fixed_len + 1 >= max_prefix_len + max_suffix_len { + // The subtraction can't underflow thanks to the above check. // The new `max_prefix_len` is larger than its previous value. - *max_prefix_len = max_fixed_len + 1 - *max_suffix_len; + max_prefix_len = max_fixed_len + 1 - max_suffix_len; } + // The smallest slice bigger than any slice seen. `max_slice.arity()` is the length `L` + // described above. + let mut max_slice = VarLen(max_prefix_len, max_suffix_len); // We cap the arity of `max_slice` at the array size. - match self.array_len { - Some(len) if self.max_slice.arity() >= len => self.max_slice = FixedLen(len), + match array_len { + Some(len) if max_slice.arity() >= len => max_slice = FixedLen(len), _ => {} } - } - - /// Iterate over the partition of this slice. - fn iter(&self) -> impl Iterator + Captures<'_> { - let smaller_lengths = match self.array_len { - // The only admissible fixed-length slice is one of the array size. Whether `max_slice` - // is fixed-length or variable-length, it will be the only relevant slice to output - // here. + let smaller_lengths = match array_len { + // If the array has known length, the only admissible patterns are fixed-length of the + // array size or variable-length of a smaller size. This is exactly what `max_slice` + // captures, so we only output that. Some(_) => 0..0, // empty range - // We cover all arities in the range `(self.arity..infinity)`. We split that range into - // two: lengths smaller than `max_slice.arity()` are treated independently as - // fixed-lengths slices, and lengths above are captured by `max_slice`. - None => self.arity..self.max_slice.arity(), + // We cover all arities in the range `(0..infinity)`. We split that range into two: + // lengths smaller than `max_slice.arity()` are treated independently as fixed-lengths + // slices, and lengths above are captured by `max_slice`. + None => 0..max_slice.arity(), }; - smaller_lengths - .map(FixedLen) - .chain(once(self.max_slice)) - .map(move |kind| Slice::new(self.array_len, kind)) + smaller_lengths.map(FixedLen).chain(once(max_slice)).map(move |slice| { + let arity = slice.arity(); + let seen = if min_var_len <= arity || seen_fixed_lens.contains(&arity) { + Presence::Seen + } else { + Presence::Unseen + }; + (seen, Slice::new(array_len, slice)) + }) + } +} + +/// A globally unique id to distinguish `Opaque` patterns. +#[derive(Clone, Debug, PartialEq, Eq)] +pub(super) struct OpaqueId(u32); + +impl OpaqueId { + fn new() -> Self { + use std::sync::atomic::{AtomicU32, Ordering}; + static OPAQUE_ID: AtomicU32 = AtomicU32::new(0); + OpaqueId(OPAQUE_ID.fetch_add(1, Ordering::SeqCst)) } } @@ -635,17 +649,17 @@ pub(super) enum Constructor<'tcx> { Str(mir::ConstantKind<'tcx>), /// Array and slice patterns. Slice(Slice), - /// Constants that must not be matched structurally. They are treated as black - /// boxes for the purposes of exhaustiveness: we must not inspect them, and they - /// don't count towards making a match exhaustive. - Opaque, + /// Constants that must not be matched structurally. They are treated as black boxes for the + /// purposes of exhaustiveness: we must not inspect them, and they don't count towards making a + /// match exhaustive. + /// Carries an id that must be globally unique. + Opaque(OpaqueId), /// Fake extra constructor for enums that aren't allowed to be matched exhaustively. Also used /// for those types for which we cannot list constructors explicitly, like `f64` and `str`. NonExhaustive, /// Stands for constructors that are not seen in the matrix, as explained in the documentation - /// for [`SplitWildcard`]. The carried `bool` is used for the `non_exhaustive_omitted_patterns` - /// lint. - Missing { nonexhaustive_enum_missing_real_variants: bool }, + /// for `SplitWildcard`. + Missing, /// Wildcard pattern. Wildcard, /// Or-pattern. @@ -661,13 +675,18 @@ impl<'tcx> Constructor<'tcx> { matches!(self, NonExhaustive) } + fn as_variant(&self) -> Option { + match self { + Variant(i) => Some(*i), + _ => None, + } + } fn as_int_range(&self) -> Option<&IntRange> { match self { IntRange(range) => Some(range), _ => None, } } - fn as_slice(&self) -> Option { match self { Slice(slice) => Some(*slice), @@ -736,66 +755,20 @@ impl<'tcx> Constructor<'tcx> { | FloatRange(..) | IntRange(..) | NonExhaustive - | Opaque + | Opaque(..) | Missing { .. } | Wildcard => 0, Or => bug!("The `Or` constructor doesn't have a fixed arity"), } } - /// Some constructors (namely `Wildcard`, `IntRange` and `Slice`) actually stand for a set of actual - /// constructors (like variants, integers or fixed-sized slices). When specializing for these - /// constructors, we want to be specialising for the actual underlying constructors. - /// Naively, we would simply return the list of constructors they correspond to. We instead are - /// more clever: if there are constructors that we know will behave the same wrt the current - /// matrix, we keep them grouped. For example, all slices of a sufficiently large length - /// will either be all useful or all non-useful with a given matrix. - /// - /// See the branches for details on how the splitting is done. - /// - /// This function may discard some irrelevant constructors if this preserves behavior and - /// diagnostics. Eg. for the `_` case, we ignore the constructors already present in the - /// matrix, unless all of them are. - pub(super) fn split<'a>( - &self, - pcx: &PatCtxt<'_, '_, 'tcx>, - ctors: impl Iterator> + Clone, - ) -> SmallVec<[Self; 1]> - where - 'tcx: 'a, - { - match self { - Wildcard => { - let mut split_wildcard = SplitWildcard::new(pcx); - split_wildcard.split(pcx, ctors); - split_wildcard.into_ctors(pcx) - } - // Fast-track if the range is trivial. In particular, we don't do the overlapping - // ranges check. - IntRange(ctor_range) if !ctor_range.is_singleton() => { - let mut split_range = SplitIntRange::new(ctor_range.clone()); - let int_ranges = ctors.filter_map(|ctor| ctor.as_int_range()); - split_range.split(int_ranges.cloned()); - split_range.iter().map(IntRange).collect() - } - &Slice(Slice { kind: VarLen(self_prefix, self_suffix), array_len }) => { - let mut split_self = SplitVarLenSlice::new(self_prefix, self_suffix, array_len); - let slices = ctors.filter_map(|c| c.as_slice()).map(|s| s.kind); - split_self.split(slices); - split_self.iter().map(Slice).collect() - } - // Any other constructor can be used unchanged. - _ => smallvec![self.clone()], - } - } - /// Returns whether `self` is covered by `other`, i.e. whether `self` is a subset of `other`. /// For the simple cases, this is simply checking for equality. For the "grouped" constructors, /// this checks for inclusion. // We inline because this has a single call site in `Matrix::specialize_constructor`. #[inline] pub(super) fn is_covered_by<'p>(&self, pcx: &PatCtxt<'_, 'p, 'tcx>, other: &Self) -> bool { - // This must be kept in sync with `is_covered_by_any`. + // This must be compatible with `ConstructorSet::split`. match (self, other) { // Wildcards cover anything (_, Wildcard) => true, @@ -805,7 +778,7 @@ impl<'tcx> Constructor<'tcx> { (Single, Single) => true, (Variant(self_id), Variant(other_id)) => self_id == other_id, - (IntRange(self_range), IntRange(other_range)) => self_range.is_covered_by(other_range), + (IntRange(self_range), IntRange(other_range)) => self_range.is_subrange(other_range), ( FloatRange(self_from, self_to, self_end), FloatRange(other_from, other_to, other_end), @@ -829,109 +802,104 @@ impl<'tcx> Constructor<'tcx> { } (Slice(self_slice), Slice(other_slice)) => self_slice.is_covered_by(*other_slice), - // We are trying to inspect an opaque constant. Thus we skip the row. - (Opaque, _) | (_, Opaque) => false, + // Opaque constructors don't interact with anything unless they come from the + // structurally identical pattern. + (Opaque(self_id), Opaque(other_id)) => self_id == other_id, + (Opaque(..), _) | (_, Opaque(..)) => false, // Only a wildcard pattern can match the special extra constructor. (NonExhaustive, _) => false, - _ => span_bug!( - pcx.span, - "trying to compare incompatible constructors {:?} and {:?}", - self, - other - ), - } - } - - /// Faster version of `is_covered_by` when applied to many constructors. `used_ctors` is - /// assumed to be built from `matrix.head_ctors()` with wildcards and opaques filtered out, - /// and `self` is assumed to have been split from a wildcard. - fn is_covered_by_any<'p>( - &self, - pcx: &PatCtxt<'_, 'p, 'tcx>, - used_ctors: &[Constructor<'tcx>], - ) -> bool { - if used_ctors.is_empty() { - return false; - } - - // This must be kept in sync with `is_covered_by`. - match self { - // If `self` is `Single`, `used_ctors` cannot contain anything else than `Single`s. - Single => !used_ctors.is_empty(), - Variant(vid) => used_ctors.iter().any(|c| matches!(c, Variant(i) if i == vid)), - IntRange(range) => used_ctors - .iter() - .filter_map(|c| c.as_int_range()) - .any(|other| range.is_covered_by(other)), - Slice(slice) => used_ctors - .iter() - .filter_map(|c| c.as_slice()) - .any(|other| slice.is_covered_by(other)), - // This constructor is never covered by anything else - NonExhaustive => false, - Str(..) | FloatRange(..) | Opaque | Missing { .. } | Wildcard | Or => { - span_bug!(pcx.span, "found unexpected ctor in all_ctors: {:?}", self) - } + _ => bug!("trying to compare incompatible constructors {:?} and {:?}", self, other), } } } -/// A wildcard constructor that we split relative to the constructors in the matrix, as explained -/// at the top of the file. -/// -/// A constructor that is not present in the matrix rows will only be covered by the rows that have -/// wildcards. Thus we can group all of those constructors together; we call them "missing -/// constructors". Splitting a wildcard would therefore list all present constructors individually -/// (or grouped if they are integers or slices), and then all missing constructors together as a -/// group. -/// -/// However we can go further: since any constructor will match the wildcard rows, and having more -/// rows can only reduce the amount of usefulness witnesses, we can skip the present constructors -/// and only try the missing ones. -/// This will not preserve the whole list of witnesses, but will preserve whether the list is empty -/// or not. In fact this is quite natural from the point of view of diagnostics too. This is done -/// in `to_ctors`: in some cases we only return `Missing`. -#[derive(Debug)] -pub(super) struct SplitWildcard<'tcx> { - /// Constructors (other than wildcards and opaques) seen in the matrix. - matrix_ctors: Vec>, - /// All the constructors for this type - all_ctors: SmallVec<[Constructor<'tcx>; 1]>, +/// Describes the set of all constructors for a type. +pub(super) enum ConstructorSet { + /// The type has a single constructor, e.g. `&T` or a newtype. + Single, + /// This type has the following list of constructors. + Variants { variants: Vec, non_exhaustive: bool }, + /// The type is spanned by integer values. The range or ranges give the set of allowed values. + /// The second range is only useful for `char`. + /// This is reused for bool. FIXME: don't. + /// `non_exhaustive` is used when the range is not allowed to be matched exhaustively (that's + /// for usize/isize). + Integers { range_1: IntRange, range_2: Option, non_exhaustive: bool }, + /// The type is matched by slices. The usize is the compile-time length of the array, if known. + Slice(Option), + /// The type is matched by slices whose elements are uninhabited. + SliceOfEmpty, + /// The constructors cannot be listed, and the type cannot be matched exhaustively. E.g. `str`, + /// floats. + Unlistable, + /// The type has no inhabitants. + Uninhabited, } -impl<'tcx> SplitWildcard<'tcx> { - pub(super) fn new<'p>(pcx: &PatCtxt<'_, 'p, 'tcx>) -> Self { - debug!("SplitWildcard::new({:?})", pcx.ty); +impl ConstructorSet { + pub(super) fn new<'p, 'tcx>(pcx: &PatCtxt<'_, 'p, 'tcx>) -> Self { + debug!("ConstructorSet::new({:?})", pcx.ty); let cx = pcx.cx; + let ty = pcx.ty; let make_range = |start, end| { - IntRange( - // `unwrap()` is ok because we know the type is an integer. - IntRange::from_range(cx.tcx, start, end, pcx.ty, &RangeEnd::Included).unwrap(), - ) + // `unwrap()` is ok because we know the type is an integer. + IntRange::from_range(cx.tcx, start, end, ty, &RangeEnd::Included).unwrap() }; - // This determines the set of all possible constructors for the type `pcx.ty`. For numbers, + // 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 + // Invariant: this is `Uninhabited` if and only if the type is uninhabited (as determined by // `cx.is_uninhabited()`). - let all_ctors = match pcx.ty.kind() { - ty::Bool => smallvec![make_range(0, 1)], + match ty.kind() { + ty::Bool => { + Self::Integers { range_1: make_range(0, 1), range_2: None, non_exhaustive: false } + } + ty::Char => { + // The valid Unicode Scalar Value ranges. + Self::Integers { + range_1: make_range('\u{0000}' as u128, '\u{D7FF}' as u128), + range_2: Some(make_range('\u{E000}' as u128, '\u{10FFFF}' as u128)), + non_exhaustive: false, + } + } + &ty::Int(ity) => { + // `usize`/`isize` are not allowed to be matched exhaustively unless the + // `precise_pointer_size_matching` feature is enabled. + let non_exhaustive = + ty.is_ptr_sized_integral() && !cx.tcx.features().precise_pointer_size_matching; + let bits = Integer::from_int_ty(&cx.tcx, ity).size().bits() as u128; + let min = 1u128 << (bits - 1); + let max = min - 1; + Self::Integers { range_1: make_range(min, max), non_exhaustive, range_2: None } + } + &ty::Uint(uty) => { + // `usize`/`isize` are not allowed to be matched exhaustively unless the + // `precise_pointer_size_matching` feature is enabled. + let non_exhaustive = + ty.is_ptr_sized_integral() && !cx.tcx.features().precise_pointer_size_matching; + let size = Integer::from_uint_ty(&cx.tcx, uty).size(); + let max = size.truncate(u128::MAX); + Self::Integers { range_1: make_range(0, max), non_exhaustive, range_2: None } + } ty::Array(sub_ty, len) if len.try_eval_target_usize(cx.tcx, cx.param_env).is_some() => { let len = len.eval_target_usize(cx.tcx, cx.param_env) as usize; if len != 0 && cx.is_uninhabited(*sub_ty) { - smallvec![] + Self::Uninhabited } else { - smallvec![Slice(Slice::new(Some(len), VarLen(0, 0)))] + Self::Slice(Some(len)) } } // Treat arrays of a constant but unknown length like slices. ty::Array(sub_ty, _) | ty::Slice(sub_ty) => { - let kind = if cx.is_uninhabited(*sub_ty) { FixedLen(0) } else { VarLen(0, 0) }; - smallvec![Slice(Slice::new(None, kind))] + if cx.is_uninhabited(*sub_ty) { + Self::SliceOfEmpty + } else { + Self::Slice(None) + } } ty::Adt(def, substs) if def.is_enum() => { // If the enum is declared as `#[non_exhaustive]`, we treat it as if it had an @@ -950,159 +918,183 @@ impl<'tcx> SplitWildcard<'tcx> { // // we don't want to show every possible IO error, but instead have only `_` as the // witness. - let is_declared_nonexhaustive = cx.is_foreign_non_exhaustive_enum(pcx.ty); - - let is_exhaustive_pat_feature = cx.tcx.features().exhaustive_patterns; - - // If `exhaustive_patterns` is disabled and our scrutinee is an empty enum, we treat it - // as though it had an "unknown" constructor to avoid exposing its emptiness. The - // exception is if the pattern is at the top level, because we want empty matches to be - // considered exhaustive. - let is_secretly_empty = - def.variants().is_empty() && !is_exhaustive_pat_feature && !pcx.is_top_level; - - let mut ctors: SmallVec<[_; 1]> = def - .variants() - .iter_enumerated() - .filter(|(_, v)| { - // If `exhaustive_patterns` is enabled, we exclude variants known to be - // uninhabited. - !is_exhaustive_pat_feature - || v.inhabited_predicate(cx.tcx, *def).subst(cx.tcx, substs).apply( - cx.tcx, - cx.param_env, - cx.module, - ) - }) - .map(|(idx, _)| Variant(idx)) - .collect(); + let is_declared_nonexhaustive = cx.is_foreign_non_exhaustive_enum(ty); - if is_secretly_empty || is_declared_nonexhaustive { - ctors.push(NonExhaustive); + if def.variants().is_empty() && !is_declared_nonexhaustive { + Self::Uninhabited + } else { + let is_exhaustive_pat_feature = cx.tcx.features().exhaustive_patterns; + let variants: Vec<_> = + def.variants() + .iter_enumerated() + .filter(|(_, v)| { + // If `exhaustive_patterns` is enabled, we exclude variants known to be + // uninhabited. + !is_exhaustive_pat_feature + || v.inhabited_predicate(cx.tcx, *def) + .subst(cx.tcx, substs) + .apply(cx.tcx, cx.param_env, cx.module) + }) + .map(|(idx, _)| idx) + .collect(); + + Self::Variants { variants, non_exhaustive: is_declared_nonexhaustive } } - ctors - } - ty::Char => { - smallvec![ - // The valid Unicode Scalar Value ranges. - make_range('\u{0000}' as u128, '\u{D7FF}' as u128), - make_range('\u{E000}' as u128, '\u{10FFFF}' as u128), - ] - } - ty::Int(_) | ty::Uint(_) - if pcx.ty.is_ptr_sized_integral() - && !cx.tcx.features().precise_pointer_size_matching => - { - // `usize`/`isize` are not allowed to be matched exhaustively unless the - // `precise_pointer_size_matching` feature is enabled. So we treat those types like - // `#[non_exhaustive]` enums by returning a special unmatchable constructor. - smallvec![NonExhaustive] } - &ty::Int(ity) => { - let bits = Integer::from_int_ty(&cx.tcx, ity).size().bits() as u128; - let min = 1u128 << (bits - 1); - let max = min - 1; - smallvec![make_range(min, max)] - } - &ty::Uint(uty) => { - let size = Integer::from_uint_ty(&cx.tcx, uty).size(); - let max = size.truncate(u128::MAX); - smallvec![make_range(0, max)] - } - // If `exhaustive_patterns` is disabled and our scrutinee is the never type, we cannot - // expose its emptiness. The exception is if the pattern is at the top level, because we - // want empty matches to be considered exhaustive. - ty::Never if !cx.tcx.features().exhaustive_patterns && !pcx.is_top_level => { - smallvec![NonExhaustive] - } - ty::Never => smallvec![], - _ if cx.is_uninhabited(pcx.ty) => smallvec![], - ty::Adt(..) | ty::Tuple(..) | ty::Ref(..) => smallvec![Single], + ty::Never => Self::Uninhabited, + _ if cx.is_uninhabited(ty) => Self::Uninhabited, + ty::Adt(..) | ty::Tuple(..) | ty::Ref(..) => Self::Single, // This type is one for which we cannot list constructors, like `str` or `f64`. - _ => smallvec![NonExhaustive], - }; - - SplitWildcard { matrix_ctors: Vec::new(), all_ctors } + _ => Self::Unlistable, + } } - /// Pass a set of constructors relative to which to split this one. Don't call twice, it won't - /// do what you want. - pub(super) fn split<'a>( - &mut self, + /// Split the set into two vecs `split` and `missing`, respecting the following: + /// - `split` covers the whole type + /// - constructors in `split` and `missing` are split for the `ctors` list + /// - all non-wildcards in `ctors` cover something in `split` (including `Opaques`) + /// - no non-wildcard in `ctors` covers anything in `missing` + /// - if we replace any `Wildcard`/`Missing` in `split` with all of `missing`, this also + /// covers the whole type + pub(super) fn split<'a, 'tcx>( + &self, pcx: &PatCtxt<'_, '_, 'tcx>, ctors: impl Iterator> + Clone, - ) where + // Whether the current pattern is the whole pattern as found in a match arm, or if it's a + // subpattern. + is_top_level: bool, + ) -> (Vec>, Vec>) + where 'tcx: 'a, { - // Since `all_ctors` never contains wildcards, this won't recurse further. - self.all_ctors = - self.all_ctors.iter().flat_map(|ctor| ctor.split(pcx, ctors.clone())).collect(); - self.matrix_ctors = ctors.filter(|c| !matches!(c, Wildcard | Opaque)).cloned().collect(); - } - - /// Whether there are any value constructors for this type that are not present in the matrix. - fn any_missing(&self, pcx: &PatCtxt<'_, '_, 'tcx>) -> bool { - self.iter_missing(pcx).next().is_some() - } - - /// Iterate over the constructors for this type that are not present in the matrix. - pub(super) fn iter_missing<'a, 'p>( - &'a self, - pcx: &'a PatCtxt<'a, 'p, 'tcx>, - ) -> impl Iterator> + Captures<'p> { - self.all_ctors.iter().filter(move |ctor| !ctor.is_covered_by_any(pcx, &self.matrix_ctors)) - } - - /// Return the set of constructors resulting from splitting the wildcard. As explained at the - /// top of the file, if any constructors are missing we can ignore the present ones. - fn into_ctors(self, pcx: &PatCtxt<'_, '_, 'tcx>) -> SmallVec<[Constructor<'tcx>; 1]> { - if self.any_missing(pcx) { - // Some constructors are missing, thus we can specialize with the special `Missing` - // constructor, which stands for those constructors that are not seen in the matrix, - // and matches the same rows as any of them (namely the wildcard rows). See the top of - // the file for details. - // However, when all constructors are missing we can also specialize with the full - // `Wildcard` constructor. The difference will depend on what we want in diagnostics. - - // If some constructors are missing, we typically want to report those constructors, - // e.g.: - // ``` - // enum Direction { N, S, E, W } - // let Direction::N = ...; - // ``` - // we can report 3 witnesses: `S`, `E`, and `W`. - // - // However, if the user didn't actually specify a constructor - // in this arm, e.g., in - // ``` - // let x: (Direction, Direction, bool) = ...; - // let (_, _, false) = x; - // ``` - // we don't want to show all 16 possible witnesses `(, , - // true)` - we are satisfied with `(_, _, true)`. So if all constructors are missing we - // prefer to report just a wildcard `_`. - // - // The exception is: if we are at the top-level, for example in an empty match, we - // sometimes prefer reporting the list of constructors instead of just `_`. - let report_when_all_missing = pcx.is_top_level && !IntRange::is_integral(pcx.ty); - let ctor = if !self.matrix_ctors.is_empty() || report_when_all_missing { - if pcx.is_non_exhaustive { - Missing { - nonexhaustive_enum_missing_real_variants: self - .iter_missing(pcx) - .any(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx))), + let mut missing = Vec::new(); + let mut split = Vec::new(); + // Constructors in `ctors`, except wildcards and opaques. + let mut seen = Vec::new(); + for ctor in ctors.cloned() { + match ctor { + Wildcard => {} + Constructor::Opaque(..) => { + split.push(ctor); + } + _ => { + seen.push(ctor); + } + } + } + let seen_any_non_wildcard = !(split.is_empty() && seen.is_empty()); + match self { + ConstructorSet::Single => { + if seen.is_empty() { + missing.push(Single); + } else { + split.push(Single); + } + } + ConstructorSet::Variants { variants, non_exhaustive } if seen.is_empty() => { + missing.extend(variants.iter().copied().map(Variant).filter(|ctor| { + !(ctor.is_doc_hidden_variant(pcx) || ctor.is_unstable_variant(pcx)) + })); + if *non_exhaustive { + missing.push(NonExhaustive); + } + } + ConstructorSet::Variants { variants, non_exhaustive } => { + let seen_set: FxHashSet<_> = seen.iter().map(|c| c.as_variant().unwrap()).collect(); + let mut skipped_any_missing_variant = false; + for variant in variants { + let ctor = Variant(*variant); + if seen_set.contains(&variant) { + split.push(ctor); + } else if ctor.is_doc_hidden_variant(pcx) || ctor.is_unstable_variant(pcx) { + // We don't want to mention any variants that are `doc(hidden)` or + // behind an unstable feature gate. + skipped_any_missing_variant = true; + } else { + missing.push(ctor); } + } + if skipped_any_missing_variant { + missing.push(Wildcard); + } + if *non_exhaustive { + missing.push(NonExhaustive); + } + } + ConstructorSet::Integers { range_1, range_2, non_exhaustive } if seen.is_empty() => { + if *non_exhaustive { + missing.push(NonExhaustive); } else { - Missing { nonexhaustive_enum_missing_real_variants: false } + missing.push(IntRange(range_1.clone())); + if let Some(range_2) = range_2 { + missing.push(IntRange(range_2.clone())); + } + } + } + ConstructorSet::Integers { range_1, range_2, non_exhaustive } => { + let range = match range_2 { + None => SplitIntRange::Single(range_1.clone()), + Some(range_2) => SplitIntRange::Double([range_1.clone(), range_2.clone()]), + }; + let seen_ranges = seen.iter().map(|ctor| ctor.as_int_range().unwrap()); + for (seen, splitted_range) in range.split(seen_ranges.cloned()) { + let ctor = IntRange(splitted_range); + match seen { + Presence::Unseen => missing.push(ctor), + Presence::Seen => split.push(ctor), + } + } + if *non_exhaustive { + missing.push(NonExhaustive); + } + } + &ConstructorSet::Slice(array_len) if seen.is_empty() => { + missing.push(Slice(Slice::new(array_len, VarLen(0, 0)))); + } + &ConstructorSet::Slice(array_len) => { + let seen_slices = seen.iter().map(|c| c.as_slice().unwrap()).map(|s| s.kind); + for (seen, splitted_slice) in SplitVarLenSlice::split(array_len, seen_slices) { + let ctor = Slice(splitted_slice); + match seen { + Presence::Unseen => missing.push(ctor), + Presence::Seen => split.push(ctor), + } } + } + ConstructorSet::SliceOfEmpty => { + // Behaves essentially like `Single`. + let slice = Slice(Slice::new(None, FixedLen(0))); + if seen.is_empty() { + missing.push(slice); + } else { + split.push(slice); + } + } + ConstructorSet::Unlistable => { + // Since we can't list constructors, we take the ones in the matrix. This might list + // some constructors several times but there's not much we can do. + split.extend(seen); + missing.push(NonExhaustive); + } + // If `exhaustive_patterns` is disabled and our scrutinee is an empty type, we cannot + // expose its emptiness. The exception is if the pattern is at the top level, because we + // want empty matches to be considered exhaustive. + ConstructorSet::Uninhabited + if !pcx.cx.tcx.features().exhaustive_patterns && !is_top_level => + { + missing.push(NonExhaustive); + } + ConstructorSet::Uninhabited => {} + } + if !missing.is_empty() { + let report_when_all_missing = is_top_level && !matches!(self, Self::Integers { .. }); + if seen_any_non_wildcard || report_when_all_missing { + split.push(Missing); } else { - Wildcard - }; - return smallvec![ctor]; + split.push(Wildcard); + } } - - // All the constructors are present in the matrix, so we just go through them all. - self.all_ctors + (split, missing) } } @@ -1189,21 +1181,25 @@ impl<'p, 'tcx> Fields<'p, 'tcx> { /// Creates a new list of wildcard fields for a given constructor. The result must have a /// length of `constructor.arity()`. #[instrument(level = "trace")] - pub(super) fn wildcards(pcx: &PatCtxt<'_, 'p, 'tcx>, constructor: &Constructor<'tcx>) -> Self { + pub(super) fn wildcards( + pcx: &PatCtxt<'_, 'p, 'tcx>, + constructor: &Constructor<'tcx>, + span: Span, + ) -> Self { let ret = match constructor { Single | Variant(_) => match pcx.ty.kind() { - ty::Tuple(fs) => Fields::wildcards_from_tys(pcx.cx, fs.iter(), pcx.span), - ty::Ref(_, rty, _) => Fields::wildcards_from_tys(pcx.cx, once(*rty), pcx.span), + ty::Tuple(fs) => Fields::wildcards_from_tys(pcx.cx, fs.iter(), span), + ty::Ref(_, rty, _) => Fields::wildcards_from_tys(pcx.cx, once(*rty), span), ty::Adt(adt, substs) => { if adt.is_box() { // 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. - Fields::wildcards_from_tys(pcx.cx, once(substs.type_at(0)), pcx.span) + Fields::wildcards_from_tys(pcx.cx, once(substs.type_at(0)), span) } else { let variant = &adt.variant(constructor.variant_index_for_adt(*adt)); let tys = Fields::list_variant_nonhidden_fields(pcx.cx, pcx.ty, variant) .map(|(_, ty)| ty); - Fields::wildcards_from_tys(pcx.cx, tys, pcx.span) + Fields::wildcards_from_tys(pcx.cx, tys, span) } } _ => bug!("Unexpected type for `Single` constructor: {:?}", pcx), @@ -1211,7 +1207,7 @@ impl<'p, 'tcx> Fields<'p, 'tcx> { Slice(slice) => match *pcx.ty.kind() { ty::Slice(ty) | ty::Array(ty, _) => { let arity = slice.arity(); - Fields::wildcards_from_tys(pcx.cx, (0..arity).map(|_| ty), pcx.span) + Fields::wildcards_from_tys(pcx.cx, (0..arity).map(|_| ty), span) } _ => bug!("bad slice pattern {:?} {:?}", constructor, pcx), }, @@ -1219,7 +1215,7 @@ impl<'p, 'tcx> Fields<'p, 'tcx> { | FloatRange(..) | IntRange(..) | NonExhaustive - | Opaque + | Opaque(..) | Missing { .. } | Wildcard => Fields::empty(), Or => { @@ -1240,9 +1236,10 @@ impl<'p, 'tcx> Fields<'p, 'tcx> { /// Values and patterns can be represented as a constructor applied to some fields. This represents /// a pattern in this form. -/// This also keeps track of whether the pattern has been found reachable during analysis. For this -/// reason we should be careful not to clone patterns for which we care about that. Use -/// `clone_and_forget_reachability` if you're sure. +/// This also uses interior mutability to keep track of whether the pattern has been found reachable +/// during analysis. For this reason we should be careful not to clone patterns for which we care +/// about that. +#[derive(Clone)] pub(crate) struct DeconstructedPat<'p, 'tcx> { ctor: Constructor<'tcx>, fields: Fields<'p, 'tcx>, @@ -1269,14 +1266,8 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { /// For example, if `ctor` is a `Constructor::Variant` for `Option::Some`, we get the pattern /// `Some(_)`. pub(super) fn wild_from_ctor(pcx: &PatCtxt<'_, 'p, 'tcx>, ctor: Constructor<'tcx>) -> Self { - let fields = Fields::wildcards(pcx, &ctor); - DeconstructedPat::new(ctor, fields, pcx.ty, pcx.span) - } - - /// Clone this value. This method emphasizes that cloning loses reachability information and - /// should be done carefully. - pub(super) fn clone_and_forget_reachability(&self) -> Self { - DeconstructedPat::new(self.ctor.clone(), self.fields, self.ty, self.span) + let fields = Fields::wildcards(pcx, &ctor, DUMMY_SP); + DeconstructedPat::new(ctor, fields, pcx.ty, DUMMY_SP) } pub(crate) fn from_pat(cx: &MatchCheckCtxt<'p, 'tcx>, pat: &Pat<'tcx>) -> Self { @@ -1382,7 +1373,7 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { // into the corresponding `Pat`s by `const_to_pat`. Constants that remain are // opaque. _ => { - ctor = Opaque; + ctor = Opaque(OpaqueId::new()); fields = Fields::empty(); } } @@ -1511,7 +1502,7 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { "trying to convert a `Missing` constructor into a `Pat`; this is probably a bug, `Missing` should have been processed in `apply_constructors`" ), - Opaque | Or => { + Opaque(..) | Or => { bug!("can't convert to pattern: {:?}", self) } }; @@ -1549,7 +1540,7 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { match (&self.ctor, other_ctor) { (Wildcard, _) => { // We return a wildcard for each field of `other_ctor`. - Fields::wildcards(pcx, other_ctor).iter_patterns().collect() + Fields::wildcards(pcx, other_ctor, self.span()).iter_patterns().collect() } (Slice(self_slice), Slice(other_slice)) if self_slice.arity() != other_slice.arity() => @@ -1570,7 +1561,7 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { let wildcard: &_ = pcx .cx .pattern_arena - .alloc(DeconstructedPat::wildcard(inner_ty, pcx.span)); + .alloc(DeconstructedPat::wildcard(inner_ty, self.span())); let extra_wildcards = other_slice.arity() - self_slice.arity(); let extra_wildcards = (0..extra_wildcards).map(|_| wildcard); prefix.iter().chain(extra_wildcards).chain(suffix).collect() @@ -1587,7 +1578,16 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { self.reachable.set(true) } pub(super) fn is_reachable(&self) -> bool { - self.reachable.get() + if self.reachable.get() { + true + } else if self.is_or_pat() && self.iter_fields().any(|f| f.is_reachable()) { + // We always expand or patterns in the matrix, so the algo will never see the or-pattern + // itself, only the children. We recover this information here. + self.set_reachable(); + true + } else { + false + } } /// Report the spans of subpatterns that were not reachable, if any. @@ -1596,7 +1596,6 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { self.collect_unreachable_spans(&mut spans); spans } - fn collect_unreachable_spans(&self, spans: &mut Vec) { // We don't look at subpatterns if we already reported the whole pattern as unreachable. if !self.is_reachable() { @@ -1700,7 +1699,7 @@ impl<'p, 'tcx> fmt::Debug for DeconstructedPat<'p, 'tcx> { Ok(()) } Str(value) => write!(f, "{}", value), - Opaque => write!(f, ""), + Opaque(..) => write!(f, ""), } } } diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index e5b6350690609..6d46f22d77d75 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -272,7 +272,7 @@ //! //==>> we have tried all the constructors. The output is the single witness `[Some(false)]`. //! ``` //! -//! This computation is done in [`is_useful`]. In practice we don't care about the list of +//! This computation is done in `is_useful`. In practice we don't care about the list of //! witnesses when computing reachability; we only need to know whether any exist. We do keep the //! witnesses when computing exhaustiveness to report them to the user. //! @@ -287,7 +287,7 @@ //! group together constructors that behave the same. //! //! The details are not necessary to understand this file, so we explain them in -//! [`super::deconstruct_pat`]. Splitting is done by the [`Constructor::split`] function. +//! [`super::deconstruct_pat`]. Splitting is done by the `Constructor::split` function. //! //! # Constants in patterns //! @@ -305,14 +305,11 @@ //! stay as a full constant and become an `Opaque` pattern. These `Opaque` patterns do not participate //! in exhaustiveness, specialization or overlap checking. -use self::ArmType::*; -use self::Usefulness::*; -use super::deconstruct_pat::{Constructor, DeconstructedPat, Fields, SplitWildcard}; +use super::deconstruct_pat::{Constructor, ConstructorSet, DeconstructedPat, Fields}; use crate::errors::{NonExhaustiveOmittedPattern, Uncovered}; -use rustc_data_structures::captures::Captures; - use rustc_arena::TypedArena; +use rustc_data_structures::captures::Captures; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_hir::def_id::DefId; use rustc_hir::HirId; @@ -322,7 +319,6 @@ use rustc_span::{Span, DUMMY_SP}; use smallvec::{smallvec, SmallVec}; use std::fmt; -use std::iter::once; pub(crate) struct MatchCheckCtxt<'p, 'tcx> { pub(crate) tcx: TyCtxt<'tcx>, @@ -363,13 +359,6 @@ pub(super) struct PatCtxt<'a, 'p, 'tcx> { pub(super) cx: &'a MatchCheckCtxt<'p, 'tcx>, /// Type of the current column under investigation. pub(super) ty: Ty<'tcx>, - /// Span of the current pattern under investigation. - pub(super) span: Span, - /// Whether the current pattern is the whole pattern as found in a match arm, or if it's a - /// subpattern. - pub(super) is_top_level: bool, - /// Whether the current pattern is from a `non_exhaustive` enum. - pub(super) is_non_exhaustive: bool, } impl<'a, 'p, 'tcx> fmt::Debug for PatCtxt<'a, 'p, 'tcx> { @@ -381,17 +370,33 @@ impl<'a, 'p, 'tcx> fmt::Debug for PatCtxt<'a, 'p, 'tcx> { /// A row of a matrix. Rows of len 1 are very common, which is why `SmallVec[_; 2]` /// works well. #[derive(Clone)] -pub(crate) struct PatStack<'p, 'tcx> { - pub(crate) pats: SmallVec<[&'p DeconstructedPat<'p, 'tcx>; 2]>, +struct PatStack<'p, 'tcx> { + pats: SmallVec<[&'p DeconstructedPat<'p, 'tcx>; 2]>, + /// The row (in the matrix) of the `PatStack` from which this one is derived. When there is + /// none, this is the id of the arm. + parent_row: usize, + is_under_guard: bool, + arm_hir_id: HirId, + is_useful: bool, } impl<'p, 'tcx> PatStack<'p, 'tcx> { - fn from_pattern(pat: &'p DeconstructedPat<'p, 'tcx>) -> Self { - Self::from_vec(smallvec![pat]) + fn from_pattern( + pat: &'p DeconstructedPat<'p, 'tcx>, + parent_row: usize, + is_under_guard: bool, + arm_hir_id: HirId, + ) -> Self { + Self::from_vec(smallvec![pat], parent_row, is_under_guard, arm_hir_id) } - fn from_vec(vec: SmallVec<[&'p DeconstructedPat<'p, 'tcx>; 2]>) -> Self { - PatStack { pats: vec } + fn from_vec( + vec: SmallVec<[&'p DeconstructedPat<'p, 'tcx>; 2]>, + parent_row: usize, + is_under_guard: bool, + arm_hir_id: HirId, + ) -> Self { + PatStack { pats: vec, parent_row, is_under_guard, arm_hir_id, is_useful: false } } fn is_empty(&self) -> bool { @@ -410,31 +415,17 @@ impl<'p, 'tcx> PatStack<'p, 'tcx> { self.pats.iter().copied() } - // Recursively expand the first pattern into its subpatterns. Only useful if the pattern is an + // Expand the first pattern into its subpatterns. Only useful if the pattern is an // or-pattern. Panics if `self` is empty. fn expand_or_pat<'a>(&'a self) -> impl Iterator> + Captures<'a> { self.head().iter_fields().map(move |pat| { - let mut new_patstack = PatStack::from_pattern(pat); + let mut new_patstack = + PatStack::from_pattern(pat, self.parent_row, self.is_under_guard, self.arm_hir_id); new_patstack.pats.extend_from_slice(&self.pats[1..]); new_patstack }) } - // Recursively expand all patterns into their subpatterns and push each `PatStack` to matrix. - fn expand_and_extend<'a>(&'a self, matrix: &mut Matrix<'p, 'tcx>) { - if !self.is_empty() && self.head().is_or_pat() { - for pat in self.head().iter_fields() { - let mut new_patstack = PatStack::from_pattern(pat); - new_patstack.pats.extend_from_slice(&self.pats[1..]); - if !new_patstack.is_empty() && new_patstack.head().is_or_pat() { - new_patstack.expand_and_extend(matrix); - } else if !new_patstack.is_empty() { - matrix.push(new_patstack); - } - } - } - } - /// This computes `S(self.head().ctor(), self)`. See top of the file for explanations. /// /// Structure patterns with a partial wild pattern (Foo { a: 42, .. }) have their missing @@ -445,12 +436,13 @@ impl<'p, 'tcx> PatStack<'p, 'tcx> { &self, pcx: &PatCtxt<'_, 'p, 'tcx>, ctor: &Constructor<'tcx>, + parent_row: usize, ) -> PatStack<'p, 'tcx> { // We pop the head pattern and push the new fields extracted from the arguments of // `self.head()`. let mut new_fields: SmallVec<[_; 2]> = self.head().specialize(pcx, ctor); new_fields.extend_from_slice(&self.pats[1..]); - PatStack::from_vec(new_fields) + PatStack::from_vec(new_fields, parent_row, self.is_under_guard, self.arm_hir_id) } } @@ -467,35 +459,64 @@ impl<'p, 'tcx> fmt::Debug for PatStack<'p, 'tcx> { /// A 2D matrix. #[derive(Clone)] -pub(super) struct Matrix<'p, 'tcx> { - pub patterns: Vec>, +struct Matrix<'p, 'tcx> { + /// The rows of this matrix. + /// Invariant: `row.head()` is never an or-pattern. + rows: Vec>, + /// Fake row that stores wildcard patterns that match the other rows. This is used only to track + /// the type and number of columns of the matrix. + wildcard_row: PatStack<'p, 'tcx>, } impl<'p, 'tcx> Matrix<'p, 'tcx> { - fn empty() -> Self { - Matrix { patterns: vec![] } - } - - /// Number of columns of this matrix. `None` is the matrix is empty. - pub(super) fn column_count(&self) -> Option { - self.patterns.get(0).map(|r| r.len()) + fn empty(wildcard_row: PatStack<'p, 'tcx>) -> Self { + Matrix { rows: vec![], wildcard_row } } /// Pushes a new row to the matrix. If the row starts with an or-pattern, this recursively /// expands it. fn push(&mut self, row: PatStack<'p, 'tcx>) { if !row.is_empty() && row.head().is_or_pat() { - row.expand_and_extend(self); + for new_row in row.expand_or_pat() { + self.push(new_row); + } } else { - self.patterns.push(row); + self.rows.push(row); } } + fn column_count(&self) -> usize { + self.wildcard_row.len() + } + fn head_ty(&self) -> Ty<'tcx> { + let mut ty = self.wildcard_row.head().ty(); + // Opaque types can't get destructured/split, but the patterns can + // actually hint at hidden types, so we use the patterns' types instead. + if let ty::Alias(ty::Opaque, ..) = ty.kind() { + if let Some(row) = self.rows().next() { + ty = row.head().ty(); + } + } + ty + } + + fn rows<'a>( + &'a self, + ) -> impl Iterator> + Clone + DoubleEndedIterator + ExactSizeIterator + { + self.rows.iter() + } + fn rows_mut<'a>( + &'a mut self, + ) -> impl Iterator> + DoubleEndedIterator + ExactSizeIterator + { + self.rows.iter_mut() + } /// Iterate over the first component of each row fn heads<'a>( &'a self, ) -> impl Iterator> + Clone + Captures<'a> { - self.patterns.iter().map(|r| r.head()) + self.rows().map(|r| r.head()) } /// This computes `S(constructor, self)`. See top of the file for explanations. @@ -504,10 +525,11 @@ impl<'p, 'tcx> Matrix<'p, 'tcx> { pcx: &PatCtxt<'_, 'p, 'tcx>, ctor: &Constructor<'tcx>, ) -> Matrix<'p, 'tcx> { - let mut matrix = Matrix::empty(); - for row in &self.patterns { + let mut matrix = + Matrix::empty(self.wildcard_row.pop_head_constructor(pcx, ctor, usize::MAX)); + for (i, row) in self.rows().enumerate() { if ctor.is_covered_by(pcx, row.head().ctor()) { - let new_row = row.pop_head_constructor(pcx, ctor); + let new_row = row.pop_head_constructor(pcx, ctor, i); matrix.push(new_row); } } @@ -528,7 +550,7 @@ impl<'p, 'tcx> fmt::Debug for Matrix<'p, 'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "\n")?; - let Matrix { patterns: m, .. } = self; + let Matrix { rows: m, .. } = self; let pretty_printed_matrix: Vec> = m.iter().map(|row| row.iter().map(|pat| format!("{:?}", pat)).collect()).collect(); @@ -551,152 +573,14 @@ impl<'p, 'tcx> fmt::Debug for Matrix<'p, 'tcx> { } } -/// This carries the results of computing usefulness, as described at the top of the file. When -/// checking usefulness of a match branch, we use the `NoWitnesses` variant, which also keeps track -/// of potential unreachable sub-patterns (in the presence of or-patterns). When checking -/// exhaustiveness of a whole match, we use the `WithWitnesses` variant, which carries a list of -/// witnesses of non-exhaustiveness when there are any. -/// Which variant to use is dictated by `ArmType`. -#[derive(Debug)] -enum Usefulness<'p, 'tcx> { - /// If we don't care about witnesses, simply remember if the pattern was useful. - NoWitnesses { useful: bool }, - /// Carries a list of witnesses of non-exhaustiveness. If empty, indicates that the whole - /// pattern is unreachable. - WithWitnesses(Vec>), -} - -impl<'p, 'tcx> Usefulness<'p, 'tcx> { - fn new_useful(preference: ArmType) -> Self { - match preference { - // A single (empty) witness of reachability. - FakeExtraWildcard => WithWitnesses(vec![Witness(vec![])]), - RealArm => NoWitnesses { useful: true }, - } - } - - fn new_not_useful(preference: ArmType) -> Self { - match preference { - FakeExtraWildcard => WithWitnesses(vec![]), - RealArm => NoWitnesses { useful: false }, - } - } - - fn is_useful(&self) -> bool { - match self { - Usefulness::NoWitnesses { useful } => *useful, - Usefulness::WithWitnesses(witnesses) => !witnesses.is_empty(), - } - } - - /// Combine usefulnesses from two branches. This is an associative operation. - fn extend(&mut self, other: Self) { - match (&mut *self, other) { - (WithWitnesses(_), WithWitnesses(o)) if o.is_empty() => {} - (WithWitnesses(s), WithWitnesses(o)) if s.is_empty() => *self = WithWitnesses(o), - (WithWitnesses(s), WithWitnesses(o)) => s.extend(o), - (NoWitnesses { useful: s_useful }, NoWitnesses { useful: o_useful }) => { - *s_useful = *s_useful || o_useful - } - _ => unreachable!(), - } - } - - /// After calculating usefulness after a specialization, call this to reconstruct a usefulness - /// that makes sense for the matrix pre-specialization. This new usefulness can then be merged - /// with the results of specializing with the other constructors. - fn apply_constructor( - self, - pcx: &PatCtxt<'_, 'p, 'tcx>, - matrix: &Matrix<'p, 'tcx>, // used to compute missing ctors - ctor: &Constructor<'tcx>, - ) -> Self { - match self { - NoWitnesses { .. } => self, - WithWitnesses(ref witnesses) if witnesses.is_empty() => self, - WithWitnesses(witnesses) => { - let new_witnesses = if let Constructor::Missing { .. } = ctor { - // We got the special `Missing` constructor, so each of the missing constructors - // gives a new pattern that is not caught by the match. We list those patterns. - let new_patterns = if pcx.is_non_exhaustive { - // Here we don't want the user to try to list all variants, we want them to add - // a wildcard, so we only suggest that. - vec![DeconstructedPat::wildcard(pcx.ty, pcx.span)] - } else { - let mut split_wildcard = SplitWildcard::new(pcx); - split_wildcard.split(pcx, matrix.heads().map(DeconstructedPat::ctor)); - - // This lets us know if we skipped any variants because they are marked - // `doc(hidden)` or they are unstable feature gate (only stdlib types). - let mut hide_variant_show_wild = false; - // Construct for each missing constructor a "wild" version of this - // constructor, that matches everything that can be built with - // it. For example, if `ctor` is a `Constructor::Variant` for - // `Option::Some`, we get the pattern `Some(_)`. - let mut new: Vec> = split_wildcard - .iter_missing(pcx) - .filter_map(|missing_ctor| { - // Check if this variant is marked `doc(hidden)` - if missing_ctor.is_doc_hidden_variant(pcx) - || missing_ctor.is_unstable_variant(pcx) - { - hide_variant_show_wild = true; - return None; - } - Some(DeconstructedPat::wild_from_ctor(pcx, missing_ctor.clone())) - }) - .collect(); - - if hide_variant_show_wild { - new.push(DeconstructedPat::wildcard(pcx.ty, pcx.span)); - } - - new - }; - - witnesses - .into_iter() - .flat_map(|witness| { - new_patterns.iter().map(move |pat| { - Witness( - witness - .0 - .iter() - .chain(once(pat)) - .map(DeconstructedPat::clone_and_forget_reachability) - .collect(), - ) - }) - }) - .collect() - } else { - witnesses - .into_iter() - .map(|witness| witness.apply_constructor(pcx, &ctor)) - .collect() - }; - WithWitnesses(new_witnesses) - } - } - } -} - -#[derive(Copy, Clone, Debug)] -enum ArmType { - FakeExtraWildcard, - RealArm, -} - -/// A witness of non-exhaustiveness for error reporting, represented -/// as a list of patterns (in reverse order of construction) with -/// wildcards inside to represent elements that can take any inhabitant -/// of the type as a value. +/// A partially-constructed witness of non-exhaustiveness for error reporting, represented as a list +/// of patterns (in reverse order of construction). This is very similar to `PatStack`, with two +/// differences: the patterns are stored in the opposite order for historical reasons, and where the +/// API of `PatStack` is oriented towards deconstructing it by peeling layers off, this is oriented +/// towards reconstructing by rewrapping layers. /// -/// A witness against a list of patterns should have the same types -/// and length as the pattern matched against. Because Rust `match` -/// is always against a single pattern, at the end the witness will -/// have length 1, but in the middle of the algorithm, it can contain -/// multiple patterns. +/// This should have the same length and types as the `PatStack`s and `Matrix` it is constructed +/// nearby of. At the end of the algorithm this will have length 1 and represent and actual pattern. /// /// For example, if we are constructing a witness for the match against /// @@ -712,50 +596,126 @@ enum ArmType { /// /// We'll perform the following steps: /// 1. Start with an empty witness -/// `Witness(vec![])` +/// `WitnessStack(vec![])` /// 2. Push a witness `true` against the `false` -/// `Witness(vec![true])` +/// `WitnessStack(vec![true])` /// 3. Push a witness `Some(_)` against the `None` -/// `Witness(vec![true, Some(_)])` +/// `WitnessStack(vec![true, Some(_)])` /// 4. Apply the `Pair` constructor to the witnesses -/// `Witness(vec![Pair(Some(_), true)])` +/// `WitnessStack(vec![Pair(Some(_), true)])` /// /// The final `Pair(Some(_), true)` is then the resulting witness. -#[derive(Debug)] -pub(crate) struct Witness<'p, 'tcx>(Vec>); +#[derive(Debug, Clone)] +struct WitnessStack<'p, 'tcx>(Vec>); -impl<'p, 'tcx> Witness<'p, 'tcx> { +impl<'p, 'tcx> WitnessStack<'p, 'tcx> { /// Asserts that the witness contains a single pattern, and returns it. fn single_pattern(self) -> DeconstructedPat<'p, 'tcx> { assert_eq!(self.0.len(), 1); self.0.into_iter().next().unwrap() } - /// Constructs a partial witness for a pattern given a list of - /// patterns expanded by the specialization step. + /// Reverses specialization. Given a witness obtained after specialization, this constructs a + /// new witness valid for before specialization. Examples: /// - /// When a pattern P is discovered to be useful, this function is used bottom-up - /// to reconstruct a complete witness, e.g., a pattern P' that covers a subset - /// of values, V, where each value in that set is not covered by any previously - /// used patterns and is covered by the pattern P'. Examples: + /// ctor: tuple of 2 elements + /// pats: [false, "foo", _, true] + /// result: [(false, "foo"), _, true] /// - /// left_ty: tuple of 3 elements - /// pats: [10, 20, _] => (10, 20, _) - /// - /// left_ty: struct X { a: (bool, &'static str), b: usize} - /// pats: [(false, "foo"), 42] => X { a: (false, "foo"), b: 42 } - fn apply_constructor(mut self, pcx: &PatCtxt<'_, 'p, 'tcx>, ctor: &Constructor<'tcx>) -> Self { - let pat = { - let len = self.0.len(); - let arity = ctor.arity(pcx); + /// ctor: Enum::Variant { a: (bool, &'static str), b: usize} + /// pats: [(false, "foo"), _, true] + /// result: [Enum::Variant { a: (false, "foo"), b: _ }, true] + fn apply_constructor(&mut self, pcx: &PatCtxt<'_, 'p, 'tcx>, ctor: &Constructor<'tcx>) { + let len = self.0.len(); + let arity = ctor.arity(pcx); + let fields = { let pats = self.0.drain((len - arity)..).rev(); - let fields = Fields::from_iter(pcx.cx, pats); - DeconstructedPat::new(ctor.clone(), fields, pcx.ty, pcx.span) + Fields::from_iter(pcx.cx, pats) }; + let pat = DeconstructedPat::new(ctor.clone(), fields, pcx.ty, DUMMY_SP); self.0.push(pat); + } - self + /// Reverses specialization by the `Missing` constructor by pushing a whole new pattern. + fn push_pattern(&mut self, pat: DeconstructedPat<'p, 'tcx>) { + self.0.push(pat); + } +} + +/// Represents a set of partially-constructed witnesses of non-exhaustiveness for error reporting. +/// This has similar invariants as `Matrix` does. +/// Throughout the exhaustiveness phase of the algorithm, `is_useful` maintains the invariant that +/// the union of the `Matrix` and the `WitnessMatrix` together matches the type exhaustively. By the +/// end of the algorithm, this has a single column, which contains the patterns that are missing for +/// the match to be exhaustive. +#[derive(Debug, Clone)] +pub struct WitnessMatrix<'p, 'tcx>(Vec>); + +impl<'p, 'tcx> WitnessMatrix<'p, 'tcx> { + /// New matrix with no rows. + fn new_empty() -> Self { + WitnessMatrix(vec![]) + } + /// New matrix with one row and no columns. + fn new_unit() -> Self { + WitnessMatrix(vec![WitnessStack(vec![])]) + } + + /// Whether this has any rows. + fn is_empty(&self) -> bool { + self.0.is_empty() + } + /// Asserts that there is a single column and returns the patterns in it. + fn single_column(self) -> Vec> { + self.0.into_iter().map(|w| w.single_pattern()).collect() + } + + /// Reverses specialization by the `Missing` constructor. This constructs a "wild" version of + /// `ctor`, that matches everything that can be built with it. For example, if `ctor` is a + /// `Constructor::Variant` for `Option::Some`, we get the pattern `Some(_)`. We push repeated + /// copies of that pattern to all rows. + fn push_wild_ctor(&mut self, pcx: &PatCtxt<'_, 'p, 'tcx>, ctor: Constructor<'tcx>) { + let pat = DeconstructedPat::wild_from_ctor(pcx, ctor); + for witness in self.0.iter_mut() { + witness.push_pattern(pat.clone()) + } + } + + /// Reverses specialization by `ctor`. + fn apply_constructor( + &mut self, + pcx: &PatCtxt<'_, 'p, 'tcx>, + missing_ctors: &[Constructor<'tcx>], + ctor: &Constructor<'tcx>, + ) { + if self.is_empty() { + return; + } + if !matches!(ctor, Constructor::Missing { .. }) { + for witness in self.0.iter_mut() { + witness.apply_constructor(pcx, ctor) + } + } else if missing_ctors.iter().any(|c| c.is_non_exhaustive()) { + // Here we don't want the user to try to list all variants, we want them to add a + // wildcard, so we only suggest that. + self.push_wild_ctor(pcx, Constructor::NonExhaustive); + } else { + // We got the special `Missing` constructor, so each of the missing constructors gives a + // new pattern that is not caught by the match. We list those patterns and push them + // onto our current witnesses. + let old_witnesses = std::mem::replace(self, Self::new_empty()); + for missing_ctor in missing_ctors { + let mut witnesses_with_missing_ctor = old_witnesses.clone(); + witnesses_with_missing_ctor.push_wild_ctor(pcx, missing_ctor.clone()); + self.extend(witnesses_with_missing_ctor) + } + } + } + + /// Merges the rows of two witness matrices. Their column types must match. + fn extend(&mut self, other: Self) { + self.0.extend(other.0) } } @@ -781,160 +741,130 @@ impl<'p, 'tcx> Witness<'p, 'tcx> { /// `is_under_guard` is used to inform if the pattern has a guard. If it /// has one it must not be inserted into the matrix. This shouldn't be /// relied on for soundness. -#[instrument(level = "debug", skip(cx, matrix, lint_root), ret)] -fn is_useful<'p, 'tcx>( +#[instrument(level = "debug", skip(cx, is_top_level), ret)] +fn compute_usefulness<'p, 'tcx>( cx: &MatchCheckCtxt<'p, 'tcx>, - matrix: &Matrix<'p, 'tcx>, - v: &PatStack<'p, 'tcx>, - witness_preference: ArmType, + matrix: &mut Matrix<'p, 'tcx>, + collect_witnesses: bool, lint_root: HirId, - is_under_guard: bool, is_top_level: bool, -) -> Usefulness<'p, 'tcx> { - debug!(?matrix, ?v); - let Matrix { patterns: rows, .. } = matrix; - - // The base case. We are pattern-matching on () and the return value is - // based on whether our matrix has a row or not. - // NOTE: This could potentially be optimized by checking rows.is_empty() - // first and then, if v is non-empty, the return value is based on whether - // the type of the tuple we're checking is inhabited or not. - if v.is_empty() { - let ret = if rows.is_empty() { - Usefulness::new_useful(witness_preference) - } else { - Usefulness::new_not_useful(witness_preference) - }; - debug!(?ret); - return ret; - } - - debug_assert!(rows.iter().all(|r| r.len() == v.len())); - - // If the first pattern is an or-pattern, expand it. - let mut ret = Usefulness::new_not_useful(witness_preference); - if v.head().is_or_pat() { - debug!("expanding or-pattern"); - // We try each or-pattern branch in turn. - let mut matrix = matrix.clone(); - for v in v.expand_or_pat() { - debug!(?v); - let usefulness = ensure_sufficient_stack(|| { - is_useful(cx, &matrix, &v, witness_preference, lint_root, is_under_guard, false) - }); - debug!(?usefulness); - ret.extend(usefulness); - // If pattern has a guard don't add it to the matrix. - if !is_under_guard { - // We push the already-seen patterns into the matrix in order to detect redundant - // branches like `Some(_) | Some(0)`. - matrix.push(v); +) -> WitnessMatrix<'p, 'tcx> { + debug_assert!(matrix.rows().all(|r| r.len() == matrix.column_count())); + + // The base case. We are pattern-matching on () and the return value is based on whether our + // matrix has a row or not. + if matrix.column_count() == 0 { + let mut useful = true; + for row in matrix.rows_mut() { + row.is_useful = useful; + useful = useful && row.is_under_guard; + if !useful { + break; } } - } else { - let mut ty = v.head().ty(); + if useful && collect_witnesses { + return WitnessMatrix::new_unit(); + } else { + return WitnessMatrix::new_empty(); + } + } - // Opaque types can't get destructured/split, but the patterns can - // actually hint at hidden types, so we use the patterns' types instead. - if let ty::Alias(ty::Opaque, ..) = ty.kind() { - if let Some(row) = rows.first() { - ty = row.head().ty(); - } + let ty = matrix.head_ty(); + debug!("ty: {ty:?}"); + let pcx = &PatCtxt { cx, ty }; + + let ctor_set = ConstructorSet::new(pcx); + let (split_ctors, missing_ctors) = + ctor_set.split(pcx, matrix.heads().map(|p| p.ctor()), is_top_level); + + // For each constructor, we compute whether there's a value that starts with it that would + // witness the usefulness of `v`. + let mut ret = WitnessMatrix::new_empty(); + let orig_column_count = matrix.column_count(); + for ctor in split_ctors { + // If some ctors are missing we only report those. Could report all if that's useful for + // some applications. + let collect_witnesses = collect_witnesses + && (missing_ctors.is_empty() + || matches!(ctor, Constructor::Wildcard | Constructor::Missing)); + debug!("specialize({:?})", ctor); + let mut spec_matrix = matrix.specialize_constructor(pcx, &ctor); + let mut witnesses = ensure_sufficient_stack(|| { + compute_usefulness(cx, &mut spec_matrix, collect_witnesses, lint_root, false) + }); + if collect_witnesses { + witnesses.apply_constructor(pcx, &missing_ctors, &ctor); + ret.extend(witnesses); } - let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty); - debug!("v.head: {:?}, v.span: {:?}", v.head(), v.head().span()); - let pcx = &PatCtxt { cx, ty, span: v.head().span(), is_top_level, is_non_exhaustive }; - - let v_ctor = v.head().ctor(); - debug!(?v_ctor); - if let Constructor::IntRange(ctor_range) = &v_ctor { - // Lint on likely incorrect range patterns (#63987) - ctor_range.lint_overlapping_range_endpoints( - pcx, - matrix.heads(), - matrix.column_count().unwrap_or(0), - lint_root, - ) + + // Lint on likely incorrect range patterns (#63987) + if spec_matrix.rows().len() >= 2 { + if let Constructor::IntRange(overlap_range) = &ctor { + // If two ranges overlap on their boundaries, that boundary will be found as a singleton + // range after splitting. + // We limit to a single column for now, see `lint_overlapping_range_endpoints`. + if overlap_range.is_singleton() && orig_column_count == 1 { + overlap_range.lint_overlapping_range_endpoints( + pcx, + spec_matrix.rows().map(|child_row| &matrix.rows[child_row.parent_row]).map( + |parent_row| { + ( + parent_row.head(), + parent_row.is_under_guard, + parent_row.arm_hir_id, + ) + }, + ), + orig_column_count, + ); + } + } } - // We split the head constructor of `v`. - let split_ctors = v_ctor.split(pcx, matrix.heads().map(DeconstructedPat::ctor)); - let is_non_exhaustive_and_wild = is_non_exhaustive && v_ctor.is_wildcard(); - // For each constructor, we compute whether there's a value that starts with it that would - // witness the usefulness of `v`. - let start_matrix = &matrix; - for ctor in split_ctors { - debug!("specialize({:?})", ctor); - // We cache the result of `Fields::wildcards` because it is used a lot. - let spec_matrix = start_matrix.specialize_constructor(pcx, &ctor); - let v = v.pop_head_constructor(pcx, &ctor); - let usefulness = ensure_sufficient_stack(|| { - is_useful( - cx, - &spec_matrix, - &v, - witness_preference, - lint_root, - is_under_guard, - false, - ) - }); - let usefulness = usefulness.apply_constructor(pcx, start_matrix, &ctor); - - // When all the conditions are met we have a match with a `non_exhaustive` enum - // that has the potential to trigger the `non_exhaustive_omitted_patterns` lint. - // To understand the workings checkout `Constructor::split` and `SplitWildcard::new/into_ctors` - if is_non_exhaustive_and_wild - // Only emit a lint on refutable patterns. - && cx.refutable - // We check that the match has a wildcard pattern and that wildcard is useful, - // meaning there are variants that are covered by the wildcard. Without the check - // for `witness_preference` the lint would trigger on `if let NonExhaustiveEnum::A = foo {}` - && usefulness.is_useful() && matches!(witness_preference, RealArm) - && matches!( - &ctor, - Constructor::Missing { nonexhaustive_enum_missing_real_variants: true } - ) - { - let patterns = { - let mut split_wildcard = SplitWildcard::new(pcx); - split_wildcard.split(pcx, matrix.heads().map(DeconstructedPat::ctor)); - // Construct for each missing constructor a "wild" version of this - // constructor, that matches everything that can be built with - // it. For example, if `ctor` is a `Constructor::Variant` for - // `Option::Some`, we get the pattern `Some(_)`. - split_wildcard - .iter_missing(pcx) - // Filter out the `NonExhaustive` because we want to list only real - // variants. Also remove any unstable feature gated variants. - // Because of how we computed `nonexhaustive_enum_missing_real_variants`, - // this will not return an empty `Vec`. - .filter(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx))) - .cloned() - .map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor)) - .collect::>() - }; + // When all the conditions are met we have a match with a `non_exhaustive` enum + // that has the potential to trigger the `non_exhaustive_omitted_patterns` lint. + if cx.refutable + && matches!(&ctor, Constructor::Missing) + && matches!(&ctor_set, ConstructorSet::Variants { non_exhaustive: true, .. }) + && spec_matrix.rows().len() != 0 + { + let patterns = missing_ctors + .iter() + // We want to list only real variants. + .filter(|c| !(c.is_non_exhaustive() || c.is_wildcard())) + .cloned() + .map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor)) + .collect::>(); + + if !patterns.is_empty() { + let first_spec_row = spec_matrix.rows().next().unwrap(); + let first_wildcard_row = &matrix.rows[first_spec_row.parent_row]; + let wildcard_span = first_wildcard_row.head().span(); // Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns` // is not exhaustive enough. - // // NB: The partner lint for structs lives in `compiler/rustc_hir_analysis/src/check/pat.rs`. cx.tcx.emit_spanned_lint( NON_EXHAUSTIVE_OMITTED_PATTERNS, - lint_root, - pcx.span, + first_wildcard_row.arm_hir_id, + wildcard_span, NonExhaustiveOmittedPattern { scrut_ty: pcx.ty, - uncovered: Uncovered::new(pcx.span, pcx.cx, patterns), + uncovered: Uncovered::new(wildcard_span, pcx.cx, patterns), }, ); } + } - ret.extend(usefulness); + for child_row in spec_matrix.rows() { + let parent_row = &mut matrix.rows[child_row.parent_row]; + parent_row.is_useful = parent_row.is_useful || child_row.is_useful; } } - if ret.is_useful() { - v.head().set_reachable(); + for row in matrix.rows() { + if row.is_useful { + row.head().set_reachable(); + } } ret @@ -980,18 +910,23 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>( arms: &[MatchArm<'p, 'tcx>], lint_root: HirId, scrut_ty: Ty<'tcx>, + scrut_span: Span, ) -> UsefulnessReport<'p, 'tcx> { - let mut matrix = Matrix::empty(); + let wild_pattern = cx.pattern_arena.alloc(DeconstructedPat::wildcard(scrut_ty, DUMMY_SP)); + let wildcard_row = PatStack::from_pattern(wild_pattern, usize::MAX, false, lint_root); + let mut matrix = Matrix::empty(wildcard_row); + for (row_id, arm) in arms.iter().enumerate() { + let v = PatStack::from_pattern(arm.pat, row_id, arm.has_guard, arm.hir_id); + matrix.push(v); + } + + let non_exhaustiveness_witnesses = compute_usefulness(cx, &mut matrix, true, lint_root, true); + let non_exhaustiveness_witnesses: Vec<_> = non_exhaustiveness_witnesses.single_column(); let arm_usefulness: Vec<_> = arms .iter() .copied() .map(|arm| { debug!(?arm); - let v = PatStack::from_pattern(arm.pat); - is_useful(cx, &matrix, &v, RealArm, arm.hir_id, arm.has_guard, true); - if !arm.has_guard { - matrix.push(v); - } let reachability = if arm.pat.is_reachable() { Reachability::Reachable(arm.pat.unreachable_spans()) } else { @@ -1001,12 +936,5 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>( }) .collect(); - let wild_pattern = cx.pattern_arena.alloc(DeconstructedPat::wildcard(scrut_ty, DUMMY_SP)); - let v = PatStack::from_pattern(wild_pattern); - let usefulness = is_useful(cx, &matrix, &v, FakeExtraWildcard, lint_root, false, true); - let non_exhaustiveness_witnesses = match usefulness { - WithWitnesses(pats) => pats.into_iter().map(|w| w.single_pattern()).collect(), - NoWitnesses { .. } => bug!(), - }; UsefulnessReport { arm_usefulness, non_exhaustiveness_witnesses } } diff --git a/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.rs b/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.rs index 9b646060adfd9..47f4a964d0822 100644 --- a/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.rs +++ b/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.rs @@ -9,7 +9,9 @@ fn main() { enum Foo { - A, B, C, + A, + B, + C, } #[allow(non_exhaustive_omitted_patterns)] @@ -18,17 +20,18 @@ fn main() { //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` match Foo::A { - Foo::A => {} - Foo::B => {} + _ => {} } - //~^^^^ ERROR non-exhaustive patterns: `Foo::C` not covered + #[warn(non_exhaustive_omitted_patterns)] + //~^ WARNING unknown lint: `non_exhaustive_omitted_patterns` + //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` + //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` + //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` match Foo::A { - Foo::A => {} - Foo::B => {} - #[warn(non_exhaustive_omitted_patterns)] _ => {} } - //~^^^ WARNING unknown lint: `non_exhaustive_omitted_patterns` - //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` + + match Foo::A {} + //~^ ERROR non-exhaustive patterns } diff --git a/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.stderr b/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.stderr index fb39c404c207e..07c05110c712c 100644 --- a/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.stderr +++ b/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.stderr @@ -20,7 +20,7 @@ LL | #![allow(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:15:5 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:17:5 | LL | #[allow(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -30,7 +30,7 @@ LL | #[allow(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:15:5 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:17:5 | LL | #[allow(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -40,10 +40,20 @@ LL | #[allow(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:29:9 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:26:5 | -LL | #[warn(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[warn(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the `non_exhaustive_omitted_patterns` lint is unstable + = note: see issue #89554 for more information + = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable + +warning: unknown lint: `non_exhaustive_omitted_patterns` + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:26:5 + | +LL | #[warn(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: the `non_exhaustive_omitted_patterns` lint is unstable = note: see issue #89554 for more information @@ -70,7 +80,7 @@ LL | #![allow(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:15:5 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:17:5 | LL | #[allow(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -80,7 +90,7 @@ LL | #[allow(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:15:5 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:17:5 | LL | #[allow(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -90,35 +100,50 @@ LL | #[allow(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:29:9 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:26:5 + | +LL | #[warn(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the `non_exhaustive_omitted_patterns` lint is unstable + = note: see issue #89554 for more information + = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable + +warning: unknown lint: `non_exhaustive_omitted_patterns` + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:26:5 | -LL | #[warn(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[warn(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: the `non_exhaustive_omitted_patterns` lint is unstable = note: see issue #89554 for more information = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable -error[E0004]: non-exhaustive patterns: `Foo::C` not covered - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:20:11 +error[E0004]: non-exhaustive patterns: `Foo::A`, `Foo::B` and `Foo::C` not covered + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:35:11 | -LL | match Foo::A { - | ^^^^^^ pattern `Foo::C` not covered +LL | match Foo::A {} + | ^^^^^^ patterns `Foo::A`, `Foo::B` and `Foo::C` not covered | note: `Foo` defined here - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:12:15 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:12:9 | LL | enum Foo { | --- -LL | A, B, C, - | ^ not covered +LL | A, + | ^ not covered +LL | B, + | ^ not covered +LL | C, + | ^ not covered = note: the matched value is of type `Foo` -help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown +help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern, a match arm with multiple or-patterns as shown, or multiple match arms | -LL ~ Foo::B => {}, -LL + Foo::C => todo!() +LL ~ match Foo::A { +LL + Foo::A | Foo::B | Foo::C => todo!(), +LL + } | -error: aborting due to previous error; 10 warnings emitted +error: aborting due to previous error; 12 warnings emitted For more information about this error, try `rustc --explain E0004`. diff --git a/tests/ui/or-patterns/exhaustiveness-pass.rs b/tests/ui/or-patterns/exhaustiveness-pass.rs index e8c8a0e7ba52a..1b8793101841c 100644 --- a/tests/ui/or-patterns/exhaustiveness-pass.rs +++ b/tests/ui/or-patterns/exhaustiveness-pass.rs @@ -35,4 +35,12 @@ fn main() { ((0, 0) | (1, 0),) => {} _ => {} } + match ((0, 0),) { + ((x, y) | (y, x),) if x == 0 => {} + _ => {} + } + match 0 { + 0 | 0 if 0 == 0 => {} + _ => {} + } } diff --git a/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.rs b/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.rs index 8429799cabf15..20a8d7549961f 100644 --- a/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.rs +++ b/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.rs @@ -1,6 +1,7 @@ #![deny(unreachable_patterns)] // We wrap patterns in a tuple because top-level or-patterns were special-cased. +#[rustfmt::skip] fn main() { match (0u8,) { (1 | 2,) => {} @@ -73,6 +74,11 @@ fn main() { | 0] => {} //~ ERROR unreachable _ => {} } + match (true, 0) { + (true, 0 | 0) => {} //~ ERROR unreachable + (_, 0 | 0) => {} //~ ERROR unreachable + _ => {} + } match &[][..] { [0] => {} [0, _] => {} @@ -149,4 +155,8 @@ fn main() { | true, //~ ERROR unreachable false | true) => {} } + match (true, true) { + (x, y) + | (y, x) => {} //~ ERROR unreachable + } } diff --git a/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.stderr b/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.stderr index 3f7d47dcb8ceb..3616dda99812f 100644 --- a/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.stderr +++ b/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.stderr @@ -1,5 +1,5 @@ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:7:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:8:9 | LL | (1,) => {} | ^^^^ @@ -11,128 +11,140 @@ LL | #![deny(unreachable_patterns)] | ^^^^^^^^^^^^^^^^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:12:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:13:9 | LL | (2,) => {} | ^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:18:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:19:9 | LL | (1 | 2,) => {} | ^^^^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:23:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:24:9 | LL | (1, 3) => {} | ^^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:24:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:25:9 | LL | (1, 4) => {} | ^^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:25:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:26:9 | LL | (2, 4) => {} | ^^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:26:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:27:9 | LL | (2 | 1, 4) => {} | ^^^^^^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:28:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:29:9 | LL | (1, 4 | 5) => {} | ^^^^^^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:36:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:37:9 | LL | (Some(1),) => {} | ^^^^^^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:37:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:38:9 | LL | (None,) => {} | ^^^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:42:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:43:9 | LL | ((1..=4,),) => {} | ^^^^^^^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:47:14 + --> $DIR/exhaustiveness-unreachable-pattern.rs:48:14 | LL | (1 | 1,) => {} | ^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:51:19 + --> $DIR/exhaustiveness-unreachable-pattern.rs:52:19 | LL | (0 | 1) | 1 => {} | ^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:57:14 + --> $DIR/exhaustiveness-unreachable-pattern.rs:58:14 | LL | 0 | (0 | 0) => {} | ^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:57:18 + --> $DIR/exhaustiveness-unreachable-pattern.rs:58:18 | LL | 0 | (0 | 0) => {} | ^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:65:13 + --> $DIR/exhaustiveness-unreachable-pattern.rs:66:13 | LL | / Some( LL | | 0 | 0) => {} | |______________________^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:71:15 + --> $DIR/exhaustiveness-unreachable-pattern.rs:72:15 | LL | | 0 | ^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:73:15 + --> $DIR/exhaustiveness-unreachable-pattern.rs:74:15 | LL | | 0] => {} | ^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:81:10 + --> $DIR/exhaustiveness-unreachable-pattern.rs:78:20 + | +LL | (true, 0 | 0) => {} + | ^ + +error: unreachable pattern + --> $DIR/exhaustiveness-unreachable-pattern.rs:79:17 + | +LL | (_, 0 | 0) => {} + | ^ + +error: unreachable pattern + --> $DIR/exhaustiveness-unreachable-pattern.rs:87:10 | LL | [1 | ^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:93:10 + --> $DIR/exhaustiveness-unreachable-pattern.rs:99:10 | LL | [true | ^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:100:36 + --> $DIR/exhaustiveness-unreachable-pattern.rs:106:36 | LL | (true | false, None | Some(true | ^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:105:14 + --> $DIR/exhaustiveness-unreachable-pattern.rs:111:14 | LL | (true | ^^^^ @@ -143,28 +155,34 @@ LL | (true | false, None | Some(t_or_f!())) => {} = note: this error originates in the macro `t_or_f` (in Nightly builds, run with -Z macro-backtrace for more info) error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:116:14 + --> $DIR/exhaustiveness-unreachable-pattern.rs:122:14 | LL | Some(0 | ^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:135:19 + --> $DIR/exhaustiveness-unreachable-pattern.rs:141:19 | LL | | false) => {} | ^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:143:15 + --> $DIR/exhaustiveness-unreachable-pattern.rs:149:15 | LL | | true) => {} | ^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:149:15 + --> $DIR/exhaustiveness-unreachable-pattern.rs:155:15 | LL | | true, | ^^^^ -error: aborting due to 26 previous errors +error: unreachable pattern + --> $DIR/exhaustiveness-unreachable-pattern.rs:160:15 + | +LL | | (y, x) => {} + | ^^^^^^ + +error: aborting due to 29 previous errors diff --git a/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.rs b/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.rs index 5ea92b07081af..e5855edce735d 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.rs +++ b/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.rs @@ -8,7 +8,7 @@ macro_rules! m { $t2 => {} _ => {} } - } + }; } fn main() { @@ -16,9 +16,9 @@ fn main() { m!(0u8, 30..=40, 20..=30); //~ ERROR multiple patterns overlap on their endpoints m!(0u8, 20..=30, 31..=40); m!(0u8, 20..=30, 29..=40); - m!(0u8, 20.. 30, 29..=40); //~ ERROR multiple patterns overlap on their endpoints - m!(0u8, 20.. 30, 28..=40); - m!(0u8, 20.. 30, 30..=40); + m!(0u8, 20..30, 29..=40); //~ ERROR multiple patterns overlap on their endpoints + m!(0u8, 20..30, 28..=40); + m!(0u8, 20..30, 30..=40); m!(0u8, 20..=30, 30..=30); m!(0u8, 20..=30, 30..=31); //~ ERROR multiple patterns overlap on their endpoints m!(0u8, 20..=30, 29..=30); @@ -28,15 +28,37 @@ fn main() { m!(0u8, 20..=30, 20); m!(0u8, 20..=30, 25); m!(0u8, 20..=30, 30); - m!(0u8, 20.. 30, 29); + m!(0u8, 20..30, 29); m!(0u8, 20, 20..=30); m!(0u8, 25, 20..=30); m!(0u8, 30, 20..=30); match 0u8 { + 1..=10 => {} 0..=10 => {} 20..=30 => {} - 10..=20 => {} //~ ERROR multiple patterns overlap on their endpoints + 10..=20 => {} + //~^ ERROR multiple patterns overlap on their endpoints + //~| ERROR multiple patterns overlap on their endpoints + _ => {} + } + // Selectively allow lint + match 0u8 { + 0..=10 => {} + #[allow(overlapping_range_endpoints)] + 10..=20 => {} + 10..=20 => {} + //~^ ERROR multiple patterns overlap on their endpoints + _ => {} + } + match 0u8 { + 0..=10 => {} + 10..=20 if true => {} //~ ERROR multiple patterns overlap on their endpoints + _ => {} + } + match 0u8 { + 0..=10 if true => {} + 10..=20 => {} _ => {} } match (0u8, true) { @@ -51,6 +73,13 @@ fn main() { (false, 10..20) => {} _ => {} } + // this probably shouldn't lint + match (true, 0u8) { + (true, 0..=10) => {} + (false, _) => {} + (_, 10..20) => {} //~ ERROR multiple patterns overlap on their endpoints + _ => {} + } match Some(0u8) { Some(0..=10) => {} Some(10..20) => {} //~ ERROR multiple patterns overlap on their endpoints diff --git a/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.stderr b/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.stderr index ea0e8f6e49e07..9a61c849d0af8 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.stderr +++ b/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.stderr @@ -24,10 +24,10 @@ LL | m!(0u8, 30..=40, 20..=30); = note: you likely meant to write mutually exclusive ranges error: multiple patterns overlap on their endpoints - --> $DIR/overlapping_range_endpoints.rs:19:22 + --> $DIR/overlapping_range_endpoints.rs:19:21 | -LL | m!(0u8, 20.. 30, 29..=40); - | ------- ^^^^^^^ ... with this range +LL | m!(0u8, 20..30, 29..=40); + | ------ ^^^^^^^ ... with this range | | | this range overlaps on `29_u8`... | @@ -54,10 +54,21 @@ LL | m!(0u8, 20..=30, 19..=20); = note: you likely meant to write mutually exclusive ranges error: multiple patterns overlap on their endpoints - --> $DIR/overlapping_range_endpoints.rs:39:9 + --> $DIR/overlapping_range_endpoints.rs:40:9 | +LL | 1..=10 => {} + | ------ this range overlaps on `10_u8`... LL | 0..=10 => {} | ------ this range overlaps on `10_u8`... +LL | 20..=30 => {} +LL | 10..=20 => {} + | ^^^^^^^ ... with this range + | + = note: you likely meant to write mutually exclusive ranges + +error: multiple patterns overlap on their endpoints + --> $DIR/overlapping_range_endpoints.rs:40:9 + | LL | 20..=30 => {} | ------- this range overlaps on `20_u8`... LL | 10..=20 => {} @@ -66,7 +77,28 @@ LL | 10..=20 => {} = note: you likely meant to write mutually exclusive ranges error: multiple patterns overlap on their endpoints - --> $DIR/overlapping_range_endpoints.rs:50:16 + --> $DIR/overlapping_range_endpoints.rs:50:9 + | +LL | 0..=10 => {} + | ------ this range overlaps on `10_u8`... +... +LL | 10..=20 => {} + | ^^^^^^^ ... with this range + | + = note: you likely meant to write mutually exclusive ranges + +error: multiple patterns overlap on their endpoints + --> $DIR/overlapping_range_endpoints.rs:56:9 + | +LL | 0..=10 => {} + | ------ this range overlaps on `10_u8`... +LL | 10..=20 if true => {} + | ^^^^^^^ ... with this range + | + = note: you likely meant to write mutually exclusive ranges + +error: multiple patterns overlap on their endpoints + --> $DIR/overlapping_range_endpoints.rs:72:16 | LL | (true, 0..=10) => {} | ------ this range overlaps on `10_u8`... @@ -76,7 +108,18 @@ LL | (true, 10..20) => {} = note: you likely meant to write mutually exclusive ranges error: multiple patterns overlap on their endpoints - --> $DIR/overlapping_range_endpoints.rs:56:14 + --> $DIR/overlapping_range_endpoints.rs:80:13 + | +LL | (true, 0..=10) => {} + | ------ this range overlaps on `10_u8`... +LL | (false, _) => {} +LL | (_, 10..20) => {} + | ^^^^^^ ... with this range + | + = note: you likely meant to write mutually exclusive ranges + +error: multiple patterns overlap on their endpoints + --> $DIR/overlapping_range_endpoints.rs:85:14 | LL | Some(0..=10) => {} | ------ this range overlaps on `10_u8`... @@ -85,5 +128,5 @@ LL | Some(10..20) => {} | = note: you likely meant to write mutually exclusive ranges -error: aborting due to 8 previous errors +error: aborting due to 12 previous errors diff --git a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.allow.stderr b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.allow.stderr index 9f277fa1e1800..4ee9eb275a491 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.allow.stderr +++ b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.allow.stderr @@ -1,5 +1,5 @@ error[E0004]: non-exhaustive patterns: type `usize` is non-empty - --> $DIR/pointer-sized-int.rs:48:11 + --> $DIR/pointer-sized-int.rs:65:11 | LL | match 7usize {} | ^^^^^^ diff --git a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.deny.stderr b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.deny.stderr index 0e0f0c3e11ee8..28f793b0cdcd2 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.deny.stderr +++ b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.deny.stderr @@ -9,7 +9,7 @@ LL | match 0usize { = help: add `#![feature(precise_pointer_size_matching)]` to the crate attributes to enable precise `usize` matching help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown | -LL ~ 0 ..= usize::MAX => {}, +LL ~ 0..=usize::MAX => {}, LL + _ => todo!() | @@ -24,7 +24,7 @@ LL | match 0isize { = help: add `#![feature(precise_pointer_size_matching)]` to the crate attributes to enable precise `isize` matching help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown | -LL ~ isize::MIN ..= isize::MAX => {}, +LL ~ isize::MIN..=isize::MAX => {}, LL + _ => todo!() | @@ -85,6 +85,60 @@ LL | match $s { $($t)+ => {}, (_, _) => todo!() } error[E0004]: non-exhaustive patterns: `_` not covered --> $DIR/pointer-sized-int.rs:31:8 | +LL | m!(0usize, 0..); + | ^^^^^^ pattern `_` not covered + | + = note: the matched value is of type `usize` + = note: `usize` does not have a fixed maximum value, so a wildcard `_` is necessary to match exhaustively + = help: add `#![feature(precise_pointer_size_matching)]` to the crate attributes to enable precise `usize` matching +help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown + | +LL | match $s { $($t)+ => {}, _ => todo!() } + | ++++++++++++++ + +error[E0004]: non-exhaustive patterns: `_` not covered + --> $DIR/pointer-sized-int.rs:33:8 + | +LL | m!(0usize, 0..5 | 5..); + | ^^^^^^ pattern `_` not covered + | + = note: the matched value is of type `usize` + = note: `usize` does not have a fixed maximum value, so a wildcard `_` is necessary to match exhaustively + = help: add `#![feature(precise_pointer_size_matching)]` to the crate attributes to enable precise `usize` matching +help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown + | +LL | match $s { $($t)+ => {}, _ => todo!() } + | ++++++++++++++ + +error[E0004]: non-exhaustive patterns: `_` not covered + --> $DIR/pointer-sized-int.rs:35:8 + | +LL | m!(0usize, ..5 | 5..); + | ^^^^^^ pattern `_` not covered + | + = note: the matched value is of type `usize` + = note: `usize` does not have a fixed maximum value, so a wildcard `_` is necessary to match exhaustively + = help: add `#![feature(precise_pointer_size_matching)]` to the crate attributes to enable precise `usize` matching +help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown + | +LL | match $s { $($t)+ => {}, _ => todo!() } + | ++++++++++++++ + +error[E0004]: non-exhaustive patterns: `(_, _)` not covered + --> $DIR/pointer-sized-int.rs:37:8 + | +LL | m!((0usize, true), (0..5, true) | (5.., true) | (0.., false)); + | ^^^^^^^^^^^^^^ pattern `(_, _)` not covered + | + = note: the matched value is of type `(usize, bool)` +help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown + | +LL | match $s { $($t)+ => {}, (_, _) => todo!() } + | +++++++++++++++++++ + +error[E0004]: non-exhaustive patterns: `_` not covered + --> $DIR/pointer-sized-int.rs:40:8 + | LL | m!(0isize, isize::MIN..=isize::MAX); | ^^^^^^ pattern `_` not covered | @@ -97,7 +151,7 @@ LL | match $s { $($t)+ => {}, _ => todo!() } | ++++++++++++++ error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:33:8 + --> $DIR/pointer-sized-int.rs:42:8 | LL | m!(0isize, isize::MIN..5 | 5..=isize::MAX); | ^^^^^^ pattern `_` not covered @@ -111,7 +165,7 @@ LL | match $s { $($t)+ => {}, _ => todo!() } | ++++++++++++++ error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:35:8 + --> $DIR/pointer-sized-int.rs:44:8 | LL | m!(0isize, isize::MIN..isize::MAX | isize::MAX); | ^^^^^^ pattern `_` not covered @@ -125,9 +179,35 @@ LL | match $s { $($t)+ => {}, _ => todo!() } | ++++++++++++++ error[E0004]: non-exhaustive patterns: `(_, _)` not covered - --> $DIR/pointer-sized-int.rs:37:8 + --> $DIR/pointer-sized-int.rs:47:9 + | +LL | (0isize, true), + | ^^^^^^^^^^^^^^ pattern `(_, _)` not covered + | + = note: the matched value is of type `(isize, bool)` +help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown + | +LL | match $s { $($t)+ => {}, (_, _) => todo!() } + | +++++++++++++++++++ + +error[E0004]: non-exhaustive patterns: `_` not covered + --> $DIR/pointer-sized-int.rs:52:8 + | +LL | m!(0isize, ..5 | 5..); + | ^^^^^^ pattern `_` not covered + | + = note: the matched value is of type `isize` + = note: `isize` does not have a fixed maximum value, so a wildcard `_` is necessary to match exhaustively + = help: add `#![feature(precise_pointer_size_matching)]` to the crate attributes to enable precise `isize` matching +help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown + | +LL | match $s { $($t)+ => {}, _ => todo!() } + | ++++++++++++++ + +error[E0004]: non-exhaustive patterns: `(_, _)` not covered + --> $DIR/pointer-sized-int.rs:54:8 | -LL | m!((0isize, true), (isize::MIN..5, true) +LL | m!((0isize, true), (..5, true) | ^^^^^^^^^^^^^^ pattern `(_, _)` not covered | = note: the matched value is of type `(isize, bool)` @@ -137,7 +217,7 @@ LL | match $s { $($t)+ => {}, (_, _) => todo!() } | +++++++++++++++++++ error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:41:11 + --> $DIR/pointer-sized-int.rs:58:11 | LL | match 0isize { | ^^^^^^ pattern `_` not covered @@ -147,12 +227,12 @@ LL | match 0isize { = help: add `#![feature(precise_pointer_size_matching)]` to the crate attributes to enable precise `isize` matching help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown | -LL ~ 1 ..= isize::MAX => {}, +LL ~ 1..=isize::MAX => {}, LL + _ => todo!() | error[E0004]: non-exhaustive patterns: type `usize` is non-empty - --> $DIR/pointer-sized-int.rs:48:11 + --> $DIR/pointer-sized-int.rs:65:11 | LL | match 7usize {} | ^^^^^^ @@ -165,6 +245,6 @@ LL + _ => todo!(), LL + } | -error: aborting due to 12 previous errors +error: aborting due to 18 previous errors For more information about this error, try `rustc --explain E0004`. diff --git a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.rs b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.rs index 1ed18c2676358..8487734514af8 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.rs +++ b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.rs @@ -11,12 +11,12 @@ macro_rules! m { fn main() { match 0usize { //[deny]~^ ERROR non-exhaustive patterns - 0 ..= usize::MAX => {} + 0..=usize::MAX => {} } match 0isize { //[deny]~^ ERROR non-exhaustive patterns - isize::MIN ..= isize::MAX => {} + isize::MIN..=isize::MAX => {} } m!(0usize, 0..=usize::MAX); @@ -28,21 +28,38 @@ fn main() { m!((0usize, true), (0..5, true) | (5..=usize::MAX, true) | (0..=usize::MAX, false)); //[deny]~^ ERROR non-exhaustive patterns + m!(0usize, 0..); + //[deny]~^ ERROR non-exhaustive patterns + m!(0usize, 0..5 | 5..); + //[deny]~^ ERROR non-exhaustive patterns + m!(0usize, ..5 | 5..); + //[deny]~^ ERROR non-exhaustive patterns + m!((0usize, true), (0..5, true) | (5.., true) | (0.., false)); + //[deny]~^ ERROR non-exhaustive patterns + m!(0isize, isize::MIN..=isize::MAX); //[deny]~^ ERROR non-exhaustive patterns m!(0isize, isize::MIN..5 | 5..=isize::MAX); //[deny]~^ ERROR non-exhaustive patterns m!(0isize, isize::MIN..isize::MAX | isize::MAX); //[deny]~^ ERROR non-exhaustive patterns - m!((0isize, true), (isize::MIN..5, true) - | (5..=isize::MAX, true) | (isize::MIN..=isize::MAX, false)); + m!( + (0isize, true), + (isize::MIN..5, true) | (5..=isize::MAX, true) | (isize::MIN..=isize::MAX, false) + ); + //[deny]~^^^ ERROR non-exhaustive patterns + + m!(0isize, ..5 | 5..); + //[deny]~^ ERROR non-exhaustive patterns + m!((0isize, true), (..5, true) + | (5.., true) | (..0 | 0.., false)); //[deny]~^^ ERROR non-exhaustive patterns match 0isize { //[deny]~^ ERROR non-exhaustive patterns - isize::MIN ..= -1 => {} + isize::MIN..=-1 => {} 0 => {} - 1 ..= isize::MAX => {} + 1..=isize::MAX => {} } match 7usize {} diff --git a/tests/ui/pattern/usefulness/integer-ranges/reachability.rs b/tests/ui/pattern/usefulness/integer-ranges/reachability.rs index fb4d59b05780e..247fdd91572cd 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/reachability.rs +++ b/tests/ui/pattern/usefulness/integer-ranges/reachability.rs @@ -9,9 +9,10 @@ macro_rules! m { $t2 => {} _ => {} } - } + }; } +#[rustfmt::skip] fn main() { m!(0u8, 42, 41); m!(0u8, 42, 42); //~ ERROR unreachable pattern @@ -85,7 +86,7 @@ fn main() { match 'a' { '\u{0}'..='\u{D7FF}' => {}, '\u{E000}'..='\u{10_FFFF}' => {}, - '\u{D7FF}'..='\u{E000}' => {}, // FIXME should be unreachable + '\u{D7FF}'..='\u{E000}' => {}, //~ ERROR unreachable pattern } match (0u8, true) { diff --git a/tests/ui/pattern/usefulness/integer-ranges/reachability.stderr b/tests/ui/pattern/usefulness/integer-ranges/reachability.stderr index 0ffb0ffd82aa0..c5b028d2038c3 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/reachability.stderr +++ b/tests/ui/pattern/usefulness/integer-ranges/reachability.stderr @@ -1,5 +1,5 @@ error: unreachable pattern - --> $DIR/reachability.rs:17:17 + --> $DIR/reachability.rs:18:17 | LL | m!(0u8, 42, 42); | ^^ @@ -11,127 +11,127 @@ LL | #![deny(unreachable_patterns)] | ^^^^^^^^^^^^^^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:21:22 + --> $DIR/reachability.rs:22:22 | LL | m!(0u8, 20..=30, 20); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:22:22 + --> $DIR/reachability.rs:23:22 | LL | m!(0u8, 20..=30, 21); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:23:22 + --> $DIR/reachability.rs:24:22 | LL | m!(0u8, 20..=30, 25); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:24:22 + --> $DIR/reachability.rs:25:22 | LL | m!(0u8, 20..=30, 29); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:25:22 + --> $DIR/reachability.rs:26:22 | LL | m!(0u8, 20..=30, 30); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:28:21 + --> $DIR/reachability.rs:29:21 | LL | m!(0u8, 20..30, 20); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:29:21 + --> $DIR/reachability.rs:30:21 | LL | m!(0u8, 20..30, 21); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:30:21 + --> $DIR/reachability.rs:31:21 | LL | m!(0u8, 20..30, 25); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:31:21 + --> $DIR/reachability.rs:32:21 | LL | m!(0u8, 20..30, 29); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:35:22 + --> $DIR/reachability.rs:36:22 | LL | m!(0u8, 20..=30, 20..=30); | ^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:36:22 + --> $DIR/reachability.rs:37:22 | LL | m!(0u8, 20.. 30, 20.. 30); | ^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:37:22 + --> $DIR/reachability.rs:38:22 | LL | m!(0u8, 20..=30, 20.. 30); | ^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:39:22 + --> $DIR/reachability.rs:40:22 | LL | m!(0u8, 20..=30, 21..=30); | ^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:40:22 + --> $DIR/reachability.rs:41:22 | LL | m!(0u8, 20..=30, 20..=29); | ^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:42:24 + --> $DIR/reachability.rs:43:24 | LL | m!('a', 'A'..='z', 'a'..='z'); | ^^^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:49:9 + --> $DIR/reachability.rs:50:9 | LL | 5..=8 => {}, | ^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:55:9 + --> $DIR/reachability.rs:56:9 | LL | 5..15 => {}, | ^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:62:9 + --> $DIR/reachability.rs:63:9 | LL | 5..25 => {}, | ^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:70:9 + --> $DIR/reachability.rs:71:9 | LL | 5..25 => {}, | ^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:76:9 + --> $DIR/reachability.rs:77:9 | LL | 5..15 => {}, | ^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:83:9 + --> $DIR/reachability.rs:84:9 | LL | _ => {}, | - matches any value @@ -139,16 +139,22 @@ LL | '\u{D7FF}'..='\u{E000}' => {}, | ^^^^^^^^^^^^^^^^^^^^^^^ unreachable pattern error: unreachable pattern - --> $DIR/reachability.rs:104:9 + --> $DIR/reachability.rs:89:9 + | +LL | '\u{D7FF}'..='\u{E000}' => {}, + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: unreachable pattern + --> $DIR/reachability.rs:105:9 | LL | &FOO => {} | ^^^^ error: unreachable pattern - --> $DIR/reachability.rs:105:9 + --> $DIR/reachability.rs:106:9 | LL | BAR => {} | ^^^ -error: aborting due to 24 previous errors +error: aborting due to 25 previous errors diff --git a/tests/ui/pattern/usefulness/issue-3601.stderr b/tests/ui/pattern/usefulness/issue-3601.stderr index 2f6b167d4f8bd..4a25473f3f070 100644 --- a/tests/ui/pattern/usefulness/issue-3601.stderr +++ b/tests/ui/pattern/usefulness/issue-3601.stderr @@ -1,8 +1,8 @@ -error[E0004]: non-exhaustive patterns: `box _` not covered +error[E0004]: non-exhaustive patterns: `box ElementKind::HTMLImageElement(_)` not covered --> $DIR/issue-3601.rs:30:44 | LL | box NodeKind::Element(ed) => match ed.kind { - | ^^^^^^^ pattern `box _` not covered + | ^^^^^^^ pattern `box ElementKind::HTMLImageElement(_)` not covered | note: `Box` defined here --> $SRC_DIR/alloc/src/boxed.rs:LL:COL @@ -10,7 +10,7 @@ note: `Box` defined here help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown | LL ~ box ElementKind::HTMLImageElement(ref d) if d.image.is_some() => { true }, -LL + box _ => todo!() +LL + box ElementKind::HTMLImageElement(_) => todo!() | error: aborting due to previous error diff --git a/tests/ui/pattern/usefulness/match-non-exhaustive.stderr b/tests/ui/pattern/usefulness/match-non-exhaustive.stderr index 08dde523a15fb..f226bdf80646e 100644 --- a/tests/ui/pattern/usefulness/match-non-exhaustive.stderr +++ b/tests/ui/pattern/usefulness/match-non-exhaustive.stderr @@ -10,17 +10,17 @@ help: ensure that all possible cases are being handled by adding a match arm wit LL | match 0 { 1 => (), i32::MIN..=0_i32 | 2_i32..=i32::MAX => todo!() } | ++++++++++++++++++++++++++++++++++++++++++++++++ -error[E0004]: non-exhaustive patterns: `_` not covered +error[E0004]: non-exhaustive patterns: `i32::MIN..=-1_i32` and `1_i32..=i32::MAX` not covered --> $DIR/match-non-exhaustive.rs:3:11 | LL | match 0 { 0 if false => () } - | ^ pattern `_` not covered + | ^ patterns `i32::MIN..=-1_i32` and `1_i32..=i32::MAX` not covered | = note: the matched value is of type `i32` -help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown +help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern, a match arm with multiple or-patterns as shown, or multiple match arms | -LL | match 0 { 0 if false => (), _ => todo!() } - | ++++++++++++++ +LL | match 0 { 0 if false => (), i32::MIN..=-1_i32 | 1_i32..=i32::MAX => todo!() } + | +++++++++++++++++++++++++++++++++++++++++++++++++ error: aborting due to 2 previous errors diff --git a/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.rs b/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.rs index 3482af74752f8..3a4fa24eb4284 100644 --- a/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.rs +++ b/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.rs @@ -13,8 +13,8 @@ use enums::{ EmptyNonExhaustiveEnum, NestedNonExhaustive, NonExhaustiveEnum, NonExhaustiveSingleVariant, VariantNonExhaustive, }; -use unstable::{UnstableEnum, OnlyUnstableEnum, UnstableStruct, OnlyUnstableStruct}; use structs::{FunctionalRecord, MixedVisFields, NestedStruct, NormalStruct}; +use unstable::{OnlyUnstableEnum, OnlyUnstableStruct, UnstableEnum, UnstableStruct}; #[non_exhaustive] #[derive(Default)] @@ -66,6 +66,7 @@ fn main() { } //~^^ some variants are not matched explicitly + // Ok: guards don't change anything let x = 5; match non_enum { NonExhaustiveEnum::Unit if x > 10 => {} @@ -74,7 +75,6 @@ fn main() { #[deny(non_exhaustive_omitted_patterns)] _ => {} } - //~^^ some variants are not matched explicitly // Ok: all covered and not `unreachable-patterns` #[deny(unreachable_patterns)] @@ -127,11 +127,22 @@ fn main() { _ => {} } + // No variants are mentioned #[deny(non_exhaustive_omitted_patterns)] match NonExhaustiveSingleVariant::A(true) { _ => {} } //~^^ some variants are not matched explicitly + #[deny(non_exhaustive_omitted_patterns)] + match Some(NonExhaustiveSingleVariant::A(true)) { + Some(_) => {} + None => {} + } + #[deny(non_exhaustive_omitted_patterns)] + match Some(&NonExhaustiveSingleVariant::A(true)) { + Some(_) => {} + None => {} + } // Ok: we don't lint on `if let` expressions #[deny(non_exhaustive_omitted_patterns)] @@ -202,6 +213,27 @@ fn main() { _ => {} } //~^^ some variants are not matched explicitly + + #[deny(non_exhaustive_omitted_patterns)] + match (true, &non_enum) { + (true, NonExhaustiveEnum::Unit) => {} + _ => {} + } + //~^^ some variants are not matched explicitly + + #[deny(non_exhaustive_omitted_patterns)] + match (&non_enum, true) { + (NonExhaustiveEnum::Unit, true) => {} + _ => {} + } + //~^^ some variants are not matched explicitly + + #[deny(non_exhaustive_omitted_patterns)] + match Some(&non_enum) { + Some(NonExhaustiveEnum::Unit | NonExhaustiveEnum::Tuple(_)) => {} + _ => {} + } + //~^^ some variants are not matched explicitly } #[deny(non_exhaustive_omitted_patterns)] diff --git a/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.stderr b/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.stderr index 923394474b262..580aa2fea73a8 100644 --- a/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.stderr +++ b/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.stderr @@ -50,7 +50,7 @@ LL | let NestedStruct { bar: NormalStruct { first_field, .. }, .. } = Nested = note: the pattern is of type `NestedStruct` and the `non_exhaustive_omitted_patterns` attribute was found warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:173:9 + --> $DIR/omitted-patterns.rs:184:9 | LL | let OnlyUnstableStruct { unstable, .. } = OnlyUnstableStruct::new(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `unstable2` not listed @@ -58,13 +58,13 @@ LL | let OnlyUnstableStruct { unstable, .. } = OnlyUnstableStruct::new(); = help: ensure that all fields are mentioned explicitly by adding the suggested fields = note: the pattern is of type `OnlyUnstableStruct` and the `non_exhaustive_omitted_patterns` attribute was found note: the lint level is defined here - --> $DIR/omitted-patterns.rs:172:12 + --> $DIR/omitted-patterns.rs:183:12 | LL | #[warn(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:181:9 + --> $DIR/omitted-patterns.rs:192:9 | LL | let UnstableStruct { stable, stable2, .. } = UnstableStruct::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `unstable` not listed @@ -72,7 +72,7 @@ LL | let UnstableStruct { stable, stable2, .. } = UnstableStruct::default(); = help: ensure that all fields are mentioned explicitly by adding the suggested fields = note: the pattern is of type `UnstableStruct` and the `non_exhaustive_omitted_patterns` attribute was found note: the lint level is defined here - --> $DIR/omitted-patterns.rs:180:12 + --> $DIR/omitted-patterns.rs:191:12 | LL | #[warn(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -105,20 +105,6 @@ note: the lint level is defined here LL | #[deny(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:75:9 - | -LL | _ => {} - | ^ pattern `NonExhaustiveEnum::Unit` not covered - | - = help: ensure that all variants are matched explicitly by adding the suggested match arms - = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found -note: the lint level is defined here - --> $DIR/omitted-patterns.rs:74:16 - | -LL | #[deny(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - error: some variants are not matched explicitly --> $DIR/omitted-patterns.rs:92:32 | @@ -143,7 +129,7 @@ LL | _ => {} = note: the matched value is of type `NestedNonExhaustive` and the `non_exhaustive_omitted_patterns` attribute was found error: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:132:9 + --> $DIR/omitted-patterns.rs:133:9 | LL | _ => {} | ^ pattern `NonExhaustiveSingleVariant::A(_)` not covered @@ -151,13 +137,13 @@ LL | _ => {} = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `NonExhaustiveSingleVariant` and the `non_exhaustive_omitted_patterns` attribute was found note: the lint level is defined here - --> $DIR/omitted-patterns.rs:130:12 + --> $DIR/omitted-patterns.rs:131:12 | LL | #[deny(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:144:9 + --> $DIR/omitted-patterns.rs:155:9 | LL | _ => {} | ^ pattern `UnstableEnum::Unstable` not covered @@ -165,13 +151,13 @@ LL | _ => {} = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `UnstableEnum` and the `non_exhaustive_omitted_patterns` attribute was found note: the lint level is defined here - --> $DIR/omitted-patterns.rs:143:16 + --> $DIR/omitted-patterns.rs:154:16 | LL | #[deny(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:168:9 + --> $DIR/omitted-patterns.rs:179:9 | LL | _ => {} | ^ pattern `OnlyUnstableEnum::Unstable2` not covered @@ -179,13 +165,13 @@ LL | _ => {} = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `OnlyUnstableEnum` and the `non_exhaustive_omitted_patterns` attribute was found note: the lint level is defined here - --> $DIR/omitted-patterns.rs:165:12 + --> $DIR/omitted-patterns.rs:176:12 | LL | #[deny(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0005]: refutable pattern in local binding - --> $DIR/omitted-patterns.rs:194:9 + --> $DIR/omitted-patterns.rs:205:9 | LL | let local_refutable @ NonExhaustiveEnum::Unit = NonExhaustiveEnum::Unit; | ^^^^^^^^^^^^^^^ pattern `_` not covered @@ -199,7 +185,49 @@ LL | let local_refutable @ NonExhaustiveEnum::Unit = NonExhaustiveEnum::Unit | ++++++++++++++++ error: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:202:9 + --> $DIR/omitted-patterns.rs:213:9 + | +LL | _ => {} + | ^ pattern `NonExhaustiveEnum::Struct { .. }` not covered + | + = help: ensure that all variants are matched explicitly by adding the suggested match arms + = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found +note: the lint level is defined here + --> $DIR/omitted-patterns.rs:209:12 + | +LL | #[deny(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:220:9 + | +LL | _ => {} + | ^ patterns `NonExhaustiveEnum::Tuple(_)` and `NonExhaustiveEnum::Struct { .. }` not covered + | + = help: ensure that all variants are matched explicitly by adding the suggested match arms + = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found +note: the lint level is defined here + --> $DIR/omitted-patterns.rs:217:12 + | +LL | #[deny(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:227:9 + | +LL | _ => {} + | ^ patterns `NonExhaustiveEnum::Tuple(_)` and `NonExhaustiveEnum::Struct { .. }` not covered + | + = help: ensure that all variants are matched explicitly by adding the suggested match arms + = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found +note: the lint level is defined here + --> $DIR/omitted-patterns.rs:224:12 + | +LL | #[deny(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:234:9 | LL | _ => {} | ^ pattern `NonExhaustiveEnum::Struct { .. }` not covered @@ -207,11 +235,11 @@ LL | _ => {} = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found note: the lint level is defined here - --> $DIR/omitted-patterns.rs:198:12 + --> $DIR/omitted-patterns.rs:231:12 | LL | #[deny(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 10 previous errors; 6 warnings emitted +error: aborting due to 12 previous errors; 6 warnings emitted For more information about this error, try `rustc --explain E0005`.