Skip to content

Commit ad4f829

Browse files
committed
Auto merge of rust-lang#6119 - rsulli55:find_is_some_on_strs, r=flip1995
Add a case to `lint_search_is_some` to handle searching strings Fixes: rust-lang#6010 This adds a lint which recommends using `contains()` instead of `find()` followed by `is_some()` on strings as suggested in rust-lang#6010. This was added as an additional case to https://github.com/rust-lang/rust-clippy/blob/5af88e3c2d8cc4fb74a0e455381669930ee3a31a/clippy_lints/src/methods/mod.rs#L3037 I would really appreciate any comments/suggestions for my code! changelog: Added case to `lint_search_is_some` to handle searching strings
2 parents df3bb58 + 5c1c50e commit ad4f829

9 files changed

+280
-121
lines changed

clippy_lints/src/methods/mod.rs

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -515,11 +515,11 @@ declare_clippy_lint! {
515515
}
516516

517517
declare_clippy_lint! {
518-
/// **What it does:** Checks for an iterator search (such as `find()`,
518+
/// **What it does:** Checks for an iterator or string search (such as `find()`,
519519
/// `position()`, or `rposition()`) followed by a call to `is_some()`.
520520
///
521521
/// **Why is this bad?** Readability, this can be written more concisely as
522-
/// `_.any(_)`.
522+
/// `_.any(_)` or `_.contains(_)`.
523523
///
524524
/// **Known problems:** None.
525525
///
@@ -535,7 +535,7 @@ declare_clippy_lint! {
535535
/// ```
536536
pub SEARCH_IS_SOME,
537537
complexity,
538-
"using an iterator search followed by `is_some()`, which is more succinctly expressed as a call to `any()`"
538+
"using an iterator or string search followed by `is_some()`, which is more succinctly expressed as a call to `any()` or `contains()`"
539539
}
540540

541541
declare_clippy_lint! {
@@ -3041,6 +3041,7 @@ fn lint_flat_map_identity<'tcx>(
30413041
}
30423042

30433043
/// lint searching an Iterator followed by `is_some()`
3044+
/// or calling `find()` on a string followed by `is_some()`
30443045
fn lint_search_is_some<'tcx>(
30453046
cx: &LateContext<'tcx>,
30463047
expr: &'tcx hir::Expr<'_>,
@@ -3052,10 +3053,10 @@ fn lint_search_is_some<'tcx>(
30523053
// lint if caller of search is an Iterator
30533054
if match_trait_method(cx, &is_some_args[0], &paths::ITERATOR) {
30543055
let msg = format!(
3055-
"called `is_some()` after searching an `Iterator` with {}. This is more succinctly \
3056-
expressed by calling `any()`.",
3056+
"called `is_some()` after searching an `Iterator` with `{}`",
30573057
search_method
30583058
);
3059+
let hint = "this is more succinctly expressed by calling `any()`";
30593060
let search_snippet = snippet(cx, search_args[1].span, "..");
30603061
if search_snippet.lines().count() <= 1 {
30613062
// suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()`
@@ -3083,15 +3084,44 @@ fn lint_search_is_some<'tcx>(
30833084
SEARCH_IS_SOME,
30843085
method_span.with_hi(expr.span.hi()),
30853086
&msg,
3086-
"try this",
3087+
"use `any()` instead",
30873088
format!(
30883089
"any({})",
30893090
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
30903091
),
30913092
Applicability::MachineApplicable,
30923093
);
30933094
} else {
3094-
span_lint(cx, SEARCH_IS_SOME, expr.span, &msg);
3095+
span_lint_and_help(cx, SEARCH_IS_SOME, expr.span, &msg, None, hint);
3096+
}
3097+
}
3098+
// lint if `find()` is called by `String` or `&str`
3099+
else if search_method == "find" {
3100+
let is_string_or_str_slice = |e| {
3101+
let self_ty = cx.typeck_results().expr_ty(e).peel_refs();
3102+
if is_type_diagnostic_item(cx, self_ty, sym!(string_type)) {
3103+
true
3104+
} else {
3105+
*self_ty.kind() == ty::Str
3106+
}
3107+
};
3108+
if_chain! {
3109+
if is_string_or_str_slice(&search_args[0]);
3110+
if is_string_or_str_slice(&search_args[1]);
3111+
then {
3112+
let msg = "called `is_some()` after calling `find()` on a string";
3113+
let mut applicability = Applicability::MachineApplicable;
3114+
let find_arg = snippet_with_applicability(cx, search_args[1].span, "..", &mut applicability);
3115+
span_lint_and_sugg(
3116+
cx,
3117+
SEARCH_IS_SOME,
3118+
method_span.with_hi(expr.span.hi()),
3119+
msg,
3120+
"use `contains()` instead",
3121+
format!("contains({})", find_arg),
3122+
applicability,
3123+
);
3124+
}
30953125
}
30963126
}
30973127
}

