Skip to content

Commit 6ef2726

Browse files
committed
Add lint for b = Box::new(value)
1 parent b936ee1 commit 6ef2726

File tree

4 files changed

+66
-9
lines changed

4 files changed

+66
-9
lines changed

clippy_lints/src/default_box_assignments.rs

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::sugg::Sugg;
33
use clippy_utils::ty::implements_trait;
4-
use clippy_utils::{is_default_equivalent_call, local_is_initialized, path_to_local};
4+
use clippy_utils::{is_default_equivalent_call, local_is_initialized, path_def_id, path_to_local};
55
use rustc_errors::Applicability;
6-
use rustc_hir::{Expr, ExprKind, LangItem};
6+
use rustc_hir::{Expr, ExprKind, LangItem, QPath};
77
use rustc_lint::{LateContext, LateLintPass};
88
use rustc_middle::ty::{self, Ty};
99
use rustc_session::declare_lint_pass;
1010
use rustc_span::sym;
1111

1212
declare_clippy_lint! {
1313
/// ### What it does
14-
/// Detects assignments of `Default::default()` to a place of type `Box<T>`.
14+
/// Detects assignments of `Default::default()` or `Box::new(value)`
15+
/// to a place of type `Box<T>`.
1516
///
1617
/// ### Why is this bad?
1718
/// This incurs an extra heap allocation compared to assigning the boxed
@@ -46,7 +47,18 @@ impl LateLintPass<'_> for DefaultBoxAssignments {
4647
return;
4748
}
4849

49-
if is_box_of_default(cx, lhs_ty) && is_default_call(cx, rhs) && !rhs.span.from_expansion() {
50+
let Some(inner_ty) = get_box_inner_type(cx, lhs_ty) else {
51+
return;
52+
};
53+
54+
let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default) else {
55+
return;
56+
};
57+
58+
if implements_trait(cx, inner_ty, default_trait_id, &[])
59+
&& is_default_call(cx, rhs)
60+
&& !rhs.span.from_expansion()
61+
{
5062
span_lint_and_then(
5163
cx,
5264
DEFAULT_BOX_ASSIGNMENTS,
@@ -68,21 +80,52 @@ impl LateLintPass<'_> for DefaultBoxAssignments {
6880
},
6981
);
7082
}
83+
84+
if inner_ty.is_sized(cx.tcx, cx.typing_env())
85+
&& let Some(rhs_inner) = get_new_call_value(cx, rhs)
86+
{
87+
span_lint_and_then(cx, DEFAULT_BOX_ASSIGNMENTS, expr.span, "creating a new box", |diag| {
88+
let mut app = Applicability::MachineApplicable;
89+
let suggestion = format!(
90+
"{} = {}",
91+
Sugg::hir_with_applicability(cx, lhs, "_", &mut app).deref(),
92+
Sugg::hir_with_applicability(cx, rhs_inner, "_", &mut app),
93+
);
94+
95+
diag.note("this creates a needless allocation").span_suggestion(
96+
expr.span,
97+
"replace existing content with inner value instead",
98+
suggestion,
99+
app,
100+
);
101+
});
102+
}
71103
}
72104
}
73105
}
74106

75-
fn is_box_of_default<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
107+
fn get_box_inner_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
76108
if let ty::Adt(def, args) = ty.kind()
77109
&& cx.tcx.is_lang_item(def.did(), LangItem::OwnedBox)
78-
&& let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default)
79110
{
80-
implements_trait(cx, args.type_at(0), default_trait_id, &[])
111+
Some(args.type_at(0))
81112
} else {
82-
false
113+
None
83114
}
84115
}
85116

86117
fn is_default_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
87118
matches!(expr.kind, ExprKind::Call(func, _args) if is_default_equivalent_call(cx, func, Some(expr)))
88119
}
120+
121+
fn get_new_call_value<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
122+
if let ExprKind::Call(box_new, [arg]) = expr.kind
123+
&& let ExprKind::Path(QPath::TypeRelative(ty, seg)) = box_new.kind
124+
&& seg.ident.name == sym::new
125+
&& path_def_id(cx, ty).is_some_and(|id| Some(id) == cx.tcx.lang_items().owned_box())
126+
{
127+
Some(arg)
128+
} else {
129+
None
130+
}
131+
}

tests/ui/default_box_assignments.fixed

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ fn main() {
2020
// No lint for call originating in macro
2121
b = create_default!();
2222

23+
*b = 5;
24+
//~^ default_box_assignments
25+
2326
// No lint for assigning to Box<T> where T: !Default
2427
let mut b = Box::<str>::from("hi".to_string());
2528
b = Default::default();

tests/ui/default_box_assignments.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ fn main() {
2020
// No lint for call originating in macro
2121
b = create_default!();
2222

23+
b = Box::new(5);
24+
//~^ default_box_assignments
25+
2326
// No lint for assigning to Box<T> where T: !Default
2427
let mut b = Box::<str>::from("hi".to_string());
2528
b = Default::default();

tests/ui/default_box_assignments.stderr

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,13 @@ LL | b = Box::default();
1616
|
1717
= note: this creates a needless allocation
1818

19-
error: aborting due to 2 previous errors
19+
error: creating a new box
20+
--> tests/ui/default_box_assignments.rs:23:5
21+
|
22+
LL | b = Box::new(5);
23+
| ^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*b = 5`
24+
|
25+
= note: this creates a needless allocation
26+
27+
error: aborting due to 3 previous errors
2028

0 commit comments

Comments
 (0)