about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJason Newcomb <jsnewcomb@pm.me>2023-08-31 01:24:49 -0400
committerJason Newcomb <jsnewcomb@pm.me>2024-08-07 11:40:15 -0400
commit8bcecfff950de9bdcdd5d642523ac337a4c426f8 (patch)
treedabc3fc223466250efc9bfefb4568aaff448555d
parent514824ff9d6b8f03eaf82e26a60080dea219bcbe (diff)
downloadrust-8bcecfff950de9bdcdd5d642523ac337a4c426f8.tar.gz
rust-8bcecfff950de9bdcdd5d642523ac337a4c426f8.zip
Handle or patterns in `single_match` and `single_match_else`
-rw-r--r--clippy_lints/src/matches/match_wild_enum.rs5
-rw-r--r--clippy_lints/src/matches/overlapping_arms.rs22
-rw-r--r--clippy_lints/src/matches/single_match.rs335
-rw-r--r--tests/ui-toml/excessive_nesting/excessive_nesting.rs20
-rw-r--r--tests/ui-toml/excessive_nesting/excessive_nesting.stderr74
-rw-r--r--tests/ui/crashes/ice-6254.rs3
-rw-r--r--tests/ui/patterns.fixed2
-rw-r--r--tests/ui/patterns.rs2
-rw-r--r--tests/ui/single_match.fixed43
-rw-r--r--tests/ui/single_match.rs49
-rw-r--r--tests/ui/single_match.stderr20
-rw-r--r--tests/ui/unneeded_field_pattern.rs2
12 files changed, 419 insertions, 158 deletions
diff --git a/clippy_lints/src/matches/match_wild_enum.rs b/clippy_lints/src/matches/match_wild_enum.rs
index 894b6c42612..91e40e4275c 100644
--- a/clippy_lints/src/matches/match_wild_enum.rs
+++ b/clippy_lints/src/matches/match_wild_enum.rs
@@ -180,9 +180,8 @@ enum CommonPrefixSearcher<'a> {
 }
 impl<'a> CommonPrefixSearcher<'a> {
     fn with_path(&mut self, path: &'a [PathSegment<'a>]) {
-        match path {
-            [path @ .., _] => self.with_prefix(path),
-            [] => (),
+        if let [path @ .., _] = path {
+            self.with_prefix(path);
         }
     }
 
diff --git a/clippy_lints/src/matches/overlapping_arms.rs b/clippy_lints/src/matches/overlapping_arms.rs
index 45b375dbe3d..7ad66360d8a 100644
--- a/clippy_lints/src/matches/overlapping_arms.rs
+++ b/clippy_lints/src/matches/overlapping_arms.rs
@@ -33,19 +33,17 @@ fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>)
         .filter_map(|arm| {
             if let Arm { pat, guard: None, .. } = *arm {
                 if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind {
-                    let lhs_const = match lhs {
-                        Some(lhs) => constant(cx, cx.typeck_results(), lhs)?,
-                        None => {
-                            let min_val_const = ty.numeric_min_val(cx.tcx)?;
-                            mir_to_const(cx, mir::Const::from_ty_const(min_val_const, ty, cx.tcx))?
-                        },
+                    let lhs_const = if let Some(lhs) = lhs {
+                        constant(cx, cx.typeck_results(), lhs)?
+                    } else {
+                        let min_val_const = ty.numeric_min_val(cx.tcx)?;
+                        mir_to_const(cx, mir::Const::from_ty_const(min_val_const, ty, cx.tcx))?
                     };
-                    let rhs_const = match rhs {
-                        Some(rhs) => constant(cx, cx.typeck_results(), rhs)?,
-                        None => {
-                            let max_val_const = ty.numeric_max_val(cx.tcx)?;
-                            mir_to_const(cx, mir::Const::from_ty_const(max_val_const, ty, cx.tcx))?
-                        },
+                    let rhs_const = if let Some(rhs) = rhs {
+                        constant(cx, cx.typeck_results(), rhs)?
+                    } else {
+                        let max_val_const = ty.numeric_max_val(cx.tcx)?;
+                        mir_to_const(cx, mir::Const::from_ty_const(max_val_const, ty, cx.tcx))?
                     };
                     let lhs_val = lhs_const.int_value(cx, ty)?;
                     let rhs_val = rhs_const.int_value(cx, ty)?;
diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs
index 8b413ff892c..24dea03601c 100644
--- a/clippy_lints/src/matches/single_match.rs
+++ b/clippy_lints/src/matches/single_match.rs
@@ -1,14 +1,17 @@
 use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::source::{expr_block, snippet, SpanRangeExt};
-use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
+use clippy_utils::ty::implements_trait;
 use clippy_utils::{
-    is_lint_allowed, is_unit_expr, is_wild, peel_blocks, peel_hir_pat_refs, peel_middle_ty_refs, peel_n_hir_expr_refs,
+    is_lint_allowed, is_unit_expr, peel_blocks, peel_hir_pat_refs, peel_middle_ty_refs, peel_n_hir_expr_refs,
 };
-use core::cmp::max;
+use core::ops::ControlFlow;
+use rustc_arena::DroplessArena;
 use rustc_errors::Applicability;
-use rustc_hir::{Arm, BindingMode, Expr, ExprKind, Pat, PatKind};
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::intravisit::{walk_pat, Visitor};
+use rustc_hir::{Arm, Expr, ExprKind, HirId, Pat, PatKind, QPath};
 use rustc_lint::LateContext;
-use rustc_middle::ty::{self, Ty};
+use rustc_middle::ty::{self, AdtDef, ParamEnv, TyCtxt, TypeckResults, VariantDef};
 use rustc_span::{sym, Span};
 
 use super::{MATCH_BOOL, SINGLE_MATCH, SINGLE_MATCH_ELSE};
@@ -29,7 +32,7 @@ fn empty_arm_has_comment(cx: &LateContext<'_>, span: Span) -> bool {
 }
 
 #[rustfmt::skip]
-pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
+pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>], expr: &'tcx Expr<'_>) {
     if let [arm1, arm2] = arms
         && arm1.guard.is_none()
         && arm2.guard.is_none()
@@ -52,10 +55,29 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr:
             return;
         };
 
-        let ty = cx.typeck_results().expr_ty(ex);
-        if (*ty.kind() != ty::Bool || is_lint_allowed(cx, MATCH_BOOL, ex.hir_id))
-            && (is_wild(arm2.pat) || form_exhaustive_matches(cx, ty, arm1.pat, arm2.pat))
-        {
+        let typeck = cx.typeck_results();
+        if *typeck.expr_ty(ex).peel_refs().kind() != ty::Bool || is_lint_allowed(cx, MATCH_BOOL, ex.hir_id) {
+            let mut v = PatVisitor {
+                typeck,
+                has_enum: false,
+            };
+            if v.visit_pat(arm2.pat).is_break() {
+                return;
+            }
+            if v.has_enum {
+                let cx = PatCtxt {
+                    tcx: cx.tcx,
+                    param_env: cx.param_env,
+                    typeck,
+                    arena: DroplessArena::default(),
+                };
+                let mut state = PatState::Other;
+                if !(state.add_pat(&cx, arm2.pat) || state.add_pat(&cx, arm1.pat)) {
+                    // Don't lint if the pattern contains an enum which doesn't have a wild match.
+                    return;
+                }
+            }
+
             report_single_pattern(cx, ex, arm1, expr, els);
         }
     }
@@ -121,100 +143,227 @@ fn report_single_pattern(cx: &LateContext<'_>, ex: &Expr<'_>, arm: &Arm<'_>, exp
     span_lint_and_sugg(cx, lint, expr.span, msg, "try", sugg, app);
 }
 
-/// Returns `true` if all of the types in the pattern are enums which we know
-/// won't be expanded in the future
-fn pat_in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'a>, pat: &Pat<'_>) -> bool {
-    let mut paths_and_types = Vec::new();
-    collect_pat_paths(&mut paths_and_types, cx, pat, ty);
-    paths_and_types.iter().all(|ty| in_candidate_enum(cx, *ty))
+struct PatVisitor<'tcx> {
+    typeck: &'tcx TypeckResults<'tcx>,
+    has_enum: bool,
+}
+impl<'tcx> Visitor<'tcx> for PatVisitor<'tcx> {
+    type Result = ControlFlow<()>;
+    fn visit_pat(&mut self, pat: &'tcx Pat<'_>) -> Self::Result {
+        if matches!(pat.kind, PatKind::Binding(..)) {
+            ControlFlow::Break(())
+        } else {
+            self.has_enum |= self.typeck.pat_ty(pat).ty_adt_def().map_or(false, AdtDef::is_enum);
+            walk_pat(self, pat)
+        }
+    }
 }
 
-/// Returns `true` if the given type is an enum we know won't be expanded in the future
-fn in_candidate_enum(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
-    // list of candidate `Enum`s we know will never get any more members
-    let candidates = [sym::Cow, sym::Option, sym::Result];
+/// The context needed to manipulate a `PatState`.
+struct PatCtxt<'tcx> {
+    tcx: TyCtxt<'tcx>,
+    param_env: ParamEnv<'tcx>,
+    typeck: &'tcx TypeckResults<'tcx>,
+    arena: DroplessArena,
+}
 
-    for candidate_ty in candidates {
-        if is_type_diagnostic_item(cx, ty, candidate_ty) {
-            return true;
+/// State for tracking whether a match can become non-exhaustive by adding a variant to a contained
+/// enum.
+///
+/// This treats certain std enums as if they will never be extended.
+enum PatState<'a> {
+    /// Either a wild match or an uninteresting type. Uninteresting types include:
+    /// * builtin types (e.g. `i32` or `!`)
+    /// * A struct/tuple/array containing only uninteresting types.
+    /// * A std enum containing only uninteresting types.
+    Wild,
+    /// A std enum we know won't be extended. Tracks the states of each variant separately.
+    ///
+    /// This is not used for `Option` since it uses the current pattern to track it's state.
+    StdEnum(&'a mut [PatState<'a>]),
+    /// Either the initial state for a pattern or a non-std enum. There is currently no need to
+    /// distinguish these cases.
+    ///
+    /// For non-std enums there's no need to track the state of sub-patterns as the state of just
+    /// this pattern on it's own is enough for linting. Consider two cases:
+    /// * This enum has no wild match. This case alone is enough to determine we can lint.
+    /// * This enum has a wild match and therefore all sub-patterns also have a wild match.
+    ///
+    /// In both cases the sub patterns are not needed to determine whether to lint.
+    Other,
+}
+impl<'a> PatState<'a> {
+    /// Adds a set of patterns as a product type to the current state. Returns whether or not the
+    /// current state is a wild match after the merge.
+    fn add_product_pat<'tcx>(
+        &mut self,
+        cx: &'a PatCtxt<'tcx>,
+        pats: impl IntoIterator<Item = &'tcx Pat<'tcx>>,
+    ) -> bool {
+        // Ideally this would actually keep track of the state separately for each pattern. Doing so would
+        // require implementing something similar to exhaustiveness checking which is a significant increase
+        // in complexity.
+        //
+        // For now treat this as a wild match only if all the sub-patterns are wild
+        let is_wild = pats.into_iter().all(|p| {
+            let mut state = Self::Other;
+            state.add_pat(cx, p)
+        });
+        if is_wild {
+            *self = Self::Wild;
         }
+        is_wild
     }
-    false
-}
 
-/// Collects types from the given pattern
-fn collect_pat_paths<'a>(acc: &mut Vec<Ty<'a>>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) {
-    match pat.kind {
-        PatKind::Tuple(inner, _) => inner.iter().for_each(|p| {
-            let p_ty = cx.typeck_results().pat_ty(p);
-            collect_pat_paths(acc, cx, p, p_ty);
-        }),
-        PatKind::TupleStruct(..) | PatKind::Binding(BindingMode::NONE, .., None) | PatKind::Path(_) => {
-            acc.push(ty);
-        },
-        _ => {},
+    /// Attempts to get the state for the enum variant, initializing the current state if necessary.
+    fn get_std_enum_variant<'tcx>(
+        &mut self,
+        cx: &'a PatCtxt<'tcx>,
+        adt: AdtDef<'tcx>,
+        path: &'tcx QPath<'_>,
+        hir_id: HirId,
+    ) -> Option<(&mut Self, &'tcx VariantDef)> {
+        let states = match self {
+            Self::Wild => return None,
+            Self::Other => {
+                *self = Self::StdEnum(cx.arena.alloc_from_iter((0..adt.variants().len()).map(|_| Self::Other)));
+                let Self::StdEnum(x) = self else {
+                    unreachable!();
+                };
+                x
+            },
+            Self::StdEnum(x) => x,
+        };
+        let i = match cx.typeck.qpath_res(path, hir_id) {
+            Res::Def(DefKind::Ctor(..), id) => adt.variant_index_with_ctor_id(id),
+            Res::Def(DefKind::Variant, id) => adt.variant_index_with_id(id),
+            _ => return None,
+        };
+        Some((&mut states[i.as_usize()], adt.variant(i)))
     }
-}
 
