1 use clippy_utils
::diagnostics
::span_lint_and_sugg
;
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, peel_blocks, SpanlessEq}
;
5 use if_chain
::if_chain
;
6 use rustc_errors
::Applicability
;
8 use rustc_hir
::def
::Res
;
9 use rustc_hir
::{Expr, ExprKind, PatKind, PathSegment, QPath, UnOp}
;
10 use rustc_lint
::LateContext
;
11 use rustc_span
::source_map
::Span
;
12 use rustc_span
::symbol
::{sym, Symbol}
;
15 use super::MANUAL_FILTER_MAP
;
16 use super::MANUAL_FIND_MAP
;
17 use super::OPTION_FILTER_MAP
;
19 fn is_method
<'tcx
>(cx
: &LateContext
<'tcx
>, expr
: &hir
::Expr
<'_
>, method_name
: Symbol
) -> bool
{
21 hir
::ExprKind
::Path(QPath
::TypeRelative(_
, mname
)) => mname
.ident
.name
== method_name
,
22 hir
::ExprKind
::Path(QPath
::Resolved(_
, segments
)) => {
23 segments
.segments
.last().unwrap().ident
.name
== method_name
25 hir
::ExprKind
::Closure { body, .. }
=> {
26 let body
= cx
.tcx
.hir().body(*body
);
27 let closure_expr
= peel_blocks(&body
.value
);
28 let arg_id
= body
.params
[0].pat
.hir_id
;
29 match closure_expr
.kind
{
30 hir
::ExprKind
::MethodCall(hir
::PathSegment { ident, .. }
, args
, _
) => {
32 if ident
.name
== method_name
;
33 if let hir
::ExprKind
::Path(path
) = &args
[0].kind
;
34 if let Res
::Local(ref local
) = cx
.qpath_res(path
, args
[0].hir_id
);
36 return arg_id
== *local
48 fn is_option_filter_map
<'tcx
>(cx
: &LateContext
<'tcx
>, filter_arg
: &hir
::Expr
<'_
>, map_arg
: &hir
::Expr
<'_
>) -> bool
{
49 is_method(cx
, map_arg
, sym
::unwrap
) && is_method(cx
, filter_arg
, sym
!(is_some
))
52 /// lint use of `filter().map()` for `Iterators`
53 fn lint_filter_some_map_unwrap(
56 filter_recv
: &hir
::Expr
<'_
>,
57 filter_arg
: &hir
::Expr
<'_
>,
58 map_arg
: &hir
::Expr
<'_
>,
62 let iterator
= is_trait_method(cx
, expr
, sym
::Iterator
);
63 let option
= is_type_diagnostic_item(cx
, cx
.typeck_results().expr_ty(filter_recv
), sym
::Option
);
64 if (iterator
|| option
) && is_option_filter_map(cx
, filter_arg
, map_arg
) {
65 let msg
= "`filter` for `Some` followed by `unwrap`";
66 let help
= "consider using `flatten` instead";
69 reindent_multiline(Cow
::Borrowed("flatten()"), true, indent_of(cx
, target_span
),)
78 Applicability
::MachineApplicable
,
83 /// lint use of `filter().map()` or `find().map()` for `Iterators`
84 #[allow(clippy::too_many_arguments)]
85 pub(super) fn check
<'tcx
>(
86 cx
: &LateContext
<'tcx
>,
88 filter_recv
: &hir
::Expr
<'_
>,
89 filter_arg
: &hir
::Expr
<'_
>,
91 map_recv
: &hir
::Expr
<'_
>,
92 map_arg
: &hir
::Expr
<'_
>,
96 lint_filter_some_map_unwrap(
103 filter_span
.with_hi(expr
.span
.hi()),
106 if is_trait_method(cx
, map_recv
, sym
::Iterator
);
108 // filter(|x| ...is_some())...
109 if let ExprKind
::Closure { body: filter_body_id, .. }
= filter_arg
.kind
;
110 let filter_body
= cx
.tcx
.hir().body(filter_body_id
);
111 if let [filter_param
] = filter_body
.params
;
112 // optional ref pattern: `filter(|&x| ..)`
113 let (filter_pat
, is_filter_param_ref
) = if let PatKind
::Ref(ref_pat
, _
) = filter_param
.pat
.kind
{
116 (filter_param
.pat
, false)
118 // closure ends with is_some() or is_ok()
119 if let PatKind
::Binding(_
, filter_param_id
, _
, None
) = filter_pat
.kind
;
120 if let ExprKind
::MethodCall(path
, [filter_arg
], _
) = filter_body
.value
.kind
;
121 if let Some(opt_ty
) = cx
.typeck_results().expr_ty(filter_arg
).ty_adt_def();
122 if let Some(is_result
) = if cx
.tcx
.is_diagnostic_item(sym
::Option
, opt_ty
.did()) {
124 } else if cx
.tcx
.is_diagnostic_item(sym
::Result
, opt_ty
.did()) {
129 if path
.ident
.name
.as_str() == if is_result { "is_ok" }
else { "is_some" }
;
131 // ...map(|x| ...unwrap())
132 if let ExprKind
::Closure { body: map_body_id, .. }
= map_arg
.kind
;
133 let map_body
= cx
.tcx
.hir().body(map_body_id
);
134 if let [map_param
] = map_body
.params
;
135 if let PatKind
::Binding(_
, map_param_id
, map_param_ident
, None
) = map_param
.pat
.kind
;
136 // closure ends with expect() or unwrap()
137 if let ExprKind
::MethodCall(seg
, [map_arg
, ..], _
) = map_body
.value
.kind
;
138 if matches
!(seg
.ident
.name
, sym
::expect
| sym
::unwrap
| sym
::unwrap_or
);
140 let eq_fallback
= |a
: &Expr
<'_
>, b
: &Expr
<'_
>| {
141 // in `filter(|x| ..)`, replace `*x` with `x`
142 let a_path
= if_chain
! {
143 if !is_filter_param_ref
;
144 if let ExprKind
::Unary(UnOp
::Deref
, expr_path
) = a
.kind
;
145 then { expr_path }
else { a }
147 // let the filter closure arg and the map closure arg be equal
149 if path_to_local_id(a_path
, filter_param_id
);
150 if path_to_local_id(b
, map_param_id
);
151 if cx
.typeck_results().expr_ty_adjusted(a
) == cx
.typeck_results().expr_ty_adjusted(b
);
159 if match map_arg
.kind
{
160 ExprKind
::MethodCall(method
, [original_arg
], _
) => {
161 acceptable_methods(method
)
162 && SpanlessEq
::new(cx
).expr_fallback(eq_fallback
).eq_expr(filter_arg
, original_arg
)
164 _
=> SpanlessEq
::new(cx
).expr_fallback(eq_fallback
).eq_expr(filter_arg
, map_arg
)
168 let span
= filter_span
.with_hi(expr
.span
.hi());
169 let (filter_name
, lint
) = if is_find
{
170 ("find", MANUAL_FIND_MAP
)
172 ("filter", MANUAL_FILTER_MAP
)
174 let msg
= format
!("`{}(..).map(..)` can be simplified as `{0}_map(..)`", filter_name
);
175 let to_opt
= if is_result { ".ok()" }
else { "" }
;
176 let sugg
= format
!("{}_map(|{}| {}{})", filter_name
, map_param_ident
,
177 snippet(cx
, map_arg
.span
, ".."), to_opt
);
178 span_lint_and_sugg(cx
, lint
, span
, &msg
, "try", sugg
, Applicability
::MachineApplicable
);
183 fn acceptable_methods(method
: &PathSegment
<'_
>) -> bool
{
184 let methods
: [Symbol
; 8] = [
195 methods
.contains(&method
.ident
.name
)