44
55use hir:: def_id:: { LocalDefIdMap , LocalDefIdSet } ;
66use itertools:: Itertools ;
7+ use rustc_data_structures:: unord:: UnordSet ;
78use rustc_errors:: MultiSpan ;
89use rustc_hir as hir;
910use rustc_hir:: def:: { CtorOf , DefKind , Res } ;
@@ -42,8 +43,16 @@ fn should_explore(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
4243 )
4344}
4445
46+ /// Determine if a work from the worklist is coming from the a `#[allow]`
47+ /// or a `#[expect]` of `dead_code`
48+ #[ derive( Debug , Copy , Clone , Eq , PartialEq , Hash ) ]
49+ enum ComesFromAllowExpect {
50+ Yes ,
51+ No ,
52+ }
53+
4554struct MarkSymbolVisitor < ' tcx > {
46- worklist : Vec < LocalDefId > ,
55+ worklist : Vec < ( LocalDefId , ComesFromAllowExpect ) > ,
4756 tcx : TyCtxt < ' tcx > ,
4857 maybe_typeck_results : Option < & ' tcx ty:: TypeckResults < ' tcx > > ,
4958 live_symbols : LocalDefIdSet ,
@@ -72,7 +81,7 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
7281 fn check_def_id ( & mut self , def_id : DefId ) {
7382 if let Some ( def_id) = def_id. as_local ( ) {
7483 if should_explore ( self . tcx , def_id) || self . struct_constructors . contains_key ( & def_id) {
75- self . worklist . push ( def_id) ;
84+ self . worklist . push ( ( def_id, ComesFromAllowExpect :: No ) ) ;
7685 }
7786 self . live_symbols . insert ( def_id) ;
7887 }
@@ -269,12 +278,14 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
269278 }
270279
271280 fn mark_live_symbols ( & mut self ) {
272- let mut scanned = LocalDefIdSet :: default ( ) ;
273- while let Some ( id ) = self . worklist . pop ( ) {
274- if !scanned. insert ( id ) {
281+ let mut scanned = UnordSet :: default ( ) ;
282+ while let Some ( work ) = self . worklist . pop ( ) {
283+ if !scanned. insert ( work ) {
275284 continue ;
276285 }
277286
287+ let ( id, comes_from_allow_expect) = work;
288+
278289 // Avoid accessing the HIR for the synthesized associated type generated for RPITITs.
279290 if self . tcx . is_impl_trait_in_trait ( id. to_def_id ( ) ) {
280291 self . live_symbols . insert ( id) ;
@@ -286,7 +297,30 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
286297 let id = self . struct_constructors . get ( & id) . copied ( ) . unwrap_or ( id) ;
287298
288299 if let Some ( node) = self . tcx . hir ( ) . find_by_def_id ( id) {
289- self . live_symbols . insert ( id) ;
300+ // When using `#[allow]` or `#[expect]` of `dead_code`, we do a QOL improvement
301+ // by declaring fn calls, statics, ... within said items as live, as well as
302+ // the item itself, although technically this is not the case.
303+ //
304+ // This means that the lint for said items will never be fired.
305+ //
306+ // This doesn't make any difference for the item declared with `#[allow]`, as
307+ // the lint firing will be a nop, as it will be silenced by the `#[allow]` of
308+ // the item.
309+ //
310+ // However, for `#[expect]`, the presence or absence of the lint is relevant,
311+ // so we don't add it to the list of live symbols when it comes from a
312+ // `#[expect]`. This means that we will correctly report an item as live or not
313+ // for the `#[expect]` case.
314+ //
315+ // Note that an item can and will be duplicated on the worklist with different
316+ // `ComesFromAllowExpect`, particulary if it was added from the
317+ // `effective_visibilities` query or from the `#[allow]`/`#[expect]` checks,
318+ // this "duplication" is essential as otherwise a function with `#[expect]`
319+ // called from a `pub fn` may be falsely reported as not live, falsely
320+ // triggering the `unfulfilled_lint_expectations` lint.
321+ if comes_from_allow_expect != ComesFromAllowExpect :: Yes {
322+ self . live_symbols . insert ( id) ;
323+ }
290324 self . visit_node ( node) ;
291325 }
292326 }
@@ -513,16 +547,20 @@ impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> {
513547 }
514548}
515549
516- fn has_allow_dead_code_or_lang_attr ( tcx : TyCtxt < ' _ > , def_id : LocalDefId ) -> bool {
550+ fn has_allow_dead_code_or_lang_attr (
551+ tcx : TyCtxt < ' _ > ,
552+ def_id : LocalDefId ,
553+ ) -> Option < ComesFromAllowExpect > {
517554 fn has_lang_attr ( tcx : TyCtxt < ' _ > , def_id : LocalDefId ) -> bool {
518555 tcx. has_attr ( def_id, sym:: lang)
519556 // Stable attribute for #[lang = "panic_impl"]
520557 || tcx. has_attr ( def_id, sym:: panic_handler)
521558 }
522559
523- fn has_allow_dead_code ( tcx : TyCtxt < ' _ > , def_id : LocalDefId ) -> bool {
560+ fn has_allow_expect_dead_code ( tcx : TyCtxt < ' _ > , def_id : LocalDefId ) -> bool {
524561 let hir_id = tcx. hir ( ) . local_def_id_to_hir_id ( def_id) ;
525- tcx. lint_level_at_node ( lint:: builtin:: DEAD_CODE , hir_id) . 0 == lint:: Allow
562+ let lint_level = tcx. lint_level_at_node ( lint:: builtin:: DEAD_CODE , hir_id) . 0 ;
563+ matches ! ( lint_level, lint:: Allow | lint:: Expect ( _) )
526564 }
527565
528566 fn has_used_like_attr ( tcx : TyCtxt < ' _ > , def_id : LocalDefId ) -> bool {
@@ -537,9 +575,13 @@ fn has_allow_dead_code_or_lang_attr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool
537575 }
538576 }
539577
540- has_allow_dead_code ( tcx, def_id)
541- || has_used_like_attr ( tcx, def_id)
542- || has_lang_attr ( tcx, def_id)
578+ if has_allow_expect_dead_code ( tcx, def_id) {
579+ Some ( ComesFromAllowExpect :: Yes )
580+ } else if has_used_like_attr ( tcx, def_id) || has_lang_attr ( tcx, def_id) {
581+ Some ( ComesFromAllowExpect :: No )
582+ } else {
583+ None
584+ }
543585}
544586
545587// These check_* functions seeds items that
@@ -557,21 +599,23 @@ fn has_allow_dead_code_or_lang_attr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool
557599// * Implementations of traits and trait methods
558600fn check_item < ' tcx > (
559601 tcx : TyCtxt < ' tcx > ,
560- worklist : & mut Vec < LocalDefId > ,
602+ worklist : & mut Vec < ( LocalDefId , ComesFromAllowExpect ) > ,
561603 struct_constructors : & mut LocalDefIdMap < LocalDefId > ,
562604 id : hir:: ItemId ,
563605) {
564606 let allow_dead_code = has_allow_dead_code_or_lang_attr ( tcx, id. owner_id . def_id ) ;
565- if allow_dead_code {
566- worklist. push ( id. owner_id . def_id ) ;
607+ if let Some ( comes_from_allow ) = allow_dead_code {
608+ worklist. push ( ( id. owner_id . def_id , comes_from_allow ) ) ;
567609 }
568610
569611 match tcx. def_kind ( id. owner_id ) {
570612 DefKind :: Enum => {
571613 let item = tcx. hir ( ) . item ( id) ;
572614 if let hir:: ItemKind :: Enum ( ref enum_def, _) = item. kind {
573- if allow_dead_code {
574- worklist. extend ( enum_def. variants . iter ( ) . map ( |variant| variant. def_id ) ) ;
615+ if let Some ( comes_from_allow) = allow_dead_code {
616+ worklist. extend (
617+ enum_def. variants . iter ( ) . map ( |variant| ( variant. def_id , comes_from_allow) ) ,
618+ ) ;
575619 }
576620
577621 for variant in enum_def. variants {
@@ -583,7 +627,7 @@ fn check_item<'tcx>(
583627 }
584628 DefKind :: Impl { of_trait } => {
585629 if of_trait {
586- worklist. push ( id. owner_id . def_id ) ;
630+ worklist. push ( ( id. owner_id . def_id , ComesFromAllowExpect :: No ) ) ;
587631 }
588632
589633 // get DefIds from another query
@@ -594,8 +638,10 @@ fn check_item<'tcx>(
594638
595639 // And we access the Map here to get HirId from LocalDefId
596640 for id in local_def_ids {
597- if of_trait || has_allow_dead_code_or_lang_attr ( tcx, id) {
598- worklist. push ( id) ;
641+ if of_trait {
642+ worklist. push ( ( id, ComesFromAllowExpect :: No ) ) ;
643+ } else if let Some ( comes_from_allow) = has_allow_dead_code_or_lang_attr ( tcx, id) {
644+ worklist. push ( ( id, comes_from_allow) ) ;
599645 }
600646 }
601647 }
@@ -609,43 +655,59 @@ fn check_item<'tcx>(
609655 }
610656 DefKind :: GlobalAsm => {
611657 // global_asm! is always live.
612- worklist. push ( id. owner_id . def_id ) ;
658+ worklist. push ( ( id. owner_id . def_id , ComesFromAllowExpect :: No ) ) ;
613659 }
614660 _ => { }
615661 }
616662}
617663
618- fn check_trait_item ( tcx : TyCtxt < ' _ > , worklist : & mut Vec < LocalDefId > , id : hir:: TraitItemId ) {
664+ fn check_trait_item (
665+ tcx : TyCtxt < ' _ > ,
666+ worklist : & mut Vec < ( LocalDefId , ComesFromAllowExpect ) > ,
667+ id : hir:: TraitItemId ,
668+ ) {
619669 use hir:: TraitItemKind :: { Const , Fn } ;
620670 if matches ! ( tcx. def_kind( id. owner_id) , DefKind :: AssocConst | DefKind :: AssocFn ) {
621671 let trait_item = tcx. hir ( ) . trait_item ( id) ;
622672 if matches ! ( trait_item. kind, Const ( _, Some ( _) ) | Fn ( _, hir:: TraitFn :: Provided ( _) ) )
623- && has_allow_dead_code_or_lang_attr ( tcx, trait_item. owner_id . def_id )
673+ && let Some ( comes_from_allow ) = has_allow_dead_code_or_lang_attr ( tcx, trait_item. owner_id . def_id )
624674 {
625- worklist. push ( trait_item. owner_id . def_id ) ;
675+ worklist. push ( ( trait_item. owner_id . def_id , comes_from_allow ) ) ;
626676 }
627677 }
628678}
629679
630- fn check_foreign_item ( tcx : TyCtxt < ' _ > , worklist : & mut Vec < LocalDefId > , id : hir:: ForeignItemId ) {
680+ fn check_foreign_item (
681+ tcx : TyCtxt < ' _ > ,
682+ worklist : & mut Vec < ( LocalDefId , ComesFromAllowExpect ) > ,
683+ id : hir:: ForeignItemId ,
684+ ) {
631685 if matches ! ( tcx. def_kind( id. owner_id) , DefKind :: Static ( _) | DefKind :: Fn )
632- && has_allow_dead_code_or_lang_attr ( tcx, id. owner_id . def_id )
686+ && let Some ( comes_from_allow ) = has_allow_dead_code_or_lang_attr ( tcx, id. owner_id . def_id )
633687 {
634- worklist. push ( id. owner_id . def_id ) ;
688+ worklist. push ( ( id. owner_id . def_id , comes_from_allow ) ) ;
635689 }
636690}
637691
638- fn create_and_seed_worklist ( tcx : TyCtxt < ' _ > ) -> ( Vec < LocalDefId > , LocalDefIdMap < LocalDefId > ) {
692+ fn create_and_seed_worklist (
693+ tcx : TyCtxt < ' _ > ,
694+ ) -> ( Vec < ( LocalDefId , ComesFromAllowExpect ) > , LocalDefIdMap < LocalDefId > ) {
639695 let effective_visibilities = & tcx. effective_visibilities ( ( ) ) ;
640696 // see `MarkSymbolVisitor::struct_constructors`
641697 let mut struct_constructors = Default :: default ( ) ;
642698 let mut worklist = effective_visibilities
643699 . iter ( )
644700 . filter_map ( |( & id, effective_vis) | {
645- effective_vis. is_public_at_level ( Level :: Reachable ) . then_some ( id)
701+ effective_vis
702+ . is_public_at_level ( Level :: Reachable )
703+ . then_some ( id)
704+ . map ( |id| ( id, ComesFromAllowExpect :: No ) )
646705 } )
647706 // Seed entry point
648- . chain ( tcx. entry_fn ( ( ) ) . and_then ( |( def_id, _) | def_id. as_local ( ) ) )
707+ . chain (
708+ tcx. entry_fn ( ( ) )
709+ . and_then ( |( def_id, _) | def_id. as_local ( ) . map ( |id| ( id, ComesFromAllowExpect :: No ) ) ) ,
710+ )
649711 . collect :: < Vec < _ > > ( ) ;
650712
651713 let crate_items = tcx. hir_crate_items ( ( ) ) ;
@@ -878,9 +940,7 @@ impl<'tcx> DeadVisitor<'tcx> {
878940 return true ;
879941 } ;
880942
881- self . live_symbols . contains ( & def_id)
882- || has_allow_dead_code_or_lang_attr ( self . tcx , def_id)
883- || name. as_str ( ) . starts_with ( '_' )
943+ self . live_symbols . contains ( & def_id) || name. as_str ( ) . starts_with ( '_' )
884944 }
885945}
886946
0 commit comments