Skip to content

Commit e48d9e5

Browse files
committed
Rename lint, generalize function, add known issues, use multispan
1 parent fb239f3 commit e48d9e5

7 files changed

+105
-161
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5735,6 +5735,7 @@ Released 2018-09-13
57355735
[`semicolon_outside_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_outside_block
57365736
[`separated_literal_suffix`]: https://rust-lang.github.io/rust-clippy/master/index.html#separated_literal_suffix
57375737
[`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse
5738+
[`set_contains_or_insert`]: https://rust-lang.github.io/rust-clippy/master/index.html#set_contains_or_insert
57385739
[`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse
57395740
[`shadow_same`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_same
57405741
[`shadow_unrelated`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_unrelated

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
212212
crate::functions::TOO_MANY_ARGUMENTS_INFO,
213213
crate::functions::TOO_MANY_LINES_INFO,
214214
crate::future_not_send::FUTURE_NOT_SEND_INFO,
215-
crate::hashset_insert_after_contains::HASHSET_INSERT_AFTER_CONTAINS_INFO,
216215
crate::if_let_mutex::IF_LET_MUTEX_INFO,
217216
crate::if_not_else::IF_NOT_ELSE_INFO,
218217
crate::if_then_some_else_none::IF_THEN_SOME_ELSE_NONE_INFO,
@@ -643,6 +642,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
643642
crate::semicolon_block::SEMICOLON_OUTSIDE_BLOCK_INFO,
644643
crate::semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED_INFO,
645644
crate::serde_api::SERDE_API_MISUSE_INFO,
645+
crate::set_contains_or_insert::SET_CONTAINS_OR_INSERT_INFO,
646646
crate::shadow::SHADOW_REUSE_INFO,
647647
crate::shadow::SHADOW_SAME_INFO,
648648
crate::shadow::SHADOW_UNRELATED_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,6 @@ mod from_raw_with_void_ptr;
149149
mod from_str_radix_10;
150150
mod functions;
151151
mod future_not_send;
152-
mod hashset_insert_after_contains;
153152
mod if_let_mutex;
154153
mod if_not_else;
155154
mod if_then_some_else_none;
@@ -316,6 +315,7 @@ mod self_named_constructors;
316315
mod semicolon_block;
317316
mod semicolon_if_nothing_returned;
318317
mod serde_api;
318+
mod set_contains_or_insert;
319319
mod shadow;
320320
mod significant_drop_tightening;
321321
mod single_call_fn;
@@ -1166,7 +1166,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
11661166
..Default::default()
11671167
})
11681168
});
1169-
store.register_late_pass(|_| Box::new(hashset_insert_after_contains::HashsetInsertAfterContains));
1169+
store.register_late_pass(|_| Box::new(set_contains_or_insert::HashsetInsertAfterContains));
11701170
// add lints here, do not remove this comment, it's used in `new_lint`
11711171
}
11721172

Lines changed: 33 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use std::ops::ControlFlow;
22

3-
use clippy_utils::diagnostics::span_lint_and_then;
3+
use clippy_utils::diagnostics::span_lint;
44
use clippy_utils::ty::is_type_diagnostic_item;
55
use clippy_utils::visitors::for_each_expr;
66
use clippy_utils::{higher, peel_hir_expr_while, SpanlessEq};
77
use rustc_hir::{Expr, ExprKind, UnOp};
88
use rustc_lint::{LateContext, LateLintPass};
99
use rustc_session::declare_lint_pass;
10+
use rustc_span::symbol::Symbol;
1011
use rustc_span::{sym, Span};
1112

1213
declare_clippy_lint! {
@@ -17,6 +18,11 @@ declare_clippy_lint! {
1718
/// ### Why is this bad?
1819
/// Using just `insert` and checking the returned `bool` is more efficient.
1920
///
21+
/// ### Known problems
22+
/// In case the value that wants to be inserted is borrowed and also expensive or impossible
23+
/// to clone. In such scenario, the developer might want to check with `contain` before inserting,
24+
/// to avoid the clone. In this case, it will report a false positive.
25+
///
2026
/// ### Example
2127
/// ```rust
2228
/// use std::collections::HashSet;
@@ -37,12 +43,12 @@ declare_clippy_lint! {
3743
/// }
3844
/// ```
3945
#[clippy::version = "1.80.0"]
40-
pub HASHSET_INSERT_AFTER_CONTAINS,
46+
pub SET_CONTAINS_OR_INSERT,
4147
nursery,
42-
"unnecessary call to `HashSet::contains` followed by `HashSet::insert`"
48+
"call to `HashSet::contains` followed by `HashSet::insert`"
4349
}
4450

45-
declare_lint_pass!(HashsetInsertAfterContains => [HASHSET_INSERT_AFTER_CONTAINS]);
51+
declare_lint_pass!(HashsetInsertAfterContains => [SET_CONTAINS_OR_INSERT]);
4652

4753
impl<'tcx> LateLintPass<'tcx> for HashsetInsertAfterContains {
4854
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
@@ -52,56 +58,35 @@ impl<'tcx> LateLintPass<'tcx> for HashsetInsertAfterContains {
5258
then: then_expr,
5359
..
5460
}) = higher::If::hir(expr)
55-
&& let Some(contains_expr) = try_parse_contains(cx, cond_expr)
56-
&& find_insert_calls(cx, &contains_expr, then_expr)
61+
&& let Some(contains_expr) = try_parse_op_call(cx, cond_expr, sym!(contains))//try_parse_contains(cx, cond_expr)
62+
&& let Some(insert_expr) = find_insert_calls(cx, &contains_expr, then_expr)
5763
{
58-
span_lint_and_then(
64+
span_lint(
5965
cx,
60-
HASHSET_INSERT_AFTER_CONTAINS,
61-
expr.span,
66+
SET_CONTAINS_OR_INSERT,
67+
vec![contains_expr.span, insert_expr.span],
6268
"usage of `HashSet::insert` after `HashSet::contains`",
63-
|diag| {
64-
diag.note("`HashSet::insert` returns whether it was inserted")
65-
.span_help(contains_expr.span, "remove the `HashSet::contains` call");
66-
},
6769
);
6870
}
6971
}
7072
}
7173

72-
struct ContainsExpr<'tcx> {
74+
struct OpExpr<'tcx> {
7375
receiver: &'tcx Expr<'tcx>,
7476
value: &'tcx Expr<'tcx>,
7577
span: Span,
7678
}
77-
fn try_parse_contains<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<ContainsExpr<'tcx>> {
79+
80+
fn try_parse_op_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, symbol: Symbol) -> Option<OpExpr<'tcx>> {
7881
let expr = peel_hir_expr_while(expr, |e| {
7982
if let ExprKind::Unary(UnOp::Not, e) = e.kind {
8083
Some(e)
8184
} else {
8285
None
8386
}
8487
});
85-
if let ExprKind::MethodCall(path, receiver, [value], span) = expr.kind {
86-
let value = value.peel_borrows();
87-
let receiver = receiver.peel_borrows();
88-
let receiver_ty = cx.typeck_results().expr_ty(receiver).peel_refs();
89-
if value.span.eq_ctxt(expr.span)
90-
&& is_type_diagnostic_item(cx, receiver_ty, sym::HashSet)
91-
&& path.ident.name == sym!(contains)
92-
{
93-
return Some(ContainsExpr { receiver, value, span });
94-
}
95-
}
96-
None
97-
}
9888