src/lintlist/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2121,7 +2121,7 @@ vec![
21212121
Lint {
21222122
name: "search_is_some",
21232123
group: "complexity",
2124-
desc: "using an iterator search followed by `is_some()`, which is more succinctly expressed as a call to `any()`",
2124+
desc: "using an iterator or string search followed by `is_some()`, which is more succinctly expressed as a call to `any()` or `contains()`",
21252125
deprecation: None,
21262126
module: "methods",
21272127
},

tests/ui/methods.rs

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -133,50 +133,6 @@ fn filter_next() {
133133
let _ = foo.filter().next();
134134
}
135135

136-
/// Checks implementation of `SEARCH_IS_SOME` lint.
137-
#[rustfmt::skip]
138-
fn search_is_some() {
139-
let v = vec![3, 2, 1, 0, -1, -2, -3];
140-
let y = &&42;
141-
142-
// Check `find().is_some()`, single-line case.
143-
let _ = v.iter().find(|&x| *x < 0).is_some();
144-
let _ = (0..1).find(|x| **y == *x).is_some(); // one dereference less
145-
let _ = (0..1).find(|x| *x == 0).is_some();
146-
let _ = v.iter().find(|x| **x == 0).is_some();
147-
148-
// Check `find().is_some()`, multi-line case.
149-
let _ = v.iter().find(|&x| {
150-
*x < 0
151-
}
152-
).is_some();
153-
154-
// Check `position().is_some()`, single-line case.
155-
let _ = v.iter().position(|&x| x < 0).is_some();
156-
157-
// Check `position().is_some()`, multi-line case.
158-
let _ = v.iter().position(|&x| {
159-
x < 0
160-
}
161-
).is_some();
162-
163-
// Check `rposition().is_some()`, single-line case.
164-
let _ = v.iter().rposition(|&x| x < 0).is_some();
165-
166-
// Check `rposition().is_some()`, multi-line case.
167-
let _ = v.iter().rposition(|&x| {
168-
x < 0
169-
}
170-
).is_some();
171-
172-
// Check that we don't lint if the caller is not an `Iterator`.
173-
let foo = IteratorFalsePositives { foo: 0 };
174-
let _ = foo.find().is_some();
175-
let _ = foo.position().is_some();
176-
let _ = foo.rposition().is_some();
177-
}
178-
179136
fn main() {
180137
filter_next();
181-
search_is_some();
182138
}

tests/ui/methods.stderr

Lines changed: 1 addition & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -20,73 +20,5 @@ LL | | ).next();
2020
|
2121
= note: `-D clippy::filter-next` implied by `-D warnings`
2222

