Skip to content

Commit 183daeb

Browse files
committed
move filter_map to its own module
1 parent 2561b7e commit 183daeb

File tree

2 files changed

+89
-77
lines changed

2 files changed

+89
-77
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use crate::utils::{match_trait_method, path_to_local_id, paths, snippet, span_lint_and_sugg, SpanlessEq};
2+
use if_chain::if_chain;
3+
use rustc_errors::Applicability;
4+
use rustc_hir as hir;
5+
use rustc_hir::{Expr, ExprKind, PatKind, UnOp};
6+
use rustc_lint::LateContext;
7+
use rustc_middle::ty::TyS;
8+
use rustc_span::symbol::sym;
9+
10+
use super::MANUAL_FILTER_MAP;
11+
use super::MANUAL_FIND_MAP;
12+
13+
/// lint use of `filter().map()` or `find().map()` for `Iterators`
14+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, is_find: bool) {
15+
if_chain! {
16+
if let ExprKind::MethodCall(_, _, [map_recv, map_arg], map_span) = expr.kind;
17+
if let ExprKind::MethodCall(_, _, [_, filter_arg], filter_span) = map_recv.kind;
18+
if match_trait_method(cx, map_recv, &paths::ITERATOR);
19+
20+
// filter(|x| ...is_some())...
21+
if let ExprKind::Closure(_, _, filter_body_id, ..) = filter_arg.kind;
22+
let filter_body = cx.tcx.hir().body(filter_body_id);
23+
if let [filter_param] = filter_body.params;
24+
// optional ref pattern: `filter(|&x| ..)`
25+
let (filter_pat, is_filter_param_ref) = if let PatKind::Ref(ref_pat, _) = filter_param.pat.kind {
26+
(ref_pat, true)
27+
} else {
28+
(filter_param.pat, false)
29+
};
30+
// closure ends with is_some() or is_ok()
31+
if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind;
32+
if let ExprKind::MethodCall(path, _, [filter_arg], _) = filter_body.value.kind;
33+
if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).ty_adt_def();
34+
if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::option_type, opt_ty.did) {
35+
Some(false)
36+
} else if cx.tcx.is_diagnostic_item(sym::result_type, opt_ty.did) {
37+
Some(true)
38+
} else {
39+
None
40+
};
41+
if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" };
42+
43+
// ...map(|x| ...unwrap())
44+
if let ExprKind::Closure(_, _, map_body_id, ..) = map_arg.kind;
45+
let map_body = cx.tcx.hir().body(map_body_id);
46+
if let [map_param] = map_body.params;
47+
if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind;
48+
// closure ends with expect() or unwrap()
49+
if let ExprKind::MethodCall(seg, _, [map_arg, ..], _) = map_body.value.kind;
50+
if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or);
51+
52+
let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
53+
// in `filter(|x| ..)`, replace `*x` with `x`
54+
let a_path = if_chain! {
55+
if !is_filter_param_ref;
56+
if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind;
57+
then { expr_path } else { a }
58+
};
59+
// let the filter closure arg and the map closure arg be equal
60+
if_chain! {
61+
if path_to_local_id(a_path, filter_param_id);
62+
if path_to_local_id(b, map_param_id);
63+
if TyS::same_type(cx.typeck_results().expr_ty_adjusted(a), cx.typeck_results().expr_ty_adjusted(b));
64+
then {
65+
return true;
66+
}
67+
}
68+
false
69+
};
70+
if SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg);
71+
then {
72+
let span = filter_span.to(map_span);
73+
let (filter_name, lint) = if is_find {
74+
("find", MANUAL_FIND_MAP)
75+
} else {
76+
("filter", MANUAL_FILTER_MAP)
77+
};
78+
let msg = format!("`{}(..).map(..)` can be simplified as `{0}_map(..)`", filter_name);
79+
let to_opt = if is_result { ".ok()" } else { "" };
80+
let sugg = format!("{}_map(|{}| {}{})", filter_name, map_param_ident,
81+
snippet(cx, map_arg.span, ".."), to_opt);
82+
span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable);
83+
}
84+
}
85+
}

clippy_lints/src/methods/mod.rs

