@@ -3,11 +3,17 @@ use clippy_utils::res::{MaybeDef, MaybeResPath};
33use clippy_utils:: sugg:: Sugg ;
44use clippy_utils:: ty:: implements_trait;
55use clippy_utils:: { is_default_equivalent_call, local_is_initialized} ;
6+ use rustc_data_structures:: fx:: FxHashSet ;
7+ use rustc_data_structures:: smallvec:: SmallVec ;
68use rustc_errors:: Applicability ;
7- use rustc_hir:: { Expr , ExprKind , LangItem , QPath } ;
9+ use rustc_hir:: { Body , BodyId , Expr , ExprKind , HirId , LangItem , QPath } ;
10+ use rustc_hir_typeck:: expr_use_visitor:: { Delegate , ExprUseVisitor , PlaceBase , PlaceWithHirId } ;
811use rustc_lint:: { LateContext , LateLintPass } ;
9- use rustc_session:: declare_lint_pass;
10- use rustc_span:: sym;
12+ use rustc_middle:: hir:: place:: ProjectionKind ;
13+ use rustc_middle:: mir:: FakeReadCause ;
14+ use rustc_middle:: ty;
15+ use rustc_session:: impl_lint_pass;
16+ use rustc_span:: { Symbol , sym} ;
1117
1218declare_clippy_lint ! {
1319 /// ### What it does
@@ -33,17 +39,57 @@ declare_clippy_lint! {
3339 perf,
3440 "assigning a newly created box to `Box<T>` is inefficient"
3541}
36- declare_lint_pass ! ( ReplaceBox => [ REPLACE_BOX ] ) ;
42+
43+ #[ derive( Default ) ]
44+ pub struct ReplaceBox {
45+ consumed_locals : FxHashSet < HirId > ,
46+ loaded_bodies : SmallVec < [ BodyId ; 2 ] > ,
47+ }
48+
49+ impl ReplaceBox {
50+ fn get_consumed_locals ( & mut self , cx : & LateContext < ' _ > ) -> & FxHashSet < HirId > {
51+ if let Some ( body_id) = cx. enclosing_body
52+ && !self . loaded_bodies . contains ( & body_id)
53+ {
54+ self . loaded_bodies . push ( body_id) ;
55+ ExprUseVisitor :: for_clippy (
56+ cx,
57+ cx. tcx . hir_body_owner_def_id ( body_id) ,
58+ MovedVariablesCtxt {
59+ consumed_locals : & mut self . consumed_locals ,
60+ } ,
61+ )
62+ . consume_body ( cx. tcx . hir_body ( body_id) )
63+ . into_ok ( ) ;
64+ }
65+
66+ & self . consumed_locals
67+ }
68+ }
69+
70+ impl_lint_pass ! ( ReplaceBox => [ REPLACE_BOX ] ) ;
3771
3872impl LateLintPass < ' _ > for ReplaceBox {
73+ fn check_body_post ( & mut self , _: & LateContext < ' _ > , body : & Body < ' _ > ) {
74+ if self . loaded_bodies . first ( ) . is_some_and ( |& x| x == body. id ( ) ) {
75+ self . consumed_locals . clear ( ) ;
76+ self . loaded_bodies . clear ( ) ;
77+ }
78+ }
79+
3980 fn check_expr ( & mut self , cx : & LateContext < ' _ > , expr : & ' _ Expr < ' _ > ) {
4081 if let ExprKind :: Assign ( lhs, rhs, _) = & expr. kind
4182 && !lhs. span . from_expansion ( )
4283 && !rhs. span . from_expansion ( )
4384 && let lhs_ty = cx. typeck_results ( ) . expr_ty ( lhs)
85+ && let Some ( inner_ty) = lhs_ty. boxed_ty ( )
4486 // No diagnostic for late-initialized locals
4587 && lhs. res_local_id ( ) . is_none_or ( |local| local_is_initialized ( cx, local) )
46- && let Some ( inner_ty) = lhs_ty. boxed_ty ( )
88+ // No diagnostic if this is a local that has been moved, or the field
89+ // of a local that has been moved, or several chained field accesses of a local
90+ && local_base ( lhs) . is_none_or ( |( base_id, _) | {
91+ !self . get_consumed_locals ( cx) . contains ( & base_id)
92+ } )
4793 {
4894 if let Some ( default_trait_id) = cx. tcx . get_diagnostic_item ( sym:: Default )
4995 && implements_trait ( cx, inner_ty, default_trait_id, & [ ] )
@@ -109,3 +155,46 @@ fn get_box_new_payload<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<
109155 None
110156 }
111157}
158+
159+ struct MovedVariablesCtxt < ' a > {
160+ consumed_locals : & ' a mut FxHashSet < HirId > ,
161+ }
162+
163+ impl < ' tcx > Delegate < ' tcx > for MovedVariablesCtxt < ' _ > {
164+ fn consume ( & mut self , cmt : & PlaceWithHirId < ' tcx > , _: HirId ) {
165+ if let PlaceBase :: Local ( id) = cmt. place . base
166+ && let mut projections = cmt
167+ . place
168+ . projections
169+ . iter ( )
170+ . filter ( |x| matches ! ( x. kind, ProjectionKind :: Deref ) )
171+ // Either no deref or multiple derefs
172+ && ( projections. next ( ) . is_none ( ) || projections. next ( ) . is_some ( ) )
173+ {
174+ self . consumed_locals . insert ( id) ;
175+ }
176+ }
177+
178+ fn use_cloned ( & mut self , _: & PlaceWithHirId < ' tcx > , _: HirId ) { }
179+
180+ fn borrow ( & mut self , _: & PlaceWithHirId < ' tcx > , _: HirId , _: ty:: BorrowKind ) { }
181+
182+ fn mutate ( & mut self , _: & PlaceWithHirId < ' tcx > , _: HirId ) { }
183+
184+ fn fake_read ( & mut self , _: & PlaceWithHirId < ' tcx > , _: FakeReadCause , _: HirId ) { }
185+ }
186+
187+ /// A local place followed by optional fields
188+ type IdFields = ( HirId , Vec < Symbol > ) ;
189+
190+ /// If `expr` is a local variable with optional field accesses, return it.
191+ fn local_base ( expr : & Expr < ' _ > ) -> Option < IdFields > {
192+ match expr. kind {
193+ ExprKind :: Path ( qpath) => qpath. res_local_id ( ) . map ( |id| ( id, Vec :: new ( ) ) ) ,
194+ ExprKind :: Field ( expr, field) => local_base ( expr) . map ( |( id, mut fields) | {
195+ fields. push ( field. name ) ;
196+ ( id, fields)
197+ } ) ,
198+ _ => None ,
199+ }
200+ }
0 commit comments