Skip to content

Commit

Permalink
Add new lint: match with a single binding statement
Browse files Browse the repository at this point in the history
 - Lint name: MATCH_SINGLE_BINDING
  • Loading branch information
ThibsG committed Jan 18, 2020
1 parent e36a33f commit f8a9b35
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 4 deletions.
3 changes: 3 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&matches::MATCH_OVERLAPPING_ARM,
&matches::MATCH_REF_PATS,
&matches::MATCH_WILD_ERR_ARM,
&matches::MATCH_SINGLE_BINDING,
&matches::SINGLE_MATCH,
&matches::SINGLE_MATCH_ELSE,
&matches::WILDCARD_ENUM_MATCH_ARM,
Expand Down Expand Up @@ -1193,6 +1194,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&matches::MATCH_OVERLAPPING_ARM),
LintId::of(&matches::MATCH_REF_PATS),
LintId::of(&matches::MATCH_WILD_ERR_ARM),
LintId::of(&matches::MATCH_SINGLE_BINDING),
LintId::of(&matches::SINGLE_MATCH),
LintId::of(&matches::WILDCARD_IN_OR_PATTERNS),
LintId::of(&mem_discriminant::MEM_DISCRIMINANT_NON_ENUM),
Expand Down Expand Up @@ -1469,6 +1471,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&map_unit_fn::OPTION_MAP_UNIT_FN),
LintId::of(&map_unit_fn::RESULT_MAP_UNIT_FN),
LintId::of(&matches::MATCH_AS_REF),
LintId::of(&matches::MATCH_SINGLE_BINDING),
LintId::of(&matches::WILDCARD_IN_OR_PATTERNS),
LintId::of(&methods::CHARS_NEXT_CMP),
LintId::of(&methods::CLONE_ON_COPY),
Expand Down
60 changes: 56 additions & 4 deletions clippy_lints/src/matches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ use crate::utils::paths;
use crate::utils::sugg::Sugg;
use crate::utils::usage::is_unused;
use crate::utils::{
expr_block, is_allowed, is_expn_of, is_wild, match_qpath, match_type, multispan_sugg, remove_blocks, snippet,
snippet_with_applicability, span_help_and_lint, span_lint_and_sugg, span_lint_and_then, span_note_and_lint,
walk_ptrs_ty,
expr_block, in_macro, is_allowed, is_expn_of, is_wild, match_qpath, match_type, multispan_sugg, remove_blocks,
snippet, snippet_with_applicability, span_help_and_lint, span_lint_and_sugg, span_lint_and_then,
span_note_and_lint, walk_ptrs_ty,
};
use if_chain::if_chain;
use rustc::lint::in_external_macro;
Expand Down Expand Up @@ -245,6 +245,33 @@ declare_clippy_lint! {
"a wildcard pattern used with others patterns in same match arm"
}

declare_clippy_lint! {
/// **What it does:** Checks for useless match that binds to only one value.
///
/// **Why is this bad?** Readability and needless complexity.
///
/// **Known problems:** This situation frequently happen in macros, so can't lint there.
///
/// **Example:**
/// ```rust
/// # let a = 1;
/// # let b = 2;
///
/// // Bad
/// match (a, b) {
/// (c, d) => {
/// // useless match
/// }
/// }
///
/// // Good
/// let (c, d) = (a, b);
/// ```
pub MATCH_SINGLE_BINDING,
complexity,
"a match with a single binding instead of using `let` statement"
}

declare_lint_pass!(Matches => [
SINGLE_MATCH,
MATCH_REF_PATS,
Expand All @@ -254,7 +281,8 @@ declare_lint_pass!(Matches => [
MATCH_WILD_ERR_ARM,
MATCH_AS_REF,
WILDCARD_ENUM_MATCH_ARM,
WILDCARD_IN_OR_PATTERNS
WILDCARD_IN_OR_PATTERNS,
MATCH_SINGLE_BINDING,
]);

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Matches {
Expand All @@ -270,6 +298,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Matches {
check_wild_enum_match(cx, ex, arms);
check_match_as_ref(cx, ex, arms, expr);
check_wild_in_or_pats(cx, arms);
check_match_single_binding(cx, ex, arms, expr);
}
if let ExprKind::Match(ref ex, ref arms, _) = expr.kind {
check_match_ref_pats(cx, ex, arms, expr);
Expand Down Expand Up @@ -712,6 +741,29 @@ fn check_wild_in_or_pats(cx: &LateContext<'_, '_>, arms: &[Arm<'_>]) {
}
}

fn check_match_single_binding(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
if in_macro(expr.span) {
return;
}
if arms.len() == 1 {
let bind_names = arms[0].pat.span;
let matched_vars = ex.span;
span_lint_and_sugg(
cx,
MATCH_SINGLE_BINDING,
expr.span,
"this match could be written as a `let` statement",
"try this",
format!(
"let {} = {};",
snippet(cx, bind_names, ".."),
snippet(cx, matched_vars, "..")
),
Applicability::HasPlaceholders,
);
}
}

/// Gets all arms that are unbounded `PatRange`s.
fn all_ranges<'a, 'tcx>(
cx: &LateContext<'a, 'tcx>,
Expand Down
25 changes: 25 additions & 0 deletions tests/ui/match_single_binding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#![warn(clippy::match_single_binding)]
#[allow(clippy::many_single_char_names)]

fn main() {
let a = 1;
let b = 2;
let c = 3;
// Lint
match (a, b, c) {
(x, y, z) => {
println!("{} {} {}", x, y, z);
},
}
// Ok
match a {
2 => println!("2"),
_ => println!("Not 2"),
}
// Ok
let d = Some(5);
match d {
Some(d) => println!("5"),
_ => println!("None"),
}
}
14 changes: 14 additions & 0 deletions tests/ui/match_single_binding.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: this match could be written as a `let` statement
--> $DIR/match_single_binding.rs:9:5
|
LL | / match (a, b, c) {
LL | | (x, y, z) => {
LL | | println!("{} {} {}", x, y, z);
LL | | },
LL | | }
| |_____^ help: try this: `let (x, y, z) = (a, b, c);`
|
= note: `-D clippy::match-single-binding` implied by `-D warnings`

error: aborting due to previous error

0 comments on commit f8a9b35

Please sign in to comment.