99-
struct InsertExpr<'tcx> {
100-
receiver: &'tcx Expr<'tcx>,
101-
value: &'tcx Expr<'tcx>,
102-
}
103-
fn try_parse_insert<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<InsertExpr<'tcx>> {
104-
if let ExprKind::MethodCall(path, receiver, [value], _) = expr.kind {
89+
if let ExprKind::MethodCall(path, receiver, [value], span) = expr.kind {
10590
let value = value.peel_borrows();
10691
let value = peel_hir_expr_while(value, |e| {
10792
if let ExprKind::Unary(UnOp::Deref, e) = e.kind {
@@ -110,28 +95,31 @@ fn try_parse_insert<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Optio
11095
None
11196
}
11297
});
113-
98+
let receiver = receiver.peel_borrows();
11499
let receiver_ty = cx.typeck_results().expr_ty(receiver).peel_refs();
115-
if is_type_diagnostic_item(cx, receiver_ty, sym::HashSet) && path.ident.name == sym!(insert) {
116-
Some(InsertExpr { receiver, value })
117-
} else {
118-
None
100+
if value.span.eq_ctxt(expr.span)
101+
&& is_type_diagnostic_item(cx, receiver_ty, sym::HashSet)
102+
&& path.ident.name == symbol
103+
{
104+
return Some(OpExpr { receiver, value, span });
119105
}
120-
} else {
121-
None
122106
}
107+
None
123108
}
124109

125-
fn find_insert_calls<'tcx>(cx: &LateContext<'tcx>, contains_expr: &ContainsExpr<'tcx>, expr: &'tcx Expr<'_>) -> bool {
110+
fn find_insert_calls<'tcx>(
111+
cx: &LateContext<'tcx>,
112+
contains_expr: &OpExpr<'tcx>,
113+
expr: &'tcx Expr<'_>,
114+
) -> Option<OpExpr<'tcx>> {
126115
for_each_expr(expr, |e| {
127-
if let Some(insert_expr) = try_parse_insert(cx, e)
116+
if let Some(insert_expr) = try_parse_op_call(cx, e, sym!(insert))
128117
&& SpanlessEq::new(cx).eq_expr(contains_expr.receiver, insert_expr.receiver)
129118
&& SpanlessEq::new(cx).eq_expr(contains_expr.value, insert_expr.value)
130119
{
131-
ControlFlow::Break(())
120+
ControlFlow::Break(insert_expr)
132121
} else {
133122
ControlFlow::Continue(())
134123
}
135124
})
136-
.is_some()
137125
}

