@@ -4,9 +4,12 @@ use clippy_utils::sugg::Sugg;
44use clippy_utils:: ty:: implements_trait;
55use clippy_utils:: { is_default_equivalent_call, local_is_initialized} ;
66use rustc_errors:: Applicability ;
7- use rustc_hir:: { Expr , ExprKind , LangItem , QPath } ;
7+ use rustc_hir:: { Body , Expr , ExprKind , HirId , HirIdSet , LangItem , Node , QPath } ;
8+ use rustc_hir_typeck:: expr_use_visitor:: { Delegate , ExprUseVisitor , PlaceBase , PlaceWithHirId } ;
89use rustc_lint:: { LateContext , LateLintPass } ;
9- use rustc_session:: declare_lint_pass;
10+ use rustc_middle:: mir:: FakeReadCause ;
11+ use rustc_middle:: ty;
12+ use rustc_session:: impl_lint_pass;
1013use rustc_span:: sym;
1114
1215declare_clippy_lint ! {
@@ -33,16 +36,52 @@ declare_clippy_lint! {
3336 perf,
3437 "assigning a newly created box to `Box<T>` is inefficient"
3538}
36- declare_lint_pass ! ( ReplaceBox => [ REPLACE_BOX ] ) ;
39+
40+ #[ derive( Default ) ]
41+ pub struct ReplaceBox {
42+ // Stack of caches for moved vars. The latest entry is the
43+ // body being currently visited.
44+ moved_vars_caches : Vec < Option < HirIdSet > > ,
45+ }
46+
47+ impl ReplaceBox {
48+ fn current_moved_vars_cache ( & mut self ) -> & mut Option < HirIdSet > {
49+ self . moved_vars_caches . last_mut ( ) . unwrap ( )
50+ }
51+ }
52+
53+ impl_lint_pass ! ( ReplaceBox => [ REPLACE_BOX ] ) ;
3754
3855impl LateLintPass < ' _ > for ReplaceBox {
56+ fn check_body ( & mut self , _: & LateContext < ' _ > , _: & Body < ' _ > ) {
57+ self . moved_vars_caches . push ( None ) ;
58+ }
59+
60+ fn check_body_post ( & mut self , _: & LateContext < ' _ > , _: & Body < ' _ > ) {
61+ _ = self . moved_vars_caches . pop ( ) ;
62+ }
63+
3964 fn check_expr ( & mut self , cx : & LateContext < ' _ > , expr : & ' _ Expr < ' _ > ) {
4065 if let ExprKind :: Assign ( lhs, rhs, _) = & expr. kind
4166 && !lhs. span . from_expansion ( )
4267 && !rhs. span . from_expansion ( )
4368 && let lhs_ty = cx. typeck_results ( ) . expr_ty ( lhs)
4469 // No diagnostic for late-initialized locals
45- && lhs. res_local_id ( ) . is_none_or ( |local| local_is_initialized ( cx, local) )
70+ && let local = lhs. res_local_id ( )
71+ && local. is_none_or ( |local| {
72+ local_is_initialized ( cx, local)
73+ && !self . current_moved_vars_cache ( ) . get_or_insert ( {
74+ let body_id = cx. enclosing_body . unwrap ( ) ;
75+ let mut ctx = MovedVariablesCtxt {
76+ cx,
77+ moved_vars : HirIdSet :: default ( ) ,
78+ } ;
79+ ExprUseVisitor :: for_clippy ( cx, cx. tcx . hir_body_owner_def_id ( body_id) , & mut ctx)
80+ . consume_body ( cx. tcx . hir_body ( body_id) )
81+ . into_ok ( ) ;
82+ ctx. moved_vars
83+ } ) . contains ( & local)
84+ } )
4685 && let Some ( inner_ty) = lhs_ty. boxed_ty ( )
4786 {
4887 if let Some ( default_trait_id) = cx. tcx . get_diagnostic_item ( sym:: Default )
@@ -109,3 +148,27 @@ fn get_box_new_payload<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<
109148 None
110149 }
111150}
151+
152+ struct MovedVariablesCtxt < ' a , ' tcx > {
153+ cx : & ' a LateContext < ' tcx > ,
154+ moved_vars : HirIdSet ,
155+ }
156+
157+ impl < ' tcx > Delegate < ' tcx > for MovedVariablesCtxt < ' _ , ' tcx > {
158+ fn consume ( & mut self , cmt : & PlaceWithHirId < ' tcx > , _: HirId ) {
159+ if let PlaceBase :: Local ( vid) = cmt. place . base
160+ && let Node :: Expr ( expr) = self . cx . tcx . hir_node ( cmt. hir_id )
161+ && expr. res_local_id ( ) . is_some_and ( |hir_id| hir_id == vid)
162+ {
163+ self . moved_vars . insert ( vid) ;
164+ }
165+ }
166+
167+ fn use_cloned ( & mut self , _: & PlaceWithHirId < ' tcx > , _: HirId ) { }
168+
169+ fn borrow ( & mut self , _: & PlaceWithHirId < ' tcx > , _: HirId , _: ty:: BorrowKind ) { }
170+
171+ fn mutate ( & mut self , _: & PlaceWithHirId < ' tcx > , _: HirId ) { }
172+
173+ fn fake_read ( & mut self , _: & PlaceWithHirId < ' tcx > , _: FakeReadCause , _: HirId ) { }
174+ }
0 commit comments