about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--clippy_lints/src/matches.rs79
-rw-r--r--tests/ui/single_match.rs43
-rw-r--r--tests/ui/single_match.stderr38
3 files changed, 146 insertions, 14 deletions
diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs
index 04b35835c6b..2239b519632 100644
--- a/clippy_lints/src/matches.rs
+++ b/clippy_lints/src/matches.rs
@@ -2,10 +2,10 @@ use crate::consts::{constant, miri_to_const, Constant};
 use crate::utils::sugg::Sugg;
 use crate::utils::usage::is_unused;
 use crate::utils::{
-    expr_block, get_arg_name, get_parent_expr, in_macro, indent_of, is_allowed, is_expn_of, is_refutable,
-    is_type_diagnostic_item, is_wild, match_qpath, match_type, match_var, meets_msrv, multispan_sugg, remove_blocks,
-    snippet, snippet_block, snippet_opt, snippet_with_applicability, span_lint_and_help, span_lint_and_note,
-    span_lint_and_sugg, span_lint_and_then,
+    expr_block, get_arg_name, get_parent_expr, implements_trait, in_macro, indent_of, is_allowed, is_expn_of,
+    is_refutable, is_type_diagnostic_item, is_wild, match_qpath, match_type, match_var, meets_msrv, multispan_sugg,
+    remove_blocks, snippet, snippet_block, snippet_opt, snippet_with_applicability, span_lint_and_help,
+    span_lint_and_note, span_lint_and_sugg, span_lint_and_then,
 };
 use crate::utils::{paths, search_same, SpanlessEq, SpanlessHash};
 use if_chain::if_chain;
@@ -717,6 +717,28 @@ fn check_single_match_single_pattern(
     }
 }
 
+fn peel_pat_refs(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) {
+    fn peel(pat: &'a Pat<'a>, count: usize) -> (&'a Pat<'a>, usize) {
+        if let PatKind::Ref(pat, _) = pat.kind {
+            peel(pat, count + 1)
+        } else {
+            (pat, count)
+        }
+    }
+    peel(pat, 0)
+}
+
+fn peel_ty_refs(ty: Ty<'_>) -> (Ty<'_>, usize) {
+    fn peel(ty: Ty<'_>, count: usize) -> (Ty<'_>, usize) {
+        if let ty::Ref(_, ty, _) = ty.kind() {
+            peel(ty, count + 1)
+        } else {
+            (ty, count)
+        }
+    }
+    peel(ty, 0)
+}
+
 fn report_single_match_single_pattern(
     cx: &LateContext<'_>,
     ex: &Expr<'_>,
@@ -728,20 +750,51 @@ fn report_single_match_single_pattern(
     let els_str = els.map_or(String::new(), |els| {
         format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span)))
     });
+
+    let (msg, sugg) = if_chain! {
+        let (pat, pat_ref_count) = peel_pat_refs(arms[0].pat);
+        if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind;
+        let (ty, ty_ref_count) = peel_ty_refs(cx.typeck_results().expr_ty(ex));
+        if let Some(trait_id) = cx.tcx.lang_items().structural_peq_trait();
+        if ty.is_integral() || ty.is_char() || ty.is_str() || implements_trait(cx, ty, trait_id, &[]);
+        then {
+            // scrutinee derives PartialEq and the pattern is a constant.
+            let pat_ref_count = match pat.kind {
+                // string literals are already a reference.
+                PatKind::Lit(Expr { kind: ExprKind::Lit(lit), .. }) if lit.node.is_str() => pat_ref_count + 1,
+                _ => pat_ref_count,
+            };
+            let msg = "you seem to be trying to use match for an equality check. Consider using `if`";
+            let sugg = format!(
+                "if {} == {}{} {}{}",
+                snippet(cx, ex.span, ".."),
+                // PartialEq for different reference counts may not exist.
+                "&".repeat(ty_ref_count - pat_ref_count),
+                snippet(cx, arms[0].pat.span, ".."),
+                expr_block(cx, &arms[0].body, None, "..", Some(expr.span)),
+                els_str,
+            );
+            (msg, sugg)
+        } else {
+            let msg = "you seem to be trying to use match for destructuring a single pattern. Consider using `if let`";
+            let sugg = format!(
+                "if let {} = {} {}{}",
+                snippet(cx, arms[0].pat.span, ".."),
+                snippet(cx, ex.span, ".."),
+                expr_block(cx, &arms[0].body, None, "..", Some(expr.span)),
+                els_str,
+            );
+            (msg, sugg)
+        }
+    };
+
     span_lint_and_sugg(
         cx,
         lint,
         expr.span,
-        "you seem to be trying to use match for destructuring a single pattern. Consider using `if \
-         let`",
+        msg,
         "try this",
-        format!(
-            "if let {} = {} {}{}",
-            snippet(cx, arms[0].pat.span, ".."),
-            snippet(cx, ex.span, ".."),
-            expr_block(cx, &arms[0].body, None, "..", Some(expr.span)),
-            els_str,
-        ),
+        sugg,
         Applicability::HasPlaceholders,
     );
 }
