]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/bind_instead_of_map.rs
Auto merge of #83213 - rylev:update-lints-to-errors, r=nikomatsakis
[rust.git] / clippy_lints / src / methods / bind_instead_of_map.rs
1 use super::{contains_return, BIND_INSTEAD_OF_MAP};
2 use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and_sugg, span_lint_and_then};
3 use clippy_utils::source::{snippet, snippet_with_macro_callsite};
4 use clippy_utils::{in_macro, remove_blocks, visitors::find_all_ret_expressions};
5 use if_chain::if_chain;
6 use rustc_errors::Applicability;
7 use rustc_hir as hir;
8 use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
9 use rustc_hir::{LangItem, QPath};
10 use rustc_lint::LateContext;
11 use rustc_middle::ty::DefIdTree;
12 use rustc_span::Span;
13
14 pub(crate) struct OptionAndThenSome;
15
16 impl BindInsteadOfMap for OptionAndThenSome {
17     const VARIANT_LANG_ITEM: LangItem = LangItem::OptionSome;
18     const BAD_METHOD_NAME: &'static str = "and_then";
19     const GOOD_METHOD_NAME: &'static str = "map";
20 }
21
22 pub(crate) struct ResultAndThenOk;
23
24 impl BindInsteadOfMap for ResultAndThenOk {
25     const VARIANT_LANG_ITEM: LangItem = LangItem::ResultOk;
26     const BAD_METHOD_NAME: &'static str = "and_then";
27     const GOOD_METHOD_NAME: &'static str = "map";
28 }
29
30 pub(crate) struct ResultOrElseErrInfo;
31
32 impl BindInsteadOfMap for ResultOrElseErrInfo {
33     const VARIANT_LANG_ITEM: LangItem = LangItem::ResultErr;
34     const BAD_METHOD_NAME: &'static str = "or_else";
35     const GOOD_METHOD_NAME: &'static str = "map_err";
36 }
37
38 pub(crate) trait BindInsteadOfMap {
39     const VARIANT_LANG_ITEM: LangItem;
40     const BAD_METHOD_NAME: &'static str;
41     const GOOD_METHOD_NAME: &'static str;
42
43     fn no_op_msg(cx: &LateContext<'_>) -> Option<String> {
44         let variant_id = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM).ok()?;
45         let item_id = cx.tcx.parent(variant_id)?;
46         Some(format!(
47             "using `{}.{}({})`, which is a no-op",
48             cx.tcx.item_name(item_id),
49             Self::BAD_METHOD_NAME,
50             cx.tcx.item_name(variant_id),
51         ))
52     }
53
54     fn lint_msg(cx: &LateContext<'_>) -> Option<String> {
55         let variant_id = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM).ok()?;
56         let item_id = cx.tcx.parent(variant_id)?;
57         Some(format!(
58             "using `{}.{}(|x| {}(y))`, which is more succinctly expressed as `{}(|x| y)`",
59             cx.tcx.item_name(item_id),
60             Self::BAD_METHOD_NAME,
61             cx.tcx.item_name(variant_id),
62             Self::GOOD_METHOD_NAME
63         ))
64     }
65
66     fn lint_closure_autofixable(
67         cx: &LateContext<'_>,
68         expr: &hir::Expr<'_>,
69         recv: &hir::Expr<'_>,
70         closure_expr: &hir::Expr<'_>,
71         closure_args_span: Span,
72     ) -> bool {
73         if_chain! {
74             if let hir::ExprKind::Call(some_expr, [inner_expr]) = closure_expr.kind;
75             if let hir::ExprKind::Path(QPath::Resolved(_, path)) = some_expr.kind;
76             if Self::is_variant(cx, path.res);
77             if !contains_return(inner_expr);
78             if let Some(msg) = Self::lint_msg(cx);
79             then {
80                 let some_inner_snip = if inner_expr.span.from_expansion() {
81                     snippet_with_macro_callsite(cx, inner_expr.span, "_")
82                 } else {
83                     snippet(cx, inner_expr.span, "_")
84                 };
85
86                 let closure_args_snip = snippet(cx, closure_args_span, "..");
87                 let option_snip = snippet(cx, recv.span, "..");
88                 let note = format!("{}.{}({} {})", option_snip, Self::GOOD_METHOD_NAME, closure_args_snip, some_inner_snip);
89                 span_lint_and_sugg(
90                     cx,
91                     BIND_INSTEAD_OF_MAP,
92                     expr.span,
93                     &msg,
94                     "try this",
95                     note,
96                     Applicability::MachineApplicable,
97                 );
98                 true
99             } else {
100                 false
101             }
102         }
103     }
104
105     fn lint_closure(cx: &LateContext<'_>, expr: &hir::Expr<'_>, closure_expr: &hir::Expr<'_>) -> bool {
106         let mut suggs = Vec::new();
107         let can_sugg: bool = find_all_ret_expressions(cx, closure_expr, |ret_expr| {
108             if_chain! {
109                 if !in_macro(ret_expr.span);
110                 if let hir::ExprKind::Call(func_path, [arg]) = ret_expr.kind;
111                 if let hir::ExprKind::Path(QPath::Resolved(_, path)) = func_path.kind;
112                 if Self::is_variant(cx, path.res);
113                 if !contains_return(arg);
114                 then {
115                     suggs.push((ret_expr.span, arg.span.source_callsite()));
116                     true
117                 } else {
118                     false
119                 }
120             }
121         });
122         let (span, msg) = if_chain! {
123             if can_sugg;
124             if let hir::ExprKind::MethodCall(_, span, ..) = expr.kind;
125             if let Some(msg) = Self::lint_msg(cx);
126             then { (span, msg) } else { return false; }
127         };
128         span_lint_and_then(cx, BIND_INSTEAD_OF_MAP, expr.span, &msg, |diag| {
129             multispan_sugg_with_applicability(
130                 diag,
131                 "try this",
132                 Applicability::MachineApplicable,
133                 std::iter::once((span, Self::GOOD_METHOD_NAME.into())).chain(
134                     suggs
135                         .into_iter()
136                         .map(|(span1, span2)| (span1, snippet(cx, span2, "_").into())),
137                 ),
138             )
139         });
140         true
141     }
142
143     /// Lint use of `_.and_then(|x| Some(y))` for `Option`s
144     fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) -> bool {
145         if_chain! {
146             if let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def();
147             if let Ok(vid) = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM);
148             if Some(adt.did) == cx.tcx.parent(vid);
149             then {} else { return false; }
150         }
151
152         match arg.kind {
153             hir::ExprKind::Closure(_, _, body_id, closure_args_span, _) => {
154                 let closure_body = cx.tcx.hir().body(body_id);
155                 let closure_expr = remove_blocks(&closure_body.value);
156
157                 if Self::lint_closure_autofixable(cx, expr, recv, closure_expr, closure_args_span) {
158                     true
159                 } else {
160                     Self::lint_closure(cx, expr, closure_expr)
161                 }
162             },
163             // `_.and_then(Some)` case, which is no-op.
164             hir::ExprKind::Path(QPath::Resolved(_, path)) if Self::is_variant(cx, path.res) => {
165                 if let Some(msg) = Self::no_op_msg(cx) {
166                     span_lint_and_sugg(
167                         cx,
168                         BIND_INSTEAD_OF_MAP,
169                         expr.span,
170                         &msg,
171                         "use the expression directly",
172                         snippet(cx, recv.span, "..").into(),
173                         Applicability::MachineApplicable,
174                     );
175                 }
176                 true
177             },
178             _ => false,
179         }
180     }
181
182     fn is_variant(cx: &LateContext<'_>, res: Res) -> bool {
183         if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res {
184             if let Ok(variant_id) = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM) {
185                 return cx.tcx.parent(id) == Some(variant_id);
186             }
187         }
188         false
189     }
190 }