]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/redundant_pattern_matching.rs
Merge pull request #3281 from CYBAI/redundant-match
[rust.git] / clippy_lints / src / redundant_pattern_matching.rs
1 // Copyright 2014-2018 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution.
3 //
4 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. This file may not be copied, modified, or distributed
8 // except according to those terms.
9
10
11 use crate::rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
12 use crate::rustc::{declare_tool_lint, lint_array};
13 use crate::rustc::hir::*;
14 use crate::syntax::ptr::P;
15 use crate::syntax::ast::LitKind;
16 use crate::utils::{match_qpath, paths, snippet, span_lint_and_then};
17 use crate::rustc_errors::Applicability;
18
19 /// **What it does:** Lint for redundant pattern matching over `Result` or
20 /// `Option`
21 ///
22 /// **Why is this bad?** It's more concise and clear to just use the proper
23 /// utility function
24 ///
25 /// **Known problems:** None.
26 ///
27 /// **Example:**
28 ///
29 /// ```rust
30 /// if let Ok(_) = Ok::<i32, i32>(42) {}
31 /// if let Err(_) = Err::<i32, i32>(42) {}
32 /// if let None = None::<()> {}
33 /// if let Some(_) = Some(42) {}
34 /// match Ok::<i32, i32>(42) {
35 ///     Ok(_) => true,
36 ///     Err(_) => false,
37 /// };
38 /// ```
39 ///
40 /// The more idiomatic use would be:
41 ///
42 /// ```rust
43 /// if Ok::<i32, i32>(42).is_ok() {}
44 /// if Err::<i32, i32>(42).is_err() {}
45 /// if None::<()>.is_none() {}
46 /// if Some(42).is_some() {}
47 /// Ok::<i32, i32>(42).is_ok();
48 /// ```
49 ///
50 declare_clippy_lint! {
51     pub REDUNDANT_PATTERN_MATCHING,
52     style,
53     "use the proper utility function avoiding an `if let`"
54 }
55
56 #[derive(Copy, Clone)]
57 pub struct Pass;
58
59 impl LintPass for Pass {
60     fn get_lints(&self) -> LintArray {
61         lint_array!(REDUNDANT_PATTERN_MATCHING)
62     }
63 }
64
65 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
66     #[allow(clippy::similar_names)]
67     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
68         if let ExprKind::Match(ref op, ref arms, ref match_source) = expr.node {
69             match match_source {
70                 MatchSource::Normal => find_sugg_for_match(cx, expr, op, arms),
71                 MatchSource::IfLetDesugar { .. } => find_sugg_for_if_let(cx, expr, op, arms),
72                 _ => return,
73             }
74         }
75     }
76 }
77
78 fn find_sugg_for_if_let<'a, 'tcx>(
79     cx: &LateContext<'a, 'tcx>,
80     expr: &'tcx Expr,
81     op: &P<Expr>,
82     arms: &HirVec<Arm>
83 ) {
84     if arms[0].pats.len() == 1 {
85         let good_method = match arms[0].pats[0].node {
86             PatKind::TupleStruct(ref path, ref pats, _) if pats.len() == 1 => {
87                 if let PatKind::Wild = pats[0].node {
88                     if match_qpath(path, &paths::RESULT_OK) {
89                         "is_ok()"
90                     } else if match_qpath(path, &paths::RESULT_ERR) {
91                         "is_err()"
92                     } else if match_qpath(path, &paths::OPTION_SOME) {
93                         "is_some()"
94                     } else {
95                         return;
96                     }
97                 } else {
98                     return;
99                 }
100             },
101
102             PatKind::Path(ref path) if match_qpath(path, &paths::OPTION_NONE) => "is_none()",
103
104             _ => return,
105         };
106
107         span_lint_and_then(
108             cx,
109             REDUNDANT_PATTERN_MATCHING,
110             arms[0].pats[0].span,
111             &format!("redundant pattern matching, consider using `{}`", good_method),
112             |db| {
113                 let span = expr.span.to(op.span);
114                 db.span_suggestion_with_applicability(
115                     span,
116                     "try this",
117                     format!("if {}.{}", snippet(cx, op.span, "_"), good_method),
118                     Applicability::MachineApplicable, // snippet
119                 );
120             },
121         );
122     } else {
123         return;
124     }
125 }
126
127 fn find_sugg_for_match<'a, 'tcx>(
128     cx: &LateContext<'a, 'tcx>,
129     expr: &'tcx Expr,
130     op: &P<Expr>,
131     arms: &HirVec<Arm>
132 ) {
133     if arms.len() == 2 {
134         let node_pair = (&arms[0].pats[0].node, &arms[1].pats[0].node);
135
136         let found_good_method = match node_pair {
137             (
138                 PatKind::TupleStruct(ref path_left, ref pats_left, _),
139                 PatKind::TupleStruct(ref path_right, ref pats_right, _)
140             ) if pats_left.len() == 1 && pats_right.len() == 1 => {
141                 if let (PatKind::Wild, PatKind::Wild) = (&pats_left[0].node, &pats_right[0].node) {
142                     find_good_method_for_match(
143                         arms,
144                         path_left,
145                         path_right,
146                         &paths::RESULT_OK,
147                         &paths::RESULT_ERR,
148                         "is_ok()",
149                         "is_err()"
150                     )
151                 } else {
152                     None
153                 }
154             },
155             (
156                 PatKind::TupleStruct(ref path_left, ref pats, _),
157                 PatKind::Path(ref path_right)
158             ) | (
159                 PatKind::Path(ref path_left),
160                 PatKind::TupleStruct(ref path_right, ref pats, _)
161             ) if pats.len() == 1 => {
162                 if let PatKind::Wild = pats[0].node {
163                     find_good_method_for_match(
164                         arms,
165                         path_left,
166                         path_right,
167                         &paths::OPTION_SOME,
168                         &paths::OPTION_NONE,
169                         "is_some()",
170                         "is_none()"
171                     )
172                 } else {
173                     None
174                 }
175             },
176             _ => None,
177         };
178
179         if let Some(good_method) = found_good_method {
180             span_lint_and_then(
181                 cx,
182                 REDUNDANT_PATTERN_MATCHING,
183                 expr.span,
184                 &format!("redundant pattern matching, consider using `{}`", good_method),
185                 |db| {
186                     let span = expr.span.to(op.span);
187                     db.span_suggestion_with_applicability(
188                         span,
189                         "try this",
190                         format!("{}.{}", snippet(cx, op.span, "_"), good_method),
191                         Applicability::MachineApplicable, // snippet
192                     );
193                 },
194             );
195         }
196     } else {
197         return;
198     }
199 }
200
201 fn find_good_method_for_match<'a>(
202     arms: &HirVec<Arm>,
203     path_left: &QPath,
204     path_right: &QPath,
205     expected_left: &[&str],
206     expected_right: &[&str],
207     should_be_left: &'a str,
208     should_be_right: &'a str
209 ) -> Option<&'a str> {
210     let body_node_pair = if match_qpath(path_left, expected_left) && match_qpath(path_right, expected_right) {
211         (&(*arms[0].body).node, &(*arms[1].body).node)
212     } else if match_qpath(path_right, expected_left) && match_qpath(path_left, expected_right) {
213         (&(*arms[1].body).node, &(*arms[0].body).node)
214     } else {
215         return None;
216     };
217
218     match body_node_pair {
219         (ExprKind::Lit(ref lit_left), ExprKind::Lit(ref lit_right)) => {
220             match (&lit_left.node, &lit_right.node) {
221                 (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left),
222                 (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right),
223                 _ => None,
224             }
225         },
226         _ => None,
227     }
228 }