Skip to content

Commit f242c46

Browse files
committed
fix: set-contains-or-insert FP when set is mutated before insert
1 parent e629869 commit f242c46

File tree

3 files changed

+49
-4
lines changed

3 files changed

+49
-4
lines changed

clippy_lints/src/set_contains_or_insert.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,16 @@ fn try_parse_op_call<'tcx>(
112112
None
113113
}
114114

115+
fn is_set_mutated<'tcx>(cx: &LateContext<'tcx>, contains_expr: &OpExpr<'tcx>, expr: &'tcx Expr<'_>) -> bool {
116+
// Guard on type to avoid useless potentially expansive `SpanlessEq` checks
117+
cx.typeck_results().expr_ty_adjusted(expr).is_mutable_ptr()
118+
&& matches!(
119+
cx.typeck_results().expr_ty(expr).peel_refs().opt_diag_name(cx),
120+
Some(sym::HashSet | sym::BTreeSet)
121+
)
122+
&& SpanlessEq::new(cx).eq_expr(contains_expr.receiver, expr.peel_borrows())
123+
}
124+
115125
fn find_insert_calls<'tcx>(
116126
cx: &LateContext<'tcx>,
117127
contains_expr: &OpExpr<'tcx>,
@@ -122,9 +132,14 @@ fn find_insert_calls<'tcx>(
122132
&& SpanlessEq::new(cx).eq_expr(contains_expr.receiver, insert_expr.receiver)
123133
&& SpanlessEq::new(cx).eq_expr(contains_expr.value, insert_expr.value)
124134
{
125-
ControlFlow::Break(insert_expr)
126-
} else {
127-
ControlFlow::Continue(())
135+
return ControlFlow::Break(Some(insert_expr));
128136
}
137+
138+
if is_set_mutated(cx, contains_expr, e) {
139+
return ControlFlow::Break(None);
140+
}
141+
142+
ControlFlow::Continue(())
129143
})
144+
.flatten()
130145
}

tests/ui/set_contains_or_insert.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,24 @@ fn main() {
164164
should_not_warn_hashset();
165165
should_not_warn_btreeset();
166166
}
167+
168+
fn issue15990(s: &mut HashSet<usize>, v: usize) {
169+
if !s.contains(&v) {
170+
s.clear();
171+
s.insert(v);
172+
}
173+
174+
fn borrow_as_mut(v: usize, s: &mut HashSet<usize>) {
175+
s.clear();
176+
}
177+
if !s.contains(&v) {
178+
borrow_as_mut(v, s);
179+
s.insert(v);
180+
}
181+
182+
if !s.contains(&v) {
183+
//~^ set_contains_or_insert
184+
let _readonly_access = s.contains(&v);
185+
s.insert(v);
186+
}
187+
}

tests/ui/set_contains_or_insert.stderr

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,5 +127,14 @@ LL |
127127
LL | borrow_set.insert(value);
128128
| ^^^^^^^^^^^^^
129129

130-
error: aborting due to 14 previous errors
130+
error: usage of `HashSet::insert` after `HashSet::contains`
131+
--> tests/ui/set_contains_or_insert.rs:182:11
132+
|
133+
LL | if !s.contains(&v) {
134+
| ^^^^^^^^^^^^
135+
...
136+
LL | s.insert(v);
137+
| ^^^^^^^^^
138+
139+
error: aborting due to 15 previous errors
131140

0 commit comments

Comments
 (0)