|
1 | 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}; |
| 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, remove_blocks, SpanlessEq}; |
4 | 5 | use if_chain::if_chain;
|
5 | 6 | use rustc_errors::Applicability;
|
6 | 7 | use rustc_hir as hir;
|
7 |
| -use rustc_hir::{Expr, ExprKind, PatKind, UnOp}; |
| 8 | +use rustc_hir::def::Res; |
| 9 | +use rustc_hir::{Expr, ExprKind, PatKind, QPath, UnOp}; |
8 | 10 | use rustc_lint::LateContext;
|
9 | 11 | use rustc_middle::ty::TyS;
|
10 |
| -use rustc_span::symbol::sym; |
| 12 | +use rustc_span::source_map::Span; |
| 13 | +use rustc_span::symbol::{sym, Symbol}; |
| 14 | +use std::borrow::Cow; |
11 | 15 |
|
12 | 16 | use super::MANUAL_FILTER_MAP;
|
13 | 17 | use super::MANUAL_FIND_MAP;
|
| 18 | +use super::OPTION_FILTER_MAP; |
| 19 | + |
| 20 | +fn is_method<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, method_name: Symbol) -> bool { |
| 21 | + match &expr.kind { |
| 22 | + hir::ExprKind::Path(QPath::TypeRelative(_, ref 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(_, _, c, _, _) => { |
| 27 | + let body = cx.tcx.hir().body(*c); |
| 28 | + let closure_expr = remove_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, .. }, _, ref args, _) => { |
| 32 | + if_chain! { |
| 33 | + if ident.name == method_name; |
| 34 | + if let hir::ExprKind::Path(path) = &args[0].kind; |
| 35 | + if let Res::Local(ref local) = cx.qpath_res(path, args[0].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<'tcx>( |
| 50 | + cx: &LateContext<'tcx>, |
| 51 | + filter_arg: &'tcx hir::Expr<'_>, |
| 52 | + map_arg: &'tcx hir::Expr<'_>, |
| 53 | +) -> bool { |
| 54 | + is_method(cx, map_arg, sym::unwrap) && is_method(cx, filter_arg, sym!(is_some)) |
| 55 | +} |
| 56 | + |
| 57 | +/// lint use of `filter().map()` for `Iterators` |
| 58 | +fn lint_filter_some_map_unwrap<'tcx>( |
| 59 | + cx: &LateContext<'tcx>, |
| 60 | + expr: &'tcx hir::Expr<'_>, |
| 61 | + filter_recv: &'tcx hir::Expr<'_>, |
| 62 | + filter_arg: &'tcx hir::Expr<'_>, |
| 63 | + map_arg: &'tcx hir::Expr<'_>, |
| 64 | + target_span: Span, |
| 65 | + methods_span: Span, |
| 66 | +) { |
| 67 | + let iterator = is_trait_method(cx, expr, sym::Iterator); |
| 68 | + let option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&filter_recv), sym::option_type); |
| 69 | + if (iterator || option) && is_option_filter_map(cx, filter_arg, map_arg) { |
| 70 | + let msg = "`filter` for `Some` followed by `unwrap`"; |
| 71 | + let help = "consider using `flatten` instead"; |
| 72 | + let sugg = format!( |
| 73 | + "{}", |
| 74 | + reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, target_span),) |
| 75 | + ); |
| 76 | + span_lint_and_sugg( |
| 77 | + cx, |
| 78 | + OPTION_FILTER_MAP, |
| 79 | + methods_span, |
| 80 | + msg, |
| 81 | + help, |
| 82 | + sugg, |
| 83 | + Applicability::MachineApplicable, |
| 84 | + ); |
| 85 | + } |
| 86 | +} |
14 | 87 |
|
15 | 88 | /// 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) { |
| 89 | +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, is_find: bool, target_span: Span) { |
17 | 90 | 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); |
| 91 | + if let ExprKind::MethodCall(_, _, [map_recv, map_arg], map_span) = expr.kind; |
| 92 | + if let ExprKind::MethodCall(_, _, [filter_recv, filter_arg], filter_span) = map_recv.kind; |
| 93 | + then { |
| 94 | + lint_filter_some_map_unwrap(cx, expr, filter_recv, filter_arg, |
| 95 | + map_arg, target_span, filter_span.to(map_span)); |
| 96 | + if_chain! { |
| 97 | + if is_trait_method(cx, map_recv, sym::Iterator); |
21 | 98 |
|
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" }; |
| 99 | + // filter(|x| ...is_some())... |
| 100 | + if let ExprKind::Closure(_, _, filter_body_id, ..) = filter_arg.kind; |
| 101 | + let filter_body = cx.tcx.hir().body(filter_body_id); |
| 102 | + if let [filter_param] = filter_body.params; |
| 103 | + // optional ref pattern: `filter(|&x| ..)` |
| 104 | + let (filter_pat, is_filter_param_ref) = if let PatKind::Ref(ref_pat, _) = filter_param.pat.kind { |
| 105 | + (ref_pat, true) |
| 106 | + } else { |
| 107 | + (filter_param.pat, false) |
| 108 | + }; |
| 109 | + // closure ends with is_some() or is_ok() |
| 110 | + if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind; |
| 111 | + if let ExprKind::MethodCall(path, _, [filter_arg], _) = filter_body.value.kind; |
| 112 | + if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).ty_adt_def(); |
| 113 | + if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::option_type, opt_ty.did) { |
| 114 | + Some(false) |
| 115 | + } else if cx.tcx.is_diagnostic_item(sym::result_type, opt_ty.did) { |
| 116 | + Some(true) |
| 117 | + } else { |
| 118 | + None |
| 119 | + }; |
| 120 | + if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" }; |
44 | 121 |
|
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); |
| 122 | + // ...map(|x| ...unwrap()) |
| 123 | + if let ExprKind::Closure(_, _, map_body_id, ..) = map_arg.kind; |
| 124 | + let map_body = cx.tcx.hir().body(map_body_id); |
| 125 | + if let [map_param] = map_body.params; |
| 126 | + if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind; |
| 127 | + // closure ends with expect() or unwrap() |
| 128 | + if let ExprKind::MethodCall(seg, _, [map_arg, ..], _) = map_body.value.kind; |
| 129 | + if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or); |
53 | 130 |
|
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; |
| 131 | + let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| { |
| 132 | + // in `filter(|x| ..)`, replace `*x` with `x` |
| 133 | + let a_path = if_chain! { |
| 134 | + if !is_filter_param_ref; |
| 135 | + if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind; |
| 136 | + then { expr_path } else { a } |
| 137 | + }; |
| 138 | + // let the filter closure arg and the map closure arg be equal |
| 139 | + if_chain! { |
| 140 | + if path_to_local_id(a_path, filter_param_id); |
| 141 | + if path_to_local_id(b, map_param_id); |
| 142 | + if TyS::same_type(cx.typeck_results().expr_ty_adjusted(a), cx.typeck_results().expr_ty_adjusted(b)); |
| 143 | + then { |
| 144 | + return true; |
| 145 | + } |
68 | 146 | }
|
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) |
| 147 | + false |
79 | 148 | };
|
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); |
| 149 | + if SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg); |
| 150 | + then { |
| 151 | + let span = filter_span.to(map_span); |
| 152 | + let (filter_name, lint) = if is_find { |
| 153 | + ("find", MANUAL_FIND_MAP) |
| 154 | + } else { |
| 155 | + ("filter", MANUAL_FILTER_MAP) |
| 156 | + }; |
| 157 | + let msg = format!("`{}(..).map(..)` can be simplified as `{0}_map(..)`", filter_name); |
| 158 | + let to_opt = if is_result { ".ok()" } else { "" }; |
| 159 | + let sugg = format!("{}_map(|{}| {}{})", filter_name, map_param_ident, |
| 160 | + snippet(cx, map_arg.span, ".."), to_opt); |
| 161 | + span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable); |
| 162 | + } |
| 163 | + } |
85 | 164 | }
|
86 | 165 | }
|
87 | 166 | }
|
0 commit comments