Skip to content

Commit 2820511

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

File tree

2 files changed

+65
-4
lines changed

2 files changed

+65
-4
lines changed

clippy_lints/src/set_contains_or_insert.rs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ use std::ops::ControlFlow;
22

33
use clippy_utils::diagnostics::span_lint;
44
use clippy_utils::res::MaybeDef;
5+
use clippy_utils::ty::expr_sig;
56
use clippy_utils::visitors::for_each_expr;
67
use clippy_utils::{SpanlessEq, higher, peel_hir_expr_while, sym};
7-
use rustc_hir::{Expr, ExprKind, UnOp};
8+
use rustc_hir::{Expr, ExprKind, Node, UnOp};
89
use rustc_lint::{LateContext, LateLintPass};
10+
use rustc_middle::ty;
911
use rustc_session::declare_lint_pass;
1012
use rustc_span::Span;
1113
use rustc_span::symbol::Symbol;
@@ -112,6 +114,45 @@ fn try_parse_op_call<'tcx>(
112114
None
113115
}
114116

117+
fn is_set_mutated<'tcx>(cx: &LateContext<'tcx>, contains_expr: &OpExpr<'tcx>, expr: &'tcx Expr<'_>) -> bool {
118+
let expr = peel_hir_expr_while(expr, |e| {
119+
if let ExprKind::Unary(UnOp::Not, e) = e.kind {
120+
Some(e)
121+
} else {
122+
None
123+
}
124+
});
125+
126+
if let ExprKind::MethodCall(_, receiver, ..) = expr.kind
127+
&& let receiver = receiver.peel_borrows()
128+
&& let receiver_ty = cx.typeck_results().expr_ty(receiver).peel_refs()
129+
&& (receiver_ty.is_diag_item(cx, sym::HashSet) || receiver_ty.is_diag_item(cx, sym::BTreeSet))
130+
&& SpanlessEq::new(cx).eq_expr(contains_expr.receiver, receiver)
131+
&& let Some(method_def) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
132+
&& let method_fn_sig = cx.tcx.fn_sig(method_def).instantiate_identity().skip_binder()
133+
&& let Some(self_ty) = method_fn_sig.inputs().first()
134+
&& let ty::Ref(_, _, ty::Mutability::Mut) = self_ty.kind()
135+
{
136+
return true;
137+
}
138+
139+
let child = expr.peel_borrows();
140+
let child_ty = cx.typeck_results().expr_ty(child).peel_refs();
141+
if (child_ty.is_diag_item(cx, sym::HashSet) || child_ty.is_diag_item(cx, sym::BTreeSet))
142+
&& SpanlessEq::new(cx).eq_expr(contains_expr.receiver, child)
143+
&& let Node::Expr(parent) = cx.tcx.parent_hir_node(expr.hir_id)
144+
&& let ExprKind::Call(func, args) = parent.kind
145+
&& let Some(func_sig) = expr_sig(cx, func)
146+
&& let Some(set_index) = args.iter().position(|arg| arg.hir_id == expr.hir_id)
147+
&& let Some(set_ty) = func_sig.input(set_index).map(|ty| ty.skip_binder())
148+
&& let ty::Ref(_, _, ty::Mutability::Mut) = set_ty.kind()
149+
{
150+
return true;
151+
}
152+
153+
false
154+
}
155+
115156
fn find_insert_calls<'tcx>(
116157
cx: &LateContext<'tcx>,
117158
contains_expr: &OpExpr<'tcx>,
@@ -122,9 +163,14 @@ fn find_insert_calls<'tcx>(
122163
&& SpanlessEq::new(cx).eq_expr(contains_expr.receiver, insert_expr.receiver)
123164
&& SpanlessEq::new(cx).eq_expr(contains_expr.value, insert_expr.value)
124165
{
125-
ControlFlow::Break(insert_expr)
126-
} else {
127-
ControlFlow::Continue(())
166+
return ControlFlow::Break(Some(insert_expr));
167+
}
168+
169+
if is_set_mutated(cx, contains_expr, e) {
170+
return ControlFlow::Break(None);
128171
}
172+
173+
ControlFlow::Continue(())
129174
})
175+
.flatten()
130176
}

tests/ui/set_contains_or_insert.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,18 @@ 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+
}

0 commit comments

Comments
 (0)