Skip to content

Commit 2d1e129

Browse files
committed
Auto merge of rust-lang#6574 - Jarcho:single_match_eq, r=Manishearth
single_match: suggest `if` over `if let` when possible fixes: rust-lang#173 changelog: single_match: suggest `if` over `if let` when possible
2 parents 9490fdc + 36ff2f7 commit 2d1e129

File tree

5 files changed

+211
-23
lines changed

5 files changed

+211
-23
lines changed

clippy_lints/src/matches.rs

Lines changed: 53 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+
peel_hir_pat_refs, peel_mid_ty_refs, peel_n_hir_expr_refs, remove_blocks, snippet, snippet_block, snippet_opt,
8+
snippet_with_applicability, span_lint_and_help, 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;
@@ -728,20 +728,60 @@ fn report_single_match_single_pattern(
728728
let els_str = els.map_or(String::new(), |els| {
729729
format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span)))
730730
});
731+
732+
let (msg, sugg) = if_chain! {
733+
let (pat, pat_ref_count) = peel_hir_pat_refs(arms[0].pat);
734+
if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind;
735+
let (ty, ty_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(ex));
736+
if let Some(trait_id) = cx.tcx.lang_items().structural_peq_trait();
737+
if ty.is_integral() || ty.is_char() || ty.is_str() || implements_trait(cx, ty, trait_id, &[]);
738+
then {
739+
// scrutinee derives PartialEq and the pattern is a constant.
740+
let pat_ref_count = match pat.kind {
741+
// string literals are already a reference.
742+
PatKind::Lit(Expr { kind: ExprKind::Lit(lit), .. }) if lit.node.is_str() => pat_ref_count + 1,
743+
_ => pat_ref_count,
744+
};
745+
// References are only implicitly added to the pattern, so no overflow here.
746+
// e.g. will work: match &Some(_) { Some(_) => () }
747+
// will not: match Some(_) { &Some(_) => () }
748+
let ref_count_diff = ty_ref_count - pat_ref_count;
749+
750+
// Try to remove address of expressions first.
751+
let (ex, removed) = peel_n_hir_expr_refs(ex, ref_count_diff);
752+
let ref_count_diff = ref_count_diff - removed;
753+
754+
let msg = "you seem to be trying to use `match` for an equality check. Consider using `if`";
755+
let sugg = format!(
756+
"if {} == {}{} {}{}",
757+
snippet(cx, ex.span, ".."),
758+
// PartialEq for different reference counts may not exist.
759+
"&".repeat(ref_count_diff),
760+
snippet(cx, arms[0].pat.span, ".."),
761+
expr_block(cx, &arms[0].body, None, "..", Some(expr.span)),
762+
els_str,
763+
);
764+
(msg, sugg)
765+
} else {
766+
let msg = "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`";
767+
let sugg = format!(
768+
"if let {} = {} {}{}",
769+
snippet(cx, arms[0].pat.span, ".."),
770+
snippet(cx, ex.span, ".."),
771+
expr_block(cx, &arms[0].body, None, "..", Some(expr.span)),
772+
els_str,
773+
);
774+
(msg, sugg)
775+
}
776+
};
777+
731778
span_lint_and_sugg(
732779
cx,
733780
lint,
734781
expr.span,
735-
"you seem to be trying to use match for destructuring a single pattern. Consider using `if \
736-
let`",
782+
msg,
737783
"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-
),
784+
sugg,
745785
Applicability::HasPlaceholders,
746786
);
747787
}

clippy_lints/src/utils/mod.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,6 +1669,44 @@ where
16691669
match_expr_list
16701670
}
16711671

1672+
/// Peels off all references on the pattern. Returns the underlying pattern and the number of
1673+
/// references removed.
1674+
pub fn peel_hir_pat_refs(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) {
1675+
fn peel(pat: &'a Pat<'a>, count: usize) -> (&'a Pat<'a>, usize) {
1676+
if let PatKind::Ref(pat, _) = pat.kind {
1677+
peel(pat, count + 1)
1678+
} else {
1679+
(pat, count)
1680+
}
1681+
}
1682+
peel(pat, 0)
1683+
}
1684+
1685+
/// Peels off up to the given number of references on the expression. Returns the underlying
1686+
/// expression and the number of references removed.
1687+
pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
1688+
fn f(expr: &'a Expr<'a>, count: usize, target: usize) -> (&'a Expr<'a>, usize) {
1689+
match expr.kind {
1690+
ExprKind::AddrOf(_, _, expr) if count != target => f(expr, count + 1, target),
1691+
_ => (expr, count),
1692+
}
1693+
}
1694+
f(expr, 0, count)
1695+
}
1696+
1697+
/// Peels off all references on the type. Returns the underlying type and the number of references
1698+
/// removed.
1699+
pub fn peel_mid_ty_refs(ty: Ty<'_>) -> (Ty<'_>, usize) {
1700+
fn peel(ty: Ty<'_>, count: usize) -> (Ty<'_>, usize) {
1701+
if let ty::Ref(_, ty, _) = ty.kind() {
1702+
peel(ty, count + 1)
1703+
} else {
1704+
(ty, count)
1705+
}
1706+
}
1707+
peel(ty, 0)
1708+
}
1709+
16721710
#[macro_export]
16731711
macro_rules! unwrap_cargo_metadata {
16741712
($cx: ident, $lint: ident, $deps: expr) => {{

tests/ui/single_match.rs

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

84+
// issue #173
85+
fn if_suggestion() {
86+
let x = "test";
87+
match x {
88+
"test" => println!(),
89+
_ => (),
90+
}
91+
92+
#[derive(PartialEq, Eq)]
93+
enum Foo {
94+
A,
95+
B,
96+
C(u32),
97+
}
98+
99+
let x = Foo::A;
100+
match x {
101+
Foo::A => println!(),
102+
_ => (),
103+
}
104+
105+
const FOO_C: Foo = Foo::C(0);
106+
match x {
107+
FOO_C => println!(),
108+
_ => (),
109+
}
110+
111+
match &&x {
112+
Foo::A => println!(),
113+
_ => (),
114+
}
115+
116+
let x = &x;
117+
match &x {
118+
Foo::A => println!(),
119+
_ => (),
120+
}
121+
122+
enum Bar {
123+
A,
124+
B,
125+
}
126+
impl PartialEq for Bar {
127+
fn eq(&self, rhs: &Self) -> bool {
128+
matches!((self, rhs), (Self::A, Self::A) | (Self::B, Self::B))
129+
}
130+
}
131+
impl Eq for Bar {}
132+
133+
let x = Bar::A;
134+
match x {
135+
Bar::A => println!(),
136+
_ => (),
137+
}
138+
}
139+
84140
macro_rules! single_match {
85141
($num:literal) => {
86142
match $num {

tests/ui/single_match.stderr

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: you seem to be trying to use match for destructuring a single pattern. Consider using `if let`
1+
error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
22
--> $DIR/single_match.rs:8:5
33
|
44
LL | / match x {
@@ -17,7 +17,7 @@ LL | println!("{:?}", y);
1717
LL | };
1818
|
1919

20-
error: you seem to be trying to use match for destructuring a single pattern. Consider using `if let`
20+
error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
2121
--> $DIR/single_match.rs:16:5
2222
|
2323
LL | / match x {
@@ -29,7 +29,7 @@ LL | | _ => (),
2929
LL | | }
3030
| |_____^ help: try this: `if let Some(y) = x { println!("{:?}", y) }`
3131

32-
error: you seem to be trying to use match for destructuring a single pattern. Consider using `if let`
32+
error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
3333
--> $DIR/single_match.rs:25:5
3434
|
3535
LL | / match z {
@@ -38,7 +38,7 @@ LL | | _ => {},
3838
LL | | };
3939
| |_____^ help: try this: `if let (2..=3, 7..=9) = z { dummy() }`
4040

41-
error: you seem to be trying to use match for destructuring a single pattern. Consider using `if let`
41+
error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
4242
--> $DIR/single_match.rs:54:5
4343
|
4444
LL | / match x {
@@ -47,7 +47,7 @@ LL | | None => (),
4747
LL | | };
4848
| |_____^ help: try this: `if let Some(y) = x { dummy() }`
4949

50-
error: you seem to be trying to use match for destructuring a single pattern. Consider using `if let`
50+
error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
5151
--> $DIR/single_match.rs:59:5
5252
|
5353
LL | / match y {
@@ -56,7 +56,7 @@ LL | | Err(..) => (),
5656
LL | | };
5757
| |_____^ help: try this: `if let Ok(y) = y { dummy() }`
5858

59-
error: you seem to be trying to use match for destructuring a single pattern. Consider using `if let`
59+
error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
6060
--> $DIR/single_match.rs:66:5
6161
|
6262
LL | / match c {
@@ -65,5 +65,59 @@ 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:87: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:100: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:106: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 an equality check. Consider using `if`
96+
--> $DIR/single_match.rs:111:5
97+
|
98+
LL | / match &&x {
99+
LL | | Foo::A => println!(),
100+
LL | | _ => (),
101+
LL | | }
102+
| |_____^ help: try this: `if x == Foo::A { println!() }`
103+
104+
error: you seem to be trying to use `match` for an equality check. Consider using `if`
105+
--> $DIR/single_match.rs:117:5
106+
|
107+
LL | / match &x {
108+
LL | | Foo::A => println!(),
109+
LL | | _ => (),
110+
LL | | }
111+
| |_____^ help: try this: `if x == &Foo::A { println!() }`
112+
113+
error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
114+
--> $DIR/single_match.rs:134:5
115+
|
116+
LL | / match x {
117+
LL | | Bar::A => println!(),
118+
LL | | _ => (),
119+
LL | | }
120+
| |_____^ help: try this: `if let Bar::A = x { println!() }`
121+
122+
error: aborting due to 12 previous errors
69123

tests/ui/single_match_else.stderr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: you seem to be trying to use match for destructuring a single pattern. Consider using `if let`
1+
error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
22
--> $DIR/single_match_else.rs:14:5
33
|
44
LL | / match ExprNode::Butterflies {
@@ -19,7 +19,7 @@ LL | None
1919
LL | }
2020
|
2121

22-
error: you seem to be trying to use match for destructuring a single pattern. Consider using `if let`
22+
error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
2323
--> $DIR/single_match_else.rs:70:5
2424
|
2525
LL | / match Some(1) {
@@ -39,7 +39,7 @@ LL | return
3939
LL | }
4040
|
4141

42-
error: you seem to be trying to use match for destructuring a single pattern. Consider using `if let`
42+
error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
4343
--> $DIR/single_match_else.rs:79:5
4444
|
4545
LL | / match Some(1) {

0 commit comments

Comments
 (0)