]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/bind_instead_of_map.rs
Auto merge of #5937 - montrivo:option_if_let_else, r=flip1995
[rust.git] / clippy_lints / src / methods / bind_instead_of_map.rs
1 use super::{contains_return, BIND_INSTEAD_OF_MAP};
2 use crate::utils::{
3     in_macro, match_qpath, match_type, method_calls, multispan_sugg_with_applicability, paths, remove_blocks, snippet,
4     snippet_with_macro_callsite, span_lint_and_sugg, span_lint_and_then,
5 };
6 use if_chain::if_chain;
7 use rustc_errors::Applicability;
8 use rustc_hir as hir;
9 use rustc_hir::intravisit::{self, Visitor};
10 use rustc_lint::LateContext;
11 use rustc_middle::hir::map::Map;
12 use rustc_span::Span;
13
14 pub(crate) struct OptionAndThenSome;
15
16 impl BindInsteadOfMap for OptionAndThenSome {
17     const TYPE_NAME: &'static str = "Option";
18     const TYPE_QPATH: &'static [&'static str] = &paths::OPTION;
19
20     const BAD_METHOD_NAME: &'static str = "and_then";
21     const BAD_VARIANT_NAME: &'static str = "Some";
22     const BAD_VARIANT_QPATH: &'static [&'static str] = &paths::OPTION_SOME;
23
24     const GOOD_METHOD_NAME: &'static str = "map";
25 }
26
27 pub(crate) struct ResultAndThenOk;
28
29 impl BindInsteadOfMap for ResultAndThenOk {
30     const TYPE_NAME: &'static str = "Result";
31     const TYPE_QPATH: &'static [&'static str] = &paths::RESULT;
32
33     const BAD_METHOD_NAME: &'static str = "and_then";
34     const BAD_VARIANT_NAME: &'static str = "Ok";
35     const BAD_VARIANT_QPATH: &'static [&'static str] = &paths::RESULT_OK;
36
37     const GOOD_METHOD_NAME: &'static str = "map";
38 }
39
40 pub(crate) struct ResultOrElseErrInfo;
41
42 impl BindInsteadOfMap for ResultOrElseErrInfo {
43     const TYPE_NAME: &'static str = "Result";
44     const TYPE_QPATH: &'static [&'static str] = &paths::RESULT;
45
46     const BAD_METHOD_NAME: &'static str = "or_else";
47     const BAD_VARIANT_NAME: &'static str = "Err";
48     const BAD_VARIANT_QPATH: &'static [&'static str] = &paths::RESULT_ERR;
49
50     const GOOD_METHOD_NAME: &'static str = "map_err";
51 }
52
53 pub(crate) trait BindInsteadOfMap {
54     const TYPE_NAME: &'static str;
55     const TYPE_QPATH: &'static [&'static str];
56
57     const BAD_METHOD_NAME: &'static str;
58     const BAD_VARIANT_NAME: &'static str;
59     const BAD_VARIANT_QPATH: &'static [&'static str];
60
61     const GOOD_METHOD_NAME: &'static str;
62
63     fn no_op_msg() -> String {
64         format!(
65             "using `{}.{}({})`, which is a no-op",
66             Self::TYPE_NAME,
67             Self::BAD_METHOD_NAME,
68             Self::BAD_VARIANT_NAME
69         )
70     }
71
72     fn lint_msg() -> String {
73         format!(
74             "using `{}.{}(|x| {}(y))`, which is more succinctly expressed as `{}(|x| y)`",
75             Self::TYPE_NAME,
76             Self::BAD_METHOD_NAME,
77             Self::BAD_VARIANT_NAME,
78             Self::GOOD_METHOD_NAME
79         )
80     }
81
82     fn lint_closure_autofixable(
83         cx: &LateContext<'_>,
84         expr: &hir::Expr<'_>,
85         args: &[hir::Expr<'_>],
86         closure_expr: &hir::Expr<'_>,
87         closure_args_span: Span,
88     ) -> bool {
89         if_chain! {
90             if let hir::ExprKind::Call(ref some_expr, ref some_args) = closure_expr.kind;
91             if let hir::ExprKind::Path(ref qpath) = some_expr.kind;
92             if match_qpath(qpath, Self::BAD_VARIANT_QPATH);
93             if some_args.len() == 1;
94             then {
95                 let inner_expr = &some_args[0];
96
97                 if contains_return(inner_expr) {
98                     return false;
99                 }
100
101                 let some_inner_snip = if inner_expr.span.from_expansion() {
102                     snippet_with_macro_callsite(cx, inner_expr.span, "_")
103                 } else {
104                     snippet(cx, inner_expr.span, "_")
105                 };
106
107                 let closure_args_snip = snippet(cx, closure_args_span, "..");
108                 let option_snip = snippet(cx, args[0].span, "..");
109                 let note = format!("{}.{}({} {})", option_snip, Self::GOOD_METHOD_NAME, closure_args_snip, some_inner_snip);
110                 span_lint_and_sugg(
111                     cx,
112                     BIND_INSTEAD_OF_MAP,
113                     expr.span,
114                     Self::lint_msg().as_ref(),
115                     "try this",
116                     note,
117                     Applicability::MachineApplicable,
118                 );
119                 true
120             } else {
121                 false
122             }
123         }
124     }
125
126     fn lint_closure(cx: &LateContext<'_>, expr: &hir::Expr<'_>, closure_expr: &hir::Expr<'_>) -> bool {
127         let mut suggs = Vec::new();
128         let can_sugg: bool = find_all_ret_expressions(cx, closure_expr, |ret_expr| {
129             if_chain! {
130                 if !in_macro(ret_expr.span);
131                 if let hir::ExprKind::Call(ref func_path, ref args) = ret_expr.kind;
132                 if let hir::ExprKind::Path(ref qpath) = func_path.kind;
133                 if match_qpath(qpath, Self::BAD_VARIANT_QPATH);
134                 if args.len() == 1;
135                 if !contains_return(&args[0]);
136                 then {
137                     suggs.push((ret_expr.span, args[0].span.source_callsite()));
138                     true
139                 } else {
140                     false
141                 }
142             }
143         });
144
145         if can_sugg {
146             span_lint_and_then(cx, BIND_INSTEAD_OF_MAP, expr.span, Self::lint_msg().as_ref(), |diag| {
147                 multispan_sugg_with_applicability(
148                     diag,
149                     "try this",
150                     Applicability::MachineApplicable,
151                     std::iter::once((*method_calls(expr, 1).2.get(0).unwrap(), Self::GOOD_METHOD_NAME.into())).chain(
152                         suggs
153                             .into_iter()
154                             .map(|(span1, span2)| (span1, snippet(cx, span2, "_").into())),
155                     ),
156                 )
157             });
158         }
159         can_sugg
160     }
161
162     /// Lint use of `_.and_then(|x| Some(y))` for `Option`s
163     fn lint(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) -> bool {
164         if !match_type(cx, cx.typeck_results().expr_ty(&args[0]), Self::TYPE_QPATH) {
165             return false;
166         }
167
168         match args[1].kind {
169             hir::ExprKind::Closure(_, _, body_id, closure_args_span, _) => {
170                 let closure_body = cx.tcx.hir().body(body_id);
171                 let closure_expr = remove_blocks(&closure_body.value);
172
173                 if Self::lint_closure_autofixable(cx, expr, args, closure_expr, closure_args_span) {
174                     true
175                 } else {
176                     Self::lint_closure(cx, expr, closure_expr)
177                 }
178             },
179             // `_.and_then(Some)` case, which is no-op.
180             hir::ExprKind::Path(ref qpath) if match_qpath(qpath, Self::BAD_VARIANT_QPATH) => {
181                 span_lint_and_sugg(
182                     cx,
183                     BIND_INSTEAD_OF_MAP,
184                     expr.span,
185                     Self::no_op_msg().as_ref(),
186                     "use the expression directly",
187                     snippet(cx, args[0].span, "..").into(),
188                     Applicability::MachineApplicable,
189                 );
190                 true
191             },
192             _ => false,
193         }
194     }
195 }
196
197 /// returns `true` if expr contains match expr desugared from try
198 fn contains_try(expr: &hir::Expr<'_>) -> bool {
199     struct TryFinder {
200         found: bool,
201     }
202
203     impl<'hir> intravisit::Visitor<'hir> for TryFinder {
204         type Map = Map<'hir>;
205
206         fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
207             intravisit::NestedVisitorMap::None
208         }
209
210         fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
211             if self.found {
212                 return;
213             }
214             match expr.kind {
215                 hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar) => self.found = true,
216                 _ => intravisit::walk_expr(self, expr),
217             }
218         }
219     }
220
221     let mut visitor = TryFinder { found: false };
222     visitor.visit_expr(expr);
223     visitor.found
224 }
225
226 fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_>, expr: &'hir hir::Expr<'hir>, callback: F) -> bool
227 where
228     F: FnMut(&'hir hir::Expr<'hir>) -> bool,
229 {
230     struct RetFinder<F> {
231         in_stmt: bool,
232         failed: bool,
233         cb: F,
234     }
235
236     struct WithStmtGuarg<'a, F> {
237         val: &'a mut RetFinder<F>,
238         prev_in_stmt: bool,
239     }
240
241     impl<F> RetFinder<F> {
242         fn inside_stmt(&mut self, in_stmt: bool) -> WithStmtGuarg<'_, F> {
243             let prev_in_stmt = std::mem::replace(&mut self.in_stmt, in_stmt);
244             WithStmtGuarg {
245                 val: self,
246                 prev_in_stmt,
247             }
248         }
249     }
250
251     impl<F> std::ops::Deref for WithStmtGuarg<'_, F> {
252         type Target = RetFinder<F>;
253
254         fn deref(&self) -> &Self::Target {
255             self.val
256         }
257     }
258
259     impl<F> std::ops::DerefMut for WithStmtGuarg<'_, F> {
260         fn deref_mut(&mut self) -> &mut Self::Target {
261             self.val
262         }
263     }
264
265     impl<F> Drop for WithStmtGuarg<'_, F> {
266         fn drop(&mut self) {
267             self.val.in_stmt = self.prev_in_stmt;
268         }
269     }
270
271     impl<'hir, F: FnMut(&'hir hir::Expr<'hir>) -> bool> intravisit::Visitor<'hir> for RetFinder<F> {
272         type Map = Map<'hir>;
273
274         fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
275             intravisit::NestedVisitorMap::None
276         }
277
278         fn visit_stmt(&mut self, stmt: &'hir hir::Stmt<'_>) {
279             intravisit::walk_stmt(&mut *self.inside_stmt(true), stmt)
280         }
281
282         fn visit_expr(&mut self, expr: &'hir hir::Expr<'_>) {
283             if self.failed {
284                 return;
285             }
286             if self.in_stmt {
287                 match expr.kind {
288                     hir::ExprKind::Ret(Some(expr)) => self.inside_stmt(false).visit_expr(expr),
289                     _ => intravisit::walk_expr(self, expr),
290                 }
291             } else {
292                 match expr.kind {
293                     hir::ExprKind::Match(cond, arms, _) => {
294                         self.inside_stmt(true).visit_expr(cond);
295                         for arm in arms {
296                             self.visit_expr(arm.body);
297                         }
298                     },
299                     hir::ExprKind::Block(..) => intravisit::walk_expr(self, expr),
300                     hir::ExprKind::Ret(Some(expr)) => self.visit_expr(expr),
301                     _ => self.failed |= !(self.cb)(expr),
302                 }
303             }
304         }
305     }
306
307     !contains_try(expr) && {
308         let mut ret_finder = RetFinder {
309             in_stmt: false,
310             failed: false,
311             cb: callback,
312         };
313         ret_finder.visit_expr(expr);
314         !ret_finder.failed
315     }
316 }