]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/redundant_pattern_matching.rs
Auto merge of #4921 - qoh:patch-1, r=phansch
[rust.git] / clippy_lints / src / redundant_pattern_matching.rs
1 use crate::utils::{match_qpath, paths, snippet, span_lint_and_then};
2 use rustc::declare_lint_pass;
3 use rustc::hir::ptr::P;
4 use rustc::hir::*;
5 use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
6 use rustc_errors::Applicability;
7 use rustc_session::declare_tool_lint;
8 use syntax::ast::LitKind;
9
10 declare_clippy_lint! {
11     /// **What it does:** Lint for redundant pattern matching over `Result` or
12     /// `Option`
13     ///
14     /// **Why is this bad?** It's more concise and clear to just use the proper
15     /// utility function
16     ///
17     /// **Known problems:** None.
18     ///
19     /// **Example:**
20     ///
21     /// ```rust
22     /// if let Ok(_) = Ok::<i32, i32>(42) {}
23     /// if let Err(_) = Err::<i32, i32>(42) {}
24     /// if let None = None::<()> {}
25     /// if let Some(_) = Some(42) {}
26     /// match Ok::<i32, i32>(42) {
27     ///     Ok(_) => true,
28     ///     Err(_) => false,
29     /// };
30     /// ```
31     ///
32     /// The more idiomatic use would be:
33     ///
34     /// ```rust
35     /// if Ok::<i32, i32>(42).is_ok() {}
36     /// if Err::<i32, i32>(42).is_err() {}
37     /// if None::<()>.is_none() {}
38     /// if Some(42).is_some() {}
39     /// Ok::<i32, i32>(42).is_ok();
40     /// ```
41     pub REDUNDANT_PATTERN_MATCHING,
42     style,
43     "use the proper utility function avoiding an `if let`"
44 }
45
46 declare_lint_pass!(RedundantPatternMatching => [REDUNDANT_PATTERN_MATCHING]);
47
48 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for RedundantPatternMatching {
49     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
50         if let ExprKind::Match(ref op, ref arms, ref match_source) = expr.kind {
51             match match_source {
52                 MatchSource::Normal => find_sugg_for_match(cx, expr, op, arms),
53                 MatchSource::IfLetDesugar { contains_else_clause } => {
54                     find_sugg_for_if_let(cx, expr, op, arms, *contains_else_clause)
55                 },
56                 _ => return,
57             }
58         }
59     }
60 }
61
62 fn find_sugg_for_if_let<'a, 'tcx>(
63     cx: &LateContext<'a, 'tcx>,
64     expr: &'tcx Expr,
65     op: &P<Expr>,
66     arms: &HirVec<Arm>,
67     has_else: bool,
68 ) {
69     let good_method = match arms[0].pat.kind {
70         PatKind::TupleStruct(ref path, ref patterns, _) if patterns.len() == 1 => {
71             if let PatKind::Wild = patterns[0].kind {
72                 if match_qpath(path, &paths::RESULT_OK) {
73                     "is_ok()"
74                 } else if match_qpath(path, &paths::RESULT_ERR) {
75                     "is_err()"
76                 } else if match_qpath(path, &paths::OPTION_SOME) {
77                     "is_some()"
78                 } else {
79                     return;
80                 }
81             } else {
82                 return;
83             }
84         },
85
86         PatKind::Path(ref path) if match_qpath(path, &paths::OPTION_NONE) => "is_none()",
87
88         _ => return,
89     };
90
91     let maybe_semi = if has_else { "" } else { ";" };
92
93     span_lint_and_then(
94         cx,
95         REDUNDANT_PATTERN_MATCHING,
96         arms[0].pat.span,
97         &format!("redundant pattern matching, consider using `{}`", good_method),
98         |db| {
99             let span = expr.span.to(op.span);
100             db.span_suggestion(
101                 span,
102                 "try this",
103                 format!("{}.{}{}", snippet(cx, op.span, "_"), good_method, maybe_semi),
104                 Applicability::MaybeIncorrect, // snippet
105             );
106         },
107     );
108 }
109
110 fn find_sugg_for_match<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr, op: &P<Expr>, arms: &HirVec<Arm>) {
111     if arms.len() == 2 {
112         let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind);
113
114         let found_good_method = match node_pair {
115             (
116                 PatKind::TupleStruct(ref path_left, ref patterns_left, _),
117                 PatKind::TupleStruct(ref path_right, ref patterns_right, _),
118             ) if patterns_left.len() == 1 && patterns_right.len() == 1 => {
119                 if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) {
120                     find_good_method_for_match(
121                         arms,
122                         path_left,
123                         path_right,
124                         &paths::RESULT_OK,
125                         &paths::RESULT_ERR,
126                         "is_ok()",
127                         "is_err()",
128                     )
129                 } else {
130                     None
131                 }
132             },
133             (PatKind::TupleStruct(ref path_left, ref patterns, _), PatKind::Path(ref path_right))
134             | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, ref patterns, _))
135                 if patterns.len() == 1 =>
136             {
137                 if let PatKind::Wild = patterns[0].kind {
138                     find_good_method_for_match(
139                         arms,
140                         path_left,
141                         path_right,
142                         &paths::OPTION_SOME,
143                         &paths::OPTION_NONE,
144                         "is_some()",
145                         "is_none()",
146                     )
147                 } else {
148                     None
149                 }
150             },
151             _ => None,
152         };
153
154         if let Some(good_method) = found_good_method {
155             span_lint_and_then(
156                 cx,
157                 REDUNDANT_PATTERN_MATCHING,
158                 expr.span,
159                 &format!("redundant pattern matching, consider using `{}`", good_method),
160                 |db| {
161                     let span = expr.span.to(op.span);
162                     db.span_suggestion(
163                         span,
164                         "try this",
165                         format!("{}.{}", snippet(cx, op.span, "_"), good_method),
166                         Applicability::MaybeIncorrect, // snippet
167                     );
168                 },
169             );
170         }
171     }
172 }
173
174 fn find_good_method_for_match<'a>(
175     arms: &HirVec<Arm>,
176     path_left: &QPath,
177     path_right: &QPath,
178     expected_left: &[&str],
179     expected_right: &[&str],
180     should_be_left: &'a str,
181     should_be_right: &'a str,
182 ) -> Option<&'a str> {
183     let body_node_pair = if match_qpath(path_left, expected_left) && match_qpath(path_right, expected_right) {
184         (&(*arms[0].body).kind, &(*arms[1].body).kind)
185     } else if match_qpath(path_right, expected_left) && match_qpath(path_left, expected_right) {
186         (&(*arms[1].body).kind, &(*arms[0].body).kind)
187     } else {
188         return None;
189     };
190
191     match body_node_pair {
192         (ExprKind::Lit(ref lit_left), ExprKind::Lit(ref lit_right)) => match (&lit_left.node, &lit_right.node) {
193             (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left),
194             (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right),
195             _ => None,
196         },
197         _ => None,
198     }
199 }