]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/bind_instead_of_map.rs
Merge commit '6ed6f1e6a1a8f414ba7e6d9b8222e7e5a1686e42' into clippyup
[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, visitors::find_all_ret_expressions,
5 };
6 use if_chain::if_chain;
7 use rustc_errors::Applicability;
8 use rustc_hir as hir;
9 use rustc_lint::LateContext;
10 use rustc_span::Span;
11
12 pub(crate) struct OptionAndThenSome;
13
14 impl BindInsteadOfMap for OptionAndThenSome {
15     const TYPE_NAME: &'static str = "Option";
16     const TYPE_QPATH: &'static [&'static str] = &paths::OPTION;
17
18     const BAD_METHOD_NAME: &'static str = "and_then";
19     const BAD_VARIANT_NAME: &'static str = "Some";
20     const BAD_VARIANT_QPATH: &'static [&'static str] = &paths::OPTION_SOME;
21
22     const GOOD_METHOD_NAME: &'static str = "map";
23 }
24
25 pub(crate) struct ResultAndThenOk;
26
27 impl BindInsteadOfMap for ResultAndThenOk {
28     const TYPE_NAME: &'static str = "Result";
29     const TYPE_QPATH: &'static [&'static str] = &paths::RESULT;
30
31     const BAD_METHOD_NAME: &'static str = "and_then";
32     const BAD_VARIANT_NAME: &'static str = "Ok";
33     const BAD_VARIANT_QPATH: &'static [&'static str] = &paths::RESULT_OK;
34
35     const GOOD_METHOD_NAME: &'static str = "map";
36 }
37
38 pub(crate) struct ResultOrElseErrInfo;
39
40 impl BindInsteadOfMap for ResultOrElseErrInfo {
41     const TYPE_NAME: &'static str = "Result";
42     const TYPE_QPATH: &'static [&'static str] = &paths::RESULT;
43
44     const BAD_METHOD_NAME: &'static str = "or_else";
45     const BAD_VARIANT_NAME: &'static str = "Err";
46     const BAD_VARIANT_QPATH: &'static [&'static str] = &paths::RESULT_ERR;
47
48     const GOOD_METHOD_NAME: &'static str = "map_err";
49 }
50
51 pub(crate) trait BindInsteadOfMap {
52     const TYPE_NAME: &'static str;
53     const TYPE_QPATH: &'static [&'static str];
54
55     const BAD_METHOD_NAME: &'static str;
56     const BAD_VARIANT_NAME: &'static str;
57     const BAD_VARIANT_QPATH: &'static [&'static str];
58
59     const GOOD_METHOD_NAME: &'static str;
60
61     fn no_op_msg() -> String {
62         format!(
63             "using `{}.{}({})`, which is a no-op",
64             Self::TYPE_NAME,
65             Self::BAD_METHOD_NAME,
66             Self::BAD_VARIANT_NAME
67         )
68     }
69
70     fn lint_msg() -> String {
71         format!(
72             "using `{}.{}(|x| {}(y))`, which is more succinctly expressed as `{}(|x| y)`",
73             Self::TYPE_NAME,
74             Self::BAD_METHOD_NAME,
75             Self::BAD_VARIANT_NAME,
76             Self::GOOD_METHOD_NAME
77         )
78     }
79
80     fn lint_closure_autofixable(
81         cx: &LateContext<'_>,
82         expr: &hir::Expr<'_>,
83         args: &[hir::Expr<'_>],
84         closure_expr: &hir::Expr<'_>,
85         closure_args_span: Span,
86     ) -> bool {
87         if_chain! {
88             if let hir::ExprKind::Call(ref some_expr, ref some_args) = closure_expr.kind;
89             if let hir::ExprKind::Path(ref qpath) = some_expr.kind;
90             if match_qpath(qpath, Self::BAD_VARIANT_QPATH);
91             if some_args.len() == 1;
92             then {
93                 let inner_expr = &some_args[0];
94
95                 if contains_return(inner_expr) {
96                     return false;
97                 }
98
99                 let some_inner_snip = if inner_expr.span.from_expansion() {
100                     snippet_with_macro_callsite(cx, inner_expr.span, "_")
101                 } else {
102                     snippet(cx, inner_expr.span, "_")
103                 };
104
105                 let closure_args_snip = snippet(cx, closure_args_span, "..");
106                 let option_snip = snippet(cx, args[0].span, "..");
107                 let note = format!("{}.{}({} {})", option_snip, Self::GOOD_METHOD_NAME, closure_args_snip, some_inner_snip);
108                 span_lint_and_sugg(
109                     cx,
110                     BIND_INSTEAD_OF_MAP,
111                     expr.span,
112                     Self::lint_msg().as_ref(),
113                     "try this",
114                     note,
115                     Applicability::MachineApplicable,
116                 );
117                 true
118             } else {
119                 false
120             }
121         }
122     }
123
124     fn lint_closure(cx: &LateContext<'_>, expr: &hir::Expr<'_>, closure_expr: &hir::Expr<'_>) -> bool {
125         let mut suggs = Vec::new();
126         let can_sugg: bool = find_all_ret_expressions(cx, closure_expr, |ret_expr| {
127             if_chain! {
128                 if !in_macro(ret_expr.span);
129                 if let hir::ExprKind::Call(ref func_path, ref args) = ret_expr.kind;
130                 if let hir::ExprKind::Path(ref qpath) = func_path.kind;
131                 if match_qpath(qpath, Self::BAD_VARIANT_QPATH);
132                 if args.len() == 1;
133                 if !contains_return(&args[0]);
134                 then {
135                     suggs.push((ret_expr.span, args[0].span.source_callsite()));
136                     true
137                 } else {
138                     false
139                 }
140             }
141         });
142
143         if can_sugg {
144             span_lint_and_then(cx, BIND_INSTEAD_OF_MAP, expr.span, Self::lint_msg().as_ref(), |diag| {
145                 multispan_sugg_with_applicability(
146                     diag,
147                     "try this",
148                     Applicability::MachineApplicable,
149                     std::iter::once((*method_calls(expr, 1).2.get(0).unwrap(), Self::GOOD_METHOD_NAME.into())).chain(
150                         suggs
151                             .into_iter()
152                             .map(|(span1, span2)| (span1, snippet(cx, span2, "_").into())),
153                     ),
154                 )
155             });
156         }
157         can_sugg
158     }
159
160     /// Lint use of `_.and_then(|x| Some(y))` for `Option`s
161     fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) -> bool {
162         if !match_type(cx, cx.typeck_results().expr_ty(&args[0]), Self::TYPE_QPATH) {
163             return false;
164         }
165
166         match args[1].kind {
167             hir::ExprKind::Closure(_, _, body_id, closure_args_span, _) => {
168                 let closure_body = cx.tcx.hir().body(body_id);
169                 let closure_expr = remove_blocks(&closure_body.value);
170
171                 if Self::lint_closure_autofixable(cx, expr, args, closure_expr, closure_args_span) {
172                     true
173                 } else {
174                     Self::lint_closure(cx, expr, closure_expr)
175                 }
176             },
177             // `_.and_then(Some)` case, which is no-op.
178             hir::ExprKind::Path(ref qpath) if match_qpath(qpath, Self::BAD_VARIANT_QPATH) => {
179                 span_lint_and_sugg(
180                     cx,
181                     BIND_INSTEAD_OF_MAP,
182                     expr.span,
183                     Self::no_op_msg().as_ref(),
184                     "use the expression directly",
185                     snippet(cx, args[0].span, "..").into(),
186                     Applicability::MachineApplicable,
187                 );
188                 true
189             },
190             _ => false,
191         }
192     }
193 }