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