|
| 1 | +use crate::methods::DRAIN_COLLECT; |
| 2 | +use clippy_utils::diagnostics::span_lint_and_sugg; |
| 3 | +use clippy_utils::is_range_full; |
| 4 | +use clippy_utils::source::snippet; |
| 5 | +use clippy_utils::ty::is_type_lang_item; |
| 6 | +use rustc_errors::Applicability; |
| 7 | +use rustc_hir::Expr; |
| 8 | +use rustc_hir::ExprKind; |
| 9 | +use rustc_hir::LangItem; |
| 10 | +use rustc_hir::Path; |
| 11 | +use rustc_hir::QPath; |
| 12 | +use rustc_lint::LateContext; |
| 13 | +use rustc_middle::query::Key; |
| 14 | +use rustc_middle::ty; |
| 15 | +use rustc_middle::ty::Ty; |
| 16 | +use rustc_span::sym; |
| 17 | +use rustc_span::Symbol; |
| 18 | + |
| 19 | +/// Checks if both types match the given diagnostic item, e.g.: |
| 20 | +/// |
| 21 | +/// `vec![1,2].drain(..).collect::<Vec<_>>()` |
| 22 | +/// ^^^^^^^^^ ^^^^^^ true |
| 23 | +/// `vec![1,2].drain(..).collect::<HashSet<_>>()` |
| 24 | +/// ^^^^^^^^^ ^^^^^^^^^^ false |
| 25 | +fn types_match_diagnostic_item(cx: &LateContext<'_>, expr: Ty<'_>, recv: Ty<'_>, sym: Symbol) -> bool { |
| 26 | + if let Some(expr_adt_did) = expr.ty_adt_id() |
| 27 | + && let Some(recv_adt_did) = recv.ty_adt_id() |
| 28 | + { |
| 29 | + cx.tcx.is_diagnostic_item(sym, expr_adt_did) && cx.tcx.is_diagnostic_item(sym, recv_adt_did) |
| 30 | + } else { |
| 31 | + false |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +/// Checks `std::{vec::Vec, collections::VecDeque}`. |
| 36 | +fn check_vec(cx: &LateContext<'_>, args: &[Expr<'_>], expr: Ty<'_>, recv: Ty<'_>, recv_path: &Path<'_>) -> bool { |
| 37 | + (types_match_diagnostic_item(cx, expr, recv, sym::Vec) |
| 38 | + || types_match_diagnostic_item(cx, expr, recv, sym::VecDeque)) |
| 39 | + && matches!(args, [arg] if is_range_full(cx, arg, Some(recv_path))) |
| 40 | +} |
| 41 | + |
| 42 | +/// Checks `std::string::String` |
| 43 | +fn check_string(cx: &LateContext<'_>, args: &[Expr<'_>], expr: Ty<'_>, recv: Ty<'_>, recv_path: &Path<'_>) -> bool { |
| 44 | + is_type_lang_item(cx, expr, LangItem::String) |
| 45 | + && is_type_lang_item(cx, recv, LangItem::String) |
| 46 | + && matches!(args, [arg] if is_range_full(cx, arg, Some(recv_path))) |
| 47 | +} |
| 48 | + |
| 49 | +/// Checks `std::collections::{HashSet, HashMap, BinaryHeap}`. |
| 50 | +fn check_collections(cx: &LateContext<'_>, expr: Ty<'_>, recv: Ty<'_>) -> Option<&'static str> { |
| 51 | + types_match_diagnostic_item(cx, expr, recv, sym::HashSet) |
| 52 | + .then_some("HashSet") |
| 53 | + .or_else(|| types_match_diagnostic_item(cx, expr, recv, sym::HashMap).then_some("HashMap")) |
| 54 | + .or_else(|| types_match_diagnostic_item(cx, expr, recv, sym::BinaryHeap).then_some("BinaryHeap")) |
| 55 | +} |
| 56 | + |
| 57 | +pub(super) fn check(cx: &LateContext<'_>, args: &[Expr<'_>], expr: &Expr<'_>, recv: &Expr<'_>) { |
| 58 | + let expr_ty = cx.typeck_results().expr_ty(expr); |
| 59 | + let recv_ty = cx.typeck_results().expr_ty(recv); |
| 60 | + let recv_ty_no_refs = recv_ty.peel_refs(); |
| 61 | + |
| 62 | + if let ExprKind::Path(QPath::Resolved(_, recv_path)) = recv.kind |
| 63 | + && let Some(typename) = check_vec(cx, args, expr_ty, recv_ty_no_refs, recv_path) |
| 64 | + .then_some("Vec") |
| 65 | + .or_else(|| check_string(cx, args, expr_ty, recv_ty_no_refs, recv_path).then_some("String")) |
| 66 | + .or_else(|| check_collections(cx, expr_ty, recv_ty_no_refs)) |
| 67 | + { |
| 68 | + let recv = snippet(cx, recv.span, "<expr>"); |
| 69 | + let sugg = if let ty::Ref(..) = recv_ty.kind() { |
| 70 | + format!("std::mem::take({recv})") |
| 71 | + } else { |
| 72 | + format!("std::mem::take(&mut {recv})") |
| 73 | + }; |
| 74 | + |
| 75 | + span_lint_and_sugg( |
| 76 | + cx, |
| 77 | + DRAIN_COLLECT, |
| 78 | + expr.span, |
| 79 | + &format!("you seem to be trying to move all elements into a new `{typename}`"), |
| 80 | + "consider using `mem::take`", |
| 81 | + sugg, |
| 82 | + Applicability::MachineApplicable, |
| 83 | + ); |
| 84 | + } |
| 85 | +} |
0 commit comments