@@ -3,11 +3,15 @@ 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 ;
67use rustc_errors:: Applicability ;
7- use rustc_hir:: { Expr , ExprKind , LangItem , QPath } ;
8+ use rustc_hir:: { Body , Expr , ExprKind , HirId , LangItem , Node , QPath } ;
9+ use rustc_hir_typeck:: expr_use_visitor:: { Delegate , ExprUseVisitor , PlaceBase , PlaceWithHirId } ;
810use rustc_lint:: { LateContext , LateLintPass } ;
9- use rustc_session:: declare_lint_pass;
10- use rustc_span:: sym;
11+ use rustc_middle:: mir:: FakeReadCause ;
12+ use rustc_middle:: ty;
13+ use rustc_session:: impl_lint_pass;
14+ use rustc_span:: { Symbol , sym} ;
1115
1216declare_clippy_lint ! {
1317 /// ### What it does
@@ -33,17 +37,54 @@ declare_clippy_lint! {
3337 perf,
3438 "assigning a newly created box to `Box<T>` is inefficient"
3539}
36- declare_lint_pass ! ( ReplaceBox => [ REPLACE_BOX ] ) ;
40+
41+ /// A local place followed by optional fields
42+ type IdFields = ( HirId , Vec < Symbol > ) ;
43+
44+ #[ derive( Default ) ]
45+ pub struct ReplaceBox {
46+ // Stack of caches for moved vars. The latest entry is the
47+ // body being currently visited.
48+ moved_vars_caches : Vec < Option < FxHashSet < IdFields > > > ,
49+ }
50+
51+ impl ReplaceBox {
52+ fn current_moved_vars_cache ( & mut self ) -> & mut Option < FxHashSet < IdFields > > {
53+ self . moved_vars_caches . last_mut ( ) . unwrap ( )
54+ }
55+ }
56+
57+ impl_lint_pass ! ( ReplaceBox => [ REPLACE_BOX ] ) ;
3758
3859impl LateLintPass < ' _ > for ReplaceBox {
60+ fn check_body ( & mut self , _: & LateContext < ' _ > , _: & Body < ' _ > ) {
61+ self . moved_vars_caches . push ( None ) ;
62+ }
63+
64+ fn check_body_post ( & mut self , _: & LateContext < ' _ > , _: & Body < ' _ > ) {
65+ _ = self . moved_vars_caches . pop ( ) ;
66+ }
67+
3968 fn check_expr ( & mut self , cx : & LateContext < ' _ > , expr : & ' _ Expr < ' _ > ) {
4069 if let ExprKind :: Assign ( lhs, rhs, _) = & expr. kind
4170 && !lhs. span . from_expansion ( )
4271 && !rhs. span . from_expansion ( )
4372 && let lhs_ty = cx. typeck_results ( ) . expr_ty ( lhs)
73+ && let Some ( inner_ty) = lhs_ty. boxed_ty ( )
4474 // No diagnostic for late-initialized locals
4575 && lhs. res_local_id ( ) . is_none_or ( |local| local_is_initialized ( cx, local) )
46- && let Some ( inner_ty) = lhs_ty. boxed_ty ( )
76+ // No diagnostic if this is a local that has been moved, or the field
77+ // of a local that has been moved, or several chained field accesses of a local
78+ && local_base ( lhs) . is_none_or ( |base_id| {
79+ !self . current_moved_vars_cache ( ) . get_or_insert_with ( || {
80+ let body_id = cx. enclosing_body . unwrap ( ) ;
81+ let mut ctx = MovedVariablesCtxt { cx, moved_vars : FxHashSet :: default ( ) } ;
82+ ExprUseVisitor :: for_clippy ( cx, cx. tcx . hir_body_owner_def_id ( body_id) , & mut ctx)
83+ . consume_body ( cx. tcx . hir_body ( body_id) )
84+ . into_ok ( ) ;
85+ ctx. moved_vars
86+ } ) . contains ( & base_id)
87+ } )
4788 {
4889 if let Some ( default_trait_id) = cx. tcx . get_diagnostic_item ( sym:: Default )
4990 && implements_trait ( cx, inner_ty, default_trait_id, & [ ] )
@@ -109,3 +150,39 @@ fn get_box_new_payload<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<
109150 None
110151 }
111152}
153+
154+ struct MovedVariablesCtxt < ' a , ' tcx > {
155+ cx : & ' a LateContext < ' tcx > ,
156+ moved_vars : FxHashSet < IdFields > ,
157+ }
158+
159+ impl < ' tcx > Delegate < ' tcx > for MovedVariablesCtxt < ' _ , ' tcx > {
160+ fn consume ( & mut self , cmt : & PlaceWithHirId < ' tcx > , _: HirId ) {
161+ if let PlaceBase :: Local ( _) = cmt. place . base
162+ && let Node :: Expr ( expr) = self . cx . tcx . hir_node ( cmt. hir_id )
163+ && let Some ( id_fields) = local_base ( expr)
164+ {
165+ self . moved_vars . insert ( id_fields) ;
166+ }
167+ }
168+
169+ fn use_cloned ( & mut self , _: & PlaceWithHirId < ' tcx > , _: HirId ) { }
170+
171+ fn borrow ( & mut self , _: & PlaceWithHirId < ' tcx > , _: HirId , _: ty:: BorrowKind ) { }
172+
173+ fn mutate ( & mut self , _: & PlaceWithHirId < ' tcx > , _: HirId ) { }
174+
175+ fn fake_read ( & mut self , _: & PlaceWithHirId < ' tcx > , _: FakeReadCause , _: HirId ) { }
176+ }
177+
178+ /// If `expr` is a local variable with optional field accesses, return it.
179+ fn local_base ( expr : & Expr < ' _ > ) -> Option < IdFields > {
180+ match expr. kind {
181+ ExprKind :: Path ( qpath) => qpath. res_local_id ( ) . map ( |id| ( id, Vec :: new ( ) ) ) ,
182+ ExprKind :: Field ( expr, field) => local_base ( expr) . map ( |( id, mut fields) | {
183+ fields. push ( field. name ) ;
184+ ( id, fields)
185+ } ) ,
186+ _ => None ,
187+ }
188+ }
0 commit comments