131 lines
4.2 KiB
Rust
131 lines
4.2 KiB
Rust
use std::ops::ControlFlow;
|
|
|
|
use clippy_utils::diagnostics::span_lint;
|
|
use clippy_utils::ty::is_type_diagnostic_item;
|
|
use clippy_utils::visitors::for_each_expr;
|
|
use clippy_utils::{higher, peel_hir_expr_while, SpanlessEq};
|
|
use rustc_hir::{Expr, ExprKind, UnOp};
|
|
use rustc_lint::{LateContext, LateLintPass};
|
|
use rustc_session::declare_lint_pass;
|
|
use rustc_span::symbol::Symbol;
|
|
use rustc_span::{sym, Span};
|
|
|
|
declare_clippy_lint! {
|
|
/// ### What it does
|
|
/// Checks for usage of `contains` to see if a value is not present
|
|
/// in a set like `HashSet` or `BTreeSet`, followed by an `insert`.
|
|
///
|
|
/// ### Why is this bad?
|
|
/// Using just `insert` and checking the returned `bool` is more efficient.
|
|
///
|
|
/// ### Known problems
|
|
/// In case the value that wants to be inserted is borrowed and also expensive or impossible
|
|
/// to clone. In such a scenario, the developer might want to check with `contains` before inserting,
|
|
/// to avoid the clone. In this case, it will report a false positive.
|
|
///
|
|
/// ### Example
|
|
/// ```rust
|
|
/// use std::collections::HashSet;
|
|
/// let mut set = HashSet::new();
|
|
/// let value = 5;
|
|
/// if !set.contains(&value) {
|
|
/// set.insert(value);
|
|
/// println!("inserted {value:?}");
|
|
/// }
|
|
/// ```
|
|
/// Use instead:
|
|
/// ```rust
|
|
/// use std::collections::HashSet;
|
|
/// let mut set = HashSet::new();
|
|
/// let value = 5;
|
|
/// if set.insert(&value) {
|
|
/// println!("inserted {value:?}");
|
|
/// }
|
|
/// ```
|
|
#[clippy::version = "1.80.0"]
|
|
pub SET_CONTAINS_OR_INSERT,
|
|
nursery,
|
|
"call to `<set>::contains` followed by `<set>::insert`"
|
|
}
|
|
|
|
declare_lint_pass!(SetContainsOrInsert => [SET_CONTAINS_OR_INSERT]);
|
|
|
|
impl<'tcx> LateLintPass<'tcx> for SetContainsOrInsert {
|
|
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
|
if !expr.span.from_expansion()
|
|
&& let Some(higher::If {
|
|
cond: cond_expr,
|
|
then: then_expr,
|
|
..
|
|
}) = higher::If::hir(expr)
|
|
&& let Some((contains_expr, sym)) = try_parse_op_call(cx, cond_expr, sym!(contains))//try_parse_contains(cx, cond_expr)
|
|
&& let Some(insert_expr) = find_insert_calls(cx, &contains_expr, then_expr)
|
|
{
|
|
span_lint(
|
|
cx,
|
|
SET_CONTAINS_OR_INSERT,
|
|
vec![contains_expr.span, insert_expr.span],
|
|
format!("usage of `{sym}::insert` after `{sym}::contains`"),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct OpExpr<'tcx> {
|
|
receiver: &'tcx Expr<'tcx>,
|
|
value: &'tcx Expr<'tcx>,
|
|
span: Span,
|
|
}
|
|
|
|
fn try_parse_op_call<'tcx>(
|
|
cx: &LateContext<'tcx>,
|
|
expr: &'tcx Expr<'_>,
|
|
symbol: Symbol,
|
|
) -> Option<(OpExpr<'tcx>, Symbol)> {
|
|
let expr = peel_hir_expr_while(expr, |e| {
|
|
if let ExprKind::Unary(UnOp::Not, e) = e.kind {
|
|
Some(e)
|
|
} else {
|
|
None
|
|
}
|
|
});
|
|
|
|
if let ExprKind::MethodCall(path, receiver, [value], span) = expr.kind {
|
|
let value = value.peel_borrows();
|
|
let value = peel_hir_expr_while(value, |e| {
|
|
if let ExprKind::Unary(UnOp::Deref, e) = e.kind {
|
|
Some(e)
|
|
} else {
|
|
None
|
|
}
|
|
});
|
|
let receiver = receiver.peel_borrows();
|
|
let receiver_ty = cx.typeck_results().expr_ty(receiver).peel_refs();
|
|
if value.span.eq_ctxt(expr.span) && path.ident.name == symbol {
|
|
for sym in &[sym::HashSet, sym::BTreeSet] {
|
|
if is_type_diagnostic_item(cx, receiver_ty, *sym) {
|
|
return Some((OpExpr { receiver, value, span }, *sym));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn find_insert_calls<'tcx>(
|
|
cx: &LateContext<'tcx>,
|
|
contains_expr: &OpExpr<'tcx>,
|
|
expr: &'tcx Expr<'_>,
|
|
) -> Option<OpExpr<'tcx>> {
|
|
for_each_expr(cx, expr, |e| {
|
|
if let Some((insert_expr, _)) = try_parse_op_call(cx, e, sym!(insert))
|
|
&& SpanlessEq::new(cx).eq_expr(contains_expr.receiver, insert_expr.receiver)
|
|
&& SpanlessEq::new(cx).eq_expr(contains_expr.value, insert_expr.value)
|
|
{
|
|
ControlFlow::Break(insert_expr)
|
|
} else {
|
|
ControlFlow::Continue(())
|
|
}
|
|
})
|
|
}
|