Skip to content

Commit faefb4f

Browse files
authored
Merge pull request #2684 from 17cupsofcoffee/infallible-destructuring-match
Lint for destructuring tuple structs/variants with an infallible single-armed match
2 parents 841e467 + 3c38a36 commit faefb4f

File tree

4 files changed

+194
-0
lines changed

4 files changed

+194
-0
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use super::utils::{get_arg_name, match_var, remove_blocks, snippet, span_lint_and_sugg};
2+
use rustc::hir::*;
3+
use rustc::lint::*;
4+
5+
/// **What it does:** Checks for matches being used to destructure a single-variant enum
6+
/// or tuple struct where a `let` will suffice.
7+
///
8+
/// **Why is this bad?** Just readability – `let` doesn't nest, whereas a `match` does.
9+
///
10+
/// **Known problems:** None.
11+
///
12+
/// **Example:**
13+
/// ```rust
14+
/// enum Wrapper {
15+
/// Data(i32),
16+
/// }
17+
///
18+
/// let wrapper = Wrapper::Data(42);
19+
///
20+
/// let data = match wrapper {
21+
/// Wrapper::Data(i) => i,
22+
/// };
23+
/// ```
24+
///
25+
/// The correct use would be:
26+
/// ```rust
27+
/// enum Wrapper {
28+
/// Data(i32),
29+
/// }
30+
///
31+
/// let wrapper = Wrapper::Data(42);
32+
/// let Wrapper::Data(data) = wrapper;
33+
/// ```
34+
declare_clippy_lint! {
35+
pub INFALLIBLE_DESTRUCTURING_MATCH,
36+
style,
37+
"a match statement with a single infallible arm instead of a `let`"
38+
}
39+
40+
#[derive(Copy, Clone, Default)]
41+
pub struct Pass;
42+
43+
impl LintPass for Pass {
44+
fn get_lints(&self) -> LintArray {
45+
lint_array!(INFALLIBLE_DESTRUCTURING_MATCH)
46+
}
47+
}
48+
49+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
50+
fn check_local(&mut self, cx: &LateContext<'a, 'tcx>, local: &'tcx Local) {
51+
if_chain! {
52+
if let Some(ref expr) = local.init;
53+
if let Expr_::ExprMatch(ref target, ref arms, MatchSource::Normal) = expr.node;
54+
if arms.len() == 1 && arms[0].pats.len() == 1 && arms[0].guard.is_none();
55+
if let PatKind::TupleStruct(QPath::Resolved(None, ref variant_name), ref args, _) = arms[0].pats[0].node;
56+
if args.len() == 1;
57+
if let Some(arg) = get_arg_name(&args[0]);
58+
let body = remove_blocks(&arms[0].body);
59+
if match_var(body, arg);
60+
61+
then {
62+
span_lint_and_sugg(
63+
cx,
64+
INFALLIBLE_DESTRUCTURING_MATCH,
65+
local.span,
66+
"you seem to be trying to use match to destructure a single infallible pattern. \
67+
Consider using `let`",
68+
"try this",
69+
format!(
70+
"let {}({}) = {};",
71+
snippet(cx, variant_name.span, ".."),
72+
snippet(cx, local.pat.span, ".."),
73+
snippet(cx, target.span, ".."),
74+
),
75+
);
76+
}
77+
}
78+
}
79+
}

clippy_lints/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ pub mod identity_conversion;
133133
pub mod identity_op;
134134
pub mod if_let_redundant_pattern_matching;
135135
pub mod if_not_else;
136+
pub mod infallible_destructuring_match;
136137
pub mod infinite_iter;
137138
pub mod inline_fn_without_body;
138139
pub mod int_plus_one;
@@ -407,6 +408,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
407408
reg.register_late_lint_pass(box suspicious_trait_impl::SuspiciousImpl);
408409
reg.register_late_lint_pass(box redundant_field_names::RedundantFieldNames);
409410
reg.register_late_lint_pass(box map_unit_fn::Pass);
411+
reg.register_late_lint_pass(box infallible_destructuring_match::Pass);
410412

411413