-/// Returns true if the given arm of pattern matching contains wildcard patterns.
-fn contains_only_wilds(pat: &Pat<'_>) -> bool {
-    match pat.kind {
-        PatKind::Wild => true,
-        PatKind::Tuple(inner, _) | PatKind::TupleStruct(_, inner, ..) => inner.iter().all(contains_only_wilds),
-        _ => false,
+    fn check_all_wild_enum(&mut self) -> bool {
+        if let Self::StdEnum(states) = self
+            && states.iter().all(|s| matches!(s, Self::Wild))
+        {
+            *self = Self::Wild;
+            true
+        } else {
+            false
+        }
     }
-}
 
-/// Returns true if the given patterns forms only exhaustive matches that don't contain enum
-/// patterns without a wildcard.
-fn form_exhaustive_matches<'a>(cx: &LateContext<'a>, ty: Ty<'a>, left: &Pat<'_>, right: &Pat<'_>) -> bool {
-    match (&left.kind, &right.kind) {
-        (PatKind::Wild, _) | (_, PatKind::Wild) => true,
-        (PatKind::Tuple(left_in, left_pos), PatKind::Tuple(right_in, right_pos)) => {
-            // We don't actually know the position and the presence of the `..` (dotdot) operator
-            // in the arms, so we need to evaluate the correct offsets here in order to iterate in
-            // both arms at the same time.
-            let left_pos = left_pos.as_opt_usize();
-            let right_pos = right_pos.as_opt_usize();
-            let len = max(
-                left_in.len() + usize::from(left_pos.is_some()),
-                right_in.len() + usize::from(right_pos.is_some()),
-            );
-            let mut left_pos = left_pos.unwrap_or(usize::MAX);
-            let mut right_pos = right_pos.unwrap_or(usize::MAX);
-            let mut left_dot_space = 0;
-            let mut right_dot_space = 0;
-            for i in 0..len {
-                let mut found_dotdot = false;
-                if i == left_pos {
-                    left_dot_space += 1;
-                    if left_dot_space < len - left_in.len() {
-                        left_pos += 1;
-                    }
-                    found_dotdot = true;
-                }
-                if i == right_pos {
-                    right_dot_space += 1;
-                    if right_dot_space < len - right_in.len() {
-                        right_pos += 1;
-                    }
-                    found_dotdot = true;
-                }
-                if found_dotdot {
-                    continue;
+    #[expect(clippy::similar_names)]
+    fn add_struct_pats<'tcx>(
+        &mut self,
+        cx: &'a PatCtxt<'tcx>,
+        pat: &'tcx Pat<'tcx>,
+        path: &'tcx QPath<'tcx>,
+        single_pat: Option<&'tcx Pat<'tcx>>,
+        pats: impl IntoIterator<Item = &'tcx Pat<'tcx>>,
+    ) -> bool {
+        let ty::Adt(adt, _) = *cx.typeck.pat_ty(pat).kind() else {
+            // Should never happen
+            *self = Self::Wild;
+            return true;
+        };
+        if adt.is_struct() {
+            return if let Some(pat) = single_pat
+                && adt.non_enum_variant().fields.len() == 1
+            {
+                self.add_pat(cx, pat)
+            } else {
+                self.add_product_pat(cx, pats)
+            };
+        }
+        match cx.tcx.get_diagnostic_name(adt.did()) {
+            Some(sym::Option) => {
+                if let Some(pat) = single_pat {
+                    self.add_pat(cx, pat)
+                } else {
+                    *self = Self::Wild;
+                    true
                 }
-                if !contains_only_wilds(&left_in[i - left_dot_space])
-                    && !contains_only_wilds(&right_in[i - right_dot_space])
+            },
+            Some(sym::Result | sym::Cow) => {
+                let Some((state, variant)) = self.get_std_enum_variant(cx, adt, path, pat.hir_id) else {
+                    return matches!(self, Self::Wild);
+                };
+                let is_wild = if let Some(pat) = single_pat
+                    && variant.fields.len() == 1
                 {
-                    return false;
-                }
-            }
-            true
-        },
-        (PatKind::TupleStruct(..), PatKind::Path(_)) => pat_in_candidate_enum(cx, ty, right),
-        (PatKind::TupleStruct(..), PatKind::TupleStruct(_, inner, _)) => {
-            pat_in_candidate_enum(cx, ty, right) && inner.iter().all(contains_only_wilds)
-        },
-        _ => false,
+                    state.add_pat(cx, pat)
+                } else {
+                    state.add_product_pat(cx, pats)
+                };
+                is_wild && self.check_all_wild_enum()
+            },
+            _ => matches!(self, Self::Wild),
+        }
+    }
+
+    /// Adds the pattern into the current state. Returns whether or not the current state is a wild
+    /// match after the merge.
+    #[expect(clippy::similar_names)]
+    fn add_pat<'tcx>(&mut self, cx: &'a PatCtxt<'tcx>, pat: &'tcx Pat<'_>) -> bool {
+        match pat.kind {
+            PatKind::Path(_)
+                if match *cx.typeck.pat_ty(pat).peel_refs().kind() {
+                    ty::Adt(adt, _) => adt.is_enum() || (adt.is_struct() && !adt.non_enum_variant().fields.is_empty()),
+                    ty::Tuple(tys) => !tys.is_empty(),
+                    ty::Array(_, len) => len.try_eval_target_usize(cx.tcx, cx.param_env) != Some(1),
+                    ty::Slice(..) => true,
+                    _ => false,
+                } =>
+            {
+                matches!(self, Self::Wild)
+            },
+
+            // Patterns for things which can only contain a single sub-pattern.
+            PatKind::Binding(_, _, _, Some(pat)) | PatKind::Ref(pat, _) | PatKind::Box(pat) | PatKind::Deref(pat) => {
+                self.add_pat(cx, pat)
+            },
+            PatKind::Tuple([sub_pat], pos)
+                if pos.as_opt_usize().is_none() || cx.typeck.pat_ty(pat).tuple_fields().len() == 1 =>
+            {
+                self.add_pat(cx, sub_pat)
+            },
+            PatKind::Slice([sub_pat], _, []) | PatKind::Slice([], _, [sub_pat])
+                if let ty::Array(_, len) = *cx.typeck.pat_ty(pat).kind()
+                    && len.try_eval_target_usize(cx.tcx, cx.param_env) == Some(1) =>
+            {
+                self.add_pat(cx, sub_pat)
+            },
+
+            PatKind::Or(pats) => pats.iter().any(|p| self.add_pat(cx, p)),
+            PatKind::Tuple(pats, _) => self.add_product_pat(cx, pats),
+            PatKind::Slice(head, _, tail) => self.add_product_pat(cx, head.iter().chain(tail)),
+
+            PatKind::TupleStruct(ref path, pats, _) => self.add_struct_pats(
+                cx,
+                pat,
+                path,
+                if let [pat] = pats { Some(pat) } else { None },
+                pats.iter(),
+            ),
+            PatKind::Struct(ref path, pats, _) => self.add_struct_pats(
+                cx,
+                pat,
+                path,
+                if let [pat] = pats { Some(pat.pat) } else { None },
+                pats.iter().map(|p| p.pat),
+            ),
+
+            PatKind::Wild
+            | PatKind::Binding(_, _, _, None)
+            | PatKind::Lit(_)
+            | PatKind::Range(..)
+            | PatKind::Path(_)
+            | PatKind::Never
+            | PatKind::Err(_) => {
+                *self = PatState::Wild;
+                true
+            },
+        }
     }
 }
