]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/filter_map.rs
Auto merge of #6957 - camsteffen:eq-ty-kind, r=flip1995
[rust.git] / clippy_lints / src / methods / filter_map.rs
1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use clippy_utils::source::snippet;
3 use clippy_utils::{is_trait_method, path_to_local_id, SpanlessEq};
4 use if_chain::if_chain;
5 use rustc_errors::Applicability;
6 use rustc_hir as hir;
7 use rustc_hir::{Expr, ExprKind, PatKind, UnOp};
8 use rustc_lint::LateContext;
9 use rustc_middle::ty::TyS;
10 use rustc_span::symbol::sym;
11
12 use super::MANUAL_FILTER_MAP;
13 use super::MANUAL_FIND_MAP;
14
15 /// lint use of `filter().map()` or `find().map()` for `Iterators`
16 pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, is_find: bool) {
17     if_chain! {
18         if let ExprKind::MethodCall(_, _, [map_recv, map_arg], map_span) = expr.kind;
19         if let ExprKind::MethodCall(_, _, [_, filter_arg], filter_span) = map_recv.kind;
20         if is_trait_method(cx, map_recv, sym::Iterator);
21
22         // filter(|x| ...is_some())...
23         if let ExprKind::Closure(_, _, filter_body_id, ..) = filter_arg.kind;
24         let filter_body = cx.tcx.hir().body(filter_body_id);
25         if let [filter_param] = filter_body.params;
26         // optional ref pattern: `filter(|&x| ..)`
27         let (filter_pat, is_filter_param_ref) = if let PatKind::Ref(ref_pat, _) = filter_param.pat.kind {
28             (ref_pat, true)
29         } else {
30             (filter_param.pat, false)
31         };
32         // closure ends with is_some() or is_ok()
33         if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind;
34         if let ExprKind::MethodCall(path, _, [filter_arg], _) = filter_body.value.kind;
35         if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).ty_adt_def();
36         if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::option_type, opt_ty.did) {
37             Some(false)
38         } else if cx.tcx.is_diagnostic_item(sym::result_type, opt_ty.did) {
39             Some(true)
40         } else {
41             None
42         };
43         if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" };
44
45         // ...map(|x| ...unwrap())
46         if let ExprKind::Closure(_, _, map_body_id, ..) = map_arg.kind;
47         let map_body = cx.tcx.hir().body(map_body_id);
48         if let [map_param] = map_body.params;
49         if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind;
50         // closure ends with expect() or unwrap()
51         if let ExprKind::MethodCall(seg, _, [map_arg, ..], _) = map_body.value.kind;
52         if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or);
53
54         let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
55             // in `filter(|x| ..)`, replace `*x` with `x`
56             let a_path = if_chain! {
57                 if !is_filter_param_ref;
58                 if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind;
59                 then { expr_path } else { a }
60             };
61             // let the filter closure arg and the map closure arg be equal
62             if_chain! {
63                 if path_to_local_id(a_path, filter_param_id);
64                 if path_to_local_id(b, map_param_id);
65                 if TyS::same_type(cx.typeck_results().expr_ty_adjusted(a), cx.typeck_results().expr_ty_adjusted(b));
66                 then {
67                     return true;
68                 }
69             }
70             false
71         };
72         if SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg);
73         then {
74             let span = filter_span.to(map_span);
75             let (filter_name, lint) = if is_find {
76                 ("find", MANUAL_FIND_MAP)
77             } else {
78                 ("filter", MANUAL_FILTER_MAP)
79             };
80             let msg = format!("`{}(..).map(..)` can be simplified as `{0}_map(..)`", filter_name);
81             let to_opt = if is_result { ".ok()" } else { "" };
82             let sugg = format!("{}_map(|{}| {}{})", filter_name, map_param_ident,
83                 snippet(cx, map_arg.span, ".."), to_opt);
84             span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable);
85         }
86     }
87 }