Skip to content

Commit 7063e34

Browse files
committed
Auto merge of rust-lang#12094 - yuxqiu:search_is_some, r=xFrednet,ARandomDev99
fix: incorrect suggestions when `.then` and `.then_some` is used fixes rust-lang#11910 In the current implementation of `search_is_some`, if a `.is_none` call is followed by a `.then` or `.then_some` call, the generated `!` will incorrectly negate the values returned by the `then` and `.then_some` calls. To fix this, we need to add parentheses to the generated suggestions when appropriate. changelog: [`search_is_some`]: add parenthesis to suggestions when appropriate
2 parents 832fdb6 + b89fa53 commit 7063e34

File tree

4 files changed

+204
-8
lines changed

4 files changed

+204
-8
lines changed

clippy_lints/src/methods/search_is_some.rs

+31-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
22
use clippy_utils::source::{snippet, snippet_with_applicability};
33
use clippy_utils::sugg::deref_closure_args;
44
use clippy_utils::ty::is_type_lang_item;
5-
use clippy_utils::{is_trait_method, strip_pat_refs};
5+
use clippy_utils::{get_parent_expr, is_trait_method, strip_pat_refs};
6+
use hir::ExprKind;
67
use rustc_errors::Applicability;
78
use rustc_hir as hir;
89
use rustc_hir::PatKind;
@@ -35,7 +36,7 @@ pub(super) fn check<'tcx>(
3536
// suggest `any(|..| *..)` instead of `any(|..| **..)` for `find(|..| **..).is_some()`
3637
let mut applicability = Applicability::MachineApplicable;
3738
let any_search_snippet = if search_method == "find"
38-
&& let hir::ExprKind::Closure(&hir::Closure { body, .. }) = search_arg.kind
39+
&& let ExprKind::Closure(&hir::Closure { body, .. }) = search_arg.kind
3940
&& let closure_body = cx.tcx.hir().body(body)
4041
&& let Some(closure_arg) = closure_body.params.first()
4142
{
@@ -72,16 +73,24 @@ pub(super) fn check<'tcx>(
7273
);
7374
} else {
7475
let iter = snippet(cx, search_recv.span, "..");
76+
let sugg = if is_receiver_of_method_call(cx, expr) {
77+
format!(
78+
"(!{iter}.any({}))",
79+
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
80+
)
81+
} else {
82+
format!(
83+
"!{iter}.any({})",
84+
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
85+
)
86+
};
7587
span_lint_and_sugg(
7688
cx,
7789
SEARCH_IS_SOME,
7890
expr.span,
7991
msg,
8092
"consider using",
81-
format!(
82-
"!{iter}.any({})",
83-
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
84-
),
93+
sugg,
8594
applicability,
8695
);
8796
}
@@ -127,13 +136,18 @@ pub(super) fn check<'tcx>(
127136
let string = snippet(cx, search_recv.span, "..");
128137
let mut applicability = Applicability::MachineApplicable;
129138
let find_arg = snippet_with_applicability(cx, search_arg.span, "..", &mut applicability);
139+
let sugg = if is_receiver_of_method_call(cx, expr) {
140+
format!("(!{string}.contains({find_arg}))")
141+
} else {
142+
format!("!{string}.contains({find_arg})")
143+
};
130144
span_lint_and_sugg(
131145
cx,
132146
SEARCH_IS_SOME,
133147
expr.span,
134148
msg,
135149
"consider using",
136-
format!("!{string}.contains({find_arg})"),
150+
sugg,
137151
applicability,
138152
);
139153
},
@@ -142,3 +156,13 @@ pub(super) fn check<'tcx>(
142156
}
143157
}
144158
}
159+
160+
fn is_receiver_of_method_call(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
161+
if let Some(parent_expr) = get_parent_expr(cx, expr)
162+
&& let ExprKind::MethodCall(_, receiver, ..) = parent_expr.kind
163+
&& receiver.hir_id == expr.hir_id
164+
{
165+
return true;
166+
}
167+
false
168+
}

tests/ui/search_is_some_fixable_none.fixed

+50
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,53 @@ mod issue7392 {
213213
let _ = !v.iter().any(|fp| test_u32_2(*fp.field));
214214
}
215215
}
216+
217+
mod issue_11910 {
218+
fn computations() -> u32 {
219+
0
220+
}
221+
222+
struct Foo;
223+
impl Foo {
224+
fn bar(&self, _: bool) {}
225+
}
226+
227+
fn test_normal_for_iter() {
228+
let v = vec![3, 2, 1, 0, -1, -2, -3];
229+
let _ = !v.iter().any(|x| *x == 42);
230+
Foo.bar(!v.iter().any(|x| *x == 42));
231+
}
232+
233+
fn test_then_for_iter() {
234+
let v = vec![3, 2, 1, 0, -1, -2, -3];
235+
(!v.iter().any(|x| *x == 42)).then(computations);
236+
}
237+
238+
fn test_then_some_for_iter() {
239+
let v = vec![3, 2, 1, 0, -1, -2, -3];
240+
(!v.iter().any(|x| *x == 42)).then_some(0);
241+
}
242+
243+
fn test_normal_for_str() {
244+
let s = "hello";
245+
let _ = !s.contains("world");
246+
Foo.bar(!s.contains("world"));
247+
let s = String::from("hello");
248+
let _ = !s.contains("world");
249+
Foo.bar(!s.contains("world"));
250+
}
251+
252+
fn test_then_for_str() {
253+
let s = "hello";
254+
let _ = (!s.contains("world")).then(computations);
255+
let s = String::from("hello");
256+
let _ = (!s.contains("world")).then(computations);
257+
}
258+
259+
fn test_then_some_for_str() {
260+
let s = "hello";
261+
let _ = (!s.contains("world")).then_some(0);
262+
let s = String::from("hello");
263+
let _ = (!s.contains("world")).then_some(0);
264+
}
265+
}