412414
reg.register_lint_group("clippy_restriction", vec![
@@ -520,6 +522,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
520522
identity_conversion::IDENTITY_CONVERSION,
521523
identity_op::IDENTITY_OP,
522524
if_let_redundant_pattern_matching::IF_LET_REDUNDANT_PATTERN_MATCHING,
525+
infallible_destructuring_match::INFALLIBLE_DESTRUCTURING_MATCH,
523526
infinite_iter::INFINITE_ITER,
524527
inline_fn_without_body::INLINE_FN_WITHOUT_BODY,
525528
int_plus_one::INT_PLUS_ONE,
@@ -688,6 +691,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
688691
formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING,
689692
formatting::SUSPICIOUS_ELSE_FORMATTING,
690693
if_let_redundant_pattern_matching::IF_LET_REDUNDANT_PATTERN_MATCHING,
694+
infallible_destructuring_match::INFALLIBLE_DESTRUCTURING_MATCH,
691695
len_zero::LEN_WITHOUT_IS_EMPTY,
692696
len_zero::LEN_ZERO,
693697
let_if_seq::USELESS_LET_IF_SEQ,
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#![feature(exhaustive_patterns)]
2+
#![allow(let_and_return)]
3+
4+
enum SingleVariantEnum {
5+
Variant(i32),
6+
}
7+
8+
struct TupleStruct(i32);
9+
10+
enum EmptyEnum {}
11+
12+
fn infallible_destructuring_match_enum() {
13+
let wrapper = SingleVariantEnum::Variant(0);
14+
15+
// This should lint!
16+
let data = match wrapper {
17+
SingleVariantEnum::Variant(i) => i,
18+
};
19+
20+
// This shouldn't!
21+
let data = match wrapper {
22+
SingleVariantEnum::Variant(_) => -1,
23+
};
24+
25+
// Neither should this!
26+
let data = match wrapper {
27+
SingleVariantEnum::Variant(i) => -1,
28+
};
29+
30+
let SingleVariantEnum::Variant(data) = wrapper;
31+
}
32+
33+
fn infallible_destructuring_match_struct() {
34+
let wrapper = TupleStruct(0);
35+
36+
// This should lint!
37+
let data = match wrapper {
38+
TupleStruct(i) => i,
39+
};
40+
41+
// This shouldn't!
42+
let data = match wrapper {
43+
TupleStruct(_) => -1,
44+
};
45+
46+
// Neither should this!
47+
let data = match wrapper {
48+
TupleStruct(i) => -1,
49+
};
50+
51+
let TupleStruct(data) = wrapper;
52+
}
53+
54+
fn never_enum() {
55+
let wrapper: Result<i32, !> = Ok(23);
56+
57+
// This should lint!
58+
let data = match wrapper {
59+
Ok(i) => i,
60+
};
61+
62+
// This shouldn't!
63+
let data = match wrapper {
64+
Ok(_) => -1,
65+
};
66+
67+
// Neither should this!
68+
let data = match wrapper {
69+
Ok(i) => -1,
70+
};
71+
72+
let Ok(data) = wrapper;
73+
}
74+
75+
impl EmptyEnum {
76+
fn match_on(&self) -> ! {
77+
// The lint shouldn't pick this up, as `let` won't work here!
78+
let data = match *self {};
79+
data
80+
}
81+
}
82+
83+
fn main() {}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
error: you seem to be trying to use match to destructure a single infallible pattern. Consider using `let`
2+
--> $DIR/infallible_destructuring_match.rs:16:5
3+
|
4+
16 | / let data = match wrapper {
5+
17 | | SingleVariantEnum::Variant(i) => i,
6+
18 | | };
7+
| |______^ help: try this: `let SingleVariantEnum::Variant(data) = wrapper;`
8+
|
9+
= note: `-D infallible-destructuring-match` implied by `-D warnings`
10+
11+
error: you seem to be trying to use match to destructure a single infallible pattern. Consider using `let`
12+
--> $DIR/infallible_destructuring_match.rs:37:5
13+
|
14+
37 | / let data = match wrapper {
15+
38 | | TupleStruct(i) => i,
16+
39 | | };
17+
| |______^ help: try this: `let TupleStruct(data) = wrapper;`
18+
19+
error: you seem to be trying to use match to destructure a single infallible pattern. Consider using `let`
20+
--> $DIR/infallible_destructuring_match.rs:58:5
21+
|
22+
58 | / let data = match wrapper {
23+
59 | | Ok(i) => i,
24+
60 | | };
25+
| |______^ help: try this: `let Ok(data) = wrapper;`
26+
27+
error: aborting due to 3 previous errors
28+

0 commit comments

Comments
 (0)