@@ -45,6 +45,9 @@ struct Access {
4545 /// When we encounter multiple statements at the same location, we only increase the liveness,
4646 /// in order to avoid false positives.
4747 live : bool ,
48+ /// Is this a direct access to the place itself, no projections, or to a field?
49+ /// This helps distinguish `x = ...` from `x.field = ...`
50+ is_direct : bool ,
4851}
4952
5053#[ tracing:: instrument( level = "debug" , skip( tcx) , ret) ]
@@ -689,15 +692,17 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
689692 |place : Place < ' tcx > , kind, source_info : SourceInfo , live : & DenseBitSet < PlaceIndex > | {
690693 if let Some ( ( index, extra_projections) ) = checked_places. get ( place. as_ref ( ) ) {
691694 if !is_indirect ( extra_projections) {
695+ let is_direct = extra_projections. is_empty ( ) ;
692696 match assignments[ index] . entry ( source_info) {
693697 IndexEntry :: Vacant ( v) => {
694- let access = Access { kind, live : live. contains ( index) } ;
698+ let access = Access { kind, live : live. contains ( index) , is_direct } ;
695699 v. insert ( access) ;
696700 }
697701 IndexEntry :: Occupied ( mut o) => {
698702 // There were already a sighting. Mark this statement as live if it
699703 // was, to avoid false positives.
700704 o. get_mut ( ) . live |= live. contains ( index) ;
705+ o. get_mut ( ) . is_direct &= is_direct;
701706 }
702707 }
703708 }
@@ -781,7 +786,7 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
781786 continue ;
782787 } ;
783788 let source_info = body. local_decls [ place. local ] . source_info ;
784- let access = Access { kind, live : live. contains ( index) } ;
789+ let access = Access { kind, live : live. contains ( index) , is_direct : true } ;
785790 assignments[ index] . insert ( source_info, access) ;
786791 }
787792 }
@@ -875,6 +880,33 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
875880 dead_captures
876881 }
877882
883+ /// Check if a local is referenced in any reachable basic block.
884+ /// Variables in unreachable code (e.g., after `todo!()`) should not trigger unused warnings.
885+ fn is_local_in_reachable_code ( & self , local : Local ) -> bool {
886+ struct LocalVisitor {
887+ target_local : Local ,
888+ found : bool ,
889+ }
890+
891+ impl < ' tcx > Visitor < ' tcx > for LocalVisitor {
892+ fn visit_local ( & mut self , local : Local , _context : PlaceContext , _location : Location ) {
893+ if local == self . target_local {
894+ self . found = true ;
895+ }
896+ }
897+ }
898+
899+ let mut visitor = LocalVisitor { target_local : local, found : false } ;
900+ for ( bb, bb_data) in traversal:: postorder ( self . body ) {
901+ visitor. visit_basic_block_data ( bb, bb_data) ;
902+ if visitor. found {
903+ return true ;
904+ }
905+ }
906+
907+ false
908+ }
909+
878910 /// Report fully unused locals, and forget the corresponding assignments.
879911 fn report_fully_unused ( & mut self ) {
880912 let tcx = self . tcx ;
@@ -928,6 +960,10 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
928960
929961 let statements = & mut self . assignments [ index] ;
930962 if statements. is_empty ( ) {
963+ if !self . is_local_in_reachable_code ( local) {
964+ continue ;
965+ }
966+
931967 let sugg = if from_macro {
932968 errors:: UnusedVariableSugg :: NoSugg { span : def_span, name }
933969 } else {
@@ -977,8 +1013,10 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
9771013 self . checked_places ,
9781014 self . body ,
9791015 ) {
980- statements. clear ( ) ;
981- continue ;
1016+ statements. retain ( |_, access| access. is_direct ) ;
1017+ if statements. is_empty ( ) {
1018+ continue ;
1019+ }
9821020 }
9831021
9841022 let typo = maybe_suggest_typo ( ) ;
@@ -1049,26 +1087,28 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
10491087
10501088 let Some ( ( name, decl_span) ) = self . checked_places . names [ index] else { continue } ;
10511089
1052- // We have outstanding assignments and with non-trivial drop.
1053- // This is probably a drop-guard, so we do not issue a warning there.
1054- if maybe_drop_guard (
1090+ let is_maybe_drop_guard = maybe_drop_guard (
10551091 tcx,
10561092 self . typing_env ,
10571093 index,
10581094 & self . ever_dropped ,
10591095 self . checked_places ,
10601096 self . body ,
1061- ) {
1062- continue ;
1063- }
1097+ ) ;
10641098
10651099 // We probed MIR in reverse order for dataflow.
10661100 // We revert the vector to give a consistent order to the user.
1067- for ( source_info, Access { live, kind } ) in statements. into_iter ( ) . rev ( ) {
1101+ for ( source_info, Access { live, kind, is_direct } ) in statements. into_iter ( ) . rev ( ) {
10681102 if live {
10691103 continue ;
10701104 }
10711105
1106+ // If this place was dropped and has non-trivial drop,
1107+ // skip reporting field assignments.
1108+ if !is_direct && is_maybe_drop_guard {
1109+ continue ;
1110+ }
1111+
10721112 // Report the dead assignment.
10731113 let Some ( hir_id) = source_info. scope . lint_root ( & self . body . source_scopes ) else {
10741114 continue ;
0 commit comments