Skip to content

Commit aca95aa

Browse files
committed
Auto merge of #6942 - mgacek8:issue_6815_search_is_none, r=llogiq
search_is_some: add checking for `is_none()` fixes: #6815 changelog: search_is_some: add checking for `is_none()`. To be honest I don't know what is the process of renaming the lints. Appreciate any feedback if that needs to be handled differently. Thanks!
2 parents f3de78e + 2ffee89 commit aca95aa

7 files changed

+359
-48
lines changed

clippy_lints/src/methods/mod.rs

+46-11
Original file line numberDiff line numberDiff line change
@@ -588,26 +588,31 @@ declare_clippy_lint! {
588588

589589
declare_clippy_lint! {
590590
/// **What it does:** Checks for an iterator or string search (such as `find()`,
591-
/// `position()`, or `rposition()`) followed by a call to `is_some()`.
591+
/// `position()`, or `rposition()`) followed by a call to `is_some()` or `is_none()`.
592592
///
593-
/// **Why is this bad?** Readability, this can be written more concisely as
594-
/// `_.any(_)` or `_.contains(_)`.
593+
/// **Why is this bad?** Readability, this can be written more concisely as:
594+
/// * `_.any(_)`, or `_.contains(_)` for `is_some()`,
595+
/// * `!_.any(_)`, or `!_.contains(_)` for `is_none()`.
595596
///
596597
/// **Known problems:** None.
597598
///
598599
/// **Example:**
599600
/// ```rust
600-
/// # let vec = vec![1];
601+
/// let vec = vec![1];
601602
/// vec.iter().find(|x| **x == 0).is_some();
603+
///
604+
/// let _ = "hello world".find("world").is_none();
602605
/// ```
603606
/// Could be written as
604607
/// ```rust
605-
/// # let vec = vec![1];
608+
/// let vec = vec![1];
606609
/// vec.iter().any(|x| *x == 0);
610+
///
611+
/// let _ = !"hello world".contains("world");
607612
/// ```
608613
pub SEARCH_IS_SOME,
609614
complexity,
610-
"using an iterator or string search followed by `is_some()`, which is more succinctly expressed as a call to `any()` or `contains()`"
615+
"using an iterator or string search followed by `is_some()` or `is_none()`, which is more succinctly expressed as a call to `any()` or `contains()` (with negation in case of `is_none()`)"
611616
}
612617

613618
declare_clippy_lint! {
@@ -1720,12 +1725,42 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
17201725
["flat_map", "filter_map"] => filter_map_flat_map::check(cx, expr, arg_lists[1], arg_lists[0]),
17211726
["flat_map", ..] => flat_map_identity::check(cx, expr, arg_lists[0], method_spans[0]),
17221727
["flatten", "map"] => map_flatten::check(cx, expr, arg_lists[1]),
1723-
["is_some", "find"] => search_is_some::check(cx, expr, "find", arg_lists[1], arg_lists[0], method_spans[1]),
1724-
["is_some", "position"] => {
1725-
search_is_some::check(cx, expr, "position", arg_lists[1], arg_lists[0], method_spans[1])
1728+
[option_check_method, "find"] if "is_some" == *option_check_method || "is_none" == *option_check_method => {
1729+
search_is_some::check(
1730+
cx,
1731+
expr,
1732+
"find",
1733+
option_check_method,
1734+
arg_lists[1],
1735+
arg_lists[0],
1736+
method_spans[1],
1737+
)
17261738
},
1727-
["is_some", "rposition"] => {
1728-
search_is_some::check(cx, expr, "rposition", arg_lists[1], arg_lists[0], method_spans[1])
1739+
[option_check_method, "position"]
1740+
if "is_some" == *option_check_method || "is_none" == *option_check_method =>
1741+
{
1742+
search_is_some::check(
1743+
cx,
1744+
expr,
1745+
"position",
1746+
option_check_method,
1747+
arg_lists[1],
1748+
arg_lists[0],
1749+
method_spans[1],
1750+
)
1751+
},
1752+
[option_check_method, "rposition"]
1753+
if "is_some" == *option_check_method || "is_none" == *option_check_method =>
1754+
{
1755+
search_is_some::check(
1756+
cx,
1757+
expr,
1758+
"rposition",
1759+
option_check_method,
1760+
arg_lists[1],
1761+
arg_lists[0],
1762+
method_spans[1],
1763+
)
17291764
},
17301765
["extend", ..] => string_extend_chars::check(cx, expr, arg_lists[0]),
17311766
["count", "into_iter"] => iter_count::check(cx, expr, &arg_lists[1], "into_iter"),

clippy_lints/src/methods/search_is_some.rs

+78-29
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,23 @@ use rustc_span::symbol::sym;
1414
use super::SEARCH_IS_SOME;
1515

1616
/// lint searching an Iterator followed by `is_some()`
17-
/// or calling `find()` on a string followed by `is_some()`
17+
/// or calling `find()` on a string followed by `is_some()` or `is_none()`
18+
#[allow(clippy::too_many_lines)]
1819
pub(super) fn check<'tcx>(
1920
cx: &LateContext<'tcx>,
2021
expr: &'tcx hir::Expr<'_>,
2122
search_method: &str,
23+
option_check_method: &str,
2224
search_args: &'tcx [hir::Expr<'_>],
2325
is_some_args: &'tcx [hir::Expr<'_>],
2426
method_span: Span,
2527
) {
2628
// lint if caller of search is an Iterator
2729
if is_trait_method(cx, &is_some_args[0], sym::Iterator) {
2830
let msg = format!(
29-
"called `is_some()` after searching an `Iterator` with `{}`",
30-
search_method
31+
"called `{}()` after searching an `Iterator` with `{}`",
32+
option_check_method, search_method
3133
);
32-
let hint = "this is more succinctly expressed by calling `any()`";
3334
let search_snippet = snippet(cx, search_args[1].span, "..");
3435
if search_snippet.lines().count() <= 1 {
3536
// suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()`
@@ -53,20 +54,49 @@ pub(super) fn check<'tcx>(
5354
}
5455
};
5556
// 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-
);
57+
match option_check_method {
58+
"is_some" => {
59+
span_lint_and_sugg(
60+
cx,
61+
SEARCH_IS_SOME,
62+
method_span.with_hi(expr.span.hi()),
63+
&msg,
64+
"use `any()` instead",
65+
format!(
66+
"any({})",
67+
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
68+
),
69+
Applicability::MachineApplicable,
70+
);
71+
},
72+
"is_none" => {
73+
let iter = snippet(cx, search_args[0].span, "..");
74+
span_lint_and_sugg(
75+
cx,
76+
SEARCH_IS_SOME,
77+
expr.span,
78+
&msg,
79+
"use `!_.any()` instead",
80+
format!(
81+
"!{}.any({})",
82+
iter,
83+
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
84+
),
85+
Applicability::MachineApplicable,
86+
);
87+
},
88+
_ => (),
89+
}
6890
} else {
69-
span_lint_and_help(cx, SEARCH_IS_SOME, expr.span, &msg, None, hint);
91+
let hint = format!(
92+
"this is more succinctly expressed by calling `any()`{}",
93+
if option_check_method == "is_none" {
94+
" with negation"
95+
} else {
96+
""
97+
}
98+
);
99+
span_lint_and_help(cx, SEARCH_IS_SOME, expr.span, &msg, None, &hint);
70100
}
71101
}
72102
// lint if `find()` is called by `String` or `&str`
@@ -83,18 +113,37 @@ pub(super) fn check<'tcx>(
83113
if is_string_or_str_slice(&search_args[0]);
84114
if is_string_or_str_slice(&search_args[1]);
85115
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-
);
116+
let msg = format!("called `{}()` after calling `find()` on a string", option_check_method);
117+
match option_check_method {
118+
"is_some" => {
119+
let mut applicability = Applicability::MachineApplicable;
120+
let find_arg = snippet_with_applicability(cx, search_args[1].span, "..", &mut applicability);
121+
span_lint_and_sugg(
122+
cx,
123+
SEARCH_IS_SOME,
124+
method_span.with_hi(expr.span.hi()),
125+
&msg,
126+
"use `contains()` instead",
127+
format!("contains({})", find_arg),
128+
applicability,
129+
);
130+
},
131+
"is_none" => {
132+
let string = snippet(cx, search_args[0].span, "..");
133+
let mut applicability = Applicability::MachineApplicable;
134+
let find_arg = snippet_with_applicability(cx, search_args[1].span, "..", &mut applicability);
135+
span_lint_and_sugg(
136+
cx,
137+
SEARCH_IS_SOME,
138+
expr.span,
139+
&msg,
140+
"use `!_.contains()` instead",
141+
format!("!{}.contains({})", string, find_arg),
142+
applicability,
143+
);
144+
},
145+
_ => (),
146+
}
98147
}
99148
}
100149
}