diff --git a/tests/ui-toml/excessive_nesting/excessive_nesting.rs b/tests/ui-toml/excessive_nesting/excessive_nesting.rs
index 4375f324aca..858aab528a9 100644
--- a/tests/ui-toml/excessive_nesting/excessive_nesting.rs
+++ b/tests/ui-toml/excessive_nesting/excessive_nesting.rs
@@ -1,15 +1,19 @@
 //@aux-build:../../ui/auxiliary/proc_macros.rs
 #![rustfmt::skip]
 #![feature(custom_inner_attributes)]
-#![allow(unused)]
-#![allow(clippy::let_and_return)]
-#![allow(clippy::redundant_closure_call)]
-#![allow(clippy::no_effect)]
-#![allow(clippy::unnecessary_operation)]
-#![allow(clippy::never_loop)]
-#![allow(clippy::needless_if)]
 #![warn(clippy::excessive_nesting)]
-#![allow(clippy::collapsible_if, clippy::blocks_in_conditions)]
+#![allow(
+    unused,
+    clippy::let_and_return,
+    clippy::redundant_closure_call,
+    clippy::no_effect,
+    clippy::unnecessary_operation,
+    clippy::never_loop,
+    clippy::needless_if,
+    clippy::collapsible_if,
+    clippy::blocks_in_conditions,
+    clippy::single_match,
+)]
 
 #[macro_use]
 extern crate proc_macros;