tests/ui/hashset_insert_after_contains.stderr

Lines changed: 0 additions & 112 deletions
This file was deleted.

tests/ui/hashset_insert_after_contains.rs renamed to tests/ui/set_contains_or_insert.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![allow(unused)]
22
#![allow(clippy::nonminimal_bool)]
33
#![allow(clippy::needless_borrow)]
4-
#![warn(clippy::hashset_insert_after_contains)]
4+
#![warn(clippy::set_contains_or_insert)]
55

66
use std::collections::HashSet;
77

@@ -70,6 +70,12 @@ fn should_not_warn_cases() {
7070
set.replace(value); //it is not insert
7171
println!("Just a comment");
7272
}
73+
74+
if set.contains(&value) {
75+
println!("value is already in set");
76+
} else {
77+
set.insert(value);
78+
}
7379
}
7480

7581
fn simply_true() -> bool {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
error: usage of `HashSet::insert` after `HashSet::contains`
2+
--> tests/ui/set_contains_or_insert.rs:18:13
3+
|
4+
LL | if !set.contains(&value) {
5+
| ^^^^^^^^^^^^^^^^
6+
LL | set.insert(value);
7+
| ^^^^^^^^^^^^^
8+
|
9+
= note: `-D clippy::set-contains-or-insert` implied by `-D warnings`
10+
= help: to override `-D warnings` add `#[allow(clippy::set_contains_or_insert)]`
11+
12+
error: usage of `HashSet::insert` after `HashSet::contains`
13+
--> tests/ui/set_contains_or_insert.rs:23:12
14+
|
15+
LL | if set.contains(&value) {
16+
| ^^^^^^^^^^^^^^^^
17+
LL | set.insert(value);
18+
| ^^^^^^^^^^^^^
19+
20+
error: usage of `HashSet::insert` after `HashSet::contains`
21+
--> tests/ui/set_contains_or_insert.rs:28:13
22+
|
23+
LL | if !set.contains(&value) {
24+
| ^^^^^^^^^^^^^^^^
25+
LL | set.insert(value);
26+
| ^^^^^^^^^^^^^
27+
28+
error: usage of `HashSet::insert` after `HashSet::contains`
29+
--> tests/ui/set_contains_or_insert.rs:32:14
30+
|
31+
LL | if !!set.contains(&value) {
32+
| ^^^^^^^^^^^^^^^^
33+
LL | set.insert(value);
34+
| ^^^^^^^^^^^^^
35+
36+
error: usage of `HashSet::insert` after `HashSet::contains`
37+
--> tests/ui/set_contains_or_insert.rs:37:15
38+
|
39+
LL | if (&set).contains(&value) {
40+
| ^^^^^^^^^^^^^^^^
41+
LL | set.insert(value);
42+
| ^^^^^^^^^^^^^
43+
44+
error: usage of `HashSet::insert` after `HashSet::contains`
45+
--> tests/ui/set_contains_or_insert.rs:42:13
46+
|
47+
LL | if !set.contains(borrow_value) {
48+
| ^^^^^^^^^^^^^^^^^^^^^^
49+
LL | set.insert(*borrow_value);
50+
| ^^^^^^^^^^^^^^^^^^^^^
51+
52+
error: usage of `HashSet::insert` after `HashSet::contains`
53+
--> tests/ui/set_contains_or_insert.rs:47:20
54+
|
55+
LL | if !borrow_set.contains(&value) {
56+
| ^^^^^^^^^^^^^^^^
57+
LL | borrow_set.insert(value);
58+
| ^^^^^^^^^^^^^
59+
60+
error: aborting due to 7 previous errors
61+

0 commit comments

Comments
 (0)