Skip to content

Commit 8d7417d

Browse files
committed
Add: single_match will suggest using if .. == .. instead of if let when applicable
1 parent 2950c8e commit 8d7417d

File tree

3 files changed

+146
-14
lines changed

3 files changed

+146
-14
lines changed

clippy_lints/src/matches.rs

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ use crate::consts::{constant, miri_to_const, Constant};
22
use crate::utils::sugg::Sugg;
33
use crate::utils::usage::is_unused;
44
use crate::utils::{
5-
expr_block, get_arg_name, get_parent_expr, in_macro, indent_of, is_allowed, is_expn_of, is_refutable,
6-
is_type_diagnostic_item, is_wild, match_qpath, match_type, match_var, meets_msrv, multispan_sugg, remove_blocks,
7-
snippet, snippet_block, snippet_opt, snippet_with_applicability, span_lint_and_help, span_lint_and_note,
8-
span_lint_and_sugg, span_lint_and_then,
5+
expr_block, get_arg_name, get_parent_expr, implements_trait, in_macro, indent_of, is_allowed, is_expn_of,
6+
is_refutable, is_type_diagnostic_item, is_wild, match_qpath, match_type, match_var, meets_msrv, multispan_sugg,
7+
remove_blocks, snippet, snippet_block, snippet_opt, snippet_with_applicability, span_lint_and_help,
8+
span_lint_and_note, span_lint_and_sugg, span_lint_and_then,
99
};
1010
use crate::utils::{paths, search_same, SpanlessEq, SpanlessHash};
1111
use if_chain::if_chain;
@@ -717,6 +717,28 @@ fn check_single_match_single_pattern(
717717
}
718718
}
719719