diff --git a/tests/ui/single_match.rs b/tests/ui/single_match.rs
index 1c55af5dfb6..02266308fba 100644
--- a/tests/ui/single_match.rs
+++ b/tests/ui/single_match.rs
@@ -81,6 +81,49 @@ fn single_match_know_enum() {
     }
 }
 
+fn issue_173() {
+    let x = "test";
+    match x {
+        "test" => println!(),
+        _ => (),
+    }
+
+    #[derive(PartialEq, Eq)]
+    enum Foo {
+        A,
+        B,
+        C(u32),
+    }
+
+    let x = Foo::A;
+    match x {
+        Foo::A => println!(),
+        _ => (),
+    }
+
+    const FOO_C: Foo = Foo::C(0);
+    match x {
+        FOO_C => println!(),
+        _ => (),
+    }
+    enum Bar {
+        A,
+        B,
+    }
+    impl PartialEq for Bar {
+        fn eq(&self, rhs: &Self) -> bool {
+            matches!((self, rhs), (Self::A, Self::A) | (Self::B, Self::B))
+        }
+    }
+    impl Eq for Bar {}
+
+    let x = Bar::A;
+    match x {
+        Bar::A => println!(),
+        _ => (),
+    }
+}
+
 macro_rules! single_match {
     ($num:literal) => {
         match $num {
diff --git a/tests/ui/single_match.stderr b/tests/ui/single_match.stderr
index f69554d75f9..5eca07ab109 100644
--- a/tests/ui/single_match.stderr
+++ b/tests/ui/single_match.stderr
@@ -65,5 +65,41 @@ LL | |         Cow::Owned(..) => (),
 LL | |     };
    | |_____^ help: try this: `if let Cow::Borrowed(..) = c { dummy() }`
 
-error: aborting due to 6 previous errors
+error: you seem to be trying to use match for an equality check. Consider using `if`
+  --> $DIR/single_match.rs:86:5
+   |
+LL | /     match x {
+LL | |         "test" => println!(),
+LL | |         _ => (),
+LL | |     }
+   | |_____^ help: try this: `if x == "test" { println!() }`
+
+error: you seem to be trying to use match for an equality check. Consider using `if`
+  --> $DIR/single_match.rs:99:5
+   |
+LL | /     match x {
+LL | |         Foo::A => println!(),
+LL | |         _ => (),
+LL | |     }
+   | |_____^ help: try this: `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:105:5
+   |
+LL | /     match x {
+LL | |         FOO_C => println!(),
+LL | |         _ => (),
+LL | |     }
+   | |_____^ help: try this: `if x == FOO_C { println!() }`
+
+error: you seem to be trying to use match for destructuring a single pattern. Consider using `if let`
+  --> $DIR/single_match.rs:121:5
+   |
+LL | /     match x {
+LL | |         Bar::A => println!(),
+LL | |         _ => (),
+LL | |     }
+   | |_____^ help: try this: `if let Bar::A = x { println!() }`
+
+error: aborting due to 10 previous errors