blob: 6a82d8f756ad0d1b8ca1612e0fa7ec1dcd3b5107 [file] [log] [blame]
use crate::methods::DRAIN_COLLECT;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_range_full;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_type_lang_item;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem, Path, QPath};
use rustc_lint::LateContext;
use rustc_middle::query::Key;
use rustc_middle::ty;
use rustc_middle::ty::Ty;
use rustc_span::{sym, Symbol};
/// Checks if both types match the given diagnostic item, e.g.:
///
/// `vec![1,2].drain(..).collect::<Vec<_>>()`
/// ^^^^^^^^^ ^^^^^^ true
/// `vec![1,2].drain(..).collect::<HashSet<_>>()`
/// ^^^^^^^^^ ^^^^^^^^^^ false
fn types_match_diagnostic_item(cx: &LateContext<'_>, expr: Ty<'_>, recv: Ty<'_>, sym: Symbol) -> bool {
if let Some(expr_adt_did) = expr.ty_adt_id()
&& let Some(recv_adt_did) = recv.ty_adt_id()
{
cx.tcx.is_diagnostic_item(sym, expr_adt_did) && cx.tcx.is_diagnostic_item(sym, recv_adt_did)
} else {
false
}
}
/// Checks `std::{vec::Vec, collections::VecDeque}`.
fn check_vec(cx: &LateContext<'_>, args: &[Expr<'_>], expr: Ty<'_>, recv: Ty<'_>, recv_path: &Path<'_>) -> bool {
(types_match_diagnostic_item(cx, expr, recv, sym::Vec)
|| types_match_diagnostic_item(cx, expr, recv, sym::VecDeque))
&& matches!(args, [arg] if is_range_full(cx, arg, Some(recv_path)))
}
/// Checks `std::string::String`
fn check_string(cx: &LateContext<'_>, args: &[Expr<'_>], expr: Ty<'_>, recv: Ty<'_>, recv_path: &Path<'_>) -> bool {
is_type_lang_item(cx, expr, LangItem::String)
&& is_type_lang_item(cx, recv, LangItem::String)
&& matches!(args, [arg] if is_range_full(cx, arg, Some(recv_path)))
}
/// Checks `std::collections::{HashSet, HashMap, BinaryHeap}`.
fn check_collections(cx: &LateContext<'_>, expr: Ty<'_>, recv: Ty<'_>) -> Option<&'static str> {
types_match_diagnostic_item(cx, expr, recv, sym::HashSet)
.then_some("HashSet")
.or_else(|| types_match_diagnostic_item(cx, expr, recv, sym::HashMap).then_some("HashMap"))
.or_else(|| types_match_diagnostic_item(cx, expr, recv, sym::BinaryHeap).then_some("BinaryHeap"))
}
pub(super) fn check(cx: &LateContext<'_>, args: &[Expr<'_>], expr: &Expr<'_>, recv: &Expr<'_>) {
let expr_ty = cx.typeck_results().expr_ty(expr);
let recv_ty = cx.typeck_results().expr_ty(recv);
let recv_ty_no_refs = recv_ty.peel_refs();
if let ExprKind::Path(QPath::Resolved(_, recv_path)) = recv.kind
&& let Some(typename) = check_vec(cx, args, expr_ty, recv_ty_no_refs, recv_path)
.then_some("Vec")
.or_else(|| check_string(cx, args, expr_ty, recv_ty_no_refs, recv_path).then_some("String"))
.or_else(|| check_collections(cx, expr_ty, recv_ty_no_refs))
{
let recv = snippet(cx, recv.span, "<expr>");
let sugg = if let ty::Ref(..) = recv_ty.kind() {
format!("std::mem::take({recv})")
} else {
format!("std::mem::take(&mut {recv})")
};
span_lint_and_sugg(
cx,
DRAIN_COLLECT,
expr.span,
&format!("you seem to be trying to move all elements into a new `{typename}`"),
"consider using `mem::take`",
sugg,
Applicability::MachineApplicable,
);
}
}