@@ -9,7 +9,7 @@ use std::sync::atomic::Ordering;
99
1010use hir:: def_id:: { LocalDefIdMap , LocalDefIdSet } ;
1111use rustc_abi:: FieldIdx ;
12- use rustc_data_structures:: fx:: { FxHashSet , FxIndexSet } ;
12+ use rustc_data_structures:: fx:: { FxHashMap , FxHashSet , FxIndexSet } ;
1313use rustc_errors:: { ErrorGuaranteed , MultiSpan } ;
1414use rustc_hir:: def:: { CtorOf , DefKind , Res } ;
1515use rustc_hir:: def_id:: { DefId , LocalDefId , LocalModId } ;
@@ -114,6 +114,11 @@ struct WorkItem {
114114 own : ComesFromAllowExpect ,
115115}
116116
117+ enum ImplItemCheckResult {
118+ Live ( ComesFromAllowExpect ) ,
119+ Dead { require : LocalDefId } ,
120+ }
121+
117122struct MarkSymbolVisitor < ' tcx > {
118123 worklist : Vec < WorkItem > ,
119124 tcx : TyCtxt < ' tcx > ,
@@ -129,6 +134,7 @@ struct MarkSymbolVisitor<'tcx> {
129134 // macro)
130135 ignored_derived_traits : LocalDefIdMap < FxIndexSet < DefId > > ,
131136 propagated_comes_from_allow_expect : ComesFromAllowExpect ,
137+ unsolved_items : Vec < LocalDefId > ,
132138}
133139
134140impl < ' tcx > MarkSymbolVisitor < ' tcx > {
@@ -419,6 +425,11 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
419425
420426 if !self . scanned . insert ( ( id, propagated) ) {
421427 continue ;
428+ } else if propagated == ComesFromAllowExpect :: No {
429+ // If the item is not coming from an `#[allow]` or `#[expect]`,
430+ // we also mark it as scanned with `ComesFromAllowExpect::Yes`
431+ // to avoid re-scanning it in the future.
432+ self . scanned . insert ( ( id, ComesFromAllowExpect :: Yes ) ) ;
422433 }
423434
424435 // Avoid accessing the HIR for the synthesized associated type generated for RPITITs.
@@ -545,13 +556,17 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
545556 /// `local_def_id` points to an impl or an impl item,
546557 /// both impl and impl item that may be passed to this function are of a trait,
547558 /// and added into the unsolved_items during `create_and_seed_worklist`
548- fn check_impl_or_impl_item_live ( & mut self , local_def_id : LocalDefId ) -> bool {
559+ fn check_impl_or_impl_item_live (
560+ & self ,
561+ local_def_id : LocalDefId ,
562+ defer_seeds_come_from_allow : bool ,
563+ ) -> ImplItemCheckResult {
549564 let ( impl_block_id, trait_def_id) = match self . tcx . def_kind ( local_def_id) {
550565 // assoc impl items of traits are live if the corresponding trait items are live
551566 DefKind :: AssocConst { .. } | DefKind :: AssocTy | DefKind :: AssocFn => {
552- let trait_item_id =
567+ let trait_def_id =
553568 self . tcx . trait_item_of ( local_def_id) . and_then ( |def_id| def_id. as_local ( ) ) ;
554- ( self . tcx . local_parent ( local_def_id) , trait_item_id )
569+ ( self . tcx . local_parent ( local_def_id) , trait_def_id )
555570 }
556571 // impl items are live if the corresponding traits are live
557572 DefKind :: Impl { of_trait : true } => {
@@ -560,10 +575,22 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
560575 _ => bug ! ( ) ,
561576 } ;
562577
563- if let Some ( trait_def_id) = trait_def_id
564- && !self . live_symbols . contains ( & trait_def_id)
565- {
566- return false ;
578+ let mut trait_comes_from_allow = None ;
579+ if let Some ( trait_def_id) = trait_def_id {
580+ if defer_seeds_come_from_allow {
581+ if !self . live_symbols . contains ( & trait_def_id) {
582+ return ImplItemCheckResult :: Dead { require : trait_def_id } ;
583+ }
584+ } else {
585+ trait_comes_from_allow = has_allow_dead_code_or_lang_attr ( self . tcx , trait_def_id) ;
586+
587+ if !self . live_symbols . contains ( & trait_def_id) {
588+ return match trait_comes_from_allow {
589+ Some ( comes_from_allow) => ImplItemCheckResult :: Live ( comes_from_allow) ,
590+ None => ImplItemCheckResult :: Dead { require : trait_def_id } ,
591+ } ;
592+ }
593+ }
567594 }
568595
569596 // The impl or impl item is used if the corresponding trait or trait item is used and the ty is used.
@@ -572,10 +599,95 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
572599 && let Some ( adt_def_id) = adt. did ( ) . as_local ( )
573600 && !self . live_symbols . contains ( & adt_def_id)
574601 {
575- return false ;
602+ if defer_seeds_come_from_allow {
603+ return ImplItemCheckResult :: Dead { require : adt_def_id } ;
604+ } else {
605+ let comes_from_allow = trait_comes_from_allow
606+ . or_else ( || has_allow_dead_code_or_lang_attr ( self . tcx , adt_def_id) ) ;
607+
608+ return match comes_from_allow {
609+ Some ( comes_from_allow) => ImplItemCheckResult :: Live ( comes_from_allow) ,
610+ None => ImplItemCheckResult :: Dead { require : adt_def_id } ,
611+ } ;
612+ }
576613 }
577614
578- true
615+ ImplItemCheckResult :: Live ( ComesFromAllowExpect :: No )
616+ }
617+
618+ fn collect_live_items_from_unsolved_items (
619+ & mut self ,
620+ defer_seeds_come_from_allow : bool ,
621+ unsolved_items : Vec < LocalDefId > ,
622+ unsolved_map : & mut FxHashMap < LocalDefId , Vec < LocalDefId > > ,
623+ ) -> Vec < ( LocalDefId , ComesFromAllowExpect ) > {
624+ let mut items_to_check = vec ! [ ] ;
625+
626+ for def_id in unsolved_items {
627+ match self . check_impl_or_impl_item_live ( def_id, defer_seeds_come_from_allow) {
628+ ImplItemCheckResult :: Live ( comes_from_allow) => {
629+ items_to_check. push ( ( def_id, comes_from_allow) ) ;
630+ }
631+ ImplItemCheckResult :: Dead { require } => {
632+ unsolved_map. entry ( require) . or_default ( ) . push ( def_id) ;
633+ }
634+ }
635+ }
636+ items_to_check
637+ }
638+
639+ #[ expect(
640+ rustc:: potential_query_instability,
641+ reason = "The order of the unsolved items is not important, so we can just collect them into a vector."
642+ ) ]
643+ fn mark_live_symbols_and_ignored_derived_traits (
644+ & mut self ,
645+ defer_seeds_come_from_allow : bool ,
646+ ) -> Result < ( ) , ErrorGuaranteed > {
647+ if let ControlFlow :: Break ( guar) = self . mark_live_symbols ( ) {
648+ return Err ( guar) ;
649+ }
650+
651+ // We have marked the primary seeds as live. We now need to process unsolved items from traits
652+ // and trait impls: add them to the work list if the trait or the implemented type is live.
653+ let unsolved_items = std:: mem:: take ( & mut self . unsolved_items ) ;
654+ let mut unsolved_map = FxHashMap :: default ( ) ;
655+ let mut items_to_check = self . collect_live_items_from_unsolved_items (
656+ defer_seeds_come_from_allow,
657+ unsolved_items,
658+ & mut unsolved_map,
659+ ) ;
660+
661+ while !items_to_check. is_empty ( ) {
662+ self . worklist . extend ( items_to_check. into_iter ( ) . map ( |( id, comes_from_allow) | {
663+ let own = if defer_seeds_come_from_allow {
664+ ComesFromAllowExpect :: No
665+ } else {
666+ has_allow_dead_code_or_lang_attr ( self . tcx , id)
667+ . unwrap_or ( ComesFromAllowExpect :: No )
668+ } ;
669+
670+ WorkItem { id, propagated : comes_from_allow, own }
671+ } ) ) ;
672+ if let ControlFlow :: Break ( guar) = self . mark_live_symbols ( ) {
673+ return Err ( guar) ;
674+ }
675+
676+ let unsolved_items = unsolved_map
677+ . extract_if ( |require, _| self . live_symbols . contains ( require) )
678+ . flat_map ( |( _, items) | items)
679+ . collect ( ) ;
680+
681+ items_to_check = self . collect_live_items_from_unsolved_items (
682+ defer_seeds_come_from_allow,
683+ unsolved_items,
684+ & mut unsolved_map,
685+ ) ;
686+ }
687+
688+ self . unsolved_items = unsolved_map. into_values ( ) . flatten ( ) . collect ( ) ;
689+
690+ Ok ( ( ) )
579691 }
580692}
581693
@@ -850,19 +962,6 @@ fn maybe_record_as_seed<'tcx>(
850962 match tcx. def_kind ( parent) {
851963 DefKind :: Impl { of_trait : false } | DefKind :: Trait => { }
852964 DefKind :: Impl { of_trait : true } => {
853- if let Some ( trait_item_def_id) =
854- tcx. associated_item ( owner_id. def_id ) . trait_item_def_id ( )
855- && let Some ( trait_item_local_def_id) = trait_item_def_id. as_local ( )
856- && let Some ( comes_from_allow) =
857- has_allow_dead_code_or_lang_attr ( tcx, trait_item_local_def_id)
858- {
859- push_into_worklist ( WorkItem {
860- id : owner_id. def_id ,
861- propagated : comes_from_allow,
862- own : comes_from_allow,
863- } ) ;
864- }
865-
866965 // We only care about associated items of traits,
867966 // because they cannot be visited directly,
868967 // so we later mark them as live if their corresponding traits
@@ -874,22 +973,8 @@ fn maybe_record_as_seed<'tcx>(
874973 }
875974 }
876975 }
877- DefKind :: Impl { of_trait : true } => {
878- if allow_dead_code. is_none ( ) {
879- if let Some ( trait_def_id) =
880- tcx. impl_trait_ref ( owner_id. def_id ) . skip_binder ( ) . def_id . as_local ( )
881- && let Some ( comes_from_allow) =
882- has_allow_dead_code_or_lang_attr ( tcx, trait_def_id)
883- {
884- push_into_worklist ( WorkItem {
885- id : owner_id. def_id ,
886- propagated : comes_from_allow,
887- own : comes_from_allow,
888- } ) ;
889- }
890-
891- unsolved_items. push ( owner_id. def_id ) ;
892- }
976+ DefKind :: Impl { of_trait : true } if allow_dead_code. is_none ( ) => {
977+ unsolved_items. push ( owner_id. def_id ) ;
893978 }
894979 DefKind :: GlobalAsm => {
895980 // global_asm! is always live.
@@ -915,15 +1000,21 @@ fn maybe_record_as_seed<'tcx>(
9151000 }
9161001}
9171002
1003+ #[ derive( Default ) ]
1004+ struct DeferredSeeds {
1005+ pub_reachables : Vec < WorkItem > ,
1006+ come_from_allow : Vec < WorkItem > ,
1007+ }
1008+
9181009struct SeedWorklists {
9191010 worklist : Vec < WorkItem > ,
920- deferred_seeds : Vec < WorkItem > ,
1011+ deferred_seeds : DeferredSeeds ,
9211012 unsolved_items : Vec < LocalDefId > ,
9221013}
9231014
9241015fn create_and_seed_worklist ( tcx : TyCtxt < ' _ > ) -> SeedWorklists {
9251016 let mut unsolved_items = Vec :: new ( ) ;
926- let mut deferred_seeds = Vec :: new ( ) ;
1017+ let mut deferred_seeds = DeferredSeeds :: default ( ) ;
9271018 let mut worklist = Vec :: new ( ) ;
9281019
9291020 if let Some ( ( def_id, _) ) = tcx. entry_fn ( ( ) )
@@ -953,7 +1044,7 @@ fn create_and_seed_worklist(tcx: TyCtxt<'_>) -> SeedWorklists {
9531044
9541045 for ( id, effective_vis) in tcx. effective_visibilities ( ( ) ) . iter ( ) {
9551046 if effective_vis. is_public_at_level ( Level :: Reachable ) {
956- deferred_seeds. push ( WorkItem {
1047+ deferred_seeds. pub_reachables . push ( WorkItem {
9571048 id : * id,
9581049 propagated : ComesFromAllowExpect :: No ,
9591050 own : ComesFromAllowExpect :: No ,
@@ -962,7 +1053,7 @@ fn create_and_seed_worklist(tcx: TyCtxt<'_>) -> SeedWorklists {
9621053 }
9631054
9641055 let mut push_into_worklist = |work_item : WorkItem | match work_item. own {
965- ComesFromAllowExpect :: Yes => deferred_seeds. push ( work_item) ,
1056+ ComesFromAllowExpect :: Yes => deferred_seeds. come_from_allow . push ( work_item) ,
9661057 ComesFromAllowExpect :: No => worklist. push ( work_item) ,
9671058 } ;
9681059 let crate_items = tcx. hir_crate_items ( ( ) ) ;
@@ -977,8 +1068,7 @@ fn live_symbols_and_ignored_derived_traits(
9771068 tcx : TyCtxt < ' _ > ,
9781069 ( ) : ( ) ,
9791070) -> Result < DeadCodeLivenessSummary , ErrorGuaranteed > {
980- let SeedWorklists { worklist, deferred_seeds, mut unsolved_items } =
981- create_and_seed_worklist ( tcx) ;
1071+ let SeedWorklists { worklist, deferred_seeds, unsolved_items } = create_and_seed_worklist ( tcx) ;
9821072 let mut symbol_visitor = MarkSymbolVisitor {
9831073 worklist,
9841074 tcx,
@@ -991,15 +1081,23 @@ fn live_symbols_and_ignored_derived_traits(
9911081 ignore_variant_stack : vec ! [ ] ,
9921082 ignored_derived_traits : Default :: default ( ) ,
9931083 propagated_comes_from_allow_expect : ComesFromAllowExpect :: No ,
1084+ unsolved_items,
9941085 } ;
995- mark_live_symbols_and_ignored_derived_traits ( & mut symbol_visitor , & mut unsolved_items ) ?;
1086+ symbol_visitor . mark_live_symbols_and_ignored_derived_traits ( true ) ?;
9961087 let pre_deferred_seeding = DeadCodeLivenessSnapshot {
9971088 live_symbols : symbol_visitor. live_symbols . clone ( ) ,
9981089 ignored_derived_traits : symbol_visitor. ignored_derived_traits . clone ( ) ,
9991090 } ;
10001091
1001- symbol_visitor. worklist . extend ( deferred_seeds) ;
1002- mark_live_symbols_and_ignored_derived_traits ( & mut symbol_visitor, & mut unsolved_items) ?;
1092+ if !deferred_seeds. pub_reachables . is_empty ( ) {
1093+ symbol_visitor. worklist . extend ( deferred_seeds. pub_reachables ) ;
1094+ symbol_visitor. mark_live_symbols_and_ignored_derived_traits ( true ) ?;
1095+ }
1096+
1097+ if !deferred_seeds. come_from_allow . is_empty ( ) {
1098+ symbol_visitor. worklist . extend ( deferred_seeds. come_from_allow ) ;
1099+ symbol_visitor. mark_live_symbols_and_ignored_derived_traits ( false ) ?;
1100+ }
10031101
10041102 Ok ( DeadCodeLivenessSummary {
10051103 pre_deferred_seeding,
@@ -1010,40 +1108,6 @@ fn live_symbols_and_ignored_derived_traits(
10101108 } )
10111109}
10121110
1013- fn mark_live_symbols_and_ignored_derived_traits (
1014- symbol_visitor : & mut MarkSymbolVisitor < ' _ > ,
1015- unsolved_items : & mut Vec < LocalDefId > ,
1016- ) -> Result < ( ) , ErrorGuaranteed > {
1017- if let ControlFlow :: Break ( guar) = symbol_visitor. mark_live_symbols ( ) {
1018- return Err ( guar) ;
1019- }
1020-
1021- // We have marked the primary seeds as live. We now need to process unsolved items from traits
1022- // and trait impls: add them to the work list if the trait or the implemented type is live.
1023- let mut items_to_check: Vec < _ > = unsolved_items
1024- . extract_if ( .., |& mut local_def_id| {
1025- symbol_visitor. check_impl_or_impl_item_live ( local_def_id)
1026- } )
1027- . collect ( ) ;
1028-
1029- while !items_to_check. is_empty ( ) {
1030- symbol_visitor. worklist . extend ( items_to_check. drain ( ..) . map ( |id| WorkItem {
1031- id,
1032- propagated : ComesFromAllowExpect :: No ,
1033- own : ComesFromAllowExpect :: No ,
1034- } ) ) ;
1035- if let ControlFlow :: Break ( guar) = symbol_visitor. mark_live_symbols ( ) {
1036- return Err ( guar) ;
1037- }
1038-
1039- items_to_check. extend ( unsolved_items. extract_if ( .., |& mut local_def_id| {
1040- symbol_visitor. check_impl_or_impl_item_live ( local_def_id)
1041- } ) ) ;
1042- }
1043-
1044- Ok ( ( ) )
1045- }
1046-
10471111struct DeadItem {
10481112 def_id : LocalDefId ,
10491113 name : Symbol ,
0 commit comments