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