Skip to content

Commit ad31948

Browse files
Add new unnecessary_get_then_check clippy lint
1 parent a2c1d56 commit ad31948

File tree

4 files changed

+129
-5
lines changed

4 files changed

+129
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 42 additions & 5 deletions
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

Lines changed: 85 additions & 0 deletions
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+
}

0 commit comments

Comments
 (0)