]>
Commit | Line | Data |
---|---|---|
f20569fa | 1 | use super::{contains_return, BIND_INSTEAD_OF_MAP}; |
cdc7bbd5 XL |
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}; | |
a2a8927a | 4 | use clippy_utils::{peel_blocks, visitors::find_all_ret_expressions}; |
f20569fa XL |
5 | use if_chain::if_chain; |
6 | use rustc_errors::Applicability; | |
7 | use rustc_hir as hir; | |
cdc7bbd5 XL |
8 | use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; |
9 | use rustc_hir::{LangItem, QPath}; | |
f20569fa | 10 | use rustc_lint::LateContext; |
cdc7bbd5 | 11 | use rustc_middle::ty::DefIdTree; |
f20569fa XL |
12 | use rustc_span::Span; |
13 | ||
14 | pub(crate) struct OptionAndThenSome; | |
15 | ||
16 | impl 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 | ||
22 | pub(crate) struct ResultAndThenOk; | |
23 | ||
24 | impl 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 | ||
30 | pub(crate) struct ResultOrElseErrInfo; | |
31 | ||
32 | impl 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 | ||
38 | pub(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 | } |