]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/redundant_pattern_matching.rs
Merge branch 'master' into E0688
[rust.git] / src / tools / clippy / clippy_lints / src / redundant_pattern_matching.rs
1 use crate::utils::{in_constant, match_qpath, match_trait_method, paths, snippet, span_lint_and_then};
2 use if_chain::if_chain;
3 use rustc_ast::ast::LitKind;
4 use rustc_errors::Applicability;
5 use rustc_hir::{Arm, Expr, ExprKind, HirId, MatchSource, PatKind, QPath};
6 use rustc_lint::{LateContext, LateLintPass};
7 use rustc_middle::ty;
8 use rustc_mir::const_eval::is_const_fn;
9 use rustc_session::{declare_lint_pass, declare_tool_lint};
10 use rustc_span::source_map::Symbol;
11
12 declare_clippy_lint! {
13     /// **What it does:** Lint for redundant pattern matching over `Result` or
14     /// `Option`
15     ///
16     /// **Why is this bad?** It's more concise and clear to just use the proper
17     /// utility function
18     ///
19     /// **Known problems:** None.
20     ///
21     /// **Example:**
22     ///
23     /// ```rust
24     /// if let Ok(_) = Ok::<i32, i32>(42) {}
25     /// if let Err(_) = Err::<i32, i32>(42) {}
26     /// if let None = None::<()> {}
27     /// if let Some(_) = Some(42) {}
28     /// match Ok::<i32, i32>(42) {
29     ///     Ok(_) => true,
30     ///     Err(_) => false,
31     /// };
32     /// ```
33     ///
34     /// The more idiomatic use would be:
35     ///
36     /// ```rust
37     /// if Ok::<i32, i32>(42).is_ok() {}
38     /// if Err::<i32, i32>(42).is_err() {}
39     /// if None::<()>.is_none() {}
40     /// if Some(42).is_some() {}
41     /// Ok::<i32, i32>(42).is_ok();
42     /// ```
43     pub REDUNDANT_PATTERN_MATCHING,
44     style,
45     "use the proper utility function avoiding an `if let`"
46 }
47
48 declare_lint_pass!(RedundantPatternMatching => [REDUNDANT_PATTERN_MATCHING]);
49
50 impl<'tcx> LateLintPass<'tcx> for RedundantPatternMatching {
51     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
52         if let ExprKind::Match(op, arms, ref match_source) = &expr.kind {
53             match match_source {
54                 MatchSource::Normal => find_sugg_for_match(cx, expr, op, arms),
55                 MatchSource::IfLetDesugar { .. } => find_sugg_for_if_let(cx, expr, op, arms, "if"),
56                 MatchSource::WhileLetDesugar => find_sugg_for_if_let(cx, expr, op, arms, "while"),
57                 _ => return,
58             }
59         }
60     }
61 }
62
63 fn find_sugg_for_if_let<'tcx>(
64     cx: &LateContext<'tcx>,
65     expr: &'tcx Expr<'_>,
66     op: &Expr<'_>,
67     arms: &[Arm<'_>],
68     keyword: &'static str,
69 ) {
70     fn find_suggestion(cx: &LateContext<'_>, hir_id: HirId, path: &QPath<'_>) -> Option<&'static str> {
71         if match_qpath(path, &paths::RESULT_OK) && can_suggest(cx, hir_id, sym!(result_type), "is_ok") {
72             return Some("is_ok()");
73         }
74         if match_qpath(path, &paths::RESULT_ERR) && can_suggest(cx, hir_id, sym!(result_type), "is_err") {
75             return Some("is_err()");
76         }
77         if match_qpath(path, &paths::OPTION_SOME) && can_suggest(cx, hir_id, sym!(option_type), "is_some") {
78             return Some("is_some()");
79         }
80         if match_qpath(path, &paths::OPTION_NONE) && can_suggest(cx, hir_id, sym!(option_type), "is_none") {
81             return Some("is_none()");
82         }
83         None
84     }
85
86     let hir_id = expr.hir_id;
87     let good_method = match arms[0].pat.kind {
88         PatKind::TupleStruct(ref path, ref patterns, _) if patterns.len() == 1 => {
89             if let PatKind::Wild = patterns[0].kind {
90                 find_suggestion(cx, hir_id, path)
91             } else {
92                 None
93             }
94         },
95         PatKind::Path(ref path) => find_suggestion(cx, hir_id, path),
96         _ => None,
97     };
98     let good_method = match good_method {
99         Some(method) => method,
100         None => return,
101     };
102
103     // check that `while_let_on_iterator` lint does not trigger
104     if_chain! {
105         if keyword == "while";
106         if let ExprKind::MethodCall(method_path, _, _, _) = op.kind;
107         if method_path.ident.name == sym!(next);
108         if match_trait_method(cx, op, &paths::ITERATOR);
109         then {
110             return;
111         }
112     }
113
114     span_lint_and_then(
115         cx,
116         REDUNDANT_PATTERN_MATCHING,
117         arms[0].pat.span,
118         &format!("redundant pattern matching, consider using `{}`", good_method),
119         |diag| {
120             // while let ... = ... { ... }
121             // ^^^^^^^^^^^^^^^^^^^^^^^^^^^
122             let expr_span = expr.span;
123
124             // while let ... = ... { ... }
125             //                 ^^^
126             let op_span = op.span.source_callsite();
127
128             // while let ... = ... { ... }
129             // ^^^^^^^^^^^^^^^^^^^
130             let span = expr_span.until(op_span.shrink_to_hi());
131             diag.span_suggestion(
132                 span,
133                 "try this",
134                 format!("{} {}.{}", keyword, snippet(cx, op_span, "_"), good_method),
135                 Applicability::MachineApplicable, // snippet
136             );
137         },
138     );
139 }
140
141 fn find_sugg_for_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) {
142     if arms.len() == 2 {
143         let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind);
144
145         let hir_id = expr.hir_id;
146         let found_good_method = match node_pair {
147             (
148                 PatKind::TupleStruct(ref path_left, ref patterns_left, _),
149                 PatKind::TupleStruct(ref path_right, ref patterns_right, _),
150             ) if patterns_left.len() == 1 && patterns_right.len() == 1 => {
151                 if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) {
152                     find_good_method_for_match(
153                         arms,
154                         path_left,
155                         path_right,
156                         &paths::RESULT_OK,
157                         &paths::RESULT_ERR,
158                         "is_ok()",
159                         "is_err()",
160                         || can_suggest(cx, hir_id, sym!(result_type), "is_ok"),
161                         || can_suggest(cx, hir_id, sym!(result_type), "is_err"),
162                     )
163                 } else {
164                     None
165                 }
166             },
167             (PatKind::TupleStruct(ref path_left, ref patterns, _), PatKind::Path(ref path_right))
168             | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, ref patterns, _))
169                 if patterns.len() == 1 =>
170             {
171                 if let PatKind::Wild = patterns[0].kind {
172                     find_good_method_for_match(
173                         arms,
174                         path_left,
175                         path_right,
176                         &paths::OPTION_SOME,
177                         &paths::OPTION_NONE,
178                         "is_some()",
179                         "is_none()",
180                         || can_suggest(cx, hir_id, sym!(option_type), "is_some"),
181                         || can_suggest(cx, hir_id, sym!(option_type), "is_none"),
182                     )
183                 } else {
184                     None
185                 }
186             },
187             _ => None,
188         };
189
190         if let Some(good_method) = found_good_method {
191             span_lint_and_then(
192                 cx,
193                 REDUNDANT_PATTERN_MATCHING,
194                 expr.span,
195                 &format!("redundant pattern matching, consider using `{}`", good_method),
196                 |diag| {
197                     let span = expr.span.to(op.span);
198                     diag.span_suggestion(
199                         span,
200                         "try this",
201                         format!("{}.{}", snippet(cx, op.span, "_"), good_method),
202                         Applicability::MaybeIncorrect, // snippet
203                     );
204                 },
205             );
206         }
207     }
208 }
209
210 #[allow(clippy::too_many_arguments)]
211 fn find_good_method_for_match<'a>(
212     arms: &[Arm<'_>],
213     path_left: &QPath<'_>,
214     path_right: &QPath<'_>,
215     expected_left: &[&str],
216     expected_right: &[&str],
217     should_be_left: &'a str,
218     should_be_right: &'a str,
219     can_suggest_left: impl Fn() -> bool,
220     can_suggest_right: impl Fn() -> bool,
221 ) -> Option<&'a str> {
222     let body_node_pair = if match_qpath(path_left, expected_left) && match_qpath(path_right, expected_right) {
223         (&(*arms[0].body).kind, &(*arms[1].body).kind)
224     } else if match_qpath(path_right, expected_left) && match_qpath(path_left, expected_right) {
225         (&(*arms[1].body).kind, &(*arms[0].body).kind)
226     } else {
227         return None;
228     };
229
230     match body_node_pair {
231         (ExprKind::Lit(ref lit_left), ExprKind::Lit(ref lit_right)) => match (&lit_left.node, &lit_right.node) {
232             (LitKind::Bool(true), LitKind::Bool(false)) if can_suggest_left() => Some(should_be_left),
233             (LitKind::Bool(false), LitKind::Bool(true)) if can_suggest_right() => Some(should_be_right),
234             _ => None,
235         },
236         _ => None,
237     }
238 }
239
240 fn can_suggest(cx: &LateContext<'_>, hir_id: HirId, diag_item: Symbol, name: &str) -> bool {
241     if !in_constant(cx, hir_id) {
242         return true;
243     }
244
245     // Avoid suggesting calls to non-`const fn`s in const contexts, see #5697.
246     cx.tcx
247         .get_diagnostic_item(diag_item)
248         .and_then(|def_id| {
249             cx.tcx.inherent_impls(def_id).iter().find_map(|imp| {
250                 cx.tcx
251                     .associated_items(*imp)
252                     .in_definition_order()
253                     .find_map(|item| match item.kind {
254                         ty::AssocKind::Fn if item.ident.name.as_str() == name => Some(item.def_id),
255                         _ => None,
256                     })
257             })
258         })
259         .map_or(false, |def_id| is_const_fn(cx.tcx, def_id))
260 }