Lines changed: 4 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ mod bytes_nth;
33
mod clone_on_ref_ptr;
44
mod expect_used;
55
mod filetype_is_file;
6+
mod filter_map;
67
mod filter_map_identity;
78
mod filter_next;
89
mod from_iter_instead_of_collect;
@@ -43,7 +44,7 @@ use if_chain::if_chain;
4344
use rustc_ast::ast;
4445
use rustc_errors::Applicability;
4546
use rustc_hir as hir;
46-
use rustc_hir::{Expr, ExprKind, PatKind, TraitItem, TraitItemKind, UnOp};
47+
use rustc_hir::{PatKind, TraitItem, TraitItemKind};
4748
use rustc_lint::{LateContext, LateLintPass, Lint, LintContext};
4849
use rustc_middle::lint::in_external_macro;
4950
use rustc_middle::ty::{self, TraitRef, Ty, TyS};
@@ -1698,10 +1699,10 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
16981699
["next", "filter"] => filter_next::check(cx, expr, arg_lists[1]),
16991700
["next", "skip_while"] => skip_while_next::check(cx, expr, arg_lists[1]),
17001701
["next", "iter"] => iter_next_slice::check(cx, expr, arg_lists[1]),
1701-
["map", "filter"] => lint_filter_map(cx, expr, false),
1702+
["map", "filter"] => filter_map::check(cx, expr, false),
17021703
["map", "filter_map"] => lint_filter_map_map(cx, expr, arg_lists[1], arg_lists[0]),
17031704
["next", "filter_map"] => lint_filter_map_next(cx, expr, arg_lists[1], self.msrv.as_ref()),
1704-
["map", "find"] => lint_filter_map(cx, expr, true),
1705+
["map", "find"] => filter_map::check(cx, expr, true),
17051706
["flat_map", "filter"] => lint_filter_flat_map(cx, expr, arg_lists[1], arg_lists[0]),
17061707
["flat_map", "filter_map"] => lint_filter_map_flat_map(cx, expr, arg_lists[1], arg_lists[0]),
17071708
["flat_map", ..] => lint_flat_map_identity(cx, expr, arg_lists[0], method_spans[0]),
@@ -2750,80 +2751,6 @@ fn lint_map_or_none<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map
27502751
);
27512752
}
27522753

2753-
/// lint use of `filter().map()` or `find().map()` for `Iterators`
2754-
fn lint_filter_map<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, is_find: bool) {
2755-
if_chain! {
2756-
if let ExprKind::MethodCall(_, _, [map_recv, map_arg], map_span) = expr.kind;
2757-
if let ExprKind::MethodCall(_, _, [_, filter_arg], filter_span) = map_recv.kind;
2758-
if match_trait_method(cx, map_recv, &paths::ITERATOR);
2759-
2760-
// filter(|x| ...is_some())...
2761-
if let ExprKind::Closure(_, _, filter_body_id, ..) = filter_arg.kind;
2762-
let filter_body = cx.tcx.hir().body(filter_body_id);
2763-
if let [filter_param] = filter_body.params;
2764-
// optional ref pattern: `filter(|&x| ..)`
2765-
let (filter_pat, is_filter_param_ref) = if let PatKind::Ref(ref_pat, _) = filter_param.pat.kind {
2766-
(ref_pat, true)
2767-
} else {
2768-
(filter_param.pat, false)
2769-
};
2770-
// closure ends with is_some() or is_ok()
2771-
if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind;
2772-
if let ExprKind::MethodCall(path, _, [filter_arg], _) = filter_body.value.kind;
2773-
if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).ty_adt_def();
2774-
if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::option_type, opt_ty.did) {
2775-
Some(false)
2776-
} else if cx.tcx.is_diagnostic_item(sym::result_type, opt_ty.did) {
2777-
Some(true)
2778-
} else {
2779-
None
2780-
};
2781-
if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" };
2782-
2783-
// ...map(|x| ...unwrap())
2784-
if let ExprKind::Closure(_, _, map_body_id, ..) = map_arg.kind;
2785-
let map_body = cx.tcx.hir().body(map_body_id);
2786-
if let [map_param] = map_body.params;
2787-
if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind;
2788-
// closure ends with expect() or unwrap()
2789-
if let ExprKind::MethodCall(seg, _, [map_arg, ..], _) = map_body.value.kind;
2790-
if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or);
2791-
2792-
let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
2793-
// in `filter(|x| ..)`, replace `*x` with `x`
2794-
let a_path = if_chain! {
2795-
if !is_filter_param_ref;
2796-
if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind;
2797-
then { expr_path } else { a }
2798-
};
2799-
// let the filter closure arg and the map closure arg be equal
2800-
if_chain! {
2801-
if path_to_local_id(a_path, filter_param_id);
2802-
if path_to_local_id(b, map_param_id);
2803-
if TyS::same_type(cx.typeck_results().expr_ty_adjusted(a), cx.typeck_results().expr_ty_adjusted(b));
2804-
then {
2805-
return true;
2806-
}
2807-
}
2808-
false
2809-
};
2810-
if SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg);
2811-
then {
2812-
let span = filter_span.to(map_span);
2813-
let (filter_name, lint) = if is_find {
2814-
("find", MANUAL_FIND_MAP)
2815-
} else {
2816-
("filter", MANUAL_FILTER_MAP)
2817-
};
2818-
let msg = format!("`{}(..).map(..)` can be simplified as `{0}_map(..)`", filter_name);
2819-
let to_opt = if is_result { ".ok()" } else { "" };
2820-
let sugg = format!("{}_map(|{}| {}{})", filter_name, map_param_ident,
2821-
snippet(cx, map_arg.span, ".."), to_opt);
2822-
span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable);
2823-
}
2824-
}
2825-
}
2826-
28272754
const FILTER_MAP_NEXT_MSRV: RustcVersion = RustcVersion::new(1, 30, 0);
28282755

28292756
/// lint use of `filter_map().next()` for `Iterators`

0 commit comments

Comments
 (0)