]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/methods/bind_instead_of_map.rs
New upstream version 1.63.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / methods / bind_instead_of_map.rs
CommitLineData
f20569fa 1use super::{contains_return, BIND_INSTEAD_OF_MAP};
cdc7bbd5
XL
2use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and_sugg, span_lint_and_then};
3use clippy_utils::source::{snippet, snippet_with_macro_callsite};
a2a8927a 4use clippy_utils::{peel_blocks, visitors::find_all_ret_expressions};
f20569fa
XL
5use if_chain::if_chain;
6use rustc_errors::Applicability;
7use rustc_hir as hir;
cdc7bbd5
XL
8use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
9use rustc_hir::{LangItem, QPath};
f20569fa 10use rustc_lint::LateContext;
cdc7bbd5 11use rustc_middle::ty::DefIdTree;
f20569fa
XL
12use rustc_span::Span;
13
14pub(crate) struct OptionAndThenSome;
15
16impl BindInsteadOfMap for OptionAndThenSome {
cdc7bbd5 17 const VARIANT_LANG_ITEM: LangItem = LangItem::OptionSome;
f20569fa 18 const BAD_METHOD_NAME: &'static str = "and_then";
f20569fa
XL
19 const GOOD_METHOD_NAME: &'static str = "map";
20}
21
22pub(crate) struct ResultAndThenOk;
23
24impl BindInsteadOfMap for ResultAndThenOk {
cdc7bbd5 25 const VARIANT_LANG_ITEM: LangItem = LangItem::ResultOk;
f20569fa 26 const BAD_METHOD_NAME: &'static str = "and_then";
f20569fa
XL
27 const GOOD_METHOD_NAME: &'static str = "map";
28}
29
30pub(crate) struct ResultOrElseErrInfo;
31
32impl BindInsteadOfMap for ResultOrElseErrInfo {
cdc7bbd5 33 const VARIANT_LANG_ITEM: LangItem = LangItem::ResultErr;
f20569fa 34 const BAD_METHOD_NAME: &'static str = "or_else";
f20569fa
XL
35 const GOOD_METHOD_NAME: &'static str = "map_err";
36}
37
38pub(crate) trait BindInsteadOfMap {
cdc7bbd5 39 const VARIANT_LANG_ITEM: LangItem;
f20569fa 40 const BAD_METHOD_NAME: &'static str;
f20569fa
XL
41 const GOOD_METHOD_NAME: &'static str;
42
cdc7bbd5
XL
43 fn no_op_msg(cx: &LateContext<'_>) -> Option<String> {
44 let variant_id = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM).ok()?;
04454e1e 45 let item_id = cx.tcx.parent(variant_id);
cdc7bbd5 46 Some(format!(
f20569fa 47 "using `{}.{}({})`, which is a no-op",
cdc7bbd5 48 cx.tcx.item_name(item_id),
f20569fa 49 Self::BAD_METHOD_NAME,
cdc7bbd5
XL
50 cx.tcx.item_name(variant_id),
51 ))
f20569fa
XL
52 }
53
cdc7bbd5
XL
54 fn lint_msg(cx: &LateContext<'_>) -> Option<String> {
55 let variant_id = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM).ok()?;
04454e1e 56 let item_id = cx.tcx.parent(variant_id);
cdc7bbd5 57 Some(format!(
f20569fa 58 "using `{}.{}(|x| {}(y))`, which is more succinctly expressed as `{}(|x| y)`",
cdc7bbd5 59 cx.tcx.item_name(item_id),
f20569fa 60 Self::BAD_METHOD_NAME,
cdc7bbd5 61 cx.tcx.item_name(variant_id),
f20569fa 62 Self::GOOD_METHOD_NAME
cdc7bbd5 63 ))
f20569fa
XL
64 }
65
66 fn lint_closure_autofixable(
67 cx: &LateContext<'_>,
68 expr: &hir::Expr<'_>,
cdc7bbd5 69 recv: &hir::Expr<'_>,
f20569fa
XL
70 closure_expr: &hir::Expr<'_>,
71 closure_args_span: Span,
72 ) -> bool {
73 if_chain! {
cdc7bbd5
XL
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);
f20569fa 79 then {
f20569fa
XL
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, "..");
cdc7bbd5 87 let option_snip = snippet(cx, recv.span, "..");
f20569fa
XL
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,
cdc7bbd5 93 &msg,
f20569fa
XL
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! {
a2a8927a 109 if !ret_expr.span.from_expansion();
cdc7bbd5
XL
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);
f20569fa 114 then {
cdc7bbd5 115 suggs.push((ret_expr.span, arg.span.source_callsite()));
f20569fa
XL
116 true
117 } else {
118 false
119 }
120 }
121 });
cdc7bbd5
XL
122 let (span, msg) = if_chain! {
123 if can_sugg;
5099ac24 124 if let hir::ExprKind::MethodCall(segment, ..) = expr.kind;
cdc7bbd5 125 if let Some(msg) = Self::lint_msg(cx);
5099ac24 126 then { (segment.ident.span, msg) } else { return false; }
cdc7bbd5
XL
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 ),
17df50a5 138 );
cdc7bbd5
XL
139 });
140 true
f20569fa
XL
141 }
142
143 /// Lint use of `_.and_then(|x| Some(y))` for `Option`s
cdc7bbd5
XL
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);
04454e1e 148 if adt.did() == cx.tcx.parent(vid);
cdc7bbd5 149 then {} else { return false; }
f20569fa
XL
150 }
151
cdc7bbd5 152 match arg.kind {
923072b8
FG
153 hir::ExprKind::Closure { body, fn_decl_span, .. } => {
154 let closure_body = cx.tcx.hir().body(body);
a2a8927a 155 let closure_expr = peel_blocks(&closure_body.value);
f20569fa 156
923072b8 157 if Self::lint_closure_autofixable(cx, expr, recv, closure_expr, fn_decl_span) {
f20569fa
XL
158 true
159 } else {
160 Self::lint_closure(cx, expr, closure_expr)
161 }
162 },
163 // `_.and_then(Some)` case, which is no-op.
cdc7bbd5
XL
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 }
f20569fa
XL
176 true
177 },
178 _ => false,
179 }
180 }
cdc7bbd5
XL
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) {
04454e1e 185 return cx.tcx.parent(id) == variant_id;
cdc7bbd5
XL
186 }
187 }
188 false
189 }
f20569fa 190}