]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 XL |
1 | use clippy_utils::diagnostics::span_lint_and_sugg; |
2 | use clippy_utils::source::{indent_of, reindent_multiline, snippet}; | |
3 | use clippy_utils::ty::is_type_diagnostic_item; | |
4 | use clippy_utils::{is_trait_method, path_to_local_id, remove_blocks, SpanlessEq}; | |
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::Res; |
9 | use rustc_hir::{Expr, ExprKind, PatKind, QPath, UnOp}; | |
f20569fa XL |
10 | use rustc_lint::LateContext; |
11 | use rustc_middle::ty::TyS; | |
cdc7bbd5 XL |
12 | use rustc_span::source_map::Span; |
13 | use rustc_span::symbol::{sym, Symbol}; | |
14 | use std::borrow::Cow; | |
f20569fa XL |
15 | |
16 | use super::MANUAL_FILTER_MAP; | |
17 | use super::MANUAL_FIND_MAP; | |
cdc7bbd5 XL |
18 | use super::OPTION_FILTER_MAP; |
19 | ||
20 | fn is_method<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, method_name: Symbol) -> bool { | |
21 | match &expr.kind { | |
22 | hir::ExprKind::Path(QPath::TypeRelative(_, mname)) => mname.ident.name == method_name, | |
23 | hir::ExprKind::Path(QPath::Resolved(_, segments)) => { | |
24 | segments.segments.last().unwrap().ident.name == method_name | |
25 | }, | |
26 | hir::ExprKind::Closure(_, _, c, _, _) => { | |
27 | let body = cx.tcx.hir().body(*c); | |
28 | let closure_expr = remove_blocks(&body.value); | |
29 | let arg_id = body.params[0].pat.hir_id; | |
30 | match closure_expr.kind { | |
31 | hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, _, args, _) => { | |
32 | if_chain! { | |
33 | if ident.name == method_name; | |
34 | if let hir::ExprKind::Path(path) = &args[0].kind; | |
35 | if let Res::Local(ref local) = cx.qpath_res(path, args[0].hir_id); | |
36 | then { | |
37 | return arg_id == *local | |
38 | } | |
39 | } | |
40 | false | |
41 | }, | |
42 | _ => false, | |
43 | } | |
44 | }, | |
45 | _ => false, | |
46 | } | |
47 | } | |
48 | ||
49 | fn is_option_filter_map<'tcx>(cx: &LateContext<'tcx>, filter_arg: &hir::Expr<'_>, map_arg: &hir::Expr<'_>) -> bool { | |
50 | is_method(cx, map_arg, sym::unwrap) && is_method(cx, filter_arg, sym!(is_some)) | |
51 | } | |
52 | ||
53 | /// lint use of `filter().map()` for `Iterators` | |
54 | fn lint_filter_some_map_unwrap( | |
55 | cx: &LateContext<'_>, | |
56 | expr: &hir::Expr<'_>, | |
57 | filter_recv: &hir::Expr<'_>, | |
58 | filter_arg: &hir::Expr<'_>, | |
59 | map_arg: &hir::Expr<'_>, | |
60 | target_span: Span, | |
61 | methods_span: Span, | |
62 | ) { | |
63 | let iterator = is_trait_method(cx, expr, sym::Iterator); | |
64 | let option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(filter_recv), sym::option_type); | |
65 | if (iterator || option) && is_option_filter_map(cx, filter_arg, map_arg) { | |
66 | let msg = "`filter` for `Some` followed by `unwrap`"; | |
67 | let help = "consider using `flatten` instead"; | |
68 | let sugg = format!( | |
69 | "{}", | |
70 | reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, target_span),) | |
71 | ); | |
72 | span_lint_and_sugg( | |
73 | cx, | |
74 | OPTION_FILTER_MAP, | |
75 | methods_span, | |
76 | msg, | |
77 | help, | |
78 | sugg, | |
79 | Applicability::MachineApplicable, | |
80 | ); | |
81 | } | |
82 | } | |
f20569fa XL |
83 | |
84 | /// lint use of `filter().map()` or `find().map()` for `Iterators` | |
cdc7bbd5 XL |
85 | #[allow(clippy::too_many_arguments)] |
86 | pub(super) fn check<'tcx>( | |
87 | cx: &LateContext<'tcx>, | |
88 | expr: &hir::Expr<'_>, | |
89 | filter_recv: &hir::Expr<'_>, | |
90 | filter_arg: &hir::Expr<'_>, | |
91 | filter_span: Span, | |
92 | map_recv: &hir::Expr<'_>, | |
93 | map_arg: &hir::Expr<'_>, | |
94 | map_span: Span, | |
95 | is_find: bool, | |
96 | ) { | |
97 | lint_filter_some_map_unwrap( | |
98 | cx, | |
99 | expr, | |
100 | filter_recv, | |
101 | filter_arg, | |
102 | map_arg, | |
103 | map_span, | |
104 | filter_span.with_hi(expr.span.hi()), | |
105 | ); | |
f20569fa | 106 | if_chain! { |
cdc7bbd5 | 107 | if is_trait_method(cx, map_recv, sym::Iterator); |
f20569fa | 108 | |
cdc7bbd5 XL |
109 | // filter(|x| ...is_some())... |
110 | if let ExprKind::Closure(_, _, filter_body_id, ..) = filter_arg.kind; | |
111 | let filter_body = cx.tcx.hir().body(filter_body_id); | |
112 | if let [filter_param] = filter_body.params; | |
113 | // optional ref pattern: `filter(|&x| ..)` | |
114 | let (filter_pat, is_filter_param_ref) = if let PatKind::Ref(ref_pat, _) = filter_param.pat.kind { | |
115 | (ref_pat, true) | |
116 | } else { | |
117 | (filter_param.pat, false) | |
118 | }; | |
119 | // closure ends with is_some() or is_ok() | |
120 | if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind; | |
121 | if let ExprKind::MethodCall(path, _, [filter_arg], _) = filter_body.value.kind; | |
122 | if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).ty_adt_def(); | |
123 | if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::option_type, opt_ty.did) { | |
124 | Some(false) | |
125 | } else if cx.tcx.is_diagnostic_item(sym::result_type, opt_ty.did) { | |
126 | Some(true) | |
127 | } else { | |
128 | None | |
129 | }; | |
130 | if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" }; | |
f20569fa | 131 | |
cdc7bbd5 XL |
132 | // ...map(|x| ...unwrap()) |
133 | if let ExprKind::Closure(_, _, map_body_id, ..) = map_arg.kind; | |
134 | let map_body = cx.tcx.hir().body(map_body_id); | |
135 | if let [map_param] = map_body.params; | |
136 | if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind; | |
137 | // closure ends with expect() or unwrap() | |
138 | if let ExprKind::MethodCall(seg, _, [map_arg, ..], _) = map_body.value.kind; | |
139 | if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or); | |
f20569fa | 140 | |
cdc7bbd5 XL |
141 | let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| { |
142 | // in `filter(|x| ..)`, replace `*x` with `x` | |
143 | let a_path = if_chain! { | |
144 | if !is_filter_param_ref; | |
145 | if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind; | |
146 | then { expr_path } else { a } | |
147 | }; | |
148 | // let the filter closure arg and the map closure arg be equal | |
149 | if_chain! { | |
150 | if path_to_local_id(a_path, filter_param_id); | |
151 | if path_to_local_id(b, map_param_id); | |
152 | if TyS::same_type(cx.typeck_results().expr_ty_adjusted(a), cx.typeck_results().expr_ty_adjusted(b)); | |
153 | then { | |
154 | return true; | |
155 | } | |
f20569fa | 156 | } |
cdc7bbd5 | 157 | false |
f20569fa | 158 | }; |
cdc7bbd5 XL |
159 | if SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg); |
160 | then { | |
161 | let span = filter_span.with_hi(expr.span.hi()); | |
162 | let (filter_name, lint) = if is_find { | |
163 | ("find", MANUAL_FIND_MAP) | |
164 | } else { | |
165 | ("filter", MANUAL_FILTER_MAP) | |
166 | }; | |
167 | let msg = format!("`{}(..).map(..)` can be simplified as `{0}_map(..)`", filter_name); | |
168 | let to_opt = if is_result { ".ok()" } else { "" }; | |
169 | let sugg = format!("{}_map(|{}| {}{})", filter_name, map_param_ident, | |
170 | snippet(cx, map_arg.span, ".."), to_opt); | |
171 | span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable); | |
172 | } | |
f20569fa XL |
173 | } |
174 | } |