|
1 | | -use clippy_utils::diagnostics::span_lint_and_sugg; |
| 1 | +use clippy_utils::diagnostics::span_lint_and_sugg_for_edges; |
2 | 2 | use clippy_utils::is_trait_method; |
3 | | -use clippy_utils::source::snippet; |
| 3 | +use clippy_utils::source::snippet_with_applicability; |
4 | 4 | use clippy_utils::ty::is_type_diagnostic_item; |
5 | 5 | use rustc_errors::Applicability; |
6 | | -use rustc_hir as hir; |
| 6 | +use rustc_hir::Expr; |
7 | 7 | use rustc_lint::LateContext; |
8 | 8 | use rustc_middle::ty; |
9 | | -use rustc_span::symbol::sym; |
| 9 | +use rustc_span::{symbol::sym, Span}; |
10 | 10 |
|
11 | 11 | use super::MAP_FLATTEN; |
12 | 12 |
|
13 | 13 | /// lint use of `map().flatten()` for `Iterators` and 'Options' |
14 | | -pub(super) fn check<'tcx>( |
15 | | - cx: &LateContext<'tcx>, |
16 | | - expr: &'tcx hir::Expr<'_>, |
17 | | - recv: &'tcx hir::Expr<'_>, |
18 | | - map_arg: &'tcx hir::Expr<'_>, |
19 | | -) { |
20 | | - // lint if caller of `.map().flatten()` is an Iterator |
21 | | - if is_trait_method(cx, expr, sym::Iterator) { |
22 | | - let map_closure_ty = cx.typeck_results().expr_ty(map_arg); |
23 | | - let is_map_to_option = match map_closure_ty.kind() { |
24 | | - ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => { |
25 | | - let map_closure_sig = match map_closure_ty.kind() { |
26 | | - ty::Closure(_, substs) => substs.as_closure().sig(), |
27 | | - _ => map_closure_ty.fn_sig(cx.tcx), |
28 | | - }; |
29 | | - let map_closure_return_ty = cx.tcx.erase_late_bound_regions(map_closure_sig.output()); |
30 | | - is_type_diagnostic_item(cx, map_closure_return_ty, sym::Option) |
31 | | - }, |
32 | | - _ => false, |
33 | | - }; |
34 | | - |
35 | | - let method_to_use = if is_map_to_option { |
36 | | - // `(...).map(...)` has type `impl Iterator<Item=Option<...>> |
37 | | - "filter_map" |
38 | | - } else { |
39 | | - // `(...).map(...)` has type `impl Iterator<Item=impl Iterator<...>> |
40 | | - "flat_map" |
41 | | - }; |
42 | | - let func_snippet = snippet(cx, map_arg.span, ".."); |
43 | | - let hint = format!(".{0}({1})", method_to_use, func_snippet); |
44 | | - span_lint_and_sugg( |
| 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( |
45 | 23 | cx, |
46 | 24 | MAP_FLATTEN, |
47 | | - expr.span.with_lo(recv.span.hi()), |
48 | | - "called `map(..).flatten()` on an `Iterator`", |
49 | | - &format!("try using `{}` instead", method_to_use), |
50 | | - hint, |
51 | | - Applicability::MachineApplicable, |
| 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, |
52 | 30 | ); |
53 | 31 | } |
| 32 | +} |
54 | 33 |
|
55 | | - // lint if caller of `.map().flatten()` is an Option or Result |
56 | | - let caller_type = match cx.typeck_results().expr_ty(recv).kind() { |
57 | | - ty::Adt(adt, _) => { |
| 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() { |
58 | 50 | if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) { |
59 | | - "Option" |
| 51 | + return Some(("Option", "and_then")); |
60 | 52 | } else if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) { |
61 | | - "Result" |
62 | | - } else { |
63 | | - return; |
| 53 | + return Some(("Result", "and_then")); |
64 | 54 | } |
65 | | - }, |
66 | | - _ => { |
67 | | - return; |
68 | | - }, |
69 | | - }; |
| 55 | + } |
| 56 | + None |
| 57 | + } |
| 58 | +} |
70 | 59 |
|
71 | | - let func_snippet = snippet(cx, map_arg.span, ".."); |
72 | | - let hint = format!(".and_then({})", func_snippet); |
73 | | - let lint_info = format!("called `map(..).flatten()` on an `{}`", caller_type); |
74 | | - span_lint_and_sugg( |
75 | | - cx, |
76 | | - MAP_FLATTEN, |
77 | | - expr.span.with_lo(recv.span.hi()), |
78 | | - &lint_info, |
79 | | - "try using `and_then` instead", |
80 | | - hint, |
81 | | - Applicability::MachineApplicable, |
82 | | - ); |
| 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 | + } |
83 | 73 | } |
0 commit comments