Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 24909fa

Browse files
committed
move map_flatten and search_is_some to their own modules
1 parent 37ba779 commit 24909fa

File tree

3 files changed

+168
-144
lines changed

3 files changed

+168
-144
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use crate::utils::{is_type_diagnostic_item, match_trait_method, paths, snippet, span_lint_and_sugg};
2+
use rustc_errors::Applicability;
3+
use rustc_hir as hir;
4+
use rustc_lint::LateContext;
5+
use rustc_middle::ty;
6+
use rustc_span::symbol::sym;
7+
8+
use super::MAP_FLATTEN;
9+
10+
/// lint use of `map().flatten()` for `Iterators` and 'Options'
11+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map_args: &'tcx [hir::Expr<'_>]) {
12+
// lint if caller of `.map().flatten()` is an Iterator
13+
if match_trait_method(cx, expr, &paths::ITERATOR) {
14+
let map_closure_ty = cx.typeck_results().expr_ty(&map_args[1]);
15+
let is_map_to_option = match map_closure_ty.kind() {
16+
ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => {
17+
let map_closure_sig = match map_closure_ty.kind() {
18+
ty::Closure(_, substs) => substs.as_closure().sig(),
19+
_ => map_closure_ty.fn_sig(cx.tcx),
20+
};
21+
let map_closure_return_ty = cx.tcx.erase_late_bound_regions(map_closure_sig.output());
22+
is_type_diagnostic_item(cx, map_closure_return_ty, sym::option_type)
23+
},
24+
_ => false,
25+
};
26+
27+
let method_to_use = if is_map_to_option {
28+
// `(...).map(...)` has type `impl Iterator<Item=Option<...>>
29+
"filter_map"
30+
} else {
31+
// `(...).map(...)` has type `impl Iterator<Item=impl Iterator<...>>
32+
"flat_map"
33+
};
34+
let func_snippet = snippet(cx, map_args[1].span, "..");
35+
let hint = format!(".{0}({1})", method_to_use, func_snippet);
36+
span_lint_and_sugg(
37+
cx,
38+
MAP_FLATTEN,
39+
expr.span.with_lo(map_args[0].span.hi()),
40+
"called `map(..).flatten()` on an `Iterator`",
41+
&format!("try using `{}` instead", method_to_use),
42+
hint,
43+
Applicability::MachineApplicable,
44+
);
45+
}
46+
47+
// lint if caller of `.map().flatten()` is an Option
48+
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym::option_type) {
49+
let func_snippet = snippet(cx, map_args[1].span, "..");
50+
let hint = format!(".and_then({})", func_snippet);
51+
span_lint_and_sugg(
52+
cx,
53+
MAP_FLATTEN,
54+
expr.span.with_lo(map_args[0].span.hi()),
55+
"called `map(..).flatten()` on an `Option`",
56+
"try using `and_then` instead",
57+
hint,
58+
Applicability::MachineApplicable,
59+
);
60+
}
61+
}

clippy_lints/src/methods/mod.rs