23-
error: called `is_some()` after searching an `Iterator` with find. This is more succinctly expressed by calling `any()`.
24-
--> $DIR/methods.rs:143:22
25-
|
26-
LL | let _ = v.iter().find(|&x| *x < 0).is_some();
27-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|x| *x < 0)`
28-
|
29-
= note: `-D clippy::search-is-some` implied by `-D warnings`
30-
31-
error: called `is_some()` after searching an `Iterator` with find. This is more succinctly expressed by calling `any()`.
32-
--> $DIR/methods.rs:144:20
33-
|
34-
LL | let _ = (0..1).find(|x| **y == *x).is_some(); // one dereference less
35-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|x| **y == x)`
36-
37-
error: called `is_some()` after searching an `Iterator` with find. This is more succinctly expressed by calling `any()`.
38-
--> $DIR/methods.rs:145:20
39-
|
40-
LL | let _ = (0..1).find(|x| *x == 0).is_some();
41-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|x| x == 0)`
42-
43-
error: called `is_some()` after searching an `Iterator` with find. This is more succinctly expressed by calling `any()`.
44-
--> $DIR/methods.rs:146:22
45-
|
46-
LL | let _ = v.iter().find(|x| **x == 0).is_some();
47-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|x| *x == 0)`
48-
49-
error: called `is_some()` after searching an `Iterator` with find. This is more succinctly expressed by calling `any()`.
50-
--> $DIR/methods.rs:149:13
51-
|
52-
LL | let _ = v.iter().find(|&x| {
53-
| _____________^
54-
LL | | *x < 0
55-
LL | | }
56-
LL | | ).is_some();
57-
| |______________________________^
58-
59-
error: called `is_some()` after searching an `Iterator` with position. This is more succinctly expressed by calling `any()`.
60-
--> $DIR/methods.rs:155:22
61-
|
62-
LL | let _ = v.iter().position(|&x| x < 0).is_some();
63-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|&x| x < 0)`
64-
65-
error: called `is_some()` after searching an `Iterator` with position. This is more succinctly expressed by calling `any()`.
66-
--> $DIR/methods.rs:158:13
67-
|
68-
LL | let _ = v.iter().position(|&x| {
69-
| _____________^
70-
LL | | x < 0
71-
LL | | }
72-
LL | | ).is_some();
73-
| |______________________________^
74-
75-
error: called `is_some()` after searching an `Iterator` with rposition. This is more succinctly expressed by calling `any()`.
76-
--> $DIR/methods.rs:164:22
77-
|
78-
LL | let _ = v.iter().rposition(|&x| x < 0).is_some();
79-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|&x| x < 0)`
80-
81-
error: called `is_some()` after searching an `Iterator` with rposition. This is more succinctly expressed by calling `any()`.
82-
--> $DIR/methods.rs:167:13
83-
|
84-
LL | let _ = v.iter().rposition(|&x| {
85-
| _____________^
86-
LL | | x < 0
87-
LL | | }
88-
LL | | ).is_some();
89-
| |______________________________^
90-
91-
error: aborting due to 11 previous errors
23+
error: aborting due to 2 previous errors
9224

tests/ui/search_is_some.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// aux-build:option_helpers.rs
2+
extern crate option_helpers;
3+
use option_helpers::IteratorFalsePositives;
4+
5+
#[warn(clippy::search_is_some)]
6+
#[rustfmt::skip]
7+
fn main() {
8+
let v = vec![3, 2, 1, 0, -1, -2, -3];
9+
let y = &&42;
10+
11+
12+
// Check `find().is_some()`, multi-line case.
13+
let _ = v.iter().find(|&x| {
14+
*x < 0
15+
}
16+
).is_some();
17+
18+
// Check `position().is_some()`, multi-line case.
19+
let _ = v.iter().position(|&x| {
20+
x < 0
21+
}
22+
).is_some();
23+
24+
// Check `rposition().is_some()`, multi-line case.
25+
let _ = v.iter().rposition(|&x| {
26+
x < 0
27+
}
28+
).is_some();
29+
30+
// Check that we don't lint if the caller is not an `Iterator` or string
31+
let falsepos = IteratorFalsePositives { foo: 0 };
32+
let _ = falsepos.find().is_some();
33+
let _ = falsepos.position().is_some();
34+
let _ = falsepos.rposition().is_some();
35+
// check that we don't lint if `find()` is called with
36+
// `Pattern` that is not a string
37+
let _ = "hello world".find(|c: char| c == 'o' || c == 'l').is_some();
38+
}