diff --git a/tests/ui-toml/excessive_nesting/excessive_nesting.stderr b/tests/ui-toml/excessive_nesting/excessive_nesting.stderr
index dafcd442055..ccdaecdd481 100644
--- a/tests/ui-toml/excessive_nesting/excessive_nesting.stderr
+++ b/tests/ui-toml/excessive_nesting/excessive_nesting.stderr
@@ -1,5 +1,5 @@
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:21:25
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:25:25
    |
 LL |                 let w = { 3 };
    |                         ^^^^^
@@ -9,7 +9,7 @@ LL |                 let w = { 3 };
    = help: to override `-D warnings` add `#[allow(clippy::excessive_nesting)]`
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:67:17
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:71:17
    |
 LL | /                 impl C {
 LL | |                     pub fn c() {}
@@ -19,7 +19,7 @@ LL | |                 }
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:81:25
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:85:25
    |
 LL |                 let x = { 1 }; // not a warning, but cc is
    |                         ^^^^^
@@ -27,7 +27,7 @@ LL |                 let x = { 1 }; // not a warning, but cc is
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:98:17
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:102:17
    |
 LL | /                 pub mod e {
 LL | |                     pub mod f {}
@@ -37,7 +37,7 @@ LL | |                 } // not here
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:111:18
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:115:18
    |
 LL |     a_but_not({{{{{{{{0}}}}}}}});
    |                  ^^^^^^^^^^^
