about summary refs log tree commit diff
diff options
context:
space:
mode:
authorCentri3 <114838443+Centri3@users.noreply.github.com>2023-06-14 13:51:31 -0500
committerCatherine Flores <catherine.3.flores@gmail.com>2023-07-22 06:28:05 -0500
commit51b57723d18aec1b9dd26bbb1482d1f29f6d18cf (patch)
tree681b5dbc8f36ce094501eafb0debbc5b0acb06c5
parenta44dcf80ef522d5fc706d65a5d6a849f49fd623e (diff)
downloadrust-51b57723d18aec1b9dd26bbb1482d1f29f6d18cf.tar.gz
rust-51b57723d18aec1b9dd26bbb1482d1f29f6d18cf.zip
new lint `redundant_guards`
-rw-r--r--CHANGELOG.md1
-rw-r--r--clippy_lints/src/declared_lints.rs1
-rw-r--r--clippy_lints/src/loops/utils.rs4
-rw-r--r--clippy_lints/src/matches/mod.rs33
-rw-r--r--clippy_lints/src/matches/redundant_guards.rs190
-rw-r--r--tests/ui/match_expr_like_matches_macro.fixed3
-rw-r--r--tests/ui/match_expr_like_matches_macro.rs3
-rw-r--r--tests/ui/match_expr_like_matches_macro.stderr28
-rw-r--r--tests/ui/redundant_guards.fixed133
-rw-r--r--tests/ui/redundant_guards.rs133
-rw-r--r--tests/ui/redundant_guards.stderr98
-rw-r--r--tests/ui/shadow.rs7
-rw-r--r--tests/ui/shadow.stderr92
-rw-r--r--tests/ui/single_match.fixed1
-rw-r--r--tests/ui/single_match.rs1
-rw-r--r--tests/ui/single_match.stderr36
16 files changed, 681 insertions, 83 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2b53f1df7c0..9118c0e634f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5190,6 +5190,7 @@ Released 2018-09-13
 [`redundant_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_else
 [`redundant_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_feature_names
 [`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names
+[`redundant_guards`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_guards
 [`redundant_locals`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_locals
 [`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern
 [`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching
diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs
index e92d9fa7f63..189ca5b052e 100644
--- a/clippy_lints/src/declared_lints.rs
+++ b/clippy_lints/src/declared_lints.rs
@@ -308,6 +308,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
     crate::matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS_INFO,
     crate::matches::MATCH_WILD_ERR_ARM_INFO,
     crate::matches::NEEDLESS_MATCH_INFO,
+    crate::matches::REDUNDANT_GUARDS_INFO,
     crate::matches::REDUNDANT_PATTERN_MATCHING_INFO,
     crate::matches::REST_PAT_IN_FULLY_BOUND_STRUCTS_INFO,
     crate::matches::SIGNIFICANT_DROP_IN_SCRUTINEE_INFO,
diff --git a/clippy_lints/src/loops/utils.rs b/clippy_lints/src/loops/utils.rs
index 28ee24309cc..6edca2d55f6 100644
--- a/clippy_lints/src/loops/utils.rs
+++ b/clippy_lints/src/loops/utils.rs
@@ -76,7 +76,7 @@ impl<'a, 'tcx> Visitor<'tcx> for IncrementVisitor<'a, 'tcx> {
                     ExprKind::Assign(lhs, _, _) if lhs.hir_id == expr.hir_id => {
                         *state = IncrementVisitorVarState::DontWarn;
                     },
-                    ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => {
+                    ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) => {
                         *state = IncrementVisitorVarState::DontWarn;
                     },
                     _ => (),
@@ -226,7 +226,7 @@ impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> {
                             InitializeVisitorState::DontWarn
                         }
                     },
-                    ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => {
+                    ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) => {
                         self.state = InitializeVisitorState::DontWarn;
                     },
                     _ => (),
diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs
index d1061171e4d..6d16d188754 100644
--- a/clippy_lints/src/matches/mod.rs
+++ b/clippy_lints/src/matches/mod.rs
@@ -16,6 +16,7 @@ mod match_wild_enum;
 mod match_wild_err_arm;
 mod needless_match;
 mod overlapping_arms;
+mod redundant_guards;
 mod redundant_pattern_match;
 mod rest_pat_in_fully_bound_struct;
 mod significant_drop_in_scrutinee;
@@ -936,6 +937,36 @@ declare_clippy_lint! {
     "reimplementation of `filter`"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for unnecessary guards in match expressions.
+    ///
+    /// ### Why is this bad?
+    /// It's more complex and much less readable. Making it part of the pattern can improve
+    /// exhaustiveness checking as well.
+    ///
+    /// ### Example
+    /// ```rust,ignore
+    /// match x {
+    ///     Some(x) if matches!(x, Some(1)) => ..,
+    ///     Some(x) if x == Some(2) => ..,
+    ///     _ => todo!(),
+    /// }
+    /// ```
+    /// Use instead:
+    /// ```rust,ignore
+    /// match x {
+    ///     Some(Some(1)) => ..,
+    ///     Some(Some(2)) => ..,
+    ///     _ => todo!(),
+    /// }
+    /// ```
+    #[clippy::version = "1.72.0"]
+    pub REDUNDANT_GUARDS,
+    complexity,
+    "checks for unnecessary guards in match expressions"
+}
+
 #[derive(Default)]
 pub struct Matches {
     msrv: Msrv,
@@ -978,6 +1009,7 @@ impl_lint_pass!(Matches => [
     TRY_ERR,
     MANUAL_MAP,
     MANUAL_FILTER,
+    REDUNDANT_GUARDS,
 ]);
 
 impl<'tcx> LateLintPass<'tcx> for Matches {
@@ -1025,6 +1057,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
                     needless_match::check_match(cx, ex, arms, expr);
                     match_on_vec_items::check(cx, ex);
                     match_str_case_mismatch::check(cx, ex, arms);
+                    redundant_guards::check(cx, arms);
 
                     if !in_constant(cx, expr.hir_id) {
                         manual_unwrap_or::check(cx, expr, ex, arms);
diff --git a/clippy_lints/src/matches/redundant_guards.rs b/clippy_lints/src/matches/redundant_guards.rs
new file mode 100644
index 00000000000..6383326aa38
--- /dev/null
+++ b/clippy_lints/src/matches/redundant_guards.rs
@@ -0,0 +1,190 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::path_to_local;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::visitors::{for_each_expr, is_local_used};
+use rustc_errors::Applicability;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, Guard, MatchSource, Node, Pat, PatKind};
+use rustc_lint::LateContext;
+use rustc_span::Span;
+use std::ops::ControlFlow;
+
+use super::REDUNDANT_GUARDS;
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>]) {
+    for outer_arm in arms {
+        let Some(guard) = outer_arm.guard else {
+            continue;
+        };
+
+        // `Some(x) if matches!(x, y)`
+        if let Guard::If(if_expr) = guard
+            && let ExprKind::Match(
+                scrutinee,
+                [
+                    arm,
+                    Arm {
+                        pat: Pat {
+                            kind: PatKind::Wild,
+                            ..
+                        },
+                        ..
+                    },
+                ],
+                MatchSource::Normal,
+            ) = if_expr.kind
+        {
+            emit_redundant_guards(
+                cx,
+                outer_arm,
+                if_expr.span,
+                scrutinee,
+                arm.pat.span,
+                arm.guard,
+            );
+        }
+        // `Some(x) if let Some(2) = x`
+        else if let Guard::IfLet(let_expr) = guard {
+            emit_redundant_guards(
+                cx,
+                outer_arm,
+                let_expr.span,
+                let_expr.init,
+                let_expr.pat.span,
+                None,
+            );
+        }
+        // `Some(x) if x == Some(2)`
+        else if let Guard::If(if_expr) = guard
+            && let ExprKind::Binary(bin_op, local, pat) = if_expr.kind
+            && matches!(bin_op.node, BinOpKind::Eq)
+            && expr_can_be_pat(cx, pat)
+            // Ensure they have the same type. If they don't, we'd need deref coercion which isn't
+            // possible (currently) in a pattern. In some cases, you can use something like
+            // `as_deref` or similar but in general, we shouldn't lint this as it'd create an
+            // extraordinary amount of FPs.
+            //
+            // This isn't necessary in the other two checks, as they must be a pattern already.
+            && cx.typeck_results().expr_ty(local) == cx.typeck_results().expr_ty(pat)
+        {
+            emit_redundant_guards(
+                cx,
+                outer_arm,
+                if_expr.span,
+                local,
+                pat.span,
+                None,
+            );
+        }
+    }
+}
+
+fn get_pat_binding<'tcx>(cx: &LateContext<'tcx>, guard_expr: &Expr<'_>, outer_arm: &Arm<'tcx>) -> Option<(Span, bool)> {
+    if let Some(local) = path_to_local(guard_expr) && !is_local_used(cx, outer_arm.body, local) {
+        let mut span = None;
+        let mut multiple_bindings = false;
+        // `each_binding` gives the `HirId` of the `Pat` itself, not the binding
+        outer_arm.pat.walk(|pat| {
+            if let PatKind::Binding(_, hir_id, _, _) = pat.kind
+                && hir_id == local
+                && span.replace(pat.span).is_some()
+            {
+                multiple_bindings = true;
+                return false;
+            }
+
+            true
+        });
+
+        // Ignore bindings from or patterns, like `First(x) | Second(x, _) | Third(x, _, _)`
+        if !multiple_bindings {
+            return span.map(|span| {
+                (
+                    span,
+                    !matches!(cx.tcx.hir().get_parent(local), Node::PatField(_)),
+                )
+            });
+        }
+    }
+
+    None
+}
+
+fn emit_redundant_guards<'tcx>(
+    cx: &LateContext<'tcx>,
+    outer_arm: &Arm<'tcx>,
+    guard_span: Span,
+    local: &Expr<'_>,
+    pat_span: Span,
+    inner_guard: Option<Guard<'_>>,
+) {
+    let mut app = Applicability::MaybeIncorrect;
+    let Some((pat_binding, can_use_shorthand)) = get_pat_binding(cx, local, outer_arm) else {
+        return;
+    };
+
+    span_lint_and_then(
+        cx,
+        REDUNDANT_GUARDS,
+        guard_span.source_callsite(),
+        "redundant guard",
+        |diag| {
+            let binding_replacement = snippet_with_applicability(cx, pat_span, "<binding_repl>", &mut app);
+            diag.multipart_suggestion_verbose(
+                "try",
+                vec![
+                    if can_use_shorthand {
+                        (pat_binding, binding_replacement.into_owned())
+                    } else {
+                        (pat_binding.shrink_to_hi(), format!(": {binding_replacement}"))
+                    },
+                    (
+                        guard_span.source_callsite().with_lo(outer_arm.pat.span.hi()),
+                        inner_guard.map_or_else(String::new, |guard| {
+                            let (prefix, span) = match guard {
+                                Guard::If(e) => ("if", e.span),
+                                Guard::IfLet(l) => ("if let", l.span),
+                            };
+
+                            format!(
+                                " {prefix} {}",
+                                snippet_with_applicability(cx, span, "<guard>", &mut app),
+                            )
+                        }),
+                    ),
+                ],
+                app,
+            );
+        },
+    );
+}
+
+/// Checks if the given `Expr` can also be represented as a `Pat`.
+fn expr_can_be_pat(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+    for_each_expr(expr, |expr| {
+        if match expr.kind {
+            ExprKind::ConstBlock(..) => cx.tcx.features().inline_const_pat,
+            ExprKind::Call(c, ..) if let ExprKind::Path(qpath) = c.kind => {
+                // Allow ctors
+                matches!(cx.qpath_res(&qpath, c.hir_id), Res::Def(DefKind::Ctor(..), ..))
+            },
+            ExprKind::Path(qpath) => {
+                matches!(
+                    cx.qpath_res(&qpath, expr.hir_id),
+                    Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Ctor(..), ..),
+                )
+            },
+            ExprKind::AddrOf(..)
+            | ExprKind::Array(..)
+            | ExprKind::Tup(..)
+            | ExprKind::Struct(..)
+            | ExprKind::Lit(..) => true,
+            _ => false,
+        } {
+            return ControlFlow::Continue(());
+        }
+
+        ControlFlow::Break(())
+    })
+    .is_none()
+}
diff --git a/tests/ui/match_expr_like_matches_macro.fixed b/tests/ui/match_expr_like_matches_macro.fixed
index 60f59066173..f19149cf9de 100644
--- a/tests/ui/match_expr_like_matches_macro.fixed
+++ b/tests/ui/match_expr_like_matches_macro.fixed
@@ -5,7 +5,8 @@
     unreachable_patterns,
     dead_code,
     clippy::equatable_if_let,
