Skip to content

Commit cf7e34d

Browse files
committed
Auto merge of #12004 - PartiallyTyped:11843, r=xFrednet
New lints `iter_filter_is_some` and `iter_filter_is_ok` Adds a pair of lints that check for cases of an iterator over `Result` and `Option` followed by `filter` without being followed by `map` as that is covered already by a different, specialized lint. Fixes #11843 PS, I also made some minor documentations fixes in a case where a double tick (`) was included. --- * changelog: New Lint: [`iter_filter_is_some`] [#12004](rust-lang/rust#12004) * changelog: New Lint: [`iter_filter_is_ok`] [#12004](rust-lang/rust#12004)
2 parents 99c7838 + 85e1646 commit cf7e34d

12 files changed

+320
-3
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -5177,6 +5177,8 @@ Released 2018-09-13
51775177
[`items_after_test_module`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_test_module
51785178
[`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect
51795179
[`iter_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_count
5180+
[`iter_filter_is_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_filter_is_ok
5181+
[`iter_filter_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_filter_is_some
51805182
[`iter_kv_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_kv_map
51815183
[`iter_next_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_loop
51825184
[`iter_next_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_slice

clippy_config/src/msrvs.rs

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ msrv_aliases! {
4141
1,35,0 { OPTION_COPIED, RANGE_CONTAINS }
4242
1,34,0 { TRY_FROM }
4343
1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
44+
1,29,0 { ITER_FLATTEN }
4445
1,28,0 { FROM_BOOL }
4546
1,27,0 { ITERATOR_TRY_FOLD }
4647
1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN }

clippy_lints/src/declared_lints.rs

+2
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,8 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
369369
crate::methods::ITERATOR_STEP_BY_ZERO_INFO,
370370
crate::methods::ITER_CLONED_COLLECT_INFO,
371371
crate::methods::ITER_COUNT_INFO,
372+
crate::methods::ITER_FILTER_IS_OK_INFO,
373+
crate::methods::ITER_FILTER_IS_SOME_INFO,
372374
crate::methods::ITER_KV_MAP_INFO,
373375
crate::methods::ITER_NEXT_SLICE_INFO,
374376
crate::methods::ITER_NTH_INFO,

clippy_lints/src/methods/filter_map.rs

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ fn is_method(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Symbol) ->
2222
hir::ExprKind::Path(QPath::Resolved(_, segments)) => {
2323
segments.segments.last().unwrap().ident.name == method_name
2424
},
25+
hir::ExprKind::MethodCall(segment, _, _, _) => segment.ident.name == method_name,
2526
hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
2627
let body = cx.tcx.hir().body(body);
2728
let closure_expr = peel_blocks(body.value);
+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use rustc_lint::{LateContext, LintContext};
2+
3+
use super::{ITER_FILTER_IS_OK, ITER_FILTER_IS_SOME};
4+
5+
use clippy_utils::diagnostics::span_lint_and_sugg;
6+
use clippy_utils::source::{indent_of, reindent_multiline};
7+
use clippy_utils::{is_trait_method, peel_blocks, span_contains_comment};
8+
use rustc_errors::Applicability;
9+
use rustc_hir as hir;
10+
use rustc_hir::def::Res;
11+
use rustc_hir::QPath;
12+
use rustc_span::symbol::{sym, Symbol};
13+
use rustc_span::Span;
14+
use std::borrow::Cow;
15+
16+
fn is_method(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Symbol) -> bool {
17+
match &expr.kind {
18+
hir::ExprKind::Path(QPath::TypeRelative(_, mname)) => mname.ident.name == method_name,
19+
hir::ExprKind::Path(QPath::Resolved(_, segments)) => {
20+
segments.segments.last().unwrap().ident.name == method_name
21+
},
22+
hir::ExprKind::MethodCall(segment, _, _, _) => segment.ident.name == method_name,
23+
hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
24+
let body = cx.tcx.hir().body(body);
25+
let closure_expr = peel_blocks(body.value);
26+
let arg_id = body.params[0].pat.hir_id;
27+
match closure_expr.kind {
28+
hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, receiver, ..) => {
29+
if ident.name == method_name
30+
&& let hir::ExprKind::Path(path) = &receiver.kind
31+
&& let Res::Local(ref local) = cx.qpath_res(path, receiver.hir_id)
32+
{
33+
return arg_id == *local;
34+
}
35+
false
36+
},
37+
_ => false,
38+
}
39+
},
40+
_ => false,
41+
}
42+
}
43+
44+
fn parent_is_map(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
45+
if let hir::Node::Expr(parent_expr) = cx.tcx.hir().get_parent(expr.hir_id) {
46+
is_method(cx, parent_expr, rustc_span::sym::map)
47+
} else {
48+
false
49+
}
50+
}
51+
52+
#[allow(clippy::too_many_arguments)]
53+
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_arg: &hir::Expr<'_>, filter_span: Span) {
54+
let is_iterator = is_trait_method(cx, expr, sym::Iterator);
55+
let parent_is_not_map = !parent_is_map(cx, expr);
56+
57+
if is_iterator
58+
&& parent_is_not_map
59+
&& is_method(cx, filter_arg, sym!(is_some))
60+
&& !span_contains_comment(cx.sess().source_map(), filter_span.with_hi(expr.span.hi()))
61+
{
62+
span_lint_and_sugg(
63+
cx,
64+
ITER_FILTER_IS_SOME,
65+
filter_span.with_hi(expr.span.hi()),
66+
"`filter` for `is_some` on iterator over `Option`",
67+
"consider using `flatten` instead",
68+
reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, filter_span)).into_owned(),
69+
Applicability::HasPlaceholders,
70+
);
71+
}
72+
if is_iterator
73+
&& parent_is_not_map
74+
&& is_method(cx, filter_arg, sym!(is_ok))
75+
&& !span_contains_comment(cx.sess().source_map(), filter_span.with_hi(expr.span.hi()))
76+
{
77+
span_lint_and_sugg(
78+
cx,
79+
ITER_FILTER_IS_OK,
80+
filter_span.with_hi(expr.span.hi()),
81+
"`filter` for `is_ok` on iterator over `Result`s",
82+
"consider using `flatten` instead",
83+
reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, filter_span)).into_owned(),
84+
Applicability::HasPlaceholders,
85+
);
86+
}
87+
}

clippy_lints/src/methods/mod.rs

+75-3
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ mod into_iter_on_ref;
3838
mod is_digit_ascii_radix;
3939
mod iter_cloned_collect;
4040
mod iter_count;
41+
mod iter_filter;
4142
mod iter_kv_map;
4243
mod iter_next_slice;
4344
mod iter_nth;
@@ -1175,7 +1176,7 @@ declare_clippy_lint! {
11751176

11761177
declare_clippy_lint! {
11771178
/// ### What it does
1178-
/// Checks for iterators of `Option`s using ``.filter(Option::is_some).map(Option::unwrap)` that may
1179+
/// Checks for iterators of `Option`s using `.filter(Option::is_some).map(Option::unwrap)` that may
11791180
/// be replaced with a `.flatten()` call.
11801181
///
11811182
/// ### Why is this bad?
@@ -3755,7 +3756,7 @@ declare_clippy_lint! {
37553756

37563757
declare_clippy_lint! {
37573758
/// ### What it does
3758-
/// Checks for iterators of `Result`s using ``.filter(Result::is_ok).map(Result::unwrap)` that may
3759+
/// Checks for iterators of `Result`s using `.filter(Result::is_ok).map(Result::unwrap)` that may
37593760
/// be replaced with a `.flatten()` call.
37603761
///
37613762
/// ### Why is this bad?
@@ -3776,6 +3777,58 @@ declare_clippy_lint! {
37763777
"filtering `Result` for `Ok` then force-unwrapping, which can be one type-safe operation"
37773778
}
37783779

3780+
declare_clippy_lint! {
3781+
/// ### What it does
3782+
/// Checks for usage of `.filter(Option::is_some)` that may be replaced with a `.flatten()` call.
3783+
/// This lint will require additional changes to the follow-up calls as it appects the type.
3784+
///
3785+
/// ### Why is this bad?
3786+
/// This pattern is often followed by manual unwrapping of the `Option`. The simplification
3787+
/// results in more readable and succint code without the need for manual unwrapping.
3788+
///
3789+
/// ### Example
3790+
/// ```no_run
3791+
/// // example code where clippy issues a warning
3792+
/// vec![Some(1)].into_iter().filter(Option::is_some);
3793+
///
3794+
/// ```
3795+
/// Use instead:
3796+
/// ```no_run
3797+
/// // example code which does not raise clippy warning
3798+
/// vec![Some(1)].into_iter().flatten();
3799+
/// ```
3800+
#[clippy::version = "1.76.0"]
3801+
pub ITER_FILTER_IS_SOME,
3802+
pedantic,
3803+
"filtering an iterator over `Option`s for `Some` can be achieved with `flatten`"
3804+
}
3805+
3806+
declare_clippy_lint! {
3807+
/// ### What it does
3808+
/// Checks for usage of `.filter(Result::is_ok)` that may be replaced with a `.flatten()` call.
3809+
/// This lint will require additional changes to the follow-up calls as it appects the type.
3810+
///
3811+
/// ### Why is this bad?
3812+
/// This pattern is often followed by manual unwrapping of `Result`. The simplification
3813+
/// results in more readable and succint code without the need for manual unwrapping.
3814+
///
3815+
/// ### Example
3816+
/// ```no_run
3817+
/// // example code where clippy issues a warning
3818+
/// vec![Ok::<i32, String>(1)].into_iter().filter(Result::is_ok);
3819+
///
3820+
/// ```
3821+
/// Use instead:
3822+
/// ```no_run
3823+
/// // example code which does not raise clippy warning
3824+
/// vec![Ok::<i32, String>(1)].into_iter().flatten();
3825+
/// ```
3826+
#[clippy::version = "1.76.0"]
3827+
pub ITER_FILTER_IS_OK,
3828+
pedantic,
3829+
"filtering an iterator over `Result`s for `Ok` can be achieved with `flatten`"
3830+
}
3831+
37793832
pub struct Methods {
37803833
avoid_breaking_exported_api: bool,
37813834
msrv: Msrv,
@@ -3928,6 +3981,8 @@ impl_lint_pass!(Methods => [
39283981
JOIN_ABSOLUTE_PATHS,
39293982
OPTION_MAP_OR_ERR_OK,
39303983
RESULT_FILTER_MAP,
3984+
ITER_FILTER_IS_SOME,
3985+
ITER_FILTER_IS_OK,
39313986
]);
39323987

39333988
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4257,7 +4312,24 @@ impl Methods {
42574312
string_extend_chars::check(cx, expr, recv, arg);
42584313
extend_with_drain::check(cx, expr, recv, arg);
42594314
},
4260-
(name @ ("filter" | "find"), [arg]) => {
4315+
("filter", [arg]) => {
4316+
if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) {
4317+
// if `arg` has side-effect, the semantic will change
4318+
iter_overeager_cloned::check(
4319+
cx,
4320+
expr,
4321+
recv,
4322+
recv2,
4323+
iter_overeager_cloned::Op::FixClosure(name, arg),
4324+
false,
4325+
);
4326+
}
4327+
if self.msrv.meets(msrvs::ITER_FLATTEN) {
4328+
// use the sourcemap to get the span of the closure
4329+
iter_filter::check(cx, expr, arg, span);
4330+
}
4331+
},
4332+
("find", [arg]) => {
42614333
if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) {
42624334
// if `arg` has side-effect, the semantic will change
42634335
iter_overeager_cloned::check(

tests/ui/iter_filter_is_ok.fixed

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#![warn(clippy::iter_filter_is_ok)]
2+
3+
fn main() {
4+
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().flatten();
5+
//~^ HELP: consider using `flatten` instead
6+
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().flatten();
7+
//~^ HELP: consider using `flatten` instead
8+
9+
#[rustfmt::skip]
10+
let _ = vec![Ok(1), Err(2)].into_iter().flatten();
11+
//~^ HELP: consider using `flatten` instead
12+
13+
// Don't lint below
14+
let mut counter = 0;
15+
let _ = vec![Ok(1), Err(2)].into_iter().filter(|o| {
16+
counter += 1;
17+
o.is_ok()
18+
});
19+
let _ = vec![Ok(1), Err(2)].into_iter().filter(|o| {
20+
// Roses are red,
21+
// Violets are blue,
22+
// `Err` is not an `Option`,
23+
// and this doesn't ryme
24+
o.is_ok()
25+
});
26+
}

tests/ui/iter_filter_is_ok.rs

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#![warn(clippy::iter_filter_is_ok)]
2+
3+
fn main() {
4+
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().filter(Result::is_ok);
5+
//~^ HELP: consider using `flatten` instead
6+
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().filter(|a| a.is_ok());
7+
//~^ HELP: consider using `flatten` instead
8+
9+
#[rustfmt::skip]
10+
let _ = vec![Ok(1), Err(2)].into_iter().filter(|o| { o.is_ok() });
11+
//~^ HELP: consider using `flatten` instead
12+
13+
// Don't lint below
14+
let mut counter = 0;
15+
let _ = vec![Ok(1), Err(2)].into_iter().filter(|o| {
16+
counter += 1;
17+
o.is_ok()
18+
});
19+
let _ = vec![Ok(1), Err(2)].into_iter().filter(|o| {
20+
// Roses are red,
21+
// Violets are blue,
22+
// `Err` is not an `Option`,
23+
// and this doesn't ryme
24+
o.is_ok()
25+
});
26+
}

tests/ui/iter_filter_is_ok.stderr

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error: `filter` for `is_ok` on iterator over `Result`s
2+
--> $DIR/iter_filter_is_ok.rs:4:52
3+
|
4+
LL | let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().filter(Result::is_ok);
5+
| ^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
6+
|
7+
= note: `-D clippy::iter-filter-is-ok` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::iter_filter_is_ok)]`
9+
10+
error: `filter` for `is_ok` on iterator over `Result`s
11+
--> $DIR/iter_filter_is_ok.rs:6:52
12+
|
13+
LL | let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().filter(|a| a.is_ok());
14+
| ^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
15+
16+
error: `filter` for `is_ok` on iterator over `Result`s
17+
--> $DIR/iter_filter_is_ok.rs:10:45
18+
|
19+
LL | let _ = vec![Ok(1), Err(2)].into_iter().filter(|o| { o.is_ok() });
20+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
21+
22+
error: aborting due to 3 previous errors
23+

tests/ui/iter_filter_is_some.fixed

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#![warn(clippy::iter_filter_is_some)]
2+
3+
fn main() {
4+
let _ = vec![Some(1)].into_iter().flatten();
5+
//~^ HELP: consider using `flatten` instead
6+
let _ = vec![Some(1)].into_iter().flatten();
7+
//~^ HELP: consider using `flatten` instead
8+
9+
#[rustfmt::skip]
10+
let _ = vec![Some(1)].into_iter().flatten();
11+
//~^ HELP: consider using `flatten` instead
12+
13+
// Don't lint below
14+
let mut counter = 0;
15+
let _ = vec![Some(1)].into_iter().filter(|o| {
16+
counter += 1;
17+
o.is_some()
18+
});
19+
20+
let _ = vec![Some(1)].into_iter().filter(|o| {
21+
// Roses are red,
22+
// Violets are blue,
23+
// `Err` is not an `Option`,
24+
// and this doesn't ryme
25+
o.is_some()
26+
});
27+
}

tests/ui/iter_filter_is_some.rs

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#![warn(clippy::iter_filter_is_some)]
2+
3+
fn main() {
4+
let _ = vec![Some(1)].into_iter().filter(Option::is_some);
5+
//~^ HELP: consider using `flatten` instead
6+
let _ = vec![Some(1)].into_iter().filter(|o| o.is_some());
7+
//~^ HELP: consider using `flatten` instead
8+
9+
#[rustfmt::skip]
10+
let _ = vec![Some(1)].into_iter().filter(|o| { o.is_some() });
11+
//~^ HELP: consider using `flatten` instead
12+
13+
// Don't lint below
14+
let mut counter = 0;
15+
let _ = vec![Some(1)].into_iter().filter(|o| {
16+
counter += 1;
17+
o.is_some()
18+
});
19+
20+
let _ = vec![Some(1)].into_iter().filter(|o| {
21+
// Roses are red,
22+
// Violets are blue,
23+
// `Err` is not an `Option`,
24+
// and this doesn't ryme
25+
o.is_some()
26+
});
27+
}

tests/ui/iter_filter_is_some.stderr

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error: `filter` for `is_some` on iterator over `Option`
2+
--> $DIR/iter_filter_is_some.rs:4:39
3+
|
4+
LL | let _ = vec![Some(1)].into_iter().filter(Option::is_some);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
6+
|
7+
= note: `-D clippy::iter-filter-is-some` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::iter_filter_is_some)]`
9+
10+
error: `filter` for `is_some` on iterator over `Option`
11+
--> $DIR/iter_filter_is_some.rs:6:39
12+
|
13+
LL | let _ = vec![Some(1)].into_iter().filter(|o| o.is_some());
14+
| ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
15+
16+
error: `filter` for `is_some` on iterator over `Option`
17+
--> $DIR/iter_filter_is_some.rs:10:39
18+
|
19+
LL | let _ = vec![Some(1)].into_iter().filter(|o| { o.is_some() });
20+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
21+
22+
error: aborting due to 3 previous errors
23+

0 commit comments

Comments
 (0)