Skip to content

Commit 76e4864

Browse files
committed
Auto merge of rust-lang#12339 - GuillaumeGomez:add-unnecessary_get_then_check, r=llogiq
Add new `unnecessary_get_then_check` lint No issue linked to this as far as I can see. It's a lint I discovered that could be added when I worked on another lint. r? `@llogiq` changelog: Add new `unnecessary_get_then_check` lint
2 parents 8ef1b73 + 5e2707d commit 76e4864

11 files changed

+273
-20
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5732,6 +5732,7 @@ Released 2018-09-13
57325732
[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map
57335733
[`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map
57345734
[`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold
5735+
[`unnecessary_get_then_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_get_then_check
57355736
[`unnecessary_join`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_join
57365737
[`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
57375738
[`unnecessary_literal_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_literal_unwrap

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
455455
crate::methods::UNNECESSARY_FILTER_MAP_INFO,
456456
crate::methods::UNNECESSARY_FIND_MAP_INFO,
457457
crate::methods::UNNECESSARY_FOLD_INFO,
458+
crate::methods::UNNECESSARY_GET_THEN_CHECK_INFO,
458459
crate::methods::UNNECESSARY_JOIN_INFO,
459460
crate::methods::UNNECESSARY_LAZY_EVALUATIONS_INFO,
460461
crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO,

clippy_lints/src/methods/mod.rs

+42-5
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ mod unit_hash;
110110
mod unnecessary_fallible_conversions;
111111
mod unnecessary_filter_map;
112112
mod unnecessary_fold;
113+
mod unnecessary_get_then_check;
113114
mod unnecessary_iter_cloned;
114115
mod unnecessary_join;
115116
mod unnecessary_lazy_eval;
@@ -4011,6 +4012,35 @@ declare_clippy_lint! {
40114012
r#"creating a `CStr` through functions when `c""` literals can be used"#
40124013
}
40134014

4015+
declare_clippy_lint! {
4016+
/// ### What it does
4017+
/// Checks the usage of `.get().is_some()` or `.get().is_none()` on std map types.
4018+
///
4019+
/// ### Why is this bad?
4020+
/// It can be done in one call with `.contains()`/`.contains_keys()`.
4021+
///
4022+
/// ### Example
4023+
/// ```no_run
4024+
/// # use std::collections::HashSet;
4025+
/// let s: HashSet<String> = HashSet::new();
4026+
/// if s.get("a").is_some() {
4027+
/// // code
4028+
/// }
4029+
/// ```
4030+
/// Use instead:
4031+
/// ```no_run
4032+
/// # use std::collections::HashSet;
4033+
/// let s: HashSet<String> = HashSet::new();
4034+
/// if s.contains("a") {
4035+
/// // code
4036+
/// }
4037+
/// ```
4038+
#[clippy::version = "1.78.0"]
4039+
pub UNNECESSARY_GET_THEN_CHECK,
4040+
suspicious,
4041+
"calling `.get().is_some()` or `.get().is_none()` instead of `.contains()` or `.contains_key()`"
4042+
}
4043+
40144044
pub struct Methods {
40154045
avoid_breaking_exported_api: bool,
40164046
msrv: Msrv,
@@ -4171,6 +4201,7 @@ impl_lint_pass!(Methods => [
41714201
OPTION_AS_REF_CLONED,
41724202
UNNECESSARY_RESULT_MAP_OR_ELSE,
41734203
MANUAL_C_STR_LITERALS,
4204+
UNNECESSARY_GET_THEN_CHECK,
41744205
]);
41754206

41764207
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4587,8 +4618,8 @@ impl Methods {
45874618
},
45884619
("is_file", []) => filetype_is_file::check(cx, expr, recv),
45894620
("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, &self.msrv),
4590-
("is_none", []) => check_is_some_is_none(cx, expr, recv, false),
4591-
("is_some", []) => check_is_some_is_none(cx, expr, recv, true),
4621+
("is_none", []) => check_is_some_is_none(cx, expr, recv, call_span, false),
4622+
("is_some", []) => check_is_some_is_none(cx, expr, recv, call_span, true),
45924623
("iter" | "iter_mut" | "into_iter", []) => {
45934624
iter_on_single_or_empty_collections::check(cx, expr, name, recv);
45944625
},
@@ -4899,9 +4930,15 @@ impl Methods {
48994930
}
49004931
}
49014932

4902-
fn check_is_some_is_none(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, is_some: bool) {
4903-
if let Some((name @ ("find" | "position" | "rposition"), f_recv, [arg], span, _)) = method_call(recv) {
4904-
search_is_some::check(cx, expr, name, is_some, f_recv, arg, recv, span);
4933+
fn check_is_some_is_none(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, call_span: Span, is_some: bool) {
4934+
match method_call(recv) {
4935+
Some((name @ ("find" | "position" | "rposition"), f_recv, [arg], span, _)) => {
4936+
search_is_some::check(cx, expr, name, is_some, f_recv, arg, recv, span);
4937+
},
4938+
Some(("get", f_recv, [arg], _, _)) => {
4939+
unnecessary_get_then_check::check(cx, call_span, recv, f_recv, arg, is_some);
4940+
},
4941+
_ => {},
49054942
}
49064943
}
49074944

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
2+
use clippy_utils::source::snippet_opt;
3+
use clippy_utils::ty::is_type_diagnostic_item;
4+
5+
use rustc_errors::Applicability;
6+
use rustc_hir::{Expr, ExprKind};
7+
use rustc_lint::LateContext;
8+
use rustc_middle::ty::Ty;
9+
use rustc_span::{sym, Span};
10+
11+
use super::UNNECESSARY_GET_THEN_CHECK;
12+
13+
fn is_a_std_set_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
14+
is_type_diagnostic_item(cx, ty, sym::HashSet) || is_type_diagnostic_item(cx, ty, sym::BTreeSet)
15+
}
16+
17+
fn is_a_std_map_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
18+
is_type_diagnostic_item(cx, ty, sym::HashMap) || is_type_diagnostic_item(cx, ty, sym::BTreeMap)
19+
}
20+
21+
pub(super) fn check(
22+
cx: &LateContext<'_>,
23+
call_span: Span,
24+
get_call: &Expr<'_>,
25+
get_caller: &Expr<'_>,
26+
arg: &Expr<'_>,
27+
is_some: bool,
28+
) {
29+
let caller_ty = cx.typeck_results().expr_ty(get_caller);
30+
31+
let is_set = is_a_std_set_type(cx, caller_ty);
32+
let is_map = is_a_std_map_type(cx, caller_ty);
33+
34+
if !is_set && !is_map {
35+
return;
36+
}
37+
let ExprKind::MethodCall(path, _, _, get_call_span) = get_call.kind else {
38+
return;
39+
};
40+
let both_calls_span = get_call_span.with_hi(call_span.hi());
41+
if let Some(snippet) = snippet_opt(cx, both_calls_span)
42+
&& let Some(arg_snippet) = snippet_opt(cx, arg.span)
43+
{
44+
let generics_snippet = if let Some(generics) = path.args
45+
&& let Some(generics_snippet) = snippet_opt(cx, generics.span_ext)
46+
{
47+
format!("::{generics_snippet}")
48+
} else {
49+
String::new()
50+
};
51+
let suggestion = if is_set {
52+
format!("contains{generics_snippet}({arg_snippet})")
53+
} else {
54+
format!("contains_key{generics_snippet}({arg_snippet})")
55+
};
56+
if is_some {
57+
span_lint_and_sugg(
58+
cx,
59+
UNNECESSARY_GET_THEN_CHECK,
60+
both_calls_span,
61+
&format!("unnecessary use of `{snippet}`"),
62+
"replace it with",
63+
suggestion,
64+
Applicability::MaybeIncorrect,
65+
);
66+
} else if let Some(caller_snippet) = snippet_opt(cx, get_caller.span) {
67+
let full_span = get_caller.span.with_hi(call_span.hi());
68+
69+
span_lint_and_then(
70+
cx,
71+
UNNECESSARY_GET_THEN_CHECK,
72+
both_calls_span,
73+
&format!("unnecessary use of `{snippet}`"),
74+
|diag| {
75+
diag.span_suggestion(
76+
full_span,
77+
"replace it with",
78+
format!("{}{caller_snippet}.{suggestion}", if is_some { "" } else { "!" }),
79+
Applicability::MaybeIncorrect,
80+
);
81+
},
82+
);
83+
}
84+
}
85+
}

lintcheck/src/main.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -749,15 +749,15 @@ fn print_stats(old_stats: HashMap<String, usize>, new_stats: HashMap<&String, us
749749
// list all new counts (key is in new stats but not in old stats)
750750
new_stats_deduped
751751
.iter()
752-
.filter(|(new_key, _)| old_stats_deduped.get::<str>(new_key).is_none())
752+
.filter(|(new_key, _)| !old_stats_deduped.contains_key::<str>(new_key))
753753
.for_each(|(new_key, new_value)| {
754754
println!("{new_key} 0 => {new_value}");
755755
});
756756

757757
// list all changed counts (key is in both maps but value differs)
758758
new_stats_deduped
759759
.iter()
760-
.filter(|(new_key, _new_val)| old_stats_deduped.get::<str>(new_key).is_some())
760+
.filter(|(new_key, _new_val)| old_stats_deduped.contains_key::<str>(new_key))
761761
.for_each(|(new_key, new_val)| {
762762
let old_val = old_stats_deduped.get::<str>(new_key).unwrap();
763763
println!("{new_key} {old_val} => {new_val}");
@@ -766,7 +766,7 @@ fn print_stats(old_stats: HashMap<String, usize>, new_stats: HashMap<&String, us
766766
// list all gone counts (key is in old status but not in new stats)
767767
old_stats_deduped
768768
.iter()
769-
.filter(|(old_key, _)| new_stats_deduped.get::<&String>(old_key).is_none())
769+
.filter(|(old_key, _)| !new_stats_deduped.contains_key::<&String>(old_key))
770770
.filter(|(old_key, _)| lint_filter.is_empty() || lint_filter.contains(old_key))
771771
.for_each(|(old_key, old_value)| {
772772
println!("{old_key} {old_value} => 0");

tests/ui/iter_filter_is_some.fixed

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
clippy::result_filter_map,
55
clippy::needless_borrow,
66
clippy::option_filter_map,
7-
clippy::redundant_closure
7+
clippy::redundant_closure,
8+
clippy::unnecessary_get_then_check
89
)]
910

1011
use std::collections::HashMap;

tests/ui/iter_filter_is_some.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
clippy::result_filter_map,
55
clippy::needless_borrow,
66
clippy::option_filter_map,
7-
clippy::redundant_closure
7+
clippy::redundant_closure,
8+
clippy::unnecessary_get_then_check
89
)]
910

1011
use std::collections::HashMap;

tests/ui/iter_filter_is_some.stderr

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: `filter` for `is_some` on iterator over `Option`
2-
--> tests/ui/iter_filter_is_some.rs:14:58
2+
--> tests/ui/iter_filter_is_some.rs:15:58
33
|
44
LL | let _ = vec![Some(1), None, Some(3)].into_iter().filter(Option::is_some);
55
| ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
@@ -8,55 +8,55 @@ LL | let _ = vec![Some(1), None, Some(3)].into_iter().filter(Option::is_
88
= help: to override `-D warnings` add `#[allow(clippy::iter_filter_is_some)]`
99

1010
error: `filter` for `is_some` on iterator over `Option`
11-
--> tests/ui/iter_filter_is_some.rs:16:58
11+
--> tests/ui/iter_filter_is_some.rs:17:58
1212
|
1313
LL | let _ = vec![Some(1), None, Some(3)].into_iter().filter(|a| a.is_some());
1414
| ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
1515

1616
error: `filter` for `is_some` on iterator over `Option`
17-
--> tests/ui/iter_filter_is_some.rs:19:58
17+
--> tests/ui/iter_filter_is_some.rs:20:58
1818
|
1919
LL | let _ = vec![Some(1), None, Some(3)].into_iter().filter(|o| { o.is_some() });
2020
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
2121

2222
error: `filter` for `is_some` on iterator over `Option`
23-
--> tests/ui/iter_filter_is_some.rs:26:14
23+
--> tests/ui/iter_filter_is_some.rs:27:14
2424
|
2525
LL | .filter(std::option::Option::is_some);
2626
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
2727

2828
error: `filter` for `is_some` on iterator over `Option`
29-
--> tests/ui/iter_filter_is_some.rs:31:14
29+
--> tests/ui/iter_filter_is_some.rs:32:14
3030
|
3131
LL | .filter(|a| std::option::Option::is_some(a));
3232
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
3333

3434
error: `filter` for `is_some` on iterator over `Option`
35-
--> tests/ui/iter_filter_is_some.rs:34:58
35+
--> tests/ui/iter_filter_is_some.rs:35:58
3636
|
3737
LL | let _ = vec![Some(1), None, Some(3)].into_iter().filter(|a| { std::option::Option::is_some(a) });
3838
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
3939

4040
error: `filter` for `is_some` on iterator over `Option`
41-
--> tests/ui/iter_filter_is_some.rs:39:58
41+
--> tests/ui/iter_filter_is_some.rs:40:58
4242
|
4343
LL | let _ = vec![Some(1), None, Some(3)].into_iter().filter(|&a| a.is_some());
4444
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
4545

4646
error: `filter` for `is_some` on iterator over `Option`
47-
--> tests/ui/iter_filter_is_some.rs:43:58
47+
--> tests/ui/iter_filter_is_some.rs:44:58
4848
|
4949
LL | let _ = vec![Some(1), None, Some(3)].into_iter().filter(|&o| { o.is_some() });
5050
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
5151

5252
error: `filter` for `is_some` on iterator over `Option`
53-
--> tests/ui/iter_filter_is_some.rs:48:58
53+
--> tests/ui/iter_filter_is_some.rs:49:58
5454
|
5555
LL | let _ = vec![Some(1), None, Some(3)].into_iter().filter(|ref a| a.is_some());
5656
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
5757

5858
error: `filter` for `is_some` on iterator over `Option`
59-
--> tests/ui/iter_filter_is_some.rs:52:58
59+
--> tests/ui/iter_filter_is_some.rs:53:58
6060
|
6161
LL | let _ = vec![Some(1), None, Some(3)].into_iter().filter(|ref o| { o.is_some() });
6262
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#![warn(clippy::unnecessary_get_then_check)]
2+
3+
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
4+
5+
fn main() {
6+
let s: HashSet<String> = HashSet::new();
7+
let _ = s.contains("a"); //~ ERROR: unnecessary use of `get("a").is_some()`
8+
let _ = !s.contains("a"); //~ ERROR: unnecessary use of `get("a").is_none()`
9+
10+
let s: HashMap<String, ()> = HashMap::new();
11+
let _ = s.contains_key("a"); //~ ERROR: unnecessary use of `get("a").is_some()`
12+
let _ = !s.contains_key("a"); //~ ERROR: unnecessary use of `get("a").is_none()`
13+
14+
let s: BTreeSet<String> = BTreeSet::new();
15+
let _ = s.contains("a"); //~ ERROR: unnecessary use of `get("a").is_some()`
16+
let _ = !s.contains("a"); //~ ERROR: unnecessary use of `get("a").is_none()`
17+
18+
let s: BTreeMap<String, ()> = BTreeMap::new();
19+
let _ = s.contains_key("a"); //~ ERROR: unnecessary use of `get("a").is_some()`
20+
let _ = !s.contains_key("a"); //~ ERROR: unnecessary use of `get("a").is_none()`
21+
22+
// Import to check that the generic annotations are kept!
23+
let s: HashSet<String> = HashSet::new();
24+
let _ = s.contains::<str>("a"); //~ ERROR: unnecessary use of `get::<str>("a").is_some()`
25+
let _ = !s.contains::<str>("a"); //~ ERROR: unnecessary use of `get::<str>("a").is_none()`
26+
}
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#![warn(clippy::unnecessary_get_then_check)]
2+
3+
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
4+
5+
fn main() {
6+
let s: HashSet<String> = HashSet::new();
7+
let _ = s.get("a").is_some(); //~ ERROR: unnecessary use of `get("a").is_some()`
8+
let _ = s.get("a").is_none(); //~ ERROR: unnecessary use of `get("a").is_none()`
9+
10+
let s: HashMap<String, ()> = HashMap::new();
11+
let _ = s.get("a").is_some(); //~ ERROR: unnecessary use of `get("a").is_some()`
12+
let _ = s.get("a").is_none(); //~ ERROR: unnecessary use of `get("a").is_none()`
13+
14+
let s: BTreeSet<String> = BTreeSet::new();
15+
let _ = s.get("a").is_some(); //~ ERROR: unnecessary use of `get("a").is_some()`
16+
let _ = s.get("a").is_none(); //~ ERROR: unnecessary use of `get("a").is_none()`
17+
18+
let s: BTreeMap<String, ()> = BTreeMap::new();
19+
let _ = s.get("a").is_some(); //~ ERROR: unnecessary use of `get("a").is_some()`
20+
let _ = s.get("a").is_none(); //~ ERROR: unnecessary use of `get("a").is_none()`
21+
22+
// Import to check that the generic annotations are kept!
23+
let s: HashSet<String> = HashSet::new();
24+
let _ = s.get::<str>("a").is_some(); //~ ERROR: unnecessary use of `get::<str>("a").is_some()`
25+
let _ = s.get::<str>("a").is_none(); //~ ERROR: unnecessary use of `get::<str>("a").is_none()`
26+
}

0 commit comments

Comments
 (0)