blob: 8ba0f835d3ddc2d1964a68042461ae909cb6266a [file] [log] [blame] [edit]
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::eager_or_lazy::switch_to_eager_eval;
use clippy_utils::peel_hir_pat_refs;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use rustc_ast::UnOp;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, HirId, QPath};
use rustc_lint::LateContext;
use rustc_middle::ty::{self};
use rustc_span::source_map::Spanned;
use super::MANUAL_CONTAINS;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>) {
let mut app = Applicability::MachineApplicable;
if !expr.span.from_expansion()
// check if `iter().any()` can be replaced with `contains()`
&& let ExprKind::Closure(closure) = closure_arg.kind
&& let Body{params: [param],value} = cx.tcx.hir_body(closure.body)
&& let ExprKind::Binary(op, lhs, rhs) = value.kind
&& let (peeled_ref_pat, _) = peel_hir_pat_refs(param.pat)
&& let Some((snip,snip_expr)) = can_replace_with_contains(cx, op, lhs, rhs, peeled_ref_pat.hir_id, &mut app)
&& let ref_type = cx.typeck_results().expr_ty_adjusted(recv)
&& let ty::Ref(_, inner_type, _) = ref_type.kind()
&& let ty::Slice(slice_type) = inner_type.kind()
&& *slice_type == cx.typeck_results().expr_ty(snip_expr)
{
span_lint_and_sugg(
cx,
MANUAL_CONTAINS,
expr.span,
"using `contains()` instead of `iter().any()` is more efficient",
"try",
format!(
"{}.contains({})",
snippet_with_applicability(cx, recv.span, "_", &mut app),
snip
),
app,
);
}
}
enum EligibleArg {
IsClosureArg,
ContainsArg(String),
}
fn try_get_eligible_arg<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
closure_arg_id: HirId,
applicability: &mut Applicability,
) -> Option<(EligibleArg, &'tcx Expr<'tcx>)> {
let mut get_snippet = |expr: &Expr<'_>, needs_borrow: bool| {
let sugg = Sugg::hir_with_applicability(cx, expr, "_", applicability);
EligibleArg::ContainsArg((if needs_borrow { sugg.addr() } else { sugg }).to_string())
};
match expr.kind {
ExprKind::Path(QPath::Resolved(_, path)) => {
if path.res == Res::Local(closure_arg_id) {
Some((EligibleArg::IsClosureArg, expr))
} else {
Some((get_snippet(expr, true), expr))
}
},
ExprKind::Unary(UnOp::Deref, inner) => {
if let ExprKind::Path(QPath::Resolved(_, path)) = inner.kind {
if path.res == Res::Local(closure_arg_id) {
Some((EligibleArg::IsClosureArg, expr))
} else {
Some((get_snippet(inner, false), expr))
}
} else {
None
}
},
_ => {
if switch_to_eager_eval(cx, expr) {
Some((get_snippet(expr, true), expr))
} else {
None
}
},
}
}
fn can_replace_with_contains<'tcx>(
cx: &LateContext<'tcx>,
bin_op: Spanned<BinOpKind>,
left_expr: &'tcx Expr<'tcx>,
right_expr: &'tcx Expr<'tcx>,
closure_arg_id: HirId,
applicability: &mut Applicability,
) -> Option<(String, &'tcx Expr<'tcx>)> {
if bin_op.node != BinOpKind::Eq {
return None;
}
let left_candidate = try_get_eligible_arg(cx, left_expr, closure_arg_id, applicability)?;
let right_candidate = try_get_eligible_arg(cx, right_expr, closure_arg_id, applicability)?;
match (left_candidate, right_candidate) {
((EligibleArg::IsClosureArg, _), (EligibleArg::ContainsArg(snip), candidate_expr))
| ((EligibleArg::ContainsArg(snip), candidate_expr), (EligibleArg::IsClosureArg, _)) => {
Some((snip, candidate_expr))
},
_ => None,
}
}