diff --git a/compiler/rustc_errors/src/markdown/parse.rs b/compiler/rustc_errors/src/markdown/parse.rs index cd39f9b34d43a..236c99b41bf26 100644 --- a/compiler/rustc_errors/src/markdown/parse.rs +++ b/compiler/rustc_errors/src/markdown/parse.rs @@ -136,7 +136,7 @@ fn parse_recursive<'a>(buf: &'a [u8], ctx: Context) -> MdStream<'a> { _ if loop_buf.starts_with(LNK_S) => { parse_any_link(loop_buf, top_blk && prev == Prev::Newline) } - (_, Escape | _) => None, + _ => None, }; if let Some((tree, rest)) = res { diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index da2220cf7a5fd..be866acd6049e 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -895,6 +895,10 @@ declare_lint! { /// ordered. In this example, the `y` pattern will always match, so the /// five is impossible to reach. Remember, match arms match in order, you /// probably wanted to put the `5` case above the `y` case. + /// + /// This lint also detects or-pattern alternatives that are reachable but "useless": removing + /// them doesn't change the result of the match, e.g. the `0` in `0 | _` (everything the `0` + /// matches is also matched by the `_`, with the same outcome). pub UNREACHABLE_PATTERNS, Warn, "detects unreachable patterns" diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 767fa647612af..3797033b3de55 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -205,7 +205,7 @@ impl Level { "warn" => Some(Level::Warn), "deny" => Some(Level::Deny), "forbid" => Some(Level::Forbid), - "expect" | _ => None, + _ => None, } } diff --git a/compiler/rustc_mir_build/src/diagnostics.rs b/compiler/rustc_mir_build/src/diagnostics.rs index 154edfdf4a577..d5cb1eed27519 100644 --- a/compiler/rustc_mir_build/src/diagnostics.rs +++ b/compiler/rustc_mir_build/src/diagnostics.rs @@ -777,6 +777,38 @@ pub(crate) struct UnreachablePatternInner<'tcx> { pub(crate) suggest_remove: Option, } +pub(crate) struct UselessPatternOuter { + pub(crate) covered_by_many_n_more_count: Option, + pub(crate) inner: UselessPattern, +} + +impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for UselessPatternOuter { + #[track_caller] + fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, G> { + let mut diag = self.inner.into_diag(dcx, level); + if let Some(covered_by_many_n_more_count) = self.covered_by_many_n_more_count { + diag.arg("covered_by_many_n_more_count", covered_by_many_n_more_count); + } + diag + } +} + +#[derive(Diagnostic)] +#[diag("useless pattern")] +pub(crate) struct UselessPattern { + #[primary_span] + #[label( + "all the values this pattern matches are already matched by the rest of the or-pattern" + )] + pub(crate) span: Span, + #[label("matches any value")] + pub(crate) covered_by_catchall: Option, + #[label("matches all the values this pattern does")] + pub(crate) covered_by_one: Option, + #[note("multiple other patterns match some of the same values")] + pub(crate) covered_by_many: Option, +} + #[derive(Subdiagnostic)] #[suggestion( "you might have meant to pattern match against the value of {$is_typo -> 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 41806547932cd..3dbfea7fb0b2a 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -421,6 +421,16 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> { } } } + // Warn useless or-pattern alternatives, in the order in which they occur in the file. + let mut useless_subpats = report.useless_subpatterns.clone(); + useless_subpats.sort_unstable_by_key(|(_, pat, _)| pat.data().span); + for (arm_id, pat, explanation) in useless_subpats { + // Bindings make the matched alternative observable, e.g. `x` in `(0, x) | (x, _)`. + if !pat_binds_anything(pat.data()) { + let hir_id = report.arm_usefulness[arm_id].0.arm_data; + report_useless_pattern(cx, hir_id, pat, &explanation) + } + } Ok(report) } @@ -1045,6 +1055,69 @@ fn report_unreachable_pattern<'p, 'tcx>( ); } +/// Report a useless or-pattern alternative, e.g. `0` in `0 | _`: reachable but removable. +fn report_useless_pattern<'p, 'tcx>( + cx: &PatCtxt<'p, 'tcx>, + hir_id: HirId, + pat: &DeconstructedPat<'p, 'tcx>, + explanation: &RedundancyExplanation<'p, 'tcx>, +) { + static CAP_COVERED_BY_MANY: usize = 4; + let pat_span = pat.data().span; + let mut lint = UselessPattern { + span: pat_span, + covered_by_catchall: None, + covered_by_one: None, + covered_by_many: None, + }; + let mut covered_by_many_n_more_count = None; + match explanation.covered_by.as_slice() { + // Intersection info is approximated, so a covering set isn't always available. + [] => {} + [covering_pat] if pat_is_catchall(covering_pat) => { + lint.covered_by_catchall = Some(covering_pat.data().span); + } + [covering_pat] => { + lint.covered_by_one = Some(covering_pat.data().span); + } + covering_pats => { + let mut iter = covering_pats.iter(); + let mut multispan = MultiSpan::from_span(pat_span); + for p in iter.by_ref().take(CAP_COVERED_BY_MANY) { + multispan.push_span_label(p.data().span, msg!("matches some of the same values")); + } + let remain = iter.count(); + if remain == 0 { + multispan + .push_span_label(pat_span, msg!("collectively making this pattern useless")); + } else { + covered_by_many_n_more_count = Some(remain); + multispan.push_span_label( + pat_span, + msg!("...and {$covered_by_many_n_more_count} other patterns collectively make this useless"), + ); + } + lint.covered_by_many = Some(multispan); + } + } + cx.tcx.emit_node_span_lint( + UNREACHABLE_PATTERNS, + hir_id, + pat_span, + UselessPatternOuter { inner: lint, covered_by_many_n_more_count }, + ); +} + +/// Whether the pattern binds any variable. +fn pat_binds_anything(pat: &Pat<'_>) -> bool { + let mut binds = false; + pat.walk(|p| { + binds |= matches!(p.kind, PatKind::Binding { .. }); + !binds + }); + binds +} + /// Detect typos that were meant to be a `const` but were interpreted as a new pattern binding. fn find_fallback_pattern_typo<'tcx>( cx: &PatCtxt<'_, 'tcx>, diff --git a/compiler/rustc_pattern_analysis/src/usefulness.rs b/compiler/rustc_pattern_analysis/src/usefulness.rs index bf236b7737d9f..86daaab90b0a6 100644 --- a/compiler/rustc_pattern_analysis/src/usefulness.rs +++ b/compiler/rustc_pattern_analysis/src/usefulness.rs @@ -734,11 +734,18 @@ pub fn ensure_sufficient_stack(f: impl FnOnce() -> R) -> R { struct BranchPatUsefulness<'p, Cx: PatCx> { /// Whether this pattern is useful. useful: bool, + /// Whether this pattern is an or-pattern alternative whose rows were inspected. + is_or_alternative: bool, + /// Whether removing this or-pattern alternative would change the outcome of the match for + /// some value. If not, it is "useless", e.g. `0` in `0 | _` (useful, yet not needed). + needed: bool, /// A set of patterns that: /// - come before this one in the match; /// - intersect this one; /// - at the end of the algorithm, if `!self.useful`, their union covers this pattern. covered_by: FxHashSet<&'p DeconstructedPat>, + /// Like `covered_by`, but for alternatives of the same or-pattern (covers if `!needed`). + covered_by_alternatives: FxHashSet<&'p DeconstructedPat>, } impl<'p, Cx: PatCx> BranchPatUsefulness<'p, Cx> { @@ -781,11 +788,35 @@ impl<'p, Cx: PatCx> BranchPatUsefulness<'p, Cx> { Some(RedundancyExplanation { covered_by }) } } + + /// Check whether this pattern is a useless or-pattern alternative, and if so explain why. + fn is_useless(&self) -> Option> { + if self.is_or_alternative && self.useful && !self.needed { + #[cfg_attr(feature = "rustc", allow(rustc::potential_query_instability))] + let mut covered_by: Vec<_> = self + .covered_by + .iter() + .chain(self.covered_by_alternatives.iter()) + .copied() + .collect(); + covered_by.sort_by_key(|pat| pat.uid); // sort to avoid instability + covered_by.dedup_by_key(|pat| pat.uid); + Some(RedundancyExplanation { covered_by }) + } else { + None + } + } } impl<'p, Cx: PatCx> Default for BranchPatUsefulness<'p, Cx> { fn default() -> Self { - Self { useful: Default::default(), covered_by: Default::default() } + Self { + useful: Default::default(), + is_or_alternative: Default::default(), + needed: Default::default(), + covered_by: Default::default(), + covered_by_alternatives: Default::default(), + } } } @@ -1137,6 +1168,10 @@ struct MatrixRow<'p, Cx: PatCx> { /// Whether the head pattern is a branch (see definition of "branch pattern" at /// [`BranchPatUsefulness`]) head_is_branch: bool, + /// Index of the match arm this row descends from; stable under specialization. + arm_id: usize, + /// The or-pattern alternatives this row descends from, accumulated on each expansion. + or_alternatives: SmallVec<[PatId; 4]>, } impl<'p, Cx: PatCx> MatrixRow<'p, Cx> { @@ -1149,6 +1184,8 @@ impl<'p, Cx: PatCx> MatrixRow<'p, Cx> { intersects_at_least: DenseBitSet::new_empty(0), // Initialized in `Matrix::push`. // This pattern is a branch because it comes from a match arm. head_is_branch: true, + arm_id, + or_alternatives: SmallVec::new(), } } @@ -1167,13 +1204,21 @@ impl<'p, Cx: PatCx> MatrixRow<'p, Cx> { // Expand the first or-pattern (if any) into its subpatterns. Panics if `self` is empty. fn expand_or_pat(&self, parent_row: usize) -> impl Iterator> { let is_or_pat = self.pats.head().is_or_pat(); - self.pats.expand_or_pat().map(move |patstack| MatrixRow { - pats: patstack, - parent_row, - is_under_guard: self.is_under_guard, - useful: false, - intersects_at_least: DenseBitSet::new_empty(0), // Initialized in `Matrix::push`. - head_is_branch: is_or_pat, + self.pats.expand_or_pat().map(move |patstack| { + let mut or_alternatives = self.or_alternatives.clone(); + if is_or_pat && let PatOrWild::Pat(alt) = patstack.head() { + or_alternatives.push(alt.uid); + } + MatrixRow { + pats: patstack, + parent_row, + is_under_guard: self.is_under_guard, + useful: false, + intersects_at_least: DenseBitSet::new_empty(0), // Initialized in `Matrix::push`. + head_is_branch: is_or_pat, + arm_id: self.arm_id, + or_alternatives, + } }) } @@ -1194,6 +1239,8 @@ impl<'p, Cx: PatCx> MatrixRow<'p, Cx> { useful: false, intersects_at_least: DenseBitSet::new_empty(0), // Initialized in `Matrix::push`. head_is_branch: false, + arm_id: self.arm_id, + or_alternatives: self.or_alternatives.clone(), }) } } @@ -1725,6 +1772,36 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: PatCx>( // The next rows stays useful if this one is under a guard. useful &= row.is_under_guard; } + + // An alternative is `needed` here iff no unguarded row of an earlier arm matches this + // base case and its own arm matches only through rows descended from it. Relevancy skips + // never lose neededness witnesses (same argument as in `BranchPatUsefulness::update`). + if matrix.rows().any(|row| !row.or_alternatives.is_empty()) { + // (alternative, arm, first row index) + let mut alternatives: SmallVec<[(PatId, usize, usize); 8]> = SmallVec::new(); + for (i, row) in matrix.rows().enumerate() { + for &alt in &row.or_alternatives { + if !alternatives.iter().any(|&(a, ..)| a == alt) { + alternatives.push((alt, row.arm_id, i)); + } + } + } + for (alt, arm_id, first_row_id) in alternatives { + let blocked_by_earlier_arm = matrix + .rows() + .take(first_row_id) + .any(|row| row.arm_id != arm_id && !row.is_under_guard); + let arm_matches_without_it = matrix + .rows() + .any(|row| row.arm_id == arm_id && !row.or_alternatives.contains(&alt)); + let entry = mcx.branch_usefulness.entry(alt).or_default(); + entry.is_or_alternative = true; + if !blocked_by_earlier_arm && !arm_matches_without_it { + entry.needed = true; + } + } + } + return if useful && matrix.wildcard_row_is_relevant { // The wildcard row is useful; the match is non-exhaustive. Ok(WitnessMatrix::unit_witness()) @@ -1790,6 +1867,21 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: PatCx>( if row.head_is_branch { if let PatOrWild::Pat(pat) = row.head() { mcx.branch_usefulness.entry(pat.uid).or_default().update(row, matrix); + // Record intersections between same-or-pattern alternatives, to explain useless + // ones. Goes both ways: later and guarded siblings cover too (same arm). + for other_id in row.intersects_at_least.iter() { + let other = &matrix.rows[other_id]; + if other.arm_id == row.arm_id + && other.head_is_branch + && let PatOrWild::Pat(other_pat) = other.head() + && other_pat.uid != pat.uid + { + let entry = mcx.branch_usefulness.entry(pat.uid).or_default(); + entry.covered_by_alternatives.insert(other_pat); + let entry = mcx.branch_usefulness.entry(other_pat.uid).or_default(); + entry.covered_by_alternatives.insert(pat); + } + } } } } @@ -1828,6 +1920,10 @@ pub struct UsefulnessReport<'p, Cx: PatCx> { /// For each arm, a set of indices of arms above it that have non-empty intersection, i.e. there /// is a value matched by both arms. This may miss real intersections. pub arm_intersections: Vec>, + /// Or-pattern alternatives that are reachable but removable without changing the match, e.g. + /// `0` in `0 | _`, as (arm index, subpattern, explanation) tuples. Warning: bindings make + /// the matched alternative observable; check for them before reporting these. + pub useless_subpatterns: Vec<(usize, &'p DeconstructedPat, RedundancyExplanation<'p, Cx>)>, } /// Computes whether a match is exhaustive and which of its arms are useful. @@ -1854,10 +1950,12 @@ pub fn compute_match_usefulness<'p, Cx: PatCx>( let non_exhaustiveness_witnesses = compute_exhaustiveness_and_usefulness(&mut cx, &mut matrix)?; let non_exhaustiveness_witnesses: Vec<_> = non_exhaustiveness_witnesses.single_column(); + let mut useless_subpatterns = Vec::new(); let arm_usefulness: Vec<_> = arms .iter() .copied() - .map(|arm| { + .enumerate() + .map(|(arm_id, arm)| { debug!(?arm); let usefulness = cx.branch_usefulness.get(&arm.pat.uid).unwrap(); let usefulness = if let Some(explanation) = usefulness.is_redundant() { @@ -1869,6 +1967,9 @@ pub fn compute_match_usefulness<'p, Cx: PatCx>( if let Some(explanation) = u.is_redundant() { redundant_subpats.push((subpat, explanation)); false // stop recursing + } else if let Some(explanation) = u.is_useless() { + useless_subpatterns.push((arm_id, subpat, explanation)); + false // stop recursing: subpatterns of a useless pattern are too } else { true // keep recursing } @@ -1886,5 +1987,10 @@ pub fn compute_match_usefulness<'p, Cx: PatCx>( let arm_intersections: Vec<_> = matrix.rows().map(|row| row.intersects_at_least.clone()).collect(); - Ok(UsefulnessReport { arm_usefulness, non_exhaustiveness_witnesses, arm_intersections }) + Ok(UsefulnessReport { + arm_usefulness, + non_exhaustiveness_witnesses, + arm_intersections, + useless_subpatterns, + }) } diff --git a/compiler/rustc_pattern_analysis/tests/useless.rs b/compiler/rustc_pattern_analysis/tests/useless.rs new file mode 100644 index 0000000000000..ba86686867828 --- /dev/null +++ b/compiler/rustc_pattern_analysis/tests/useless.rs @@ -0,0 +1,166 @@ +//! Test detection of useless or-pattern alternatives, like `0` in `0 | _`. + +#![allow(unused_crate_dependencies)] + +use common::*; +use rustc_pattern_analysis::MatchArm; +use rustc_pattern_analysis::constructor::Constructor; +use rustc_pattern_analysis::pat::DeconstructedPat; +use rustc_pattern_analysis::usefulness::{PlaceValidity, Usefulness}; + +#[macro_use] +mod common; + +/// Construct an or-pattern with the given alternatives. +fn or_pat(ty: Ty, alts: Vec>) -> DeconstructedPat { + let arity = alts.len(); + let fields = alts.into_iter().enumerate().map(|(i, pat)| pat.at_index(i)).collect(); + DeconstructedPat::new(Constructor::Or, fields, arity, ty, ()) +} + +/// Return the path of field indices leading to `target` in the subpattern tree of `root`. +fn find_path(root: &DeconstructedPat, target: &DeconstructedPat) -> Option> { + if root == target { + return Some(Vec::new()); + } + for ipat in root.iter_fields() { + if let Some(mut path) = find_path(&ipat.pat, target) { + path.insert(0, ipat.idx); + return Some(path); + } + } + None +} + +/// Analyze a match made of these arms (pattern, has_guard) and return the (arm index, subpattern +/// path) of subpatterns found redundant resp. useless. +fn check( + ty: Ty, + arms: &[(DeconstructedPat, bool)], +) -> (Vec<(usize, Vec)>, Vec<(usize, Vec)>) { + let arms: Vec> = arms + .iter() + .map(|(pat, has_guard)| MatchArm { pat, has_guard: *has_guard, arm_data: () }) + .collect(); + let report = + compute_match_usefulness(&arms, ty, PlaceValidity::ValidOnly, usize::MAX, false).unwrap(); + let mut redundant = Vec::new(); + let mut useless = Vec::new(); + for (arm_id, (arm, usefulness)) in report.arm_usefulness.iter().enumerate() { + if let Usefulness::Useful(redundant_subpats) = usefulness { + for (pat, _) in redundant_subpats { + redundant.push((arm_id, find_path(arm.pat, pat).unwrap())); + } + } + } + for (arm_id, pat, _) in &report.useless_subpatterns { + let arm_pat = report.arm_usefulness[*arm_id].0.pat; + useless.push((*arm_id, find_path(arm_pat, pat).unwrap())); + } + (redundant, useless) +} + +#[track_caller] +fn assert_lints( + ty: Ty, + arms: &[(DeconstructedPat, bool)], + expected_redundant: &[(usize, &[usize])], + expected_useless: &[(usize, &[usize])], +) { + let (redundant, useless) = check(ty, arms); + let redundant: Vec<_> = redundant.iter().map(|(i, path)| (*i, path.as_slice())).collect(); + let useless: Vec<_> = useless.iter().map(|(i, path)| (*i, path.as_slice())).collect(); + assert_eq!(redundant.as_slice(), expected_redundant, "redundant subpatterns mismatch"); + assert_eq!(useless.as_slice(), expected_useless, "useless subpatterns mismatch"); +} + +#[test] +fn test_useless_or_alternative() { + let ty = Ty::U8; + + // `0 | _`: the `0` is useless (issue #160772). + let pat = or_pat(ty, pats!(ty; 0, _)); + assert_lints(ty, &[(pat, false)], &[], &[(0, &[0])]); + + // `_ | 0`: the `0` is redundant (existing behavior), not additionally useless. + let pat = or_pat(ty, pats!(ty; _, 0)); + assert_lints(ty, &[(pat, false)], &[(0, &[1])], &[]); + + // `0 | 0..`: the `0` is useless even though the other side isn't a wildcard. + let pat = or_pat(ty, pats!(ty; 0, 0..)); + assert_lints(ty, &[(pat, false)], &[], &[(0, &[0])]); + + // `0..=1 | 1..=2 | 2..=3`: the other two collectively cover the middle alternative. + let pat = or_pat(ty, pats!(ty; 0..=1, 1..=2, 2..=3)); + assert_lints(ty, &[(pat, false)], &[], &[(0, &[1])]); + + // `0..=1 | 1..=2`: both alternatives have values of their own. + let pat = or_pat(ty, pats!(ty; 0..=1, 1..=2)); + assert_lints(ty, &[(pat, false)], &[], &[]); + + // `0..=1 | 0..=1`: the second is unreachable (existing behavior), the first useless. + let pat = or_pat(ty, pats!(ty; 0..=1, 0..=1)); + assert_lints(ty, &[(pat, false)], &[(0, &[1])], &[(0, &[0])]); +} + +#[test] +fn test_useless_with_earlier_arms() { + let ty = Ty::U8; + + // `1` is redundant (covered on its left); `0..=1` is useless (earlier arm + sibling). + let arm1 = pat!(ty; 0); + let arm2 = or_pat(ty, pats!(ty; 0..=1, 1)); + assert_lints(ty, &[(arm1, false), (arm2, false)], &[(1, &[1])], &[(1, &[0])]); + + // `0 => ..` then `0 | 1 => ..`: `0` is redundant, and must not also be reported useless. + let arm1 = pat!(ty; 0); + let arm2 = or_pat(ty, pats!(ty; 0, 1)); + assert_lints(ty, &[(arm1, false), (arm2, false)], &[(1, &[0])], &[]); +} + +#[test] +fn test_useless_with_guards() { + let ty = Ty::U8; + + // `0` is useless but not redundant: the guard keeps it reachable. + let arm1 = or_pat(ty, pats!(ty; 0, _)); + let arm2 = pat!(ty; _); + assert_lints(ty, &[(arm1, true), (arm2, false)], &[], &[(0, &[0])]); + + // `0` in the second arm can match (when the guard fails) yet removing it changes nothing. + let arm1 = pat!(ty; 0); + let arm2 = or_pat(ty, pats!(ty; 0, _)); + assert_lints(ty, &[(arm1, true), (arm2, false)], &[], &[(1, &[0])]); +} + +#[test] +fn test_useless_nested() { + let ty = Ty::U8; + let tuple_ty = Ty::Tuple(&[Ty::U8, Ty::Bool]); + + // `(0 | _, true)`: the `0` is useless. + let inner_or = or_pat(ty, pats!(ty; 0, _)); + let true_pat = pat!(Ty::Bool; true); + let arm1 = DeconstructedPat::new( + Constructor::Struct, + vec![inner_or.at_index(0), true_pat.at_index(1)], + 2, + tuple_ty, + (), + ); + let arm2 = pat!(tuple_ty; _); + assert_lints(tuple_ty, &[(arm1, false), (arm2, false)], &[], &[(0, &[0, 0])]); + + // `(0..=1 | 1..=2, 0 | 1)`: every alternative has a value only it brings to the arm. + let or_a = or_pat(ty, pats!(ty; 0..=1, 1..=2)); + let or_b = or_pat(ty, pats!(ty; 0, 1)); + let arm1 = DeconstructedPat::new( + Constructor::Struct, + vec![or_a.at_index(0), or_b.at_index(1)], + 2, + tuple_ty, + (), + ); + let arm2 = pat!(tuple_ty; _); + assert_lints(tuple_ty, &[(arm1, false), (arm2, false)], &[], &[]); +} diff --git a/tests/ui/drop/if-let-guards.rs b/tests/ui/drop/if-let-guards.rs index 01d600ff0241b..e1d3a5cd1eb5d 100644 --- a/tests/ui/drop/if-let-guards.rs +++ b/tests/ui/drop/if-let-guards.rs @@ -8,6 +8,8 @@ //@ run-pass #![deny(rust_2024_compatibility)] +// The `_ | _ | _ if ..` patterns are deliberate: several candidates sharing a guard. +#![allow(unreachable_patterns)] use core::{cell::RefCell, ops::Drop}; diff --git a/tests/ui/or-patterns/basic-switchint.rs b/tests/ui/or-patterns/basic-switchint.rs index e4efef597300c..8f727cd834991 100644 --- a/tests/ui/or-patterns/basic-switchint.rs +++ b/tests/ui/or-patterns/basic-switchint.rs @@ -3,6 +3,9 @@ //@ run-pass +// Some patterns intentionally contain useless alternatives (e.g. `0 | _`) to test their lowering. +#![allow(unreachable_patterns)] + #[derive(Debug, PartialEq)] enum MatchArm { Arm(usize), diff --git a/tests/ui/or-patterns/exhaustiveness-pass.rs b/tests/ui/or-patterns/exhaustiveness-pass.rs index a80c6bdec20b3..837abb9f3db9f 100644 --- a/tests/ui/or-patterns/exhaustiveness-pass.rs +++ b/tests/ui/or-patterns/exhaustiveness-pass.rs @@ -24,7 +24,7 @@ fn main() { ((_,),) => {} } match (&[0u8][..],) { - ([] | [0 | 1..=255] | [_, ..],) => {} + ([] | [0 | 1..=255] | [_, _, ..],) => {} } match ((0, 0),) { @@ -40,13 +40,6 @@ fn main() { ((x, y) | (y, x),) if x == 0 => {} _ => {} } - match 0 { - // We don't warn the second one as redundant in general because of cases like the one above. - // We could technically do it if there are no bindings. - 0 | 0 if 0 == 0 => {} - _ => {} - } - // This one caused ICE https://github.com/rust-lang/rust/issues/117378 match (0u8, 0) { (x @ 0 | x @ (1 | 2), _) => {} diff --git a/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.rs b/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.rs index afdcff0346bb2..2e6c95a07b63c 100644 --- a/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.rs +++ b/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.rs @@ -51,10 +51,12 @@ fn main() { match (0,) { (1 | 1,) => {} //~ ERROR unreachable + //~^ ERROR useless pattern _ => {} } match 0 { (0 | 1) | 1 => {} //~ ERROR unreachable + //~^ ERROR useless pattern _ => {} } match 0 { @@ -63,25 +65,28 @@ fn main() { 0 | (0 | 0) => {} //~^ ERROR unreachable //~| ERROR unreachable + //~| ERROR useless pattern _ => {} } match None { // There is only one error that correctly points to the whole subpattern - Some(0) | + Some(0) | //~ ERROR useless pattern Some( //~ ERROR unreachable 0 | 0) => {} _ => {} } match [0; 2] { - [0 + [0 //~ ERROR useless pattern | 0 //~ ERROR unreachable - , 0 + , 0 //~ ERROR useless pattern | 0] => {} //~ ERROR unreachable _ => {} } match (true, 0) { (true, 0 | 0) => {} //~ ERROR unreachable + //~^ ERROR useless pattern (_, 0 | 0) => {} //~ ERROR unreachable + //~^ ERROR useless pattern _ => {} } match &[][..] { @@ -168,6 +173,7 @@ fn main() { fn unreachable_in_param((_ | (_, _)): (bool, bool)) {} //~^ ERROR unreachable +//~| ERROR useless pattern fn unreachable_in_binding() { let bool_pair = (true, true); @@ -175,8 +181,10 @@ fn unreachable_in_binding() { let (_ | (_, _)) = bool_pair; //~^ ERROR unreachable + //~| ERROR useless pattern for (_ | (_, _)) in [bool_pair] {} //~^ ERROR unreachable + //~| ERROR useless pattern let (Some(_) | Some(true)) = bool_option else { return }; //~^ ERROR unreachable diff --git a/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.stderr b/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.stderr index 6ddc059566539..4f142f3054a57 100644 --- a/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.stderr +++ b/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.stderr @@ -129,16 +129,32 @@ LL | (1 | 1,) => {} | | | matches all the relevant values +error: useless pattern + --> $DIR/exhaustiveness-unreachable-pattern.rs:53:10 + | +LL | (1 | 1,) => {} + | ^ - matches all the values this pattern does + | | + | all the values this pattern matches are already matched by the rest of the or-pattern + error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:57:19 + --> $DIR/exhaustiveness-unreachable-pattern.rs:58:19 | LL | (0 | 1) | 1 => {} | - ^ no value can reach this | | | matches all the relevant values +error: useless pattern + --> $DIR/exhaustiveness-unreachable-pattern.rs:58:14 + | +LL | (0 | 1) | 1 => {} + | ^ - matches all the values this pattern does + | | + | all the values this pattern matches are already matched by the rest of the or-pattern + error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:63:14 + --> $DIR/exhaustiveness-unreachable-pattern.rs:65:14 | LL | 0 | (0 | 0) => {} | - ^ no value can reach this @@ -146,15 +162,30 @@ LL | 0 | (0 | 0) => {} | matches all the relevant values error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:63:18 + --> $DIR/exhaustiveness-unreachable-pattern.rs:65:18 | LL | 0 | (0 | 0) => {} | - ^ no value can reach this | | | matches all the relevant values +error: useless pattern + --> $DIR/exhaustiveness-unreachable-pattern.rs:65:9 + | +LL | 0 | (0 | 0) => {} + | ^ all the values this pattern matches are already matched by the rest of the or-pattern + | +note: multiple other patterns match some of the same values + --> $DIR/exhaustiveness-unreachable-pattern.rs:65:9 + | +LL | 0 | (0 | 0) => {} + | ^ - - matches some of the same values + | | | + | | matches some of the same values + | collectively making this pattern useless + error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:71:13 + --> $DIR/exhaustiveness-unreachable-pattern.rs:74:13 | LL | Some(0) | | ------- matches all the relevant values @@ -162,8 +193,17 @@ LL | / Some( LL | | 0 | 0) => {} | |______________________^ no value can reach this +error: useless pattern + --> $DIR/exhaustiveness-unreachable-pattern.rs:73:9 + | +LL | Some(0) | + | ^^^^^^^ all the values this pattern matches are already matched by the rest of the or-pattern +LL | / Some( +LL | | 0 | 0) => {} + | |______________________- matches all the values this pattern does + error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:77:15 + --> $DIR/exhaustiveness-unreachable-pattern.rs:80:15 | LL | [0 | - matches all the relevant values @@ -171,15 +211,40 @@ LL | | 0 | ^ no value can reach this error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:79:15 + --> $DIR/exhaustiveness-unreachable-pattern.rs:82:15 | LL | , 0 | - matches all the relevant values LL | | 0] => {} | ^ no value can reach this +error: useless pattern + --> $DIR/exhaustiveness-unreachable-pattern.rs:79:10 + | +LL | [0 + | ^ all the values this pattern matches are already matched by the rest of the or-pattern +LL | | 0 + | - matches all the values this pattern does + +error: useless pattern + --> $DIR/exhaustiveness-unreachable-pattern.rs:81:11 + | +LL | , 0 + | ^ all the values this pattern matches are already matched by the rest of the or-pattern + | +note: multiple other patterns match some of the same values + --> $DIR/exhaustiveness-unreachable-pattern.rs:81:11 + | +LL | , 0 + | ^ + | | + | matches some of the same values + | collectively making this pattern useless +LL | | 0] => {} + | - matches some of the same values + error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:83:20 + --> $DIR/exhaustiveness-unreachable-pattern.rs:86:20 | LL | (true, 0 | 0) => {} | - ^ no value can reach this @@ -187,23 +252,49 @@ LL | (true, 0 | 0) => {} | matches all the relevant values error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:84:17 + --> $DIR/exhaustiveness-unreachable-pattern.rs:88:17 | LL | (_, 0 | 0) => {} | ^ no value can reach this | note: multiple earlier patterns match some of the same values - --> $DIR/exhaustiveness-unreachable-pattern.rs:84:17 + --> $DIR/exhaustiveness-unreachable-pattern.rs:88:17 | LL | (true, 0 | 0) => {} | - matches some of the same values +LL | LL | (_, 0 | 0) => {} | - ^ collectively making this unreachable | | | matches some of the same values +error: useless pattern + --> $DIR/exhaustiveness-unreachable-pattern.rs:86:16 + | +LL | (true, 0 | 0) => {} + | ^ - matches all the values this pattern does + | | + | all the values this pattern matches are already matched by the rest of the or-pattern + +error: useless pattern + --> $DIR/exhaustiveness-unreachable-pattern.rs:88:13 + | +LL | (_, 0 | 0) => {} + | ^ all the values this pattern matches are already matched by the rest of the or-pattern + | +note: multiple other patterns match some of the same values + --> $DIR/exhaustiveness-unreachable-pattern.rs:88:13 + | +LL | (true, 0 | 0) => {} + | - matches some of the same values +LL | +LL | (_, 0 | 0) => {} + | ^ - matches some of the same values + | | + | collectively making this pattern useless + error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:92:10 + --> $DIR/exhaustiveness-unreachable-pattern.rs:97:10 | LL | [1, ..] => {} | - matches all the relevant values @@ -211,7 +302,7 @@ LL | [1 | ^ no value can reach this error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:104:10 + --> $DIR/exhaustiveness-unreachable-pattern.rs:109:10 | LL | [true, ..] => {} | ---- matches all the relevant values @@ -219,13 +310,13 @@ LL | [true | ^^^^ no value can reach this error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:111:36 + --> $DIR/exhaustiveness-unreachable-pattern.rs:116:36 | LL | (true | false, None | Some(true | ^^^^ no value can reach this | note: multiple earlier patterns match some of the same values - --> $DIR/exhaustiveness-unreachable-pattern.rs:111:36 + --> $DIR/exhaustiveness-unreachable-pattern.rs:116:36 | LL | (true, Some(_)) => {} | - matches some of the same values @@ -235,7 +326,7 @@ LL | (true | false, None | Some(true | ^^^^ collectively making this unreachable error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:116:14 + --> $DIR/exhaustiveness-unreachable-pattern.rs:121:14 | LL | (true | ^^^^ no value can reach this @@ -244,7 +335,7 @@ LL | (true | false, None | Some(t_or_f!())) => {} | --------- in this macro invocation | note: multiple earlier patterns match some of the same values - --> $DIR/exhaustiveness-unreachable-pattern.rs:116:14 + --> $DIR/exhaustiveness-unreachable-pattern.rs:121:14 | LL | (true | ^^^^ collectively making this unreachable @@ -258,7 +349,7 @@ 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:127:14 + --> $DIR/exhaustiveness-unreachable-pattern.rs:132:14 | LL | Some(0) => {} | - matches all the relevant values @@ -266,7 +357,7 @@ LL | Some(0 | ^ no value can reach this error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:146:19 + --> $DIR/exhaustiveness-unreachable-pattern.rs:151:19 | LL | Some(false) => {} | ----- matches all the relevant values @@ -275,13 +366,13 @@ LL | | false) => {} | ^^^^^ no value can reach this error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:154:15 + --> $DIR/exhaustiveness-unreachable-pattern.rs:159:15 | LL | | true) => {} | ^^^^ no value can reach this | note: multiple earlier patterns match some of the same values - --> $DIR/exhaustiveness-unreachable-pattern.rs:154:15 + --> $DIR/exhaustiveness-unreachable-pattern.rs:159:15 | LL | (false, true) => {} | ---- matches some of the same values @@ -292,13 +383,13 @@ LL | | true) => {} | ^^^^ collectively making this unreachable error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:160:15 + --> $DIR/exhaustiveness-unreachable-pattern.rs:165:15 | LL | | true, | ^^^^ no value can reach this | note: multiple earlier patterns match some of the same values - --> $DIR/exhaustiveness-unreachable-pattern.rs:160:15 + --> $DIR/exhaustiveness-unreachable-pattern.rs:165:15 | LL | (true, false) => {} | ---- matches some of the same values @@ -309,7 +400,7 @@ LL | | true, | ^^^^ collectively making this unreachable error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:165:15 + --> $DIR/exhaustiveness-unreachable-pattern.rs:170:15 | LL | (x, y) | ------ matches any value @@ -317,31 +408,55 @@ LL | | (y, x) => {} | ^^^^^^ no value can reach this error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:169:30 + --> $DIR/exhaustiveness-unreachable-pattern.rs:174:30 | LL | fn unreachable_in_param((_ | (_, _)): (bool, bool)) {} | - ^^^^^^ no value can reach this | | | matches any value +error: useless pattern + --> $DIR/exhaustiveness-unreachable-pattern.rs:174:26 + | +LL | fn unreachable_in_param((_ | (_, _)): (bool, bool)) {} + | ^ ------ matches any value + | | + | all the values this pattern matches are already matched by the rest of the or-pattern + error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:176:14 + --> $DIR/exhaustiveness-unreachable-pattern.rs:182:14 | LL | let (_ | (_, _)) = bool_pair; | - ^^^^^^ no value can reach this | | | matches any value +error: useless pattern + --> $DIR/exhaustiveness-unreachable-pattern.rs:182:10 + | +LL | let (_ | (_, _)) = bool_pair; + | ^ ------ matches any value + | | + | all the values this pattern matches are already matched by the rest of the or-pattern + error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:178:14 + --> $DIR/exhaustiveness-unreachable-pattern.rs:185:14 | LL | for (_ | (_, _)) in [bool_pair] {} | - ^^^^^^ no value can reach this | | | matches any value +error: useless pattern + --> $DIR/exhaustiveness-unreachable-pattern.rs:185:10 + | +LL | for (_ | (_, _)) in [bool_pair] {} + | ^ ------ matches any value + | | + | all the values this pattern matches are already matched by the rest of the or-pattern + error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:181:20 + --> $DIR/exhaustiveness-unreachable-pattern.rs:189:20 | LL | let (Some(_) | Some(true)) = bool_option else { return }; | ------- ^^^^^^^^^^ no value can reach this @@ -349,7 +464,7 @@ LL | let (Some(_) | Some(true)) = bool_option else { return }; | matches all the relevant values error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:183:22 + --> $DIR/exhaustiveness-unreachable-pattern.rs:191:22 | LL | if let Some(_) | Some(true) = bool_option {} | ------- ^^^^^^^^^^ no value can reach this @@ -357,12 +472,12 @@ LL | if let Some(_) | Some(true) = bool_option {} | matches all the relevant values error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:185:25 + --> $DIR/exhaustiveness-unreachable-pattern.rs:193:25 | LL | while let Some(_) | Some(true) = bool_option {} | ------- ^^^^^^^^^^ no value can reach this | | | matches all the relevant values -error: aborting due to 36 previous errors +error: aborting due to 47 previous errors diff --git a/tests/ui/or-patterns/mix-with-wild.rs b/tests/ui/or-patterns/mix-with-wild.rs index 4577cba7a7d6c..f1b76ad48cfd1 100644 --- a/tests/ui/or-patterns/mix-with-wild.rs +++ b/tests/ui/or-patterns/mix-with-wild.rs @@ -5,6 +5,9 @@ //@ run-pass +// `Some(0 | _)` is deliberate: this tests the runtime behavior of exactly that mix. +#![allow(unreachable_patterns)] + pub fn test(x: Option) -> bool { match x { Some(0 | _) => true, diff --git a/tests/ui/or-patterns/search-via-bindings.rs b/tests/ui/or-patterns/search-via-bindings.rs index 42174bd7cef73..afaa077474572 100644 --- a/tests/ui/or-patterns/search-via-bindings.rs +++ b/tests/ui/or-patterns/search-via-bindings.rs @@ -2,6 +2,9 @@ //@ run-pass +// The dummy `_ | _` patterns are deliberate, so allow the "useless pattern" lint. +#![allow(unreachable_patterns)] + fn search(target: (bool, bool, bool)) -> u32 { let x = ((false, true), (false, true), (false, true)); let mut guard_count = 0; diff --git a/tests/ui/pattern/usefulness/explain-unreachable-pats.rs b/tests/ui/pattern/usefulness/explain-unreachable-pats.rs index f1af7f294cbd8..ad6b50519c8bf 100644 --- a/tests/ui/pattern/usefulness/explain-unreachable-pats.rs +++ b/tests/ui/pattern/usefulness/explain-unreachable-pats.rs @@ -80,9 +80,12 @@ fn main() { if let (0 //~^ NOTE matches all the relevant values + //~| ERROR useless pattern + //~| NOTE all the values this pattern matches are already matched | 0, _) = (0, 0) {} //~^ ERROR unreachable pattern //~| NOTE no value can reach this + //~| NOTE matches all the values this pattern does match (true, true) { (_, true) if false => {} // Guarded patterns don't cover others diff --git a/tests/ui/pattern/usefulness/explain-unreachable-pats.stderr b/tests/ui/pattern/usefulness/explain-unreachable-pats.stderr index 8651be2055f53..23d2b81d8455a 100644 --- a/tests/ui/pattern/usefulness/explain-unreachable-pats.stderr +++ b/tests/ui/pattern/usefulness/explain-unreachable-pats.stderr @@ -89,22 +89,31 @@ LL | (Err(_), Err(_)) => {} = note: to learn more about uninhabited types, see https://doc.rust-lang.org/nomicon/exotic-sizes.html#empty-types error: unreachable pattern - --> $DIR/explain-unreachable-pats.rs:83:11 + --> $DIR/explain-unreachable-pats.rs:85:11 | LL | if let (0 | - matches all the relevant values -LL | +... LL | | 0, _) = (0, 0) {} | ^ no value can reach this +error: useless pattern + --> $DIR/explain-unreachable-pats.rs:81:13 + | +LL | if let (0 + | ^ all the values this pattern matches are already matched by the rest of the or-pattern +... +LL | | 0, _) = (0, 0) {} + | - matches all the values this pattern does + error: unreachable pattern - --> $DIR/explain-unreachable-pats.rs:93:9 + --> $DIR/explain-unreachable-pats.rs:96:9 | LL | (_, true) => {} | ^^^^^^^^^ no value can reach this | note: multiple earlier patterns match some of the same values - --> $DIR/explain-unreachable-pats.rs:93:9 + --> $DIR/explain-unreachable-pats.rs:96:9 | LL | (true, _) => {} | --------- matches some of the same values @@ -116,7 +125,7 @@ LL | (_, true) => {} | ^^^^^^^^^ collectively making this unreachable error: unreachable pattern - --> $DIR/explain-unreachable-pats.rs:106:9 + --> $DIR/explain-unreachable-pats.rs:109:9 | LL | (true, _) => {} | --------- matches all the relevant values @@ -125,7 +134,7 @@ LL | (true, true) => {} | ^^^^^^^^^^^^ no value can reach this error: unreachable pattern - --> $DIR/explain-unreachable-pats.rs:118:9 + --> $DIR/explain-unreachable-pats.rs:121:9 | LL | (_, true, 0..10) => {} | ---------------- matches all the relevant values @@ -133,5 +142,5 @@ LL | (_, true, 0..10) => {} LL | (_, true, 3) => {} | ^^^^^^^^^^^^ no value can reach this -error: aborting due to 10 previous errors +error: aborting due to 11 previous errors diff --git a/tests/ui/pattern/usefulness/top-level-alternation.rs b/tests/ui/pattern/usefulness/top-level-alternation.rs index e8cd12ea4a2c4..ed1f115397dc3 100644 --- a/tests/ui/pattern/usefulness/top-level-alternation.rs +++ b/tests/ui/pattern/usefulness/top-level-alternation.rs @@ -7,11 +7,13 @@ fn main() { match 0u8 { 0 | 0 => {} //~ ERROR unreachable pattern + //~^^ ERROR useless pattern _ => {} } match Some(0u8) { Some(0) | Some(0) => {} //~ ERROR unreachable pattern + //~^^ ERROR useless pattern _ => {} } match (0u8, 0u8) { @@ -54,4 +56,5 @@ fn main() { _ => {}, } let (0 | 0) = 0 else { return }; //~ ERROR unreachable pattern + //~^ ERROR useless pattern } diff --git a/tests/ui/pattern/usefulness/top-level-alternation.stderr b/tests/ui/pattern/usefulness/top-level-alternation.stderr index 7fc03143bc372..e81fa74fcddc4 100644 --- a/tests/ui/pattern/usefulness/top-level-alternation.stderr +++ b/tests/ui/pattern/usefulness/top-level-alternation.stderr @@ -28,16 +28,32 @@ LL | 0 LL | | 0 => {} | ^ no value can reach this +error: useless pattern + --> $DIR/top-level-alternation.rs:8:9 + | +LL | 0 + | ^ all the values this pattern matches are already matched by the rest of the or-pattern +LL | | 0 => {} + | - matches all the values this pattern does + error: unreachable pattern - --> $DIR/top-level-alternation.rs:14:15 + --> $DIR/top-level-alternation.rs:15:15 | LL | Some(0) | ------- matches all the relevant values LL | | Some(0) => {} | ^^^^^^^ no value can reach this +error: useless pattern + --> $DIR/top-level-alternation.rs:14:9 + | +LL | Some(0) + | ^^^^^^^ all the values this pattern matches are already matched by the rest of the or-pattern +LL | | Some(0) => {} + | ------- matches all the values this pattern does + error: unreachable pattern - --> $DIR/top-level-alternation.rs:19:9 + --> $DIR/top-level-alternation.rs:21:9 | LL | (0, _) | (_, 0) => {} | --------------- matches all the relevant values @@ -45,7 +61,7 @@ LL | (0, 0) => {} | ^^^^^^ no value can reach this error: unreachable pattern - --> $DIR/top-level-alternation.rs:39:9 + --> $DIR/top-level-alternation.rs:41:9 | LL | None | Some(_) => {} | -------------- matches all the relevant values @@ -53,7 +69,7 @@ LL | _ => {} | ^ no value can reach this error: unreachable pattern - --> $DIR/top-level-alternation.rs:43:9 + --> $DIR/top-level-alternation.rs:45:9 | LL | None | Some(_) => {} | -------------- matches all the relevant values @@ -61,7 +77,7 @@ LL | Some(_) => {} | ^^^^^^^ no value can reach this error: unreachable pattern - --> $DIR/top-level-alternation.rs:44:9 + --> $DIR/top-level-alternation.rs:46:9 | LL | None | Some(_) => {} | -------------- matches all the relevant values @@ -70,13 +86,13 @@ LL | None => {} | ^^^^ no value can reach this error: unreachable pattern - --> $DIR/top-level-alternation.rs:49:9 + --> $DIR/top-level-alternation.rs:51:9 | LL | None | Some(_) => {} | ^^^^^^^^^^^^^^ no value can reach this | note: multiple earlier patterns match some of the same values - --> $DIR/top-level-alternation.rs:49:9 + --> $DIR/top-level-alternation.rs:51:9 | LL | Some(_) => {} | ------- matches some of the same values @@ -86,7 +102,7 @@ LL | None | Some(_) => {} | ^^^^^^^^^^^^^^ collectively making this unreachable error: unreachable pattern - --> $DIR/top-level-alternation.rs:53:9 + --> $DIR/top-level-alternation.rs:55:9 | LL | 1 | 2 => {}, | ----- matches all the relevant values @@ -94,12 +110,20 @@ LL | 1..=2 => {}, | ^^^^^ no value can reach this error: unreachable pattern - --> $DIR/top-level-alternation.rs:56:14 + --> $DIR/top-level-alternation.rs:58:14 | LL | let (0 | 0) = 0 else { return }; | - ^ no value can reach this | | | matches all the relevant values -error: aborting due to 11 previous errors +error: useless pattern + --> $DIR/top-level-alternation.rs:58:10 + | +LL | let (0 | 0) = 0 else { return }; + | ^ - matches all the values this pattern does + | | + | all the values this pattern matches are already matched by the rest of the or-pattern + +error: aborting due to 14 previous errors diff --git a/tests/ui/pattern/usefulness/useless-or-pattern.rs b/tests/ui/pattern/usefulness/useless-or-pattern.rs new file mode 100644 index 0000000000000..87fba79837a1a --- /dev/null +++ b/tests/ui/pattern/usefulness/useless-or-pattern.rs @@ -0,0 +1,78 @@ +//! Test the "useless pattern" lint on or-pattern alternatives that are reachable but don't +//! influence the result of the match, e.g. `0` in `0 | _` (issue #160772). + +#![deny(unreachable_patterns)] +#![allow(overlapping_range_endpoints)] + +fn main() { + match 0u8 { + 0 | _ => {} + //~^ ERROR useless pattern + } + + match 0u8 { + 0 | 0..=255 => {} + //~^ ERROR useless pattern + } + + // Covered by the union of the siblings, though neither covers it alone. + match 0u8 { + 0..=1 | 1..=2 | 2..=3 => {} + //~^ ERROR useless pattern + _ => {} + } + + match 0u8 { + 0 => {} + 0..=1 | 1 => {} + //~^ ERROR useless pattern + //~| ERROR unreachable pattern + _ => {} + } + + match 0u8 { + 0 | _ if false => {} + //~^ ERROR useless pattern + _ => {} + } + + match 0u8 { + 0 if false => {} + 0 | _ => {} + //~^ ERROR useless pattern + } + + match Some(0u8) { + Some(0 | _) => {} + //~^ ERROR useless pattern + None => {} + } + + // No lint: which alternative matches determines `x`. + match (0u8, 0u8) { + (0, x) | (x, _) => { + let _ = x; + } + } + + // Bindings outside the or-pattern bind the same value either way: still linted. + match 0u8 { + x @ (0 | _) => { + //~^ ERROR useless pattern + let _ = x; + } + } + + match 0u8 { + 0 | 0 if false => {} + //~^ ERROR useless pattern + //~| ERROR useless pattern + _ => {} + } + + // No lint: each alternative matches values the other doesn't. + match 0u8 { + 0..=1 | 1..=2 => {} + _ => {} + } +} diff --git a/tests/ui/pattern/usefulness/useless-or-pattern.stderr b/tests/ui/pattern/usefulness/useless-or-pattern.stderr new file mode 100644 index 0000000000000..54158e16b9792 --- /dev/null +++ b/tests/ui/pattern/usefulness/useless-or-pattern.stderr @@ -0,0 +1,111 @@ +error: useless pattern + --> $DIR/useless-or-pattern.rs:9:9 + | +LL | 0 | _ => {} + | ^ - matches any value + | | + | all the values this pattern matches are already matched by the rest of the or-pattern + | +note: the lint level is defined here + --> $DIR/useless-or-pattern.rs:4:9 + | +LL | #![deny(unreachable_patterns)] + | ^^^^^^^^^^^^^^^^^^^^ + +error: useless pattern + --> $DIR/useless-or-pattern.rs:14:9 + | +LL | 0 | 0..=255 => {} + | ^ ------- matches all the values this pattern does + | | + | all the values this pattern matches are already matched by the rest of the or-pattern + +error: useless pattern + --> $DIR/useless-or-pattern.rs:20:17 + | +LL | 0..=1 | 1..=2 | 2..=3 => {} + | ^^^^^ all the values this pattern matches are already matched by the rest of the or-pattern + | +note: multiple other patterns match some of the same values + --> $DIR/useless-or-pattern.rs:20:17 + | +LL | 0..=1 | 1..=2 | 2..=3 => {} + | ----- ^^^^^ ----- matches some of the same values + | | | + | | collectively making this pattern useless + | matches some of the same values + +error: unreachable pattern + --> $DIR/useless-or-pattern.rs:27:17 + | +LL | 0..=1 | 1 => {} + | ----- ^ no value can reach this + | | + | matches all the relevant values + +error: useless pattern + --> $DIR/useless-or-pattern.rs:27:9 + | +LL | 0..=1 | 1 => {} + | ^^^^^ all the values this pattern matches are already matched by the rest of the or-pattern + | +note: multiple other patterns match some of the same values + --> $DIR/useless-or-pattern.rs:27:9 + | +LL | 0 => {} + | - matches some of the same values +LL | 0..=1 | 1 => {} + | ^^^^^ - matches some of the same values + | | + | collectively making this pattern useless + +error: useless pattern + --> $DIR/useless-or-pattern.rs:34:9 + | +LL | 0 | _ if false => {} + | ^ - matches any value + | | + | all the values this pattern matches are already matched by the rest of the or-pattern + +error: useless pattern + --> $DIR/useless-or-pattern.rs:41:9 + | +LL | 0 | _ => {} + | ^ - matches any value + | | + | all the values this pattern matches are already matched by the rest of the or-pattern + +error: useless pattern + --> $DIR/useless-or-pattern.rs:46:14 + | +LL | Some(0 | _) => {} + | ^ - matches any value + | | + | all the values this pattern matches are already matched by the rest of the or-pattern + +error: useless pattern + --> $DIR/useless-or-pattern.rs:60:14 + | +LL | x @ (0 | _) => { + | ^ - matches any value + | | + | all the values this pattern matches are already matched by the rest of the or-pattern + +error: useless pattern + --> $DIR/useless-or-pattern.rs:67:9 + | +LL | 0 | 0 if false => {} + | ^ - matches all the values this pattern does + | | + | all the values this pattern matches are already matched by the rest of the or-pattern + +error: useless pattern + --> $DIR/useless-or-pattern.rs:67:13 + | +LL | 0 | 0 if false => {} + | - ^ all the values this pattern matches are already matched by the rest of the or-pattern + | | + | matches all the values this pattern does + +error: aborting due to 11 previous errors + diff --git a/tests/ui/rfcs/rfc-2294-if-let-guard/run-pass.rs b/tests/ui/rfcs/rfc-2294-if-let-guard/run-pass.rs index bea069b43915c..8cd3d87a52db6 100644 --- a/tests/ui/rfcs/rfc-2294-if-let-guard/run-pass.rs +++ b/tests/ui/rfcs/rfc-2294-if-let-guard/run-pass.rs @@ -1,5 +1,8 @@ //@ run-pass +// `() | ()` is deliberate: this tests if-let guards on or-patterns. +#![allow(unreachable_patterns)] + enum Foo { Bar, Baz, diff --git a/tests/ui/rfcs/rfc-2294-if-let-guard/warns.rs b/tests/ui/rfcs/rfc-2294-if-let-guard/warns.rs index f37f848d289c4..4732a8faebb59 100644 --- a/tests/ui/rfcs/rfc-2294-if-let-guard/warns.rs +++ b/tests/ui/rfcs/rfc-2294-if-let-guard/warns.rs @@ -29,6 +29,7 @@ fn unreachable_pattern() { match Some(()) { x if let None | None = x => {} //~^ ERROR unreachable pattern + //~| ERROR useless pattern _ => {} } } diff --git a/tests/ui/rfcs/rfc-2294-if-let-guard/warns.stderr b/tests/ui/rfcs/rfc-2294-if-let-guard/warns.stderr index 871d0b72b1395..f538daa0b8204 100644 --- a/tests/ui/rfcs/rfc-2294-if-let-guard/warns.stderr +++ b/tests/ui/rfcs/rfc-2294-if-let-guard/warns.stderr @@ -40,5 +40,13 @@ note: the lint level is defined here LL | #[deny(unreachable_patterns)] | ^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 3 previous errors +error: useless pattern + --> $DIR/warns.rs:30:18 + | +LL | x if let None | None = x => {} + | ^^^^ ---- matches all the values this pattern does + | | + | all the values this pattern matches are already matched by the rest of the or-pattern + +error: aborting due to 4 previous errors