tests/ui/search_is_some.rs

+36-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// aux-build:option_helpers.rs
2+
#![warn(clippy::search_is_some)]
3+
#![allow(dead_code)]
24
extern crate option_helpers;
35
use option_helpers::IteratorFalsePositives;
46

5-
#[warn(clippy::search_is_some)]
67
#[rustfmt::skip]
78
fn main() {
89
let v = vec![3, 2, 1, 0, -1, -2, -3];
@@ -36,3 +37,37 @@ fn main() {
3637
// `Pattern` that is not a string
3738
let _ = "hello world".find(|c: char| c == 'o' || c == 'l').is_some();
3839
}
40+
41+
#[rustfmt::skip]
42+
fn is_none() {
43+
let v = vec![3, 2, 1, 0, -1, -2, -3];
44+
let y = &&42;
45+
46+
47+
// Check `find().is_none()`, multi-line case.
48+
let _ = v.iter().find(|&x| {
49+
*x < 0
50+
}
51+
).is_none();
52+
53+
// Check `position().is_none()`, multi-line case.
54+
let _ = v.iter().position(|&x| {
55+
x < 0
56+
}
57+
).is_none();
58+
59+
// Check `rposition().is_none()`, multi-line case.
60+
let _ = v.iter().rposition(|&x| {
61+
x < 0
62+
}
63+
).is_none();
64+
65+
// Check that we don't lint if the caller is not an `Iterator` or string
66+
let falsepos = IteratorFalsePositives { foo: 0 };
67+
let _ = falsepos.find().is_none();
68+
let _ = falsepos.position().is_none();
69+
let _ = falsepos.rposition().is_none();
70+
// check that we don't lint if `find()` is called with
71+
// `Pattern` that is not a string
72+
let _ = "hello world".find(|c: char| c == 'o' || c == 'l').is_none();
73+
}

tests/ui/search_is_some.stderr

+40-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: called `is_some()` after searching an `Iterator` with `find`
2-
--> $DIR/search_is_some.rs:13:13
2+
--> $DIR/search_is_some.rs:14:13
33
|
44
LL | let _ = v.iter().find(|&x| {
55
| _____________^
@@ -12,7 +12,7 @@ LL | | ).is_some();
1212
= help: this is more succinctly expressed by calling `any()`
1313

1414
error: called `is_some()` after searching an `Iterator` with `position`
15-
--> $DIR/search_is_some.rs:19:13
15+
--> $DIR/search_is_some.rs:20:13
1616
|
1717
LL | let _ = v.iter().position(|&x| {
1818
| _____________^
@@ -24,7 +24,7 @@ LL | | ).is_some();
2424
= help: this is more succinctly expressed by calling `any()`
2525

2626
error: called `is_some()` after searching an `Iterator` with `rposition`
27-
--> $DIR/search_is_some.rs:25:13
27+
--> $DIR/search_is_some.rs:26:13
2828
|
2929
LL | let _ = v.iter().rposition(|&x| {
3030
| _____________^
@@ -35,5 +35,41 @@ LL | | ).is_some();
3535
|
3636
= help: this is more succinctly expressed by calling `any()`
3737

38-
error: aborting due to 3 previous errors
38+
error: called `is_none()` after searching an `Iterator` with `find`
39+
--> $DIR/search_is_some.rs:48:13
40+
|
41+
LL | let _ = v.iter().find(|&x| {
42+
| _____________^
43+
LL | | *x < 0
44+
LL | | }
45+
LL | | ).is_none();
46+
| |______________________________^
47+
|
48+
= help: this is more succinctly expressed by calling `any()` with negation
49+
50+
error: called `is_none()` after searching an `Iterator` with `position`
51+
--> $DIR/search_is_some.rs:54:13
52+
|
53+
LL | let _ = v.iter().position(|&x| {
54+
| _____________^
55+
LL | | x < 0
56+
LL | | }
57+
LL | | ).is_none();
58+
| |______________________________^
59+
|
60+
= help: this is more succinctly expressed by calling `any()` with negation
61+
62+
error: called `is_none()` after searching an `Iterator` with `rposition`
63+
--> $DIR/search_is_some.rs:60:13
64+
|
65+
LL | let _ = v.iter().rposition(|&x| {
66+
| _____________^
67+
LL | | x < 0
68+
LL | | }
69+
LL | | ).is_none();
70+
| |______________________________^
71+
|
72+
= help: this is more succinctly expressed by calling `any()` with negation
73+
74+
error: aborting due to 6 previous errors
3975

tests/ui/search_is_some_fixable.fixed

+34-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// run-rustfix
2-
2+
#![allow(dead_code)]
33
#![warn(clippy::search_is_some)]
44

55
fn main() {
@@ -33,3 +33,36 @@ fn main() {
3333
let _ = s1[2..].contains(&s2);
3434
let _ = s1[2..].contains(&s2[2..]);
3535
}
36+
37+
fn is_none() {
38+
let v = vec![3, 2, 1, 0, -1, -2, -3];
39+
let y = &&42;
40+
41+
// Check `find().is_none()`, single-line case.
42+
let _ = !v.iter().any(|x| *x < 0);
43+
let _ = !(0..1).any(|x| **y == x); // one dereference less
44+
let _ = !(0..1).any(|x| x == 0);
45+
let _ = !v.iter().any(|x| *x == 0);
46+
47+
// Check `position().is_none()`, single-line case.
48+
let _ = !v.iter().any(|&x| x < 0);
49+
50+
// Check `rposition().is_none()`, single-line case.
51+
let _ = !v.iter().any(|&x| x < 0);
52+
53+
let s1 = String::from("hello world");
54+
let s2 = String::from("world");
55+
56+
// caller of `find()` is a `&`static str`
57+
let _ = !"hello world".contains("world");
58+
let _ = !"hello world".contains(&s2);
59+
let _ = !"hello world".contains(&s2[2..]);
60+
// caller of `find()` is a `String`
61+
let _ = !s1.contains("world");
62+
let _ = !s1.contains(&s2);
63+
let _ = !s1.contains(&s2[2..]);
64+
// caller of `find()` is slice of `String`
65+
let _ = !s1[2..].contains("world");
66+
let _ = !s1[2..].contains(&s2);
67+
let _ = !s1[2..].contains(&s2[2..]);
68+
}

0 commit comments

Comments
 (0)