11use clippy_utils:: diagnostics:: span_lint_and_then;
22use clippy_utils:: sugg:: Sugg ;
33use 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} ;
55use rustc_errors:: Applicability ;
6- use rustc_hir:: { Expr , ExprKind , LangItem } ;
6+ use rustc_hir:: { Expr , ExprKind , LangItem , QPath } ;
77use rustc_lint:: { LateContext , LateLintPass } ;
88use rustc_middle:: ty:: { self , Ty } ;
99use rustc_session:: declare_lint_pass;
1010use rustc_span:: sym;
1111
1212declare_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
86117fn 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+ }
0 commit comments