@@ -45,7 +45,7 @@ LL |     a_but_not({{{{{{{{0}}}}}}}});
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:112:12
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:116:12
    |
 LL |     a.a({{{{{{{{{0}}}}}}}}});
    |            ^^^^^^^^^^^^^
@@ -53,7 +53,7 @@ LL |     a.a({{{{{{{{{0}}}}}}}}});
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:113:12
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:117:12
    |
 LL |     (0, {{{{{{{1}}}}}}});
    |            ^^^^^^^^^
@@ -61,7 +61,7 @@ LL |     (0, {{{{{{{1}}}}}}});
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:118:25
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:122:25
    |
 LL |                   if true {
    |  _________________________^
@@ -74,7 +74,7 @@ LL | |                 }
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:130:29
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:134:29
    |
 LL |                   let z = (|| {
    |  _____________________________^
@@ -86,7 +86,7 @@ LL | |                 })();
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:149:13
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:153:13
    |
 LL |     y += {{{{{5}}}}};
    |             ^^^^^
@@ -94,7 +94,7 @@ LL |     y += {{{{{5}}}}};
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:150:20
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:154:20
    |
 LL |     let z = y + {{{{{{{{{5}}}}}}}}};
    |                    ^^^^^^^^^^^^^
@@ -102,7 +102,7 @@ LL |     let z = y + {{{{{{{{{5}}}}}}}}};
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:151:12
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:155:12
    |
 LL |     [0, {{{{{{{{{{0}}}}}}}}}}];
    |            ^^^^^^^^^^^^^^^
