Skip to content

Commit e47dba0

Browse files
committed
Add default_box_assignments lint
1 parent 9b8c42c commit e47dba0

File tree

7 files changed

+144
-0
lines changed

7 files changed

+144
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5705,6 +5705,7 @@ Released 2018-09-13
57055705
[`debug_assert_with_mut_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#debug_assert_with_mut_call
57065706
[`decimal_literal_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation
57075707
[`declare_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const
5708+
[`default_box_assignments`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_box_assignments
57085709
[`default_constructed_unit_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_constructed_unit_structs
57095710
[`default_instead_of_iter_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_instead_of_iter_empty
57105711
[`default_numeric_fallback`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_numeric_fallback

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
9292
crate::dbg_macro::DBG_MACRO_INFO,
9393
crate::default::DEFAULT_TRAIT_ACCESS_INFO,
9494
crate::default::FIELD_REASSIGN_WITH_DEFAULT_INFO,
95+
crate::default_box_assignments::DEFAULT_BOX_ASSIGNMENTS_INFO,
9596
crate::default_constructed_unit_structs::DEFAULT_CONSTRUCTED_UNIT_STRUCTS_INFO,
9697
crate::default_instead_of_iter_empty::DEFAULT_INSTEAD_OF_ITER_EMPTY_INFO,
9798
crate::default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK_INFO,
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::is_default_equivalent_call;
3+
use clippy_utils::source::snippet;
4+
use clippy_utils::ty::implements_trait;
5+
use rustc_errors::Applicability;
6+
use rustc_hir::{Expr, ExprKind, LangItem};
7+
use rustc_lint::{LateContext, LateLintPass};
8+
use rustc_middle::ty::{self, GenericArgKind, Ty};
9+
use rustc_session::declare_lint_pass;
10+
use rustc_span::sym;
11+
12+
declare_clippy_lint! {
13+
/// ### What it does
14+
/// Detects assignments of `Default::default()` to a place of type `Box<T>`.
15+
///
16+
/// ### Why is this bad?
17+
/// This incurs an extra heap allocation compared to assigning the boxed
18+
/// storage.
19+
///
20+
/// ### Example
21+
/// ```no_run
22+
/// let mut b = Box::new(1u32);
23+
/// b = Default::default();
24+
/// ```
25+
/// Use instead:
26+
/// ```no_run
27+
/// *b = Default::default();
28+
/// ```
29+
#[clippy::version = "1.89.0"]
30+
pub DEFAULT_BOX_ASSIGNMENTS,
31+
perf,
32+
"assigning `Default::default()` to `Box<T>` is inefficient"
33+
}
34+
declare_lint_pass!(DefaultBoxAssignments => [DEFAULT_BOX_ASSIGNMENTS]);
35+
36+
impl LateLintPass<'_> for DefaultBoxAssignments {
37+
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
38+
if let ExprKind::Assign(lhs, rhs, _) = &expr.kind {
39+
let lhs_ty = cx.typeck_results().expr_ty(lhs);
40+
if is_box_of_default(lhs_ty, cx) && is_default_call(rhs, cx) {
41+
span_lint_and_then(
42+
cx,
43+
DEFAULT_BOX_ASSIGNMENTS,
44+
expr.span,
45+
"assigning `Default::default()` to `Box<T>`",
46+
|diag| {
47+
let suggestion = format!("*({}) = Default::default()", snippet(cx, lhs.span, "_"));
48+
49+
diag.note("this creates a needless allocation").span_suggestion(
50+
expr.span,
51+
"assign to the inner value",
52+
suggestion,
53+
Applicability::MaybeIncorrect,
54+
);
55+
},
56+
);
57+
}
58+
}
59+
}
60+
}
61+
62+
fn is_box_of_default<'a>(ty: Ty<'a>, cx: &LateContext<'a>) -> bool {
63+
if let ty::Adt(def, args) = ty.kind()
64+
&& cx.tcx.lang_items().get(LangItem::OwnedBox) == Some(def.did())
65+
&& let Some(inner) = args.iter().find_map(|arg| match arg.kind() {
66+
GenericArgKind::Type(ty) => Some(ty),
67+
_ => None,
68+
})
69+
{
70+
cx.tcx
71+
.get_diagnostic_item(sym::Default)
72+
.map_or(false, |id| implements_trait(cx, inner, id, &[]))
73+
} else {
74+
false
75+
}
76+
}
77+
78+
fn is_default_call(expr: &Expr<'_>, cx: &LateContext<'_>) -> bool {
79+
if let ExprKind::Call(func, _args) = expr.kind
80+
&& is_default_equivalent_call(cx, func, Some(expr))
81+
{
82+
true
83+
} else {
84+
false
85+
}
86+
}

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ mod crate_in_macro_def;
105105
mod create_dir;
106106
mod dbg_macro;
107107
mod default;
108+
mod default_box_assignments;
108109
mod default_constructed_unit_structs;
109110
mod default_instead_of_iter_empty;
110111
mod default_numeric_fallback;
@@ -948,5 +949,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
948949
store.register_late_pass(move |_| Box::new(redundant_test_prefix::RedundantTestPrefix));
949950
store.register_late_pass(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf)));
950951
store.register_late_pass(|_| Box::new(infallible_try_from::InfallibleTryFrom));
952+
store.register_late_pass(|_| Box::new(default_box_assignments::DefaultBoxAssignments));
951953
// add lints here, do not remove this comment, it's used in `new_lint`
952954
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#![warn(clippy::default_box_assignments)]
2+
3+
fn main() {
4+
let mut b = Box::new(1u32);
5+
*(b) = Default::default();
6+
//~^ default_box_assignments
7+
*(b) = Default::default();
8+
//~^ default_box_assignments
9+
10+
// No lint for assigning to the storage
11+
*b = Default::default();
12+
*b = u32::default();
13+
14+
// No lint for assigning to Box<T> where T is unsized
15+
let mut b = Box::<str>::from("hi".to_string());
16+
b = Default::default();
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#![warn(clippy::default_box_assignments)]
2+
3+
fn main() {
4+
let mut b = Box::new(1u32);
5+
b = Default::default();
6+
//~^ default_box_assignments
7+
b = Box::default();
8+
//~^ default_box_assignments
9+
10+
// No lint for assigning to the storage
11+
*b = Default::default();
12+
*b = u32::default();
13+
14+
// No lint for assigning to Box<T> where T is unsized
15+
let mut b = Box::<str>::from("hi".to_string());
16+
b = Default::default();
17+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: assigning `Default::default()` to `Box<T>`
2+
--> tests/ui/default_box_assignments.rs:5:5
3+
|
4+
LL | b = Default::default();
5+
| ^^^^^^^^^^^^^^^^^^^^^^ help: assign to the inner value: `*(b) = Default::default()`
6+
|
7+
= note: this creates a needless allocation
8+
= note: `-D clippy::default-box-assignments` implied by `-D warnings`
9+
= help: to override `-D warnings` add `#[allow(clippy::default_box_assignments)]`
10+
11+
error: assigning `Default::default()` to `Box<T>`
12+
--> tests/ui/default_box_assignments.rs:7:5
13+
|
14+
LL | b = Box::default();
15+
| ^^^^^^^^^^^^^^^^^^ help: assign to the inner value: `*(b) = Default::default()`
16+
|
17+
= note: this creates a needless allocation
18+
19+
error: aborting due to 2 previous errors
20+

0 commit comments

Comments
 (0)