tests/ui/search_is_some.stderr

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
error: called `is_some()` after searching an `Iterator` with `find`
2+
--> $DIR/search_is_some.rs:13:13
3+
|
4+
LL | let _ = v.iter().find(|&x| {
5+
| _____________^
6+
LL | | *x < 0
7+
LL | | }
8+
LL | | ).is_some();
9+
| |______________________________^
10+
|
11+
= note: `-D clippy::search-is-some` implied by `-D warnings`
12+
= help: this is more succinctly expressed by calling `any()`
13+
14+
error: called `is_some()` after searching an `Iterator` with `position`
15+
--> $DIR/search_is_some.rs:19:13
16+
|
17+
LL | let _ = v.iter().position(|&x| {
18+
| _____________^
19+
LL | | x < 0
20+
LL | | }
21+
LL | | ).is_some();
22+
| |______________________________^
23+
|
24+
= help: this is more succinctly expressed by calling `any()`
25+
26+
error: called `is_some()` after searching an `Iterator` with `rposition`
27+
--> $DIR/search_is_some.rs:25:13
28+
|
29+
LL | let _ = v.iter().rposition(|&x| {
30+
| _____________^
31+
LL | | x < 0
32+
LL | | }
33+
LL | | ).is_some();
34+
| |______________________________^
35+
|
36+
= help: this is more succinctly expressed by calling `any()`
37+
38+
error: aborting due to 3 previous errors
39+

tests/ui/search_is_some_fixable.fixed

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::search_is_some)]
4+
5+
fn main() {
6+
let v = vec![3, 2, 1, 0, -1, -2, -3];
7+
let y = &&42;
8+
9+
// Check `find().is_some()`, single-line case.
10+
let _ = v.iter().any(|x| *x < 0);
11+
let _ = (0..1).any(|x| **y == x); // one dereference less
12+
let _ = (0..1).any(|x| x == 0);
13+
let _ = v.iter().any(|x| *x == 0);
14+
15+
// Check `position().is_some()`, single-line case.
16+
let _ = v.iter().any(|&x| x < 0);
17+
18+
// Check `rposition().is_some()`, single-line case.
19+
let _ = v.iter().any(|&x| x < 0);
20+
21+
let s1 = String::from("hello world");
22+
let s2 = String::from("world");
23+
// caller of `find()` is a `&`static str`
24+
let _ = "hello world".contains("world");
25+
let _ = "hello world".contains(&s2);
26+
let _ = "hello world".contains(&s2[2..]);
27+
// caller of `find()` is a `String`
28+
let _ = s1.contains("world");
29+
let _ = s1.contains(&s2);
30+
let _ = s1.contains(&s2[2..]);
31+
// caller of `find()` is slice of `String`
32+
let _ = s1[2..].contains("world");
33+
let _ = s1[2..].contains(&s2);
34+
let _ = s1[2..].contains(&s2[2..]);
35+
}

tests/ui/search_is_some_fixable.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// run-rustfix
2+
3+
#![warn(clippy::search_is_some)]
4+
5+
fn main() {
6+
let v = vec![3, 2, 1, 0, -1, -2, -3];
7+
let y = &&42;
8+
9+
// Check `find().is_some()`, single-line case.
10+
let _ = v.iter().find(|&x| *x < 0).is_some();
11+
let _ = (0..1).find(|x| **y == *x).is_some(); // one dereference less
12+
let _ = (0..1).find(|x| *x == 0).is_some();
13+
let _ = v.iter().find(|x| **x == 0).is_some();
14+
15+
// Check `position().is_some()`, single-line case.
16+
let _ = v.iter().position(|&x| x < 0).is_some();
17+
18+
// Check `rposition().is_some()`, single-line case.
19+
let _ = v.iter().rposition(|&x| x < 0).is_some();
20+
21+
let s1 = String::from("hello world");
22+
let s2 = String::from("world");
23+
// caller of `find()` is a `&`static str`
24+
let _ = "hello world".find("world").is_some();
25+
let _ = "hello world".find(&s2).is_some();
26+
let _ = "hello world".find(&s2[2..]).is_some();
27+
// caller of `find()` is a `String`
28+
let _ = s1.find("world").is_some();
29+
let _ = s1.find(&s2).is_some();
30+
let _ = s1.find(&s2[2..]).is_some();
31+
// caller of `find()` is slice of `String`
32+
let _ = s1[2..].find("world").is_some();
33+
let _ = s1[2..].find(&s2).is_some();
34+
let _ = s1[2..].find(&s2[2..]).is_some();
35+
}

0 commit comments

Comments
 (0)