@@ -110,7 +110,7 @@ LL |     [0, {{{{{{{{{{0}}}}}}}}}}];
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:152:25
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:156:25
    |
 LL |     let mut xx = [0; {{{{{{{{100}}}}}}}}];
    |                         ^^^^^^^^^^^^^
@@ -118,7 +118,7 @@ LL |     let mut xx = [0; {{{{{{{{100}}}}}}}}];
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:153:11
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:157:11
    |
 LL |     xx[{{{{{{{{{{{{{{{{{{{{{{{{3}}}}}}}}}}}}}}}}}}}}}}}}];
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -126,7 +126,7 @@ LL |     xx[{{{{{{{{{{{{{{{{{{{{{{{{3}}}}}}}}}}}}}}}}}}}}}}}}];
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:154:13
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:158:13
    |
 LL |     &mut {{{{{{{{{{y}}}}}}}}}};
    |             ^^^^^^^^^^^^^^^
@@ -134,7 +134,7 @@ LL |     &mut {{{{{{{{{{y}}}}}}}}}};
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:156:17
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:160:17
    |
 LL |     for i in {{{{xx}}}} {{{{{{{{}}}}}}}}
    |                 ^^^^
@@ -142,7 +142,7 @@ LL |     for i in {{{{xx}}}} {{{{{{{{}}}}}}}}
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:156:28
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:160:28
    |
 LL |     for i in {{{{xx}}}} {{{{{{{{}}}}}}}}
    |                            ^^^^^^^^^^
@@ -150,7 +150,7 @@ LL |     for i in {{{{xx}}}} {{{{{{{{}}}}}}}}
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:158:28
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:162:28
    |
 LL |     while let Some(i) = {{{{{{Some(1)}}}}}} {{{{{{{}}}}}}}
    |                            ^^^^^^^^^^^^^
@@ -158,7 +158,7 @@ LL |     while let Some(i) = {{{{{{Some(1)}}}}}} {{{{{{{}}}}}}}
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:158:48
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:162:48
    |
 LL |     while let Some(i) = {{{{{{Some(1)}}}}}} {{{{{{{}}}}}}}
    |                                                ^^^^^^^^
@@ -166,7 +166,7 @@ LL |     while let Some(i) = {{{{{{Some(1)}}}}}} {{{{{{{}}}}}}}
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:160:14
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:164:14
    |
 LL |     while {{{{{{{{true}}}}}}}} {{{{{{{{{}}}}}}}}}
    |              ^^^^^^^^^^^^^^
@@ -174,7 +174,7 @@ LL |     while {{{{{{{{true}}}}}}}} {{{{{{{{{}}}}}}}}}
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:160:35
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:164:35
    |
 LL |     while {{{{{{{{true}}}}}}}} {{{{{{{{{}}}}}}}}}
    |                                   ^^^^^^^^^^^^
@@ -182,7 +182,7 @@ LL |     while {{{{{{{{true}}}}}}}} {{{{{{{{{}}}}}}}}}
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:162:23
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:166:23
    |
 LL |     let d = D { d: {{{{{{{{{{{{{{{{{{{{{{{3}}}}}}}}}}}}}}}}}}}}}}} };
    |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -190,7 +190,7 @@ LL |     let d = D { d: {{{{{{{{{{{{{{{{{{{{{{{3}}}}}}}}}}}}}}}}}}}}}}} };
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:164:8
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:168:8
    |
 LL |     {{{{1;}}}}..{{{{{{3}}}}}};
    |        ^^^^
@@ -198,7 +198,7 @@ LL |     {{{{1;}}}}..{{{{{{3}}}}}};
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:164:20
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:168:20
    |
 LL |     {{{{1;}}}}..{{{{{{3}}}}}};
    |                    ^^^^^^^
