|
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