diff options
| -rw-r--r-- | clippy_lints/src/matches.rs | 79 | ||||
| -rw-r--r-- | tests/ui/single_match.rs | 43 | ||||
| -rw-r--r-- | tests/ui/single_match.stderr | 38 |
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 |
