blob: 4bbd1803a78d2648c40d706f5df4424fac37c56c [file] [log] [blame] [edit]
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::res::{MaybeDef, MaybeResPath};
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::implements_trait;
use clippy_utils::{is_default_equivalent_call, local_is_initialized};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// Detects assignments of `Default::default()` or `Box::new(value)`
/// to a place of type `Box<T>`.
///
/// ### Why is this bad?
/// This incurs an extra heap allocation compared to assigning the boxed
/// storage.
///
/// ### Example
/// ```no_run
/// let mut b = Box::new(1u32);
/// b = Default::default();
/// ```
/// Use instead:
/// ```no_run
/// let mut b = Box::new(1u32);
/// *b = Default::default();
/// ```
#[clippy::version = "1.92.0"]
pub REPLACE_BOX,
perf,
"assigning a newly created box to `Box<T>` is inefficient"
}
declare_lint_pass!(ReplaceBox => [REPLACE_BOX]);
impl LateLintPass<'_> for ReplaceBox {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
if let ExprKind::Assign(lhs, rhs, _) = &expr.kind
&& !lhs.span.from_expansion()
&& !rhs.span.from_expansion()
&& let lhs_ty = cx.typeck_results().expr_ty(lhs)
// No diagnostic for late-initialized locals
&& lhs.res_local_id().is_none_or(|local| local_is_initialized(cx, local))
&& let Some(inner_ty) = lhs_ty.boxed_ty()
{
if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default)
&& implements_trait(cx, inner_ty, default_trait_id, &[])
&& is_default_call(cx, rhs)
{
span_lint_and_then(
cx,
REPLACE_BOX,
expr.span,
"creating a new box with default content",
|diag| {
let mut app = Applicability::MachineApplicable;
let suggestion = format!(
"{} = Default::default()",
Sugg::hir_with_applicability(cx, lhs, "_", &mut app).deref()
);
diag.note("this creates a needless allocation").span_suggestion(
expr.span,
"replace existing content with default instead",
suggestion,
app,
);
},
);
}
if inner_ty.is_sized(cx.tcx, cx.typing_env())
&& let Some(rhs_inner) = get_box_new_payload(cx, rhs)
{
span_lint_and_then(cx, REPLACE_BOX, expr.span, "creating a new box", |diag| {
let mut app = Applicability::MachineApplicable;
let suggestion = format!(
"{} = {}",
Sugg::hir_with_applicability(cx, lhs, "_", &mut app).deref(),
Sugg::hir_with_context(cx, rhs_inner, expr.span.ctxt(), "_", &mut app),
);
diag.note("this creates a needless allocation").span_suggestion(
expr.span,
"replace existing content with inner value instead",
suggestion,
app,
);
});
}
}
}
}
fn is_default_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
matches!(expr.kind, ExprKind::Call(func, _args) if is_default_equivalent_call(cx, func, Some(expr)))
}
fn get_box_new_payload<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
if let ExprKind::Call(box_new, [arg]) = expr.kind
&& let ExprKind::Path(QPath::TypeRelative(ty, seg)) = box_new.kind
&& seg.ident.name == sym::new
&& ty.basic_res().is_lang_item(cx, LangItem::OwnedBox)
{
Some(arg)
} else {
None
}
}