Lines changed: 6 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ mod iter_nth_zero;
2525
mod iterator_step_by_zero;
2626
mod manual_saturating_arithmetic;
2727
mod map_collect_result_unit;
28+
mod map_flatten;
2829
mod ok_expect;
2930
mod option_as_ref_deref;
3031
mod option_map_unwrap_or;
32+
mod search_is_some;
3133
mod single_char_insert_string;
3234
mod single_char_pattern;
3335
mod single_char_push_string;
@@ -1711,13 +1713,13 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
17111713
["flat_map", "filter"] => filter_flat_map::check(cx, expr, arg_lists[1], arg_lists[0]),
17121714
["flat_map", "filter_map"] => filter_map_flat_map::check(cx, expr, arg_lists[1], arg_lists[0]),
17131715
["flat_map", ..] => flat_map_identity::check(cx, expr, arg_lists[0], method_spans[0]),
1714-
["flatten", "map"] => lint_map_flatten(cx, expr, arg_lists[1]),
1715-
["is_some", "find"] => lint_search_is_some(cx, expr, "find", arg_lists[1], arg_lists[0], method_spans[1]),
1716+
["flatten", "map"] => map_flatten::check(cx, expr, arg_lists[1]),
1717+
["is_some", "find"] => search_is_some::check(cx, expr, "find", arg_lists[1], arg_lists[0], method_spans[1]),
17161718
["is_some", "position"] => {
1717-
lint_search_is_some(cx, expr, "position", arg_lists[1], arg_lists[0], method_spans[1])
1719+
search_is_some::check(cx, expr, "position", arg_lists[1], arg_lists[0], method_spans[1])
17181720
},
17191721
["is_some", "rposition"] => {
1720-
lint_search_is_some(cx, expr, "rposition", arg_lists[1], arg_lists[0], method_spans[1])
1722+
search_is_some::check(cx, expr, "rposition", arg_lists[1], arg_lists[0], method_spans[1])
17211723
},
17221724
["extend", ..] => string_extend_chars::check(cx, expr, arg_lists[0]),
17231725
["count", "into_iter"] => iter_count::check(cx, expr, &arg_lists[1], "into_iter"),
@@ -2566,59 +2568,6 @@ fn derefs_to_slice<'tcx>(
25662568
}
25672569
}
25682570

2569-
/// lint use of `map().flatten()` for `Iterators` and 'Options'
2570-
fn lint_map_flatten<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map_args: &'tcx [hir::Expr<'_>]) {
2571-
// lint if caller of `.map().flatten()` is an Iterator
2572-
if match_trait_method(cx, expr, &paths::ITERATOR) {
2573-
let map_closure_ty = cx.typeck_results().expr_ty(&map_args[1]);
2574-
let is_map_to_option = match map_closure_ty.kind() {
2575-
ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => {
2576-
let map_closure_sig = match map_closure_ty.kind() {
2577-
ty::Closure(_, substs) => substs.as_closure().sig(),
2578-
_ => map_closure_ty.fn_sig(cx.tcx),
2579-
};
2580-
let map_closure_return_ty = cx.tcx.erase_late_bound_regions(map_closure_sig.output());
2581-
is_type_diagnostic_item(cx, map_closure_return_ty, sym::option_type)
2582-
},
2583-
_ => false,
2584-
};
2585-
2586-
let method_to_use = if is_map_to_option {
2587-
// `(...).map(...)` has type `impl Iterator<Item=Option<...>>
2588-
"filter_map"
2589-
} else {
2590-
// `(...).map(...)` has type `impl Iterator<Item=impl Iterator<...>>
2591-
"flat_map"
2592-
};
2593-
let func_snippet = snippet(cx, map_args[1].span, "..");
2594-
let hint = format!(".{0}({1})", method_to_use, func_snippet);
2595-
span_lint_and_sugg(
2596-
cx,
2597-
MAP_FLATTEN,
2598-
expr.span.with_lo(map_args[0].span.hi()),
2599-
"called `map(..).flatten()` on an `Iterator`",
2600-
&format!("try using `{}` instead", method_to_use),
2601-
hint,
2602-
Applicability::MachineApplicable,
2603-
);
2604-
}
2605-
2606-
// lint if caller of `.map().flatten()` is an Option
2607-
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym::option_type) {
2608-
let func_snippet = snippet(cx, map_args[1].span, "..");
2609-
let hint = format!(".and_then({})", func_snippet);
2610-
span_lint_and_sugg(
2611-
cx,
2612-
MAP_FLATTEN,
2613-
expr.span.with_lo(map_args[0].span.hi()),
2614-
"called `map(..).flatten()` on an `Option`",
2615-
"try using `and_then` instead",
2616-
hint,
2617-
Applicability::MachineApplicable,
2618-
);
2619-
}
2620-
}
2621-
26222571
const MAP_UNWRAP_OR_MSRV: RustcVersion = RustcVersion::new(1, 41, 0);
26232572

26242573
/// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s
@@ -2756,93 +2705,6 @@ fn lint_map_or_none<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map
27562705
);
27572706
}
27582707

