1
1
use clippy_utils:: diagnostics:: span_lint_and_then;
2
2
use clippy_utils:: sugg:: Sugg ;
3
3
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} ;
5
5
use rustc_errors:: Applicability ;
6
- use rustc_hir:: { Expr , ExprKind , LangItem } ;
6
+ use rustc_hir:: { Expr , ExprKind , LangItem , QPath } ;
7
7
use rustc_lint:: { LateContext , LateLintPass } ;
8
8
use rustc_middle:: ty:: { self , Ty } ;
9
9
use rustc_session:: declare_lint_pass;
10
10
use rustc_span:: sym;
11
11
12
12
declare_clippy_lint ! {
13
13
/// ### 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>`.
15
16
///
16
17
/// ### Why is this bad?
17
18
/// This incurs an extra heap allocation compared to assigning the boxed
@@ -46,7 +47,18 @@ impl LateLintPass<'_> for DefaultBoxAssignments {
46
47
return ;
47
48
}
48
49
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
+ {
50
62
span_lint_and_then (
51
63
cx,
52
64
DEFAULT_BOX_ASSIGNMENTS ,
@@ -68,21 +80,52 @@ impl LateLintPass<'_> for DefaultBoxAssignments {
68
80
} ,
69
81
) ;
70
82
}
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
+ }
71
103
}
72
104
}
73
105
}
74
106
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 > > {
76
108
if let ty:: Adt ( def, args) = ty. kind ( )
77
109
&& cx. tcx . is_lang_item ( def. did ( ) , LangItem :: OwnedBox )
78
- && let Some ( default_trait_id) = cx. tcx . get_diagnostic_item ( sym:: Default )
79
110
{
80
- implements_trait ( cx , args. type_at ( 0 ) , default_trait_id , & [ ] )
111
+ Some ( args. type_at ( 0 ) )
81
112
} else {
82
- false
113
+ None
83
114
}
84
115
}
85
116
86
117
fn is_default_call ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> bool {
87
118
matches ! ( expr. kind, ExprKind :: Call ( func, _args) if is_default_equivalent_call( cx, func, Some ( expr) ) )
88
119
}
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
+ }
0 commit comments