tests/ui/search_is_some_fixable_none.rs

+50
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,53 @@ mod issue7392 {
219219
let _ = v.iter().find(|fp| test_u32_2(*fp.field)).is_none();
220220
}
221221
}
222+
223+
mod issue_11910 {
224+
fn computations() -> u32 {
225+
0
226+
}
227+
228+
struct Foo;
229+
impl Foo {
230+
fn bar(&self, _: bool) {}
231+
}
232+
233+
fn test_normal_for_iter() {
234+
let v = vec![3, 2, 1, 0, -1, -2, -3];
235+
let _ = v.iter().find(|x| **x == 42).is_none();
236+
Foo.bar(v.iter().find(|x| **x == 42).is_none());
237+
}
238+
239+
fn test_then_for_iter() {
240+
let v = vec![3, 2, 1, 0, -1, -2, -3];
241+
v.iter().find(|x| **x == 42).is_none().then(computations);
242+
}
243+
244+
fn test_then_some_for_iter() {
245+
let v = vec![3, 2, 1, 0, -1, -2, -3];
246+
v.iter().find(|x| **x == 42).is_none().then_some(0);
247+
}
248+
249+
fn test_normal_for_str() {
250+
let s = "hello";
251+
let _ = s.find("world").is_none();
252+
Foo.bar(s.find("world").is_none());
253+
let s = String::from("hello");
254+
let _ = s.find("world").is_none();
255+
Foo.bar(s.find("world").is_none());
256+
}
257+
258+
fn test_then_for_str() {
259+
let s = "hello";
260+
let _ = s.find("world").is_none().then(computations);
261+
let s = String::from("hello");
262+
let _ = s.find("world").is_none().then(computations);
263+
}
264+
265+
fn test_then_some_for_str() {
266+
let s = "hello";
267+
let _ = s.find("world").is_none().then_some(0);
268+
let s = String::from("hello");
269+
let _ = s.find("world").is_none().then_some(0);
270+
}
271+
}

tests/ui/search_is_some_fixable_none.stderr

+73-1
Original file line numberDiff line numberDiff line change
@@ -282,5 +282,77 @@ error: called `is_none()` after searching an `Iterator` with `find`
282282
LL | let _ = v.iter().find(|fp| test_u32_2(*fp.field)).is_none();
283283
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|fp| test_u32_2(*fp.field))`
284284

285-
error: aborting due to 43 previous errors
285+
error: called `is_none()` after searching an `Iterator` with `find`
286+
--> tests/ui/search_is_some_fixable_none.rs:235:17
287+
|
288+
LL | let _ = v.iter().find(|x| **x == 42).is_none();
289+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|x| *x == 42)`
290+
291+
error: called `is_none()` after searching an `Iterator` with `find`
292+
--> tests/ui/search_is_some_fixable_none.rs:236:17
293+
|
294+
LL | Foo.bar(v.iter().find(|x| **x == 42).is_none());
295+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!v.iter().any(|x| *x == 42)`
296+
297+
error: called `is_none()` after searching an `Iterator` with `find`
298+
--> tests/ui/search_is_some_fixable_none.rs:241:9
299+
|
300+
LL | v.iter().find(|x| **x == 42).is_none().then(computations);
301+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(!v.iter().any(|x| *x == 42))`
302+
303+
error: called `is_none()` after searching an `Iterator` with `find`
304+
--> tests/ui/search_is_some_fixable_none.rs:246:9
305+
|
306+
LL | v.iter().find(|x| **x == 42).is_none().then_some(0);
307+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(!v.iter().any(|x| *x == 42))`
308+
309+
error: called `is_none()` after calling `find()` on a string
310+
--> tests/ui/search_is_some_fixable_none.rs:251:17
311+
|
312+
LL | let _ = s.find("world").is_none();
313+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s.contains("world")`
314+
315+
error: called `is_none()` after calling `find()` on a string
316+
--> tests/ui/search_is_some_fixable_none.rs:252:17
317+
|
318+
LL | Foo.bar(s.find("world").is_none());
319+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s.contains("world")`
320+
321+
error: called `is_none()` after calling `find()` on a string
322+
--> tests/ui/search_is_some_fixable_none.rs:254:17
323+
|
324+
LL | let _ = s.find("world").is_none();
325+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s.contains("world")`
326+
327+
error: called `is_none()` after calling `find()` on a string
328+
--> tests/ui/search_is_some_fixable_none.rs:255:17
329+
|
330+
LL | Foo.bar(s.find("world").is_none());
331+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!s.contains("world")`
332+
333+
error: called `is_none()` after calling `find()` on a string
334+
--> tests/ui/search_is_some_fixable_none.rs:260:17
335+
|
336+
LL | let _ = s.find("world").is_none().then(computations);
337+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(!s.contains("world"))`
338+
339+
error: called `is_none()` after calling `find()` on a string
340+
--> tests/ui/search_is_some_fixable_none.rs:262:17
341+
|
342+
LL | let _ = s.find("world").is_none().then(computations);
343+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(!s.contains("world"))`
344+
345+
error: called `is_none()` after calling `find()` on a string
346+
--> tests/ui/search_is_some_fixable_none.rs:267:17
347+
|
348+
LL | let _ = s.find("world").is_none().then_some(0);
349+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(!s.contains("world"))`
350+
351+
error: called `is_none()` after calling `find()` on a string
352+
--> tests/ui/search_is_some_fixable_none.rs:269:17
353+
|
354+
LL | let _ = s.find("world").is_none().then_some(0);
355+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(!s.contains("world"))`
356+
357+
error: aborting due to 55 previous errors
286358

0 commit comments

Comments
 (0)