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