-    clippy::needless_borrowed_reference
+    clippy::needless_borrowed_reference,
+    clippy::redundant_guards
 )]
 
 fn main() {
diff --git a/tests/ui/match_expr_like_matches_macro.rs b/tests/ui/match_expr_like_matches_macro.rs
index afdf1069f5e..8f4e58981ea 100644
--- a/tests/ui/match_expr_like_matches_macro.rs
+++ b/tests/ui/match_expr_like_matches_macro.rs
@@ -5,7 +5,8 @@
     unreachable_patterns,
     dead_code,
     clippy::equatable_if_let,
-    clippy::needless_borrowed_reference
+    clippy::needless_borrowed_reference,
+    clippy::redundant_guards
 )]
 
 fn main() {
diff --git a/tests/ui/match_expr_like_matches_macro.stderr b/tests/ui/match_expr_like_matches_macro.stderr
index c8c1e5da05f..b57b26284ff 100644
--- a/tests/ui/match_expr_like_matches_macro.stderr
+++ b/tests/ui/match_expr_like_matches_macro.stderr
@@ -1,5 +1,5 @@
 error: match expression looks like `matches!` macro
-  --> $DIR/match_expr_like_matches_macro.rs:15:14
+  --> $DIR/match_expr_like_matches_macro.rs:16:14
    |
 LL |       let _y = match x {
    |  ______________^
@@ -11,7 +11,7 @@ LL | |     };
    = note: `-D clippy::match-like-matches-macro` implied by `-D warnings`
 
 error: redundant pattern matching, consider using `is_some()`
-  --> $DIR/match_expr_like_matches_macro.rs:21:14
+  --> $DIR/match_expr_like_matches_macro.rs:22:14
    |
 LL |       let _w = match x {
    |  ______________^
@@ -23,7 +23,7 @@ LL | |     };
    = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings`
 
 error: redundant pattern matching, consider using `is_none()`
-  --> $DIR/match_expr_like_matches_macro.rs:27:14
+  --> $DIR/match_expr_like_matches_macro.rs:28:14
    |
 LL |       let _z = match x {
    |  ______________^
@@ -33,7 +33,7 @@ LL | |     };
    | |_____^ help: try: `x.is_none()`
 
 error: match expression looks like `matches!` macro
-  --> $DIR/match_expr_like_matches_macro.rs:33:15
+  --> $DIR/match_expr_like_matches_macro.rs:34:15
    |
 LL |       let _zz = match x {
    |  _______________^
@@ -43,13 +43,13 @@ LL | |     };
    | |_____^ help: try: `!matches!(x, Some(r) if r == 0)`
 
 error: if let .. else expression looks like `matches!` macro
-  --> $DIR/match_expr_like_matches_macro.rs:39:16
+  --> $DIR/match_expr_like_matches_macro.rs:40:16
    |
 LL |     let _zzz = if let Some(5) = x { true } else { false };
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(x, Some(5))`
 
 error: match expression looks like `matches!` macro
-  --> $DIR/match_expr_like_matches_macro.rs:63:20
+  --> $DIR/match_expr_like_matches_macro.rs:64:20
    |
 LL |           let _ans = match x {
    |  ____________________^
@@ -60,7 +60,7 @@ LL | |         };
    | |_________^ help: try: `matches!(x, E::A(_) | E::B(_))`
 
 error: match expression looks like `matches!` macro
-  --> $DIR/match_expr_like_matches_macro.rs:73:20
+  --> $DIR/match_expr_like_matches_macro.rs:74:20
    |
 LL |           let _ans = match x {
    |  ____________________^
@@ -73,7 +73,7 @@ LL | |         };
    | |_________^ help: try: `matches!(x, E::A(_) | E::B(_))`
 
 error: match expression looks like `matches!` macro
-  --> $DIR/match_expr_like_matches_macro.rs:83:20
+  --> $DIR/match_expr_like_matches_macro.rs:84:20
    |
 LL |           let _ans = match x {
    |  ____________________^
@@ -84,7 +84,7 @@ LL | |         };
    | |_________^ help: try: `!matches!(x, E::B(_) | E::C)`
 
 error: match expression looks like `matches!` macro
-  --> $DIR/match_expr_like_matches_macro.rs:143:18
+  --> $DIR/match_expr_like_matches_macro.rs:144:18
    |
 LL |           let _z = match &z {
    |  __________________^
@@ -94,7 +94,7 @@ LL | |         };
    | |_________^ help: try: `matches!(z, Some(3))`
 
 error: match expression looks like `matches!` macro
-  --> $DIR/match_expr_like_matches_macro.rs:152:18
+  --> $DIR/match_expr_like_matches_macro.rs:153:18
    |
 LL |           let _z = match &z {
    |  __________________^
@@ -104,7 +104,7 @@ LL | |         };
    | |_________^ help: try: `matches!(&z, Some(3))`
 
 error: match expression looks like `matches!` macro
-  --> $DIR/match_expr_like_matches_macro.rs:169:21
+  --> $DIR/match_expr_like_matches_macro.rs:170:21
    |
 LL |               let _ = match &z {
    |  _____________________^
@@ -114,7 +114,7 @@ LL | |             };
    | |_____________^ help: try: `matches!(&z, AnEnum::X)`
 
 error: match expression looks like `matches!` macro
-  --> $DIR/match_expr_like_matches_macro.rs:183:20
+  --> $DIR/match_expr_like_matches_macro.rs:184:20
    |
 LL |           let _res = match &val {
    |  ____________________^
@@ -124,7 +124,7 @@ LL | |         };
    | |_________^ help: try: `matches!(&val, &Some(ref _a))`
 
 error: match expression looks like `matches!` macro
-  --> $DIR/match_expr_like_matches_macro.rs:195:20
+  --> $DIR/match_expr_like_matches_macro.rs:196:20
    |
 LL |           let _res = match &val {
    |  ____________________^
@@ -134,7 +134,7 @@ LL | |         };
    | |_________^ help: try: `matches!(&val, &Some(ref _a))`
 
 error: match expression looks like `matches!` macro
-  --> $DIR/match_expr_like_matches_macro.rs:253:14
+  --> $DIR/match_expr_like_matches_macro.rs:254:14
    |
 LL |       let _y = match Some(5) {
    |  ______________^
diff --git a/tests/ui/redundant_guards.fixed b/tests/ui/redundant_guards.fixed
new file mode 100644
index 00000000000..77ac7666864
--- /dev/null
+++ b/tests/ui/redundant_guards.fixed
@@ -0,0 +1,133 @@
+//@run-rustfix
+//@aux-build:proc_macros.rs:proc-macro
+#![feature(if_let_guard)]
+#![allow(clippy::no_effect, unused)]
+#![warn(clippy::redundant_guards)]
+
+#[macro_use]
+extern crate proc_macros;
+
+struct A(u32);
+
+struct B {
+    e: Option<A>,
+}
+
+struct C(u32, u32);
+
+fn main() {
+    let c = C(1, 2);
+    match c {
+        C(x, 1) => ..,
+        _ => todo!(),
+    };
+
+    let x = Some(Some(1));
+    match x {
+        Some(Some(1)) if true => ..,
+        Some(Some(1)) => {
+            println!("a");
+            ..
+        },
+        Some(Some(1)) => ..,
+        Some(Some(2)) => ..,
+        // Don't lint, since x is used in the body
+        Some(x) if let Some(1) = x => {
+            x;
+            ..
+        }
+        _ => todo!(),
+    };
+    let y = 1;
+    match x {
+        // Don't inline these, since y is not from the pat
+        Some(x) if matches!(y, 1 if true) => ..,
+        Some(x) if let 1 = y => ..,
+        Some(x) if y == 2 => ..,
+        _ => todo!(),
+    };
+    let a = A(1);
+    match a {
+        _ if a.0 == 1 => {},
+        _ => todo!(),
+    }
+    let b = B { e: Some(A(0)) };
+    match b {
+        B { e: Some(A(2)) } => ..,
+        _ => todo!(),
+    };
+    // Do not lint, since we cannot represent this as a pattern (at least, without a conversion)
+    let v = Some(vec![1u8, 2, 3]);
+    match v {
+        Some(x) if x == [1] => {},
+        _ => {},
+    }
+
+    external! {
+        let x = Some(Some(1));
+        match x {
+            Some(x) if let Some(1) = x => ..,
+            _ => todo!(),
+        };
+    }
+    with_span! {
+        span
+        let x = Some(Some(1));
+        match x {
+            Some(x) if let Some(1) = x => ..,
+            _ => todo!(),
+        };
+    }
+}
+
+enum E {
+    A(&'static str),
+    B(&'static str),
+    C(&'static str),
+}
+
+fn i() {
+    match E::A("") {
+        // Do not lint
+        E::A(x) | E::B(x) | E::C(x) if x == "from an or pattern" => {},
+        E::A("not from an or pattern") => {},
+        _ => {},
+    };
+}
+
+fn h(v: Option<u32>) {
+    match v {
+        Some(0) => ..,
+        _ => ..,
+    };
+}
+
+// Do not lint
+
+fn f(s: Option<std::ffi::OsString>) {
+    match s {
+        Some(x) if x == "a" => {},
+        _ => {},
+    }
+}
+
+struct S {
+    a: usize,
+}
+
+impl PartialEq for S {
+    fn eq(&self, _: &Self) -> bool {
+        true
+    }
+}
+
+impl Eq for S {}
+
+static CONST_S: S = S { a: 1 };
+
+fn g(opt_s: Option<S>) {
+    match opt_s {
+        Some(x) if x == CONST_S => {},
+        _ => {},
+    }
+}
diff --git a/tests/ui/redundant_guards.rs b/tests/ui/redundant_guards.rs
new file mode 100644
index 00000000000..b072e4ea14d
--- /dev/null
+++ b/tests/ui/redundant_guards.rs
@@ -0,0 +1,133 @@
+//@run-rustfix
+//@aux-build:proc_macros.rs:proc-macro
+#![feature(if_let_guard)]
+#![allow(clippy::no_effect, unused)]
+#![warn(clippy::redundant_guards)]
+
+#[macro_use]
+extern crate proc_macros;
+
+struct A(u32);
+
+struct B {
+    e: Option<A>,
+}
+
+struct C(u32, u32);
+
+fn main() {
+    let c = C(1, 2);
+    match c {
+        C(x, y) if let 1 = y => ..,
+        _ => todo!(),
+    };
+
+    let x = Some(Some(1));
+    match x {
+        Some(x) if matches!(x, Some(1) if true) => ..,
+        Some(x) if matches!(x, Some(1)) => {
+            println!("a");
+            ..
+        },
+        Some(x) if let Some(1) = x => ..,
+        Some(x) if x == Some(2) => ..,
+        // Don't lint, since x is used in the body
+        Some(x) if let Some(1) = x => {
+            x;
+            ..
+        }
+        _ => todo!(),
+    };
+    let y = 1;
+    match x {
+        // Don't inline these, since y is not from the pat
+        Some(x) if matches!(y, 1 if true) => ..,
+        Some(x) if let 1 = y => ..,
+        Some(x) if y == 2 => ..,
+        _ => todo!(),
+    };
+    let a = A(1);
+    match a {
+        _ if a.0 == 1 => {},
+        _ => todo!(),
+    }
+    let b = B { e: Some(A(0)) };
+    match b {
+        B { e } if matches!(e, Some(A(2))) => ..,
+        _ => todo!(),
+    };
+    // Do not lint, since we cannot represent this as a pattern (at least, without a conversion)
+    let v = Some(vec![1u8, 2, 3]);
+    match v {
+        Some(x) if x == [1] => {},
+        _ => {},
+    }
+
+    external! {
+        let x = Some(Some(1));
+        match x {
+            Some(x) if let Some(1) = x => ..,
+            _ => todo!(),
+        };
+    }
+    with_span! {
+        span
+        let x = Some(Some(1));
+        match x {
+            Some(x) if let Some(1) = x => ..,
+            _ => todo!(),
+        };
+    }
+}
+
+enum E {
+    A(&'static str),
+    B(&'static str),
+    C(&'static str),
+}
+
+fn i() {
+    match E::A("") {
+        // Do not lint
+        E::A(x) | E::B(x) | E::C(x) if x == "from an or pattern" => {},
+        E::A(y) if y == "not from an or pattern" => {},
+        _ => {},
+    };
+}
+
+fn h(v: Option<u32>) {
+    match v {
+        x if matches!(x, Some(0)) => ..,
+        _ => ..,
+    };
+}
+
+// Do not lint
+
+fn f(s: Option<std::ffi::OsString>) {
+    match s {
+        Some(x) if x == "a" => {},
+        _ => {},
+    }
+}
+
+struct S {
+    a: usize,
+}
+
+impl PartialEq for S {
+    fn eq(&self, _: &Self) -> bool {
+        true
+    }
+}
+
+impl Eq for S {}
+
+static CONST_S: S = S { a: 1 };
+
+fn g(opt_s: Option<S>) {
+    match opt_s {
+        Some(x) if x == CONST_S => {},
+        _ => {},
+    }
+}
diff --git a/tests/ui/redundant_guards.stderr b/tests/ui/redundant_guards.stderr
new file mode 100644
index 00000000000..c2a92071d1d
--- /dev/null
+++ b/tests/ui/redundant_guards.stderr
@@ -0,0 +1,98 @@
+error: redundant guard
+  --> $DIR/redundant_guards.rs:21:20
+   |
+LL |         C(x, y) if let 1 = y => ..,
+   |                    ^^^^^^^^^
+   |
+   = note: `-D clippy::redundant-guards` implied by `-D warnings`
+help: try
+   |
+LL -         C(x, y) if let 1 = y => ..,
+LL +         C(x, 1) => ..,
+   |
+
+error: redundant guard
+  --> $DIR/redundant_guards.rs:27:20
+   |
+LL |         Some(x) if matches!(x, Some(1) if true) => ..,
+   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: try
+   |
+LL |         Some(Some(1)) if true => ..,
+   |              ~~~~~~~  ~~~~~~~
+
+error: redundant guard
+  --> $DIR/redundant_guards.rs:28:20
+   |
+LL |         Some(x) if matches!(x, Some(1)) => {
+   |                    ^^^^^^^^^^^^^^^^^^^^
+   |
+help: try
+   |
+LL -         Some(x) if matches!(x, Some(1)) => {
+LL +         Some(Some(1)) => {
+   |
+
+error: redundant guard
+  --> $DIR/redundant_guards.rs:32:20
+   |
+LL |         Some(x) if let Some(1) = x => ..,
+   |                    ^^^^^^^^^^^^^^^
+   |
+help: try
+   |
+LL -         Some(x) if let Some(1) = x => ..,
+LL +         Some(Some(1)) => ..,
+   |
+
+error: redundant guard
+  --> $DIR/redundant_guards.rs:33:20
+   |
+LL |         Some(x) if x == Some(2) => ..,
+   |                    ^^^^^^^^^^^^
+   |
+help: try
+   |
+LL -         Some(x) if x == Some(2) => ..,
+LL +         Some(Some(2)) => ..,
+   |
+
+error: redundant guard
+  --> $DIR/redundant_guards.rs:56:20
+   |
+LL |         B { e } if matches!(e, Some(A(2))) => ..,
+   |                    ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: try
+   |
+LL -         B { e } if matches!(e, Some(A(2))) => ..,
+LL +         B { e: Some(A(2)) } => ..,
+   |
+
+error: redundant guard
+  --> $DIR/redundant_guards.rs:93:20
+   |
+LL |         E::A(y) if y == "not from an or pattern" => {},
+   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: try
+   |
+LL -         E::A(y) if y == "not from an or pattern" => {},
+LL +         E::A("not from an or pattern") => {},
+   |
+
+error: redundant guard
+  --> $DIR/redundant_guards.rs:100:14
+   |
+LL |         x if matches!(x, Some(0)) => ..,
+   |              ^^^^^^^^^^^^^^^^^^^^
+   |
+help: try
+   |
+LL -         x if matches!(x, Some(0)) => ..,
+LL +         Some(0) => ..,
+   |
+
+error: aborting due to 8 previous errors
+
diff --git a/tests/ui/shadow.rs b/tests/ui/shadow.rs
index 4d22b7d2ad0..1b40a43d019 100644
--- a/tests/ui/shadow.rs
+++ b/tests/ui/shadow.rs
@@ -1,7 +1,12 @@
 //@aux-build:proc_macro_derive.rs:proc-macro
 
 #![warn(clippy::shadow_same, clippy::shadow_reuse, clippy::shadow_unrelated)]
-#![allow(clippy::let_unit_value, clippy::needless_if, clippy::redundant_locals)]
+#![allow(
+    clippy::let_unit_value,
+    clippy::needless_if,
+    clippy::redundant_guards,
+    clippy::redundant_locals
+)]
 
 extern crate proc_macro_derive;
 
diff --git a/tests/ui/shadow.stderr b/tests/ui/shadow.stderr
index 8321f6df224..88b02f53be1 100644
--- a/tests/ui/shadow.stderr
+++ b/tests/ui/shadow.stderr
@@ -1,278 +1,278 @@
 error: `x` is shadowed by itself in `x`
-  --> $DIR/shadow.rs:19:9
+  --> $DIR/shadow.rs:24:9
    |
 LL |     let x = x;
    |         ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:18:9
+  --> $DIR/shadow.rs:23:9
    |
 LL |     let x = 1;
    |         ^
    = note: `-D clippy::shadow-same` implied by `-D warnings`
 
 error: `mut x` is shadowed by itself in `&x`
-  --> $DIR/shadow.rs:20:13
+  --> $DIR/shadow.rs:25:13
    |
 LL |     let mut x = &x;
    |             ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:19:9
+  --> $DIR/shadow.rs:24:9
    |
 LL |     let x = x;
    |         ^
 
 error: `x` is shadowed by itself in `&mut x`
-  --> $DIR/shadow.rs:21:9
+  --> $DIR/shadow.rs:26:9
    |
 LL |     let x = &mut x;
    |         ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:20:9
+  --> $DIR/shadow.rs:25:9
    |
 LL |     let mut x = &x;
    |         ^^^^^
 
 error: `x` is shadowed by itself in `*x`
-  --> $DIR/shadow.rs:22:9
+  --> $DIR/shadow.rs:27:9
    |
 LL |     let x = *x;
    |         ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:21:9
+  --> $DIR/shadow.rs:26:9
    |
 LL |     let x = &mut x;
    |         ^
 
 error: `x` is shadowed
-  --> $DIR/shadow.rs:27:9
+  --> $DIR/shadow.rs:32:9
    |
 LL |     let x = x.0;
    |         ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:26:9
+  --> $DIR/shadow.rs:31:9
    |
 LL |     let x = ([[0]], ());
    |         ^
    = note: `-D clippy::shadow-reuse` implied by `-D warnings`
 
 error: `x` is shadowed
-  --> $DIR/shadow.rs:28:9
+  --> $DIR/shadow.rs:33:9
    |
 LL |     let x = x[0];
    |         ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:27:9
+  --> $DIR/shadow.rs:32:9
    |
 LL |     let x = x.0;
    |         ^
 
 error: `x` is shadowed
-  --> $DIR/shadow.rs:29:10
+  --> $DIR/shadow.rs:34:10
    |
 LL |     let [x] = x;
    |          ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:28:9
+  --> $DIR/shadow.rs:33:9
    |
 LL |     let x = x[0];
    |         ^
 
 error: `x` is shadowed
-  --> $DIR/shadow.rs:30:9
+  --> $DIR/shadow.rs:35:9
    |
 LL |     let x = Some(x);
    |         ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:29:10
+  --> $DIR/shadow.rs:34:10
    |
 LL |     let [x] = x;
    |          ^
 
 error: `x` is shadowed
-  --> $DIR/shadow.rs:31:9
+  --> $DIR/shadow.rs:36:9
    |
 LL |     let x = foo(x);
    |         ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:30:9
+  --> $DIR/shadow.rs:35:9
    |
 LL |     let x = Some(x);
    |         ^
 
 error: `x` is shadowed
-  --> $DIR/shadow.rs:32:9
+  --> $DIR/shadow.rs:37:9
    |
 LL |     let x = || x;
    |         ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:31:9
+  --> $DIR/shadow.rs:36:9
    |
 LL |     let x = foo(x);
    |         ^
 
 error: `x` is shadowed
-  --> $DIR/shadow.rs:33:9
+  --> $DIR/shadow.rs:38:9
    |
 LL |     let x = Some(1).map(|_| x)?;
    |         ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:32:9
+  --> $DIR/shadow.rs:37:9
    |
 LL |     let x = || x;
    |         ^
 
 error: `y` is shadowed
-  --> $DIR/shadow.rs:35:9
+  --> $DIR/shadow.rs:40:9
    |
 LL |     let y = match y {
    |         ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:34:9
+  --> $DIR/shadow.rs:39:9
    |
 LL |     let y = 1;
    |         ^
 
 error: `x` shadows a previous, unrelated binding
-  --> $DIR/shadow.rs:50:9
+  --> $DIR/shadow.rs:55:9
    |
 LL |     let x = 2;
    |         ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:49:9
+  --> $DIR/shadow.rs:54:9
    |
 LL |     let x = 1;
    |         ^
    = note: `-D clippy::shadow-unrelated` implied by `-D warnings`
 
 error: `x` shadows a previous, unrelated binding
-  --> $DIR/shadow.rs:55:13
+  --> $DIR/shadow.rs:60:13
    |
 LL |         let x = 1;
    |             ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:54:10
+  --> $DIR/shadow.rs:59:10
    |
 LL |     fn f(x: u32) {
    |          ^
 
 error: `x` shadows a previous, unrelated binding
-  --> $DIR/shadow.rs:60:14
+  --> $DIR/shadow.rs:65:14
    |
 LL |         Some(x) => {
    |              ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:57:9
+  --> $DIR/shadow.rs:62:9
    |
 LL |     let x = 1;
    |         ^
 
 error: `x` shadows a previous, unrelated binding
-  --> $DIR/shadow.rs:61:17
+  --> $DIR/shadow.rs:66:17
    |
 LL |             let x = 1;
    |                 ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:60:14
+  --> $DIR/shadow.rs:65:14
    |
 LL |         Some(x) => {
    |              ^
 
 error: `x` shadows a previous, unrelated binding
-  --> $DIR/shadow.rs:65:17
+  --> $DIR/shadow.rs:70:17
    |
 LL |     if let Some(x) = Some(1) {}
    |                 ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:57:9
+  --> $DIR/shadow.rs:62:9
    |
 LL |     let x = 1;
    |         ^
 
 error: `x` shadows a previous, unrelated binding
-  --> $DIR/shadow.rs:66:20
+  --> $DIR/shadow.rs:71:20
    |
 LL |     while let Some(x) = Some(1) {}
    |                    ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:57:9
+  --> $DIR/shadow.rs:62:9
    |
 LL |     let x = 1;
    |         ^
 
 error: `x` shadows a previous, unrelated binding
-  --> $DIR/shadow.rs:67:15
+  --> $DIR/shadow.rs:72:15
    |
 LL |     let _ = |[x]: [u32; 1]| {
    |               ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:57:9
+  --> $DIR/shadow.rs:62:9
    |
 LL |     let x = 1;
    |         ^
 
 error: `x` shadows a previous, unrelated binding
-  --> $DIR/shadow.rs:68:13
+  --> $DIR/shadow.rs:73:13
    |
 LL |         let x = 1;
    |             ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:67:15
+  --> $DIR/shadow.rs:72:15
    |
 LL |     let _ = |[x]: [u32; 1]| {
    |               ^
 
 error: `y` is shadowed
-  --> $DIR/shadow.rs:71:17
+  --> $DIR/shadow.rs:76:17
    |
 LL |     if let Some(y) = y {}
    |                 ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:70:9
+  --> $DIR/shadow.rs:75:9
    |
 LL |     let y = Some(1);
    |         ^
 
 error: `_b` shadows a previous, unrelated binding
-  --> $DIR/shadow.rs:107:9
+  --> $DIR/shadow.rs:112:9
    |
 LL |     let _b = _a;
    |         ^^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:106:28
+  --> $DIR/shadow.rs:111:28
    |
 LL | pub async fn foo2(_a: i32, _b: i64) {
    |                            ^^
 
 error: `x` shadows a previous, unrelated binding
-  --> $DIR/shadow.rs:113:21
+  --> $DIR/shadow.rs:118:21
    |
 LL |         if let Some(x) = Some(1) { x } else { 1 }
    |                     ^
    |
 note: previous binding is here
-  --> $DIR/shadow.rs:112:13
+  --> $DIR/shadow.rs:117:13
    |
 LL |         let x = 1;
    |             ^
diff --git a/tests/ui/single_match.fixed b/tests/ui/single_match.fixed
index e7b1fd6a85f..163ba94aff8 100644
--- a/tests/ui/single_match.fixed
+++ b/tests/ui/single_match.fixed
@@ -4,6 +4,7 @@
     unused,
     clippy::uninlined_format_args,
     clippy::needless_if,
+    clippy::redundant_guards,
     clippy::redundant_pattern_matching
 )]
 fn dummy() {}
diff --git a/tests/ui/single_match.rs b/tests/ui/single_match.rs
index 1515a7053e5..0dcdb125ffd 100644
--- a/tests/ui/single_match.rs
+++ b/tests/ui/single_match.rs
@@ -4,6 +4,7 @@
     unused,
     clippy::uninlined_format_args,
     clippy::needless_if,
+    clippy::redundant_guards,
     clippy::redundant_pattern_matching
 )]
 fn dummy() {}
diff --git a/tests/ui/single_match.stderr b/tests/ui/single_match.stderr
index 76f7e789589..d3536159949 100644
--- a/tests/ui/single_match.stderr
+++ b/tests/ui/single_match.stderr
@@ -1,5 +1,5 @@
 error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
-  --> $DIR/single_match.rs:14:5
+  --> $DIR/single_match.rs:15:5
    |
 LL | /     match x {
 LL | |         Some(y) => {
@@ -18,7 +18,7 @@ LL ~     };
    |
 
 error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
-  --> $DIR/single_match.rs:22:5
+  --> $DIR/single_match.rs:23:5
    |
 LL | /     match x {
 LL | |         // Note the missing block braces.
@@ -30,7 +30,7 @@ LL | |     }
    | |_____^ help: try: `if let Some(y) = x { println!("{:?}", y) }`
 
 error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
-  --> $DIR/single_match.rs:31:5
+  --> $DIR/single_match.rs:32:5
    |
 LL | /     match z {
 LL | |         (2..=3, 7..=9) => dummy(),
@@ -39,7 +39,7 @@ LL | |     };
    | |_____^ help: try: `if let (2..=3, 7..=9) = z { dummy() }`
 
 error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
-  --> $DIR/single_match.rs:60:5
+  --> $DIR/single_match.rs:61:5
    |
 LL | /     match x {
 LL | |         Some(y) => dummy(),
@@ -48,7 +48,7 @@ LL | |     };
    | |_____^ help: try: `if let Some(y) = x { dummy() }`
 
 error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
-  --> $DIR/single_match.rs:65:5
+  --> $DIR/single_match.rs:66:5
    |
 LL | /     match y {
 LL | |         Ok(y) => dummy(),
@@ -57,7 +57,7 @@ LL | |     };
    | |_____^ help: try: `if let Ok(y) = y { dummy() }`
 
 error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
-  --> $DIR/single_match.rs:72:5
+  --> $DIR/single_match.rs:73:5
    |
 LL | /     match c {
 LL | |         Cow::Borrowed(..) => dummy(),
@@ -66,7 +66,7 @@ LL | |     };
    | |_____^ help: try: `if let Cow::Borrowed(..) = c { dummy() }`
 
 error: you seem to be trying to use `match` for an equality check. Consider using `if`
-  --> $DIR/single_match.rs:93:5
+  --> $DIR/single_match.rs:94:5
    |
 LL | /     match x {
 LL | |         "test" => println!(),
@@ -75,7 +75,7 @@ LL | |     }
    | |_____^ help: try: `if x == "test" { println!() }`
 
 error: you seem to be trying to use `match` for an equality check. Consider using `if`
-  --> $DIR/single_match.rs:106:5
+  --> $DIR/single_match.rs:107:5
    |
 LL | /     match x {
 LL | |         Foo::A => println!(),
@@ -84,7 +84,7 @@ LL | |     }
    | |_____^ help: try: `if x == Foo::A { println!() }`
 
 error: you seem to be trying to use `match` for an equality check. Consider using `if`
-  --> $DIR/single_match.rs:112:5
+  --> $DIR/single_match.rs:113:5
    |
 LL | /     match x {
 LL | |         FOO_C => println!(),
@@ -93,7 +93,7 @@ LL | |     }
    | |_____^ help: try: `if x == FOO_C { println!() }`
 
 error: you seem to be trying to use `match` for an equality check. Consider using `if`
-  --> $DIR/single_match.rs:117:5
+  --> $DIR/single_match.rs:118:5
    |
 LL | /     match &&x {
 LL | |         Foo::A => println!(),
@@ -102,7 +102,7 @@ LL | |     }
    | |_____^ help: try: `if x == Foo::A { println!() }`
 
 error: you seem to be trying to use `match` for an equality check. Consider using `if`
-  --> $DIR/single_match.rs:123:5
+  --> $DIR/single_match.rs:124:5
    |
 LL | /     match &x {
 LL | |         Foo::A => println!(),
@@ -111,7 +111,7 @@ LL | |     }
    | |_____^ help: try: `if x == &Foo::A { println!() }`
 
 error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
-  --> $DIR/single_match.rs:140:5
+  --> $DIR/single_match.rs:141:5
    |
 LL | /     match x {
 LL | |         Bar::A => println!(),
@@ -120,7 +120,7 @@ LL | |     }
    | |_____^ help: try: `if let Bar::A = x { println!() }`
 
 error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
-  --> $DIR/single_match.rs:148:5
+  --> $DIR/single_match.rs:149:5
    |
 LL | /     match x {
 LL | |         None => println!(),
@@ -129,7 +129,7 @@ LL | |     };
    | |_____^ help: try: `if let None = x { println!() }`
 
 error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
-  --> $DIR/single_match.rs:170:5
+  --> $DIR/single_match.rs:171:5
    |
 LL | /     match x {
 LL | |         (Some(_), _) => {},
@@ -138,7 +138,7 @@ LL | |     }
    | |_____^ help: try: `if let (Some(_), _) = x {}`
 
 error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
-  --> $DIR/single_match.rs:176:5
+  --> $DIR/single_match.rs:177:5
    |
 LL | /     match x {
 LL | |         (Some(E::V), _) => todo!(),
@@ -147,7 +147,7 @@ LL | |     }
    | |_____^ help: try: `if let (Some(E::V), _) = x { todo!() }`
 
 error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
-  --> $DIR/single_match.rs:182:5
+  --> $DIR/single_match.rs:183:5
    |
 LL | /     match (Some(42), Some(E::V), Some(42)) {
 LL | |         (.., Some(E::V), _) => {},
@@ -156,7 +156,7 @@ LL | |     }
    | |_____^ help: try: `if let (.., Some(E::V), _) = (Some(42), Some(E::V), Some(42)) {}`
 
 error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
-  --> $DIR/single_match.rs:254:5
+  --> $DIR/single_match.rs:255:5
    |
 LL | /     match bar {
 LL | |         Some(v) => unsafe {
@@ -176,7 +176,7 @@ LL +     } }
    |
 
 error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
-  --> $DIR/single_match.rs:262:5
+  --> $DIR/single_match.rs:263:5
    |
 LL | /     match bar {
 LL | |         #[rustfmt::skip]