Skip to content

Commit f8a9b35

Browse files
committed
Add new lint: match with a single binding statement
- Lint name: MATCH_SINGLE_BINDING
1 parent e36a33f commit f8a9b35

File tree

4 files changed

+98
-4
lines changed

4 files changed

+98
-4
lines changed

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
599599
&matches::MATCH_OVERLAPPING_ARM,
600600
&matches::MATCH_REF_PATS,
601601
&matches::MATCH_WILD_ERR_ARM,
602+
&matches::MATCH_SINGLE_BINDING,
602603
&matches::SINGLE_MATCH,
603604
&matches::SINGLE_MATCH_ELSE,
604605
&matches::WILDCARD_ENUM_MATCH_ARM,
@@ -1193,6 +1194,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
11931194
LintId::of(&matches::MATCH_OVERLAPPING_ARM),
11941195
LintId::of(&matches::MATCH_REF_PATS),
11951196
LintId::of(&matches::MATCH_WILD_ERR_ARM),
1197+
LintId::of(&matches::MATCH_SINGLE_BINDING),
11961198
LintId::of(&matches::SINGLE_MATCH),
11971199
LintId::of(&matches::WILDCARD_IN_OR_PATTERNS),
11981200
LintId::of(&mem_discriminant::MEM_DISCRIMINANT_NON_ENUM),
@@ -1469,6 +1471,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14691471
LintId::of(&map_unit_fn::OPTION_MAP_UNIT_FN),
14701472
LintId::of(&map_unit_fn::RESULT_MAP_UNIT_FN),
14711473
LintId::of(&matches::MATCH_AS_REF),
1474+
LintId::of(&matches::MATCH_SINGLE_BINDING),
14721475
LintId::of(&matches::WILDCARD_IN_OR_PATTERNS),
14731476
LintId::of(&methods::CHARS_NEXT_CMP),
14741477
LintId::of(&methods::CLONE_ON_COPY),

clippy_lints/src/matches.rs

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ use crate::utils::paths;
33
use crate::utils::sugg::Sugg;
44
use crate::utils::usage::is_unused;
55
use crate::utils::{
6-
expr_block, is_allowed, is_expn_of, is_wild, match_qpath, match_type, multispan_sugg, remove_blocks, snippet,
7-
snippet_with_applicability, span_help_and_lint, span_lint_and_sugg, span_lint_and_then, span_note_and_lint,
8-
walk_ptrs_ty,
6+
expr_block, in_macro, is_allowed, is_expn_of, is_wild, match_qpath, match_type, multispan_sugg, remove_blocks,
7+
snippet, snippet_with_applicability, span_help_and_lint, span_lint_and_sugg, span_lint_and_then,
8+
span_note_and_lint, walk_ptrs_ty,
99
};
1010
use if_chain::if_chain;
1111
use rustc::lint::in_external_macro;
@@ -245,6 +245,33 @@ declare_clippy_lint! {
245245
"a wildcard pattern used with others patterns in same match arm"
246246
}
247247

248+
declare_clippy_lint! {
249+
/// **What it does:** Checks for useless match that binds to only one value.
250+
///
251+
/// **Why is this bad?** Readability and needless complexity.
252+
///
253+
/// **Known problems:** This situation frequently happen in macros, so can't lint there.
254+
///
255+
/// **Example:**
256+
/// ```rust
257+
/// # let a = 1;
258+
/// # let b = 2;
259+
///
260+
/// // Bad
261+
/// match (a, b) {
262+
/// (c, d) => {
263+
/// // useless match
264+
/// }
265+
/// }
266+
///
267+
/// // Good
268+
/// let (c, d) = (a, b);
269+
/// ```
270+
pub MATCH_SINGLE_BINDING,
271+
complexity,
272+
"a match with a single binding instead of using `let` statement"
273+
}
274+
248275
declare_lint_pass!(Matches => [
249276
SINGLE_MATCH,
250277
MATCH_REF_PATS,
@@ -254,7 +281,8 @@ declare_lint_pass!(Matches => [
254281
MATCH_WILD_ERR_ARM,
255282
MATCH_AS_REF,
256283
WILDCARD_ENUM_MATCH_ARM,
257-
WILDCARD_IN_OR_PATTERNS
284+
WILDCARD_IN_OR_PATTERNS,
285+
MATCH_SINGLE_BINDING,
258286
]);
259287

260288
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Matches {
@@ -270,6 +298,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Matches {
270298
check_wild_enum_match(cx, ex, arms);
271299
check_match_as_ref(cx, ex, arms, expr);
272300
check_wild_in_or_pats(cx, arms);
301+
check_match_single_binding(cx, ex, arms, expr);
273302
}
274303
if let ExprKind::Match(ref ex, ref arms, _) = expr.kind {
275304
check_match_ref_pats(cx, ex, arms, expr);
@@ -712,6 +741,29 @@ fn check_wild_in_or_pats(cx: &LateContext<'_, '_>, arms: &[Arm<'_>]) {
712741
}
713742
}
714743

744+
fn check_match_single_binding(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
745+
if in_macro(expr.span) {
746+
return;
747+
}
748+
if arms.len() == 1 {
749+
let bind_names = arms[0].pat.span;
750+
let matched_vars = ex.span;
751+
span_lint_and_sugg(
752+
cx,
753+
MATCH_SINGLE_BINDING,
754+
expr.span,
755+
"this match could be written as a `let` statement",
756+
"try this",
757+
format!(
758+
"let {} = {};",
759+
snippet(cx, bind_names, ".."),
760+
snippet(cx, matched_vars, "..")
761+
),
762+
Applicability::HasPlaceholders,
763+
);
764+
}
765+
}
766+
715767
/// Gets all arms that are unbounded `PatRange`s.
716768
fn all_ranges<'a, 'tcx>(
717769
cx: &LateContext<'a, 'tcx>,

tests/ui/match_single_binding.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#![warn(clippy::match_single_binding)]
2+
#[allow(clippy::many_single_char_names)]
3+
4+
fn main() {
5+
let a = 1;
6+
let b = 2;
7+
let c = 3;
8+
// Lint
9+
match (a, b, c) {
10+
(x, y, z) => {
11+
println!("{} {} {}", x, y, z);
12+
},
13+
}
14+
// Ok
15+
match a {
16+
2 => println!("2"),
17+
_ => println!("Not 2"),
18+
}
19+
// Ok
20+
let d = Some(5);
21+
match d {
22+
Some(d) => println!("5"),
23+
_ => println!("None"),
24+
}
25+
}

tests/ui/match_single_binding.stderr

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: this match could be written as a `let` statement
2+
--> $DIR/match_single_binding.rs:9:5
3+
|
4+
LL | / match (a, b, c) {
5+
LL | | (x, y, z) => {
6+
LL | | println!("{} {} {}", x, y, z);
7+
LL | | },
8+
LL | | }
9+
| |_____^ help: try this: `let (x, y, z) = (a, b, c);`
10+
|
11+
= note: `-D clippy::match-single-binding` implied by `-D warnings`
12+
13+
error: aborting due to previous error
14+

0 commit comments

Comments
 (0)