Skip to content

Commit 945d4cf

Browse files
BO41flip1995
authored andcommitted
Dereference one less on search_is_some and make it auto-fixable
1 parent 888b736 commit 945d4cf

File tree

4 files changed

+363
-37
lines changed

4 files changed

+363
-37
lines changed

clippy_lints/src/methods/mod.rs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2386,31 +2386,40 @@ fn lint_search_is_some<'a, 'tcx>(
23862386
let search_snippet = snippet(cx, search_args[1].span, "..");
23872387
if search_snippet.lines().count() <= 1 {
23882388
// suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()`
2389+
// suggest `any(|..| *..)` instead of `any(|..| **..)` for `find(|..| **..).is_some()`
23892390
let any_search_snippet = if_chain! {
23902391
if search_method == "find";
23912392
if let hir::ExprKind::Closure(_, _, body_id, ..) = search_args[1].node;
23922393
let closure_body = cx.tcx.hir().body(body_id);
23932394
if let Some(closure_arg) = closure_body.params.get(0);
23942395
if let hir::PatKind::Ref(..) = closure_arg.pat.node;
23952396
then {
2396-
Some(search_snippet.replacen('&', "", 1))
2397+
match &closure_arg.pat.node {
2398+
hir::PatKind::Ref(..) => Some(search_snippet.replacen('&', "", 1)),
2399+
hir::PatKind::Binding(_, _, expr, _) => {
2400+
let closure_arg_snip = snippet(cx, expr.span, "..");
2401+
Some(search_snippet.replace(&format!("*{}", closure_arg_snip), &closure_arg_snip))
2402+
}
2403+
_ => None,
2404+
}
23972405
} else {
23982406
None
23992407
}
24002408
};
24012409
// add note if not multi-line
2402-
span_note_and_lint(
2410+
span_lint_and_sugg(
24032411
cx,
24042412
SEARCH_IS_SOME,
24052413
expr.span,
24062414
&msg,
2407-
expr.span,
2408-
&format!(
2409-
"replace `{0}({1}).is_some()` with `any({2})`",
2410-
search_method,
2411-
search_snippet,
2412-
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
2415+
"try this",
2416+
format!(
2417+
"any({})",
2418+
any_search_snippet
2419+
.as_ref()
2420+
.map_or(&*search_snippet, String::as_str)
24132421
),
2422+
Applicability::MachineApplicable,
24142423
);
24152424
} else {
24162425
span_lint(cx, SEARCH_IS_SOME, expr.span, &msg);

tests/ui/methods.fixed

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
// aux-build:option_helpers.rs
2+
// compile-flags: --edition 2018
3+
// run-rustfix
4+
5+
#![warn(clippy::all, clippy::pedantic, clippy::option_unwrap_used)]
6+
#![allow(
7+
clippy::blacklisted_name,
8+
unused,
9+
clippy::print_stdout,
10+
clippy::non_ascii_literal,
11+
clippy::new_without_default,
12+
clippy::missing_docs_in_private_items,
13+
clippy::needless_pass_by_value,
14+
clippy::default_trait_access,
15+
clippy::use_self,
16+
clippy::useless_format,
17+
clippy::wrong_self_convention
18+
)]
19+
20+
#[macro_use]
21+
extern crate option_helpers;
22+
23+
use std::collections::BTreeMap;
24+
use std::collections::HashMap;
25+
use std::collections::HashSet;
26+
use std::collections::VecDeque;
27+
use std::iter::FromIterator;
28+
use std::ops::Mul;
29+
use std::rc::{self, Rc};
30+
use std::sync::{self, Arc};
31+
32+
use option_helpers::IteratorFalsePositives;
33+
34+
pub struct T;
35+
36+
impl T {
37+
pub fn add(self, other: T) -> T {
38+
self
39+
}
40+
41+
// no error, not public interface
42+
pub(crate) fn drop(&mut self) {}
43+
44+
// no error, private function
45+
fn neg(self) -> Self {
46+
self
47+
}
48+
49+
// no error, private function
50+
fn eq(&self, other: T) -> bool {
51+
true
52+
}
53+
54+
// No error; self is a ref.
55+
fn sub(&self, other: T) -> &T {
56+
self
57+
}
58+
59+
// No error; different number of arguments.
60+
fn div(self) -> T {
61+
self
62+
}
63+
64+
// No error; wrong return type.
65+
fn rem(self, other: T) {}
66+
67+
// Fine
68+
fn into_u32(self) -> u32 {
69+
0
70+
}
71+
72+
fn into_u16(&self) -> u16 {
73+
0
74+
}
75+
76+
fn to_something(self) -> u32 {
77+
0
78+
}
79+
80+
fn new(self) -> Self {
81+
unimplemented!();
82+
}
83+
}
84+
85+
struct Lt<'a> {
86+
foo: &'a u32,
87+
}
88+
89+
impl<'a> Lt<'a> {
90+
// The lifetime is different, but that’s irrelevant; see issue #734.
91+
#[allow(clippy::needless_lifetimes)]
92+
pub fn new<'b>(s: &'b str) -> Lt<'b> {
93+
unimplemented!()
94+
}
95+
}
96+
97+
struct Lt2<'a> {
98+
foo: &'a u32,
99+
}
100+
101+
impl<'a> Lt2<'a> {
102+
// The lifetime is different, but that’s irrelevant; see issue #734.
103+
pub fn new(s: &str) -> Lt2 {
104+
unimplemented!()
105+
}
106+
}
107+
108+
struct Lt3<'a> {
109+
foo: &'a u32,
110+
}
111+
112+
impl<'a> Lt3<'a> {
113+
// The lifetime is different, but that’s irrelevant; see issue #734.
114+
pub fn new() -> Lt3<'static> {
115+
unimplemented!()
116+
}
117+
}
118+
119+
#[derive(Clone, Copy)]
120+
struct U;
121+
122+
impl U {
123+
fn new() -> Self {
124+
U
125+
}
126+
// Ok because `U` is `Copy`.
127+
fn to_something(self) -> u32 {
128+
0
129+
}
130+
}
131+
132+
struct V<T> {
133+
_dummy: T,
134+
}
135+
136+
impl<T> V<T> {
137+
fn new() -> Option<V<T>> {
138+
None
139+
}
140+
}
141+
142+
struct AsyncNew;
143+
144+
impl AsyncNew {
145+
async fn new() -> Option<Self> {
146+
None
147+
}
148+
}
149+
150+
struct BadNew;
151+
152+
impl BadNew {
153+
fn new() -> i32 {
154+
0
155+
}
156+
}
157+
158+
impl Mul<T> for T {
159+
type Output = T;
160+
// No error, obviously.
161+
fn mul(self, other: T) -> T {
162+
self
163+
}
164+
}
165+
166+
/// Checks implementation of the following lints:
167+
/// * `OPTION_MAP_UNWRAP_OR`
168+
/// * `OPTION_MAP_UNWRAP_OR_ELSE`
169+
#[rustfmt::skip]
170+
fn option_methods() {
171+
let opt = Some(1);
172+
173+
// Check `OPTION_MAP_UNWRAP_OR`.
174+
// Single line case.
175+
let _ = opt.map(|x| x + 1)
176+
// Should lint even though this call is on a separate line.
177+
.unwrap_or(0);
178+
// Multi-line cases.
179+
let _ = opt.map(|x| {
180+
x + 1
181+
}
182+
).unwrap_or(0);
183+
let _ = opt.map(|x| x + 1)
184+
.unwrap_or({
185+
0
186+
});
187+
// Single line `map(f).unwrap_or(None)` case.
188+
let _ = opt.map(|x| Some(x + 1)).unwrap_or(None);
189+
// Multi-line `map(f).unwrap_or(None)` cases.
190+
let _ = opt.map(|x| {
191+
Some(x + 1)
192+
}
193+
).unwrap_or(None);
194+
let _ = opt
195+
.map(|x| Some(x + 1))
196+
.unwrap_or(None);
197+
// macro case
198+
let _ = opt_map!(opt, |x| x + 1).unwrap_or(0); // should not lint
199+
200+
// Should not lint if not copyable
201+
let id: String = "identifier".to_string();
202+
let _ = Some("prefix").map(|p| format!("{}.{}", p, id)).unwrap_or(id);
203+
// ...but DO lint if the `unwrap_or` argument is not used in the `map`
204+
let id: String = "identifier".to_string();
205+
let _ = Some("prefix").map(|p| format!("{}.", p)).unwrap_or(id);
206+
207+
// Check OPTION_MAP_UNWRAP_OR_ELSE
208+
// single line case
209+
let _ = opt.map(|x| x + 1)
210+
// Should lint even though this call is on a separate line.
211+
.unwrap_or_else(|| 0);
212+
// Multi-line cases.
213+
let _ = opt.map(|x| {
214+
x + 1
215+
}
216+
).unwrap_or_else(|| 0);
217+
let _ = opt.map(|x| x + 1)
218+
.unwrap_or_else(||
219+
0
220+
);
221+
// Macro case.
222+
// Should not lint.
223+
let _ = opt_map!(opt, |x| x + 1).unwrap_or_else(|| 0);
224+
225+
// Issue #4144
226+
{
227+
let mut frequencies = HashMap::new();
228+
let word = "foo";
229+
230+
frequencies
231+
.get_mut(word)
232+
.map(|count| {
233+
*count += 1;
234+
})
235+
.unwrap_or_else(|| {
236+
frequencies.insert(word.to_owned(), 1);
237+
});
238+
}
239+
}
240+
241+
/// Checks implementation of `FILTER_NEXT` lint.
242+
#[rustfmt::skip]
243+
fn filter_next() {
244+
let v = vec![3, 2, 1, 0, -1, -2, -3];
245+
246+
// Single-line case.
247+
let _ = v.iter().filter(|&x| *x < 0).next();
248+
249+
// Multi-line case.
250+
let _ = v.iter().filter(|&x| {
251+
*x < 0
252+
}
253+
).next();
254+
255+
// Check that hat we don't lint if the caller is not an `Iterator`.
256+
let foo = IteratorFalsePositives { foo: 0 };
257+
let _ = foo.filter().next();
258+
}
259+
260+
/// Checks implementation of `SEARCH_IS_SOME` lint.
261+
#[rustfmt::skip]
262+
fn search_is_some() {
263+
let v = vec![3, 2, 1, 0, -1, -2, -3];
264+
let y = &&42;
265+
266+
// Check `find().is_some()`, single-line case.
267+
let _ = any(|x| *x < 0);
268+
let _ = any(|x| **y == x); // one dereference less
269+
let _ = any(|x| x == 0);
270+
271+
// Check `find().is_some()`, multi-line case.
272+
let _ = v.iter().find(|&x| {
273+
*x < 0
274+
}
275+
).is_some();
276+
277+
// Check `position().is_some()`, single-line case.
278+
let _ = any(|&x| x < 0);
279+
280+
// Check `position().is_some()`, multi-line case.
281+
let _ = v.iter().position(|&x| {
282+
x < 0
283+
}
284+
).is_some();
285+
286+
// Check `rposition().is_some()`, single-line case.
287+
let _ = any(|&x| x < 0);
288+
289+
// Check `rposition().is_some()`, multi-line case.
290+
let _ = v.iter().rposition(|&x| {
291+
x < 0
292+
}
293+
).is_some();
294+
295+
// Check that we don't lint if the caller is not an `Iterator`.
296+
let foo = IteratorFalsePositives { foo: 0 };
297+
let _ = foo.find().is_some();
298+
let _ = foo.position().is_some();
299+
let _ = foo.rposition().is_some();
300+
}
301+
302+
#[allow(clippy::similar_names)]
303+
fn main() {
304+
let opt = Some(0);
305+
let _ = opt.unwrap();
306+
}

tests/ui/methods.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// aux-build:option_helpers.rs
22
// compile-flags: --edition 2018
3+
// run-rustfix
34

45
#![warn(clippy::all, clippy::pedantic, clippy::option_unwrap_used)]
56
#![allow(
@@ -260,9 +261,12 @@ fn filter_next() {
260261
#[rustfmt::skip]
261262
fn search_is_some() {
262263
let v = vec![3, 2, 1, 0, -1, -2, -3];
264+
let y = &&42;
263265

264266
// Check `find().is_some()`, single-line case.
265267
let _ = v.iter().find(|&x| *x < 0).is_some();
268+
let _ = (0..1).find(|x| **y == *x).is_some(); // one dereference less
269+
let _ = (0..1).find(|x| *x == 0).is_some();
266270

267271
// Check `find().is_some()`, multi-line case.
268272
let _ = v.iter().find(|&x| {

0 commit comments

Comments
 (0)