]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/methods/map_flatten.rs
Merge commit 'd0cf3481a84e3aa68c2f185c460e282af36ebc42' into clippyup
[rust.git] / src / tools / clippy / clippy_lints / src / methods / map_flatten.rs
1 use clippy_utils::diagnostics::span_lint_and_sugg_for_edges;
2 use clippy_utils::is_trait_method;
3 use clippy_utils::source::snippet_with_applicability;
4 use clippy_utils::ty::is_type_diagnostic_item;
5 use rustc_errors::Applicability;
6 use rustc_hir::Expr;
7 use rustc_lint::LateContext;
8 use rustc_middle::ty;
9 use rustc_span::{symbol::sym, Span};
10
11 use super::MAP_FLATTEN;
12
13 /// lint use of `map().flatten()` for `Iterators` and 'Options'
14 pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, map_arg: &Expr<'_>, map_span: Span) {
15     if let Some((caller_ty_name, method_to_use)) = try_get_caller_ty_name_and_method_name(cx, expr, recv, map_arg) {
16         let mut applicability = Applicability::MachineApplicable;
17         let help_msgs = [
18             &format!("try replacing `map` with `{}`", method_to_use),
19             "and remove the `.flatten()`",
20         ];
21         let closure_snippet = snippet_with_applicability(cx, map_arg.span, "..", &mut applicability);
22         span_lint_and_sugg_for_edges(
23             cx,
24             MAP_FLATTEN,
25             expr.span.with_lo(map_span.lo()),
26             &format!("called `map(..).flatten()` on `{}`", caller_ty_name),
27             &help_msgs,
28             format!("{}({})", method_to_use, closure_snippet),
29             applicability,
30         );
31     }
32 }
33
34 fn try_get_caller_ty_name_and_method_name(
35     cx: &LateContext<'_>,
36     expr: &Expr<'_>,
37     caller_expr: &Expr<'_>,
38     map_arg: &Expr<'_>,
39 ) -> Option<(&'static str, &'static str)> {
40     if is_trait_method(cx, expr, sym::Iterator) {
41         if is_map_to_option(cx, map_arg) {
42             // `(...).map(...)` has type `impl Iterator<Item=Option<...>>
43             Some(("Iterator", "filter_map"))
44         } else {
45             // `(...).map(...)` has type `impl Iterator<Item=impl Iterator<...>>
46             Some(("Iterator", "flat_map"))
47         }
48     } else {
49         if let ty::Adt(adt, _) = cx.typeck_results().expr_ty(caller_expr).kind() {
50             if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) {
51                 return Some(("Option", "and_then"));
52             } else if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) {
53                 return Some(("Result", "and_then"));
54             }
55         }
56         None
57     }
58 }
59
60 fn is_map_to_option(cx: &LateContext<'_>, map_arg: &Expr<'_>) -> bool {
61     let map_closure_ty = cx.typeck_results().expr_ty(map_arg);
62     match map_closure_ty.kind() {
63         ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => {
64             let map_closure_sig = match map_closure_ty.kind() {
65                 ty::Closure(_, substs) => substs.as_closure().sig(),
66                 _ => map_closure_ty.fn_sig(cx.tcx),
67             };
68             let map_closure_return_ty = cx.tcx.erase_late_bound_regions(map_closure_sig.output());
69             is_type_diagnostic_item(cx, map_closure_return_ty, sym::Option)
70         },
71         _ => false,
72     }
73 }