4
4
5
5
use hir:: def_id:: { LocalDefIdMap , LocalDefIdSet } ;
6
6
use itertools:: Itertools ;
7
+ use rustc_data_structures:: unord:: UnordSet ;
7
8
use rustc_errors:: MultiSpan ;
8
9
use rustc_hir as hir;
9
10
use rustc_hir:: def:: { CtorOf , DefKind , Res } ;
@@ -42,8 +43,16 @@ fn should_explore(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
42
43
)
43
44
}
44
45
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
+
45
54
struct MarkSymbolVisitor < ' tcx > {
46
- worklist : Vec < LocalDefId > ,
55
+ worklist : Vec < ( LocalDefId , ComesFromAllowExpect ) > ,
47
56
tcx : TyCtxt < ' tcx > ,
48
57
maybe_typeck_results : Option < & ' tcx ty:: TypeckResults < ' tcx > > ,
49
58
live_symbols : LocalDefIdSet ,
@@ -72,7 +81,7 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
72
81
fn check_def_id ( & mut self , def_id : DefId ) {
73
82
if let Some ( def_id) = def_id. as_local ( ) {
74
83
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 ) ) ;
76
85
}
77
86
self . live_symbols . insert ( def_id) ;
78
87
}
@@ -269,12 +278,14 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
269
278
}
270
279
271
280
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 ) {
275
284
continue ;
276
285
}
277
286
287
+ let ( id, comes_from_allow_expect) = work;
288
+
278
289
// Avoid accessing the HIR for the synthesized associated type generated for RPITITs.
279
290
if self . tcx . is_impl_trait_in_trait ( id. to_def_id ( ) ) {
280
291
self . live_symbols . insert ( id) ;
@@ -286,7 +297,30 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
286
297
let id = self . struct_constructors . get ( & id) . copied ( ) . unwrap_or ( id) ;
287
298
288
299
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
+ }
290
324
self . visit_node ( node) ;
291
325
}
292
326
}
@@ -513,16 +547,20 @@ impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> {
513
547
}
514
548
}
515
549
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 > {
517
554
fn has_lang_attr ( tcx : TyCtxt < ' _ > , def_id : LocalDefId ) -> bool {
518
555
tcx. has_attr ( def_id, sym:: lang)
519
556
// Stable attribute for #[lang = "panic_impl"]
520
557
|| tcx. has_attr ( def_id, sym:: panic_handler)
521
558
}
522
559
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 {
524
561
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 ( _) )
526
564
}
527
565
528
566
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
537
575
}
538
576
}
539
577
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
+ }
543
585
}
544
586
545
587
// These check_* functions seeds items that
@@ -557,21 +599,23 @@ fn has_allow_dead_code_or_lang_attr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool
557
599
// * Implementations of traits and trait methods
558
600
fn check_item < ' tcx > (
559
601
tcx : TyCtxt < ' tcx > ,
560
- worklist : & mut Vec < LocalDefId > ,
602
+ worklist : & mut Vec < ( LocalDefId , ComesFromAllowExpect ) > ,
561
603
struct_constructors : & mut LocalDefIdMap < LocalDefId > ,
562
604
id : hir:: ItemId ,
563
605
) {
564
606
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 ) ) ;
567
609
}
568
610
569
611
match tcx. def_kind ( id. owner_id ) {
570
612
DefKind :: Enum => {
571
613
let item = tcx. hir ( ) . item ( id) ;
572
614
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
+ ) ;
575
619
}
576
620
577
621
for variant in enum_def. variants {
@@ -583,7 +627,7 @@ fn check_item<'tcx>(
583
627
}
584
628
DefKind :: Impl { of_trait } => {
585
629
if of_trait {
586
- worklist. push ( id. owner_id . def_id ) ;
630
+ worklist. push ( ( id. owner_id . def_id , ComesFromAllowExpect :: No ) ) ;
587
631
}
588
632
589
633
// get DefIds from another query
@@ -594,8 +638,10 @@ fn check_item<'tcx>(
594
638
595
639
// And we access the Map here to get HirId from LocalDefId
596
640
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) ) ;
599
645
}
600
646
}
601
647
}
@@ -609,43 +655,59 @@ fn check_item<'tcx>(
609
655
}
610
656
DefKind :: GlobalAsm => {
611
657
// global_asm! is always live.
612
- worklist. push ( id. owner_id . def_id ) ;
658
+ worklist. push ( ( id. owner_id . def_id , ComesFromAllowExpect :: No ) ) ;
613
659
}
614
660
_ => { }
615
661
}
616
662
}
617
663
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
+ ) {
619
669
use hir:: TraitItemKind :: { Const , Fn } ;
620
670
if matches ! ( tcx. def_kind( id. owner_id) , DefKind :: AssocConst | DefKind :: AssocFn ) {
621
671
let trait_item = tcx. hir ( ) . trait_item ( id) ;
622
672
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 )
624
674
{
625
- worklist. push ( trait_item. owner_id . def_id ) ;
675
+ worklist. push ( ( trait_item. owner_id . def_id , comes_from_allow ) ) ;
626
676
}
627
677
}
628
678
}
629
679
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
+ ) {
631
685
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 )
633
687
{
634
- worklist. push ( id. owner_id . def_id ) ;
688
+ worklist. push ( ( id. owner_id . def_id , comes_from_allow ) ) ;
635
689
}
636
690
}
637
691
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 > ) {
639
695
let effective_visibilities = & tcx. effective_visibilities ( ( ) ) ;
640
696
// see `MarkSymbolVisitor::struct_constructors`
641
697
let mut struct_constructors = Default :: default ( ) ;
642
698
let mut worklist = effective_visibilities
643
699
. iter ( )
644
700
. 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 ) )
646
705
} )
647
706
// 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
+ )
649
711
. collect :: < Vec < _ > > ( ) ;
650
712
651
713
let crate_items = tcx. hir_crate_items ( ( ) ) ;
@@ -878,9 +940,7 @@ impl<'tcx> DeadVisitor<'tcx> {
878
940
return true ;
879
941
} ;
880
942
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 ( '_' )
884
944
}
885
945
}
886
946
0 commit comments