use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::{path_def_id, qpath_generic_tys}; use rustc_errors::Applicability; use rustc_hir::{self as hir, def_id::DefId, QPath, TyKind}; use rustc_hir_analysis::hir_ty_to_ty; use rustc_lint::LateContext; use rustc_middle::ty::TypeVisitable; use rustc_span::symbol::sym; use super::{utils, REDUNDANT_ALLOCATION}; pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { let mut applicability = Applicability::MaybeIncorrect; let outer_sym = if Some(def_id) == cx.tcx.lang_items().owned_box() { "Box" } else if cx.tcx.is_diagnostic_item(sym::Rc, def_id) { "Rc" } else if cx.tcx.is_diagnostic_item(sym::Arc, def_id) { "Arc" } else { return false; }; if let Some(span) = utils::match_borrows_parameter(cx, qpath) { let generic_snippet = snippet_with_applicability(cx, span, "..", &mut applicability); span_lint_and_then( cx, REDUNDANT_ALLOCATION, hir_ty.span, &format!("usage of `{outer_sym}<{generic_snippet}>`"), |diag| { diag.span_suggestion(hir_ty.span, "try", format!("{generic_snippet}"), applicability); diag.note(format!( "`{generic_snippet}` is already a pointer, `{outer_sym}<{generic_snippet}>` allocates a pointer on the heap" )); }, ); return true; } let Some(ty) = qpath_generic_tys(qpath).next() else { return false }; let Some(id) = path_def_id(cx, ty) else { return false }; let (inner_sym, ty) = match cx.tcx.get_diagnostic_name(id) { Some(sym::Arc) => ("Arc", ty), Some(sym::Rc) => ("Rc", ty), _ if Some(id) == cx.tcx.lang_items().owned_box() => ("Box", ty), _ => return false, }; let TyKind::Path(inner_qpath) = &ty.kind else { return false }; let inner_span = match qpath_generic_tys(inner_qpath).next() { Some(hir_ty) => { // Reallocation of a fat pointer causes it to become thin. `hir_ty_to_ty` is safe to use // here because `mod.rs` guarantees this lint is only run on types outside of bodies and // is not run on locals. let ty = hir_ty_to_ty(cx.tcx, hir_ty); if ty.has_escaping_bound_vars() || !ty.is_sized(cx.tcx, cx.param_env) { return false; } hir_ty.span }, None => return false, }; if inner_sym == outer_sym { let generic_snippet = snippet_with_applicability(cx, inner_span, "..", &mut applicability); span_lint_and_then( cx, REDUNDANT_ALLOCATION, hir_ty.span, &format!("usage of `{outer_sym}<{inner_sym}<{generic_snippet}>>`"), |diag| { diag.span_suggestion( hir_ty.span, "try", format!("{outer_sym}<{generic_snippet}>"), applicability, ); diag.note(format!( "`{inner_sym}<{generic_snippet}>` is already on the heap, `{outer_sym}<{inner_sym}<{generic_snippet}>>` makes an extra allocation" )); }, ); } else { let generic_snippet = snippet(cx, inner_span, ".."); span_lint_and_then( cx, REDUNDANT_ALLOCATION, hir_ty.span, &format!("usage of `{outer_sym}<{inner_sym}<{generic_snippet}>>`"), |diag| { diag.note(format!( "`{inner_sym}<{generic_snippet}>` is already on the heap, `{outer_sym}<{inner_sym}<{generic_snippet}>>` makes an extra allocation" )); diag.help(format!( "consider using just `{outer_sym}<{generic_snippet}>` or `{inner_sym}<{generic_snippet}>`" )); }, ); } true }