720+
fn peel_pat_refs(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) {
721+
fn peel(pat: &'a Pat<'a>, count: usize) -> (&'a Pat<'a>, usize) {
722+
if let PatKind::Ref(pat, _) = pat.kind {
723+
peel(pat, count + 1)
724+
} else {
725+
(pat, count)
726+
}
727+
}
728+
peel(pat, 0)
729+
}
730+
731+
fn peel_ty_refs(ty: Ty<'_>) -> (Ty<'_>, usize) {
732+
fn peel(ty: Ty<'_>, count: usize) -> (Ty<'_>, usize) {
733+
if let ty::Ref(_, ty, _) = ty.kind() {
734+
peel(ty, count + 1)
735+
} else {
736+
(ty, count)
737+
}
738+
}
739+
peel(ty, 0)
740+
}
741+
720742
fn report_single_match_single_pattern(
721743
cx: &LateContext<'_>,
722744
ex: &Expr<'_>,
@@ -728,20 +750,51 @@ fn report_single_match_single_pattern(
728750
let els_str = els.map_or(String::new(), |els| {
729751
format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span)))
730752
});
753+
754+
let (msg, sugg) = if_chain! {
755+
let (pat, pat_ref_count) = peel_pat_refs(arms[0].pat);
756+
if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind;
757+
let (ty, ty_ref_count) = peel_ty_refs(cx.typeck_results().expr_ty(ex));
758+
if let Some(trait_id) = cx.tcx.lang_items().structural_peq_trait();
759+
if ty.is_integral() || ty.is_char() || ty.is_str() || implements_trait(cx, ty, trait_id, &[]);
760+
then {
761+
// scrutinee derives PartialEq and the pattern is a constant.
762+
let pat_ref_count = match pat.kind {
763+
// string literals are already a reference.
764+
PatKind::Lit(Expr { kind: ExprKind::Lit(lit), .. }) if lit.node.is_str() => pat_ref_count + 1,
765+
_ => pat_ref_count,
766+
};
767+
let msg = "you seem to be trying to use match for an equality check. Consider using `if`";
768+
let sugg = format!(
769+
"if {} == {}{} {}{}",
770+
snippet(cx, ex.span, ".."),
771+
// PartialEq for different reference counts may not exist.
772+
"&".repeat(ty_ref_count - pat_ref_count),
773+
snippet(cx, arms[0].pat.span, ".."),
774+
expr_block(cx, &arms[0].body, None, "..", Some(expr.span)),
775+
els_str,
776+
);
777+
(msg, sugg)
778+
} else {
779+
let msg = "you seem to be trying to use match for destructuring a single pattern. Consider using `if let`";
780+
let sugg = format!(
781+
"if let {} = {} {}{}",
782+
snippet(cx, arms[0].pat.span, ".."),
783+
snippet(cx, ex.span, ".."),
784+
expr_block(cx, &arms[0].body, None, "..", Some(expr.span)),
785+
els_str,
786+
);
787+
(msg, sugg)
788+
}
789+
};
790+
731791
span_lint_and_sugg(
732792
cx,
733793
lint,
734794
expr.span,
735-
"you seem to be trying to use match for destructuring a single pattern. Consider using `if \
736-
let`",
795+
msg,
737796
"try this",
738-
format!(
739-
"if let {} = {} {}{}",
740-
snippet(cx, arms[0].pat.span, ".."),
741-
snippet(cx, ex.span, ".."),
742-
expr_block(cx, &arms[0].body, None, "..", Some(expr.span)),
743-
els_str,
744-
),
797+
sugg,
745798
Applicability::HasPlaceholders,
746799
);
747800
}

tests/ui/single_match.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,49 @@ fn single_match_know_enum() {
8181
}
8282
}
8383

84+
fn issue_173() {
85+
let x = "test";
86+
match x {
87+
"test" => println!(),
88+
_ => (),
89+
}
90+
91+
#[derive(PartialEq, Eq)]
92+
enum Foo {
93+
A,
94+
B,
95+
C(u32),
96+
}
97+
98+
let x = Foo::A;
99+
match x {
100+
Foo::A => println!(),
101+
_ => (),
102+
}
103+
104+
const FOO_C: Foo = Foo::C(0);
105+
match x {
106+
FOO_C => println!(),
107+
_ => (),
108+
}
109+
enum Bar {
110+
A,
111+
B,
112+
}
113+
impl PartialEq for Bar {
114+
fn eq(&self, rhs: &Self) -> bool {
115+
matches!((self, rhs), (Self::A, Self::A) | (Self::B, Self::B))
116+
}
117+
}
118+
impl Eq for Bar {}
119+
120+
let x = Bar::A;
121+
match x {
122+
Bar::A => println!(),
123+
_ => (),
124+
}
125+
}
126+
84127
macro_rules! single_match {
85128
($num:literal) => {
86129
match $num {

tests/ui/single_match.stderr

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,41 @@ LL | | Cow::Owned(..) => (),
6565
LL | | };
6666
| |_____^ help: try this: `if let Cow::Borrowed(..) = c { dummy() }`
6767

68-
error: aborting due to 6 previous errors
68+
error: you seem to be trying to use match for an equality check. Consider using `if`
69+
--> $DIR/single_match.rs:86:5
70+
|
71+
LL | / match x {
72+
LL | | "test" => println!(),
73+
LL | | _ => (),
74+
LL | | }
75+
| |_____^ help: try this: `if x == "test" { println!() }`
76+
77+
error: you seem to be trying to use match for an equality check. Consider using `if`
78+
--> $DIR/single_match.rs:99:5
79+
|
80+
LL | / match x {
81+
LL | | Foo::A => println!(),
82+
LL | | _ => (),
83+
LL | | }
84+
| |_____^ help: try this: `if x == Foo::A { println!() }`
85+
86+
error: you seem to be trying to use match for an equality check. Consider using `if`
87+
--> $DIR/single_match.rs:105:5
88+
|
89+
LL | / match x {
90+
LL | | FOO_C => println!(),
91+
LL | | _ => (),
92+
LL | | }
93+
| |_____^ help: try this: `if x == FOO_C { println!() }`
94+
95+
error: you seem to be trying to use match for destructuring a single pattern. Consider using `if let`
96+
--> $DIR/single_match.rs:121:5
97+
|
98+
LL | / match x {
99+
LL | | Bar::A => println!(),
100+
LL | | _ => (),
101+
LL | | }
102+
| |_____^ help: try this: `if let Bar::A = x { println!() }`
103+
104+
error: aborting due to 10 previous errors
69105

0 commit comments

Comments
 (0)