]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/methods/filter_map.rs
Rollup merge of #105623 - compiler-errors:generator-type-size-fix, r=Nilstrieb
[rust.git] / src / tools / clippy / 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_middle::ty::adjustment::Adjust;
12 use rustc_span::source_map::Span;
13 use rustc_span::symbol::{sym, Symbol};
14 use std::borrow::Cow;
15
16 use super::MANUAL_FILTER_MAP;
17 use super::MANUAL_FIND_MAP;
18 use super::OPTION_FILTER_MAP;
19
20 fn is_method(cx: &LateContext<'_>, 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(&hir::Closure { body, .. }) => {
27             let body = cx.tcx.hir().body(body);
28             let closure_expr = peel_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, .. }, receiver, ..) => {
32                     if_chain! {
33                     if ident.name == method_name;
34                     if let hir::ExprKind::Path(path) = &receiver.kind;
35                     if let Res::Local(ref local) = cx.qpath_res(path, receiver.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(cx: &LateContext<'_>, 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 /// is `filter(|x| x.is_some()).map(|x| x.unwrap())`
54 fn is_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 ) -> bool {
61     let iterator = is_trait_method(cx, expr, sym::Iterator);
62     let option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(filter_recv), sym::Option);
63
64     (iterator || option) && is_option_filter_map(cx, filter_arg, map_arg)
65 }
66
67 /// lint use of `filter().map()` or `find().map()` for `Iterators`
68 #[allow(clippy::too_many_arguments)]
69 pub(super) fn check(
70     cx: &LateContext<'_>,
71     expr: &hir::Expr<'_>,
72     filter_recv: &hir::Expr<'_>,
73     filter_arg: &hir::Expr<'_>,
74     filter_span: Span,
75     map_recv: &hir::Expr<'_>,
76     map_arg: &hir::Expr<'_>,
77     map_span: Span,
78     is_find: bool,
79 ) {
80     if is_filter_some_map_unwrap(cx, expr, filter_recv, filter_arg, map_arg) {
81         span_lint_and_sugg(
82             cx,
83             OPTION_FILTER_MAP,
84             filter_span.with_hi(expr.span.hi()),
85             "`filter` for `Some` followed by `unwrap`",
86             "consider using `flatten` instead",
87             reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, map_span)).into_owned(),
88             Applicability::MachineApplicable,
89         );
90
91         return;
92     }
93
94     if_chain! {
95             if is_trait_method(cx, map_recv, sym::Iterator);
96
97             // filter(|x| ...is_some())...
98             if let ExprKind::Closure(&Closure { body: filter_body_id, .. }) = filter_arg.kind;
99             let filter_body = cx.tcx.hir().body(filter_body_id);
100             if let [filter_param] = filter_body.params;
101             // optional ref pattern: `filter(|&x| ..)`
102             let (filter_pat, is_filter_param_ref) = if let PatKind::Ref(ref_pat, _) = filter_param.pat.kind {
103                 (ref_pat, true)
104             } else {
105                 (filter_param.pat, false)
106             };
107             // closure ends with is_some() or is_ok()
108             if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind;
109             if let ExprKind::MethodCall(path, filter_arg, [], _) = filter_body.value.kind;
110             if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).peel_refs().ty_adt_def();
111             if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::Option, opt_ty.did()) {
112                 Some(false)
113             } else if cx.tcx.is_diagnostic_item(sym::Result, opt_ty.did()) {
114                 Some(true)
115             } else {
116                 None
117             };
118             if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" };
119
120             // ...map(|x| ...unwrap())
121             if let ExprKind::Closure(&Closure { body: map_body_id, .. }) = map_arg.kind;
122             let map_body = cx.tcx.hir().body(map_body_id);
123             if let [map_param] = map_body.params;
124             if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind;
125             // closure ends with expect() or unwrap()
126             if let ExprKind::MethodCall(seg, map_arg, ..) = map_body.value.kind;
127             if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or);
128
129             // .filter(..).map(|y| f(y).copied().unwrap())
130             //                     ~~~~
131             let map_arg_peeled = match map_arg.kind {
132                 ExprKind::MethodCall(method, original_arg, [], _) if acceptable_methods(method) => {
133                     original_arg
134                 },
135                 _ => map_arg,
136             };
137
138             // .filter(|x| x.is_some()).map(|y| y[.acceptable_method()].unwrap())
139             let simple_equal = path_to_local_id(filter_arg, filter_param_id)
140                 && path_to_local_id(map_arg_peeled, map_param_id);
141
142             let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
143                 // in `filter(|x| ..)`, replace `*x` with `x`
144                 let a_path = if_chain! {
145                     if !is_filter_param_ref;
146                     if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind;
147                     then { expr_path } else { a }
148                 };
149                 // let the filter closure arg and the map closure arg be equal
150                 path_to_local_id(a_path, filter_param_id)
151                     && path_to_local_id(b, map_param_id)
152                     && cx.typeck_results().expr_ty_adjusted(a) == cx.typeck_results().expr_ty_adjusted(b)
153             };
154
155             if simple_equal || SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg_peeled);
156             then {
157                 let span = filter_span.with_hi(expr.span.hi());
158                 let (filter_name, lint) = if is_find {
159                     ("find", MANUAL_FIND_MAP)
160                 } else {
161                     ("filter", MANUAL_FILTER_MAP)
162                 };
163                 let msg = format!("`{filter_name}(..).map(..)` can be simplified as `{filter_name}_map(..)`");
164                 let (to_opt, deref) = if is_result {
165                     (".ok()", String::new())
166                 } else {
167                     let derefs = cx.typeck_results()
168                         .expr_adjustments(map_arg)
169                         .iter()
170                         .filter(|adj| matches!(adj.kind, Adjust::Deref(_)))
171                         .count();
172
173                     ("", "*".repeat(derefs))
174                 };
175                 let sugg = format!(
176                     "{filter_name}_map(|{map_param_ident}| {deref}{}{to_opt})",
177                     snippet(cx, map_arg.span, ".."),
178                 );
179                 span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable);
180             }
181     }
182 }
183
184 fn acceptable_methods(method: &PathSegment<'_>) -> bool {
185     let methods: [Symbol; 8] = [
186         sym::clone,
187         sym::as_ref,
188         sym!(copied),
189         sym!(cloned),
190         sym!(as_deref),
191         sym!(as_mut),
192         sym!(as_deref_mut),
193         sym!(to_owned),
194     ];
195
196     methods.contains(&method.ident.name)
197 }