2759-
/// lint searching an Iterator followed by `is_some()`
2760-
/// or calling `find()` on a string followed by `is_some()`
2761-
fn lint_search_is_some<'tcx>(
2762-
cx: &LateContext<'tcx>,
2763-
expr: &'tcx hir::Expr<'_>,
2764-
search_method: &str,
2765-
search_args: &'tcx [hir::Expr<'_>],
2766-
is_some_args: &'tcx [hir::Expr<'_>],
2767-
method_span: Span,
2768-
) {
2769-
// lint if caller of search is an Iterator
2770-
if match_trait_method(cx, &is_some_args[0], &paths::ITERATOR) {
2771-
let msg = format!(
2772-
"called `is_some()` after searching an `Iterator` with `{}`",
2773-
search_method
2774-
);
2775-
let hint = "this is more succinctly expressed by calling `any()`";
2776-
let search_snippet = snippet(cx, search_args[1].span, "..");
2777-
if search_snippet.lines().count() <= 1 {
2778-
// suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()`
2779-
// suggest `any(|..| *..)` instead of `any(|..| **..)` for `find(|..| **..).is_some()`
2780-
let any_search_snippet = if_chain! {
2781-
if search_method == "find";
2782-
if let hir::ExprKind::Closure(_, _, body_id, ..) = search_args[1].kind;
2783-
let closure_body = cx.tcx.hir().body(body_id);
2784-
if let Some(closure_arg) = closure_body.params.get(0);
2785-
then {
2786-
if let hir::PatKind::Ref(..) = closure_arg.pat.kind {
2787-
Some(search_snippet.replacen('&', "", 1))
2788-
} else if let PatKind::Binding(_, _, ident, _) = strip_pat_refs(&closure_arg.pat).kind {
2789-
let name = &*ident.name.as_str();
2790-
Some(search_snippet.replace(&format!("*{}", name), name))
2791-
} else {
2792-
None
2793-
}
2794-
} else {
2795-
None
2796-
}
2797-
};
2798-
// add note if not multi-line
2799-
span_lint_and_sugg(
2800-
cx,
2801-
SEARCH_IS_SOME,
2802-
method_span.with_hi(expr.span.hi()),
2803-
&msg,
2804-
"use `any()` instead",
2805-
format!(
2806-
"any({})",
2807-
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
2808-
),
2809-
Applicability::MachineApplicable,
2810-
);
2811-
} else {
2812-
span_lint_and_help(cx, SEARCH_IS_SOME, expr.span, &msg, None, hint);
2813-
}
2814-
}
2815-
// lint if `find()` is called by `String` or `&str`
2816-
else if search_method == "find" {
2817-
let is_string_or_str_slice = |e| {
2818-
let self_ty = cx.typeck_results().expr_ty(e).peel_refs();
2819-
if is_type_diagnostic_item(cx, self_ty, sym::string_type) {
2820-
true
2821-
} else {
2822-
*self_ty.kind() == ty::Str
2823-
}
2824-
};
2825-
if_chain! {
2826-
if is_string_or_str_slice(&search_args[0]);
2827-
if is_string_or_str_slice(&search_args[1]);
2828-
then {
2829-
let msg = "called `is_some()` after calling `find()` on a string";
2830-
let mut applicability = Applicability::MachineApplicable;
2831-
let find_arg = snippet_with_applicability(cx, search_args[1].span, "..", &mut applicability);
2832-
span_lint_and_sugg(
2833-
cx,
2834-
SEARCH_IS_SOME,
2835-
method_span.with_hi(expr.span.hi()),
2836-
msg,
2837-
"use `contains()` instead",
2838-
format!("contains({})", find_arg),
2839-
applicability,
2840-
);
2841-
}
2842-
}
2843-
}
2844-
}
2845-
28462708
/// Used for `lint_binary_expr_with_method_call`.
28472709
#[derive(Copy, Clone)]
28482710
struct BinaryExprInfo<'a> {
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
use crate::utils::{
2+
is_type_diagnostic_item, match_trait_method, paths, snippet, snippet_with_applicability, span_lint_and_help,
3+
span_lint_and_sugg, strip_pat_refs,
4+
};
5+
use if_chain::if_chain;
6+
use rustc_errors::Applicability;
7+
use rustc_hir as hir;
8+
use rustc_hir::PatKind;
9+
use rustc_lint::LateContext;
10+
use rustc_middle::ty;
11+
use rustc_span::source_map::Span;
12+
use rustc_span::symbol::sym;
13+
14+
use super::SEARCH_IS_SOME;
15+
16+
/// lint searching an Iterator followed by `is_some()`
17+
/// or calling `find()` on a string followed by `is_some()`
18+
pub(super) fn check<'tcx>(
19+
cx: &LateContext<'tcx>,
20+
expr: &'tcx hir::Expr<'_>,
21+
search_method: &str,
22+
search_args: &'tcx [hir::Expr<'_>],
23+
is_some_args: &'tcx [hir::Expr<'_>],
24+
method_span: Span,
25+
) {
26+
// lint if caller of search is an Iterator
27+
if match_trait_method(cx, &is_some_args[0], &paths::ITERATOR) {
28+
let msg = format!(
29+
"called `is_some()` after searching an `Iterator` with `{}`",
30+
search_method
31+
);
32+
let hint = "this is more succinctly expressed by calling `any()`";
33+
let search_snippet = snippet(cx, search_args[1].span, "..");
34+
if search_snippet.lines().count() <= 1 {
35+
// suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()`
36+
// suggest `any(|..| *..)` instead of `any(|..| **..)` for `find(|..| **..).is_some()`
37+
let any_search_snippet = if_chain! {
38+
if search_method == "find";
39+
if let hir::ExprKind::Closure(_, _, body_id, ..) = search_args[1].kind;
40+
let closure_body = cx.tcx.hir().body(body_id);
41+
if let Some(closure_arg) = closure_body.params.get(0);
42+
then {
43+
if let hir::PatKind::Ref(..) = closure_arg.pat.kind {
44+
Some(search_snippet.replacen('&', "", 1))
45+
} else if let PatKind::Binding(_, _, ident, _) = strip_pat_refs(&closure_arg.pat).kind {
46+
let name = &*ident.name.as_str();
47+
Some(search_snippet.replace(&format!("*{}", name), name))
48+
} else {
49+
None
50+
}
51+
} else {
52+
None
53+
}
54+
};
55+
// add note if not multi-line
56+
span_lint_and_sugg(
57+
cx,
58+
SEARCH_IS_SOME,
59+
method_span.with_hi(expr.span.hi()),
60+
&msg,
61+
"use `any()` instead",
62+
format!(
63+
"any({})",
64+
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
65+
),
66+
Applicability::MachineApplicable,
67+
);
68+
} else {
69+
span_lint_and_help(cx, SEARCH_IS_SOME, expr.span, &msg, None, hint);
70+
}
71+
}
72+
// lint if `find()` is called by `String` or `&str`
73+
else if search_method == "find" {
74+
let is_string_or_str_slice = |e| {
75+
let self_ty = cx.typeck_results().expr_ty(e).peel_refs();
76+
if is_type_diagnostic_item(cx, self_ty, sym::string_type) {
77+
true
78+
} else {
79+
*self_ty.kind() == ty::Str
80+
}
81+
};
82+
if_chain! {
83+
if is_string_or_str_slice(&search_args[0]);
84+
if is_string_or_str_slice(&search_args[1]);
85+
then {
86+
let msg = "called `is_some()` after calling `find()` on a string";
87+
let mut applicability = Applicability::MachineApplicable;
88+
let find_arg = snippet_with_applicability(cx, search_args[1].span, "..", &mut applicability);
89+
span_lint_and_sugg(
90+
cx,
91+
SEARCH_IS_SOME,
92+
method_span.with_hi(expr.span.hi()),
93+
msg,
94+
"use `contains()` instead",
95+
format!("contains({})", find_arg),
96+
applicability,
97+
);
98+
}
99+
}
100+
}
101+
}

0 commit comments

Comments
 (0)