@@ -206,7 +206,7 @@ LL |     {{{{1;}}}}..{{{{{{3}}}}}};
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:165:8
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:169:8
    |
 LL |     {{{{1;}}}}..={{{{{{{{{{{{{{{{{{{{{{{{{{6}}}}}}}}}}}}}}}}}}}}}}}}}};
    |        ^^^^
@@ -214,7 +214,7 @@ LL |     {{{{1;}}}}..={{{{{{{{{{{{{{{{{{{{{{{{{{6}}}}}}}}}}}}}}}}}}}}}}}}}};
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:165:21
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:169:21
    |
 LL |     {{{{1;}}}}..={{{{{{{{{{{{{{{{{{{{{{{{{{6}}}}}}}}}}}}}}}}}}}}}}}}}};
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -222,7 +222,7 @@ LL |     {{{{1;}}}}..={{{{{{{{{{{{{{{{{{{{{{{{{{6}}}}}}}}}}}}}}}}}}}}}}}}}};
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:166:10
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:170:10
    |
 LL |     ..{{{{{{{5}}}}}}};
    |          ^^^^^^^^^
@@ -230,7 +230,7 @@ LL |     ..{{{{{{{5}}}}}}};
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:167:11
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:171:11
    |
 LL |     ..={{{{{3}}}}};
    |           ^^^^^
@@ -238,7 +238,7 @@ LL |     ..={{{{{3}}}}};
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:168:8
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:172:8
    |
 LL |     {{{{{1;}}}}}..;
    |        ^^^^^^
@@ -246,7 +246,7 @@ LL |     {{{{{1;}}}}}..;
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:170:20
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:174:20
    |
 LL |     loop { break {{{{1}}}} };
    |                    ^^^^^
@@ -254,7 +254,7 @@ LL |     loop { break {{{{1}}}} };
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:171:13
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:175:13
    |
 LL |     loop {{{{{{}}}}}}
    |             ^^^^^^
@@ -262,7 +262,7 @@ LL |     loop {{{{{{}}}}}}
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:173:14
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:177:14
    |
 LL |     match {{{{{{true}}}}}} {
    |              ^^^^^^^^^^
@@ -270,7 +270,7 @@ LL |     match {{{{{{true}}}}}} {
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:174:20
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:178:20
    |
 LL |         true => {{{{}}}},
    |                    ^^
@@ -278,7 +278,7 @@ LL |         true => {{{{}}}},
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:175:21
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:179:21
    |
 LL |         false => {{{{}}}},
    |                     ^^
@@ -286,7 +286,7 @@ LL |         false => {{{{}}}},
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:181:17
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:185:17
    |
 LL | /                 {
 LL | |                     println!("warning! :)");
@@ -296,7 +296,7 @@ LL | |                 }
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:190:28
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:194:28
    |
 LL |     async fn c() -> u32 {{{{{{{0}}}}}}}
    |                            ^^^^^^^^^
@@ -304,7 +304,7 @@ LL |     async fn c() -> u32 {{{{{{{0}}}}}}}
    = help: try refactoring your code to minimize nesting
 
 error: this block is too nested
-  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:196:8
+  --> tests/ui-toml/excessive_nesting/excessive_nesting.rs:200:8
    |
 LL |     {{{{b().await}}}};
    |        ^^^^^^^^^^^
diff --git a/tests/ui/crashes/ice-6254.rs b/tests/ui/crashes/ice-6254.rs
index 8af60890390..aaca32ab2d9 100644
--- a/tests/ui/crashes/ice-6254.rs
+++ b/tests/ui/crashes/ice-6254.rs
@@ -2,7 +2,8 @@
 // panicked at 'assertion failed: rows.iter().all(|r| r.len() == v.len())',
 // compiler/rustc_mir_build/src/thir/pattern/_match.rs:2030:5
 
-#[allow(clippy::derive_partial_eq_without_eq)]
+#![allow(clippy::derive_partial_eq_without_eq, clippy::single_match)]
+
 #[derive(PartialEq)]
 struct Foo(i32);
 const FOO_REF_REF: &&Foo = &&Foo(42);
diff --git a/tests/ui/patterns.fixed b/tests/ui/patterns.fixed
index 332cba97155..feaec33ac15 100644
--- a/tests/ui/patterns.fixed
+++ b/tests/ui/patterns.fixed
@@ -1,7 +1,7 @@
 //@aux-build:proc_macros.rs
 #![warn(clippy::all)]
 #![allow(unused)]
-#![allow(clippy::uninlined_format_args)]
+#![allow(clippy::uninlined_format_args, clippy::single_match)]
 
 #[macro_use]
 extern crate proc_macros;
diff --git a/tests/ui/patterns.rs b/tests/ui/patterns.rs
index 45d907688e3..53812c7deec 100644
--- a/tests/ui/patterns.rs
+++ b/tests/ui/patterns.rs
@@ -1,7 +1,7 @@
 //@aux-build:proc_macros.rs
 #![warn(clippy::all)]
 #![allow(unused)]
-#![allow(clippy::uninlined_format_args)]
+#![allow(clippy::uninlined_format_args, clippy::single_match)]
 
 #[macro_use]
 extern crate proc_macros;
diff --git a/tests/ui/single_match.fixed b/tests/ui/single_match.fixed
index acd70416d8b..5249c440889 100644
--- a/tests/ui/single_match.fixed
+++ b/tests/ui/single_match.fixed
@@ -253,3 +253,46 @@ mod issue8634 {
         }
     }
 }
+
+fn issue11365() {
+    enum Foo {
+        A,
+        B,
+        C,
+    }
+    use Foo::{A, B, C};
+
+    match Some(A) {
+        Some(A | B | C) => println!(),
+        None => {},
+    }
+
+    match Some(A) {
+        Some(A | B) => println!(),
+        Some { 0: C } | None => {},
+    }
+
+    match [A, A] {
+        [A, _] => println!(),
+        [_, A | B | C] => {},
+    }
+
+    match Ok::<_, u32>(Some(A)) {
+        Ok(Some(A)) => println!(),
+        Err(_) | Ok(None | Some(B | C)) => {},
+    }
+
+    if let Ok(Some(A)) = Ok::<_, u32>(Some(A)) { println!() }
+
+    match &Some(A) {
+        Some(A | B | C) => println!(),
+        None => {},
+    }
+
+    match &Some(A) {
+        &Some(A | B | C) => println!(),
+        None => {},
+    }
+
+    if let Some(A | B) = &Some(A) { println!() }
+}
diff --git a/tests/ui/single_match.rs b/tests/ui/single_match.rs
index bde78199810..882098a56e7 100644
--- a/tests/ui/single_match.rs
+++ b/tests/ui/single_match.rs
@@ -311,3 +311,52 @@ mod issue8634 {
         }
     }
 }
+
+fn issue11365() {
+    enum Foo {
+        A,
+        B,
+        C,
+    }
+    use Foo::{A, B, C};
+
+    match Some(A) {
+        Some(A | B | C) => println!(),
+        None => {},
+    }
+
+    match Some(A) {
+        Some(A | B) => println!(),
+        Some { 0: C } | None => {},
+    }
+
+    match [A, A] {
+        [A, _] => println!(),
+        [_, A | B | C] => {},
+    }
+
+    match Ok::<_, u32>(Some(A)) {
+        Ok(Some(A)) => println!(),
+        Err(_) | Ok(None | Some(B | C)) => {},
+    }
+
+    match Ok::<_, u32>(Some(A)) {
+        Ok(Some(A)) => println!(),
+        Err(_) | Ok(None | Some(_)) => {},
+    }
+
+    match &Some(A) {
+        Some(A | B | C) => println!(),
+        None => {},
+    }
+
+    match &Some(A) {
+        &Some(A | B | C) => println!(),
+        None => {},
+    }
+
+    match &Some(A) {
+        Some(A | B) => println!(),
+        None | Some(_) => {},
+    }
+}
diff --git a/tests/ui/single_match.stderr b/tests/ui/single_match.stderr
index a249c120ee4..ceb2a193bf7 100644
--- a/tests/ui/single_match.stderr
+++ b/tests/ui/single_match.stderr
@@ -198,5 +198,23 @@ LL +         }
 LL +     }
    |
 
-error: aborting due to 18 previous errors
+error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
+  --> tests/ui/single_match.rs:343:5
+   |
+LL | /     match Ok::<_, u32>(Some(A)) {
+LL | |         Ok(Some(A)) => println!(),
+LL | |         Err(_) | Ok(None | Some(_)) => {},
+LL | |     }
+   | |_____^ help: try: `if let Ok(Some(A)) = Ok::<_, u32>(Some(A)) { println!() }`
+
+error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
+  --> tests/ui/single_match.rs:358:5
+   |
+LL | /     match &Some(A) {
+LL | |         Some(A | B) => println!(),
+LL | |         None | Some(_) => {},
+LL | |     }
+   | |_____^ help: try: `if let Some(A | B) = &Some(A) { println!() }`
+
+error: aborting due to 20 previous errors
 
diff --git a/tests/ui/unneeded_field_pattern.rs b/tests/ui/unneeded_field_pattern.rs
index 0dc21f4ce94..1d42f81711b 100644
--- a/tests/ui/unneeded_field_pattern.rs
+++ b/tests/ui/unneeded_field_pattern.rs
@@ -1,6 +1,6 @@
 //@aux-build:proc_macros.rs
 #![warn(clippy::unneeded_field_pattern)]
-#![allow(dead_code, unused)]
+#![allow(dead_code, unused, clippy::single_match)]
 
 #[macro_use]
 extern crate proc_macros;