2
2
// closely. The idea is that all reachable symbols are live, codes called
3
3
// from live codes are live, and everything else is dead.
4
4
5
+ use itertools:: Itertools ;
5
6
use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
6
- use rustc_errors:: pluralize;
7
+ use rustc_errors:: { pluralize, MultiSpan } ;
7
8
use rustc_hir as hir;
8
9
use rustc_hir:: def:: { CtorOf , DefKind , Res } ;
9
10
use rustc_hir:: def_id:: { DefId , LocalDefId } ;
@@ -164,9 +165,10 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
164
165
if let ( Res :: Local ( id_l) , Res :: Local ( id_r) ) = (
165
166
typeck_results. qpath_res ( qpath_l, lhs. hir_id ) ,
166
167
typeck_results. qpath_res ( qpath_r, rhs. hir_id ) ,
167
- ) && id_l == id_r
168
- {
169
- return true ;
168
+ ) {
169
+ if id_l == id_r {
170
+ return true ;
171
+ }
170
172
}
171
173
return false ;
172
174
}
@@ -269,13 +271,10 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
269
271
}
270
272
271
273
fn visit_node ( & mut self , node : Node < ' tcx > ) {
272
- match node {
273
- Node :: ImplItem ( hir:: ImplItem { def_id, .. } )
274
- if self . should_ignore_item ( def_id. to_def_id ( ) ) =>
275
- {
276
- return ;
277
- }
278
- _ => ( ) ,
274
+ if let Node :: ImplItem ( hir:: ImplItem { def_id, .. } ) = node
275
+ && self . should_ignore_item ( def_id. to_def_id ( ) )
276
+ {
277
+ return ;
279
278
}
280
279
281
280
let had_repr_c = self . repr_has_repr_c ;
@@ -624,11 +623,17 @@ fn live_symbols_and_ignored_derived_traits<'tcx>(
624
623
( symbol_visitor. live_symbols , symbol_visitor. ignored_derived_traits )
625
624
}
626
625
626
+ struct DeadVariant {
627
+ hir_id : hir:: HirId ,
628
+ span : Span ,
629
+ name : Symbol ,
630
+ level : lint:: Level ,
631
+ }
632
+
627
633
struct DeadVisitor < ' tcx > {
628
634
tcx : TyCtxt < ' tcx > ,
629
635
live_symbols : & ' tcx FxHashSet < LocalDefId > ,
630
636
ignored_derived_traits : & ' tcx FxHashMap < LocalDefId , Vec < ( DefId , DefId ) > > ,
631
- ignored_struct_def : FxHashSet < LocalDefId > ,
632
637
}
633
638
634
639
impl < ' tcx > DeadVisitor < ' tcx > {
@@ -686,57 +691,119 @@ impl<'tcx> DeadVisitor<'tcx> {
686
691
false
687
692
}
688
693
694
+ fn warn_multiple_dead_codes (
695
+ & self ,
696
+ dead_codes : & [ ( hir:: HirId , Span , Symbol ) ] ,
697
+ participle : & str ,
698
+ parent_hir_id : Option < hir:: HirId > ,
699
+ ) {
700
+ if let Some ( ( id, _, name) ) = dead_codes. first ( )
701
+ && !name. as_str ( ) . starts_with ( '_' )
702
+ {
703
+ self . tcx . struct_span_lint_hir (
704
+ lint:: builtin:: DEAD_CODE ,
705
+ * id,
706
+ MultiSpan :: from_spans (
707
+ dead_codes. iter ( ) . map ( |( _, span, _) | * span) . collect ( ) ,
708
+ ) ,
709
+ |lint| {
710
+ let def_id = self . tcx . hir ( ) . local_def_id ( * id) ;
711
+ let descr = self . tcx . def_kind ( def_id) . descr ( def_id. to_def_id ( ) ) ;
712
+ let span_len = dead_codes. len ( ) ;
713
+ let names = match & dead_codes. iter ( ) . map ( |( _, _, n) | n. to_string ( ) ) . collect :: < Vec < _ > > ( ) [ ..]
714
+ {
715
+ _ if span_len > 6 => String :: new ( ) ,
716
+ [ name] => format ! ( "`{name}` " ) ,
717
+ [ names @ .., last] => {
718
+ format ! ( "{} and `{last}` " , names. iter( ) . map( |name| format!( "`{name}`" ) ) . join( ", " ) )
719
+ }
720
+ [ ] => unreachable ! ( ) ,
721
+ } ;
722
+ let mut err = lint. build ( & format ! (
723
+ "{these}{descr}{s} {names}{are} never {participle}" ,
724
+ these = if span_len > 6 { "multiple " } else { "" } ,
725
+ s = pluralize!( span_len) ,
726
+ are = pluralize!( "is" , span_len) ,
727
+ ) ) ;
728
+ let hir = self . tcx . hir ( ) ;
729
+ if let Some ( parent_hir_id) = parent_hir_id
730
+ && let Some ( parent_node) = hir. find ( parent_hir_id)
731
+ && let Node :: Item ( item) = parent_node
732
+ {
733
+ let def_id = self . tcx . hir ( ) . local_def_id ( parent_hir_id) ;
734
+ let parent_descr = self . tcx . def_kind ( def_id) . descr ( def_id. to_def_id ( ) ) ;
735
+ err. span_label (
736
+ item. ident . span ,
737
+ format ! (
738
+ "{descr}{s} in this {parent_descr}" ,
739
+ s = pluralize!( span_len)
740
+ ) ,
741
+ ) ;
742
+ }
743
+ if let Some ( encl_scope) = hir. get_enclosing_scope ( * id)
744
+ && let Some ( encl_def_id) = hir. opt_local_def_id ( encl_scope)
745
+ && let Some ( ign_traits) = self . ignored_derived_traits . get ( & encl_def_id)
746
+ {
747
+ let traits_str = ign_traits
748
+ . iter ( )
749
+ . map ( |( trait_id, _) | format ! ( "`{}`" , self . tcx. item_name( * trait_id) ) )
750
+ . collect :: < Vec < _ > > ( )
751
+ . join ( " and " ) ;
752
+ let plural_s = pluralize ! ( ign_traits. len( ) ) ;
753
+ let article = if ign_traits. len ( ) > 1 { "" } else { "a " } ;
754
+ let is_are = if ign_traits. len ( ) > 1 { "these are" } else { "this is" } ;
755
+ let msg = format ! (
756
+ "`{}` has {}derived impl{} for the trait{} {}, but {} \
757
+ intentionally ignored during dead code analysis",
758
+ self . tcx. item_name( encl_def_id. to_def_id( ) ) ,
759
+ article,
760
+ plural_s,
761
+ plural_s,
762
+ traits_str,
763
+ is_are
764
+ ) ;
765
+ err. note ( & msg) ;
766
+ }
767
+ err. emit ( ) ;
768
+ } ,
769
+ ) ;
770
+ }
771
+ }
772
+
773
+ fn warn_dead_fields_and_variants (
774
+ & self ,
775
+ hir_id : hir:: HirId ,
776
+ participle : & str ,
777
+ dead_codes : Vec < DeadVariant > ,
778
+ ) {
779
+ let mut dead_codes = dead_codes
780
+ . iter ( )
781
+ . filter ( |v| !v. name . as_str ( ) . starts_with ( '_' ) )
782
+ . map ( |v| v)
783
+ . collect :: < Vec < & DeadVariant > > ( ) ;
784
+ if dead_codes. is_empty ( ) {
785
+ return ;
786
+ }
787
+ dead_codes. sort_by_key ( |v| v. level ) ;
788
+ for ( _, group) in & dead_codes. into_iter ( ) . group_by ( |v| v. level ) {
789
+ self . warn_multiple_dead_codes (
790
+ & group
791
+ . map ( |v| ( v. hir_id , v. span , v. name ) )
792
+ . collect :: < Vec < ( hir:: HirId , Span , Symbol ) > > ( ) ,
793
+ participle,
794
+ Some ( hir_id) ,
795
+ ) ;
796
+ }
797
+ }
798
+
689
799
fn warn_dead_code (
690
800
& mut self ,
691
801
id : hir:: HirId ,
692
802
span : rustc_span:: Span ,
693
803
name : Symbol ,
694
804
participle : & str ,
695
805
) {
696
- if !name. as_str ( ) . starts_with ( '_' ) {
697
- self . tcx . struct_span_lint_hir ( lint:: builtin:: DEAD_CODE , id, span, |lint| {
698
- let def_id = self . tcx . hir ( ) . local_def_id ( id) ;
699
- let descr = self . tcx . def_kind ( def_id) . descr ( def_id. to_def_id ( ) ) ;
700
- let mut err = lint. build ( & format ! ( "{descr} is never {participle}: `{name}`" ) ) ;
701
- let hir = self . tcx . hir ( ) ;
702
- let is_field_in_same_struct =
703
- if let Some ( parent_hir_id) = self . tcx . hir ( ) . find_parent_node ( id)
704
- && let Some ( parent_node) = self . tcx . hir ( ) . find ( parent_hir_id)
705
- && let Node :: Item ( hir:: Item { kind : hir:: ItemKind :: Struct ( ..) , ..} ) = parent_node
706
- && let Some ( did) = self . tcx . hir ( ) . opt_local_def_id ( parent_hir_id)
707
- {
708
- !self . ignored_struct_def . insert ( did)
709
- } else {
710
- false
711
- } ;
712
- if !is_field_in_same_struct
713
- && let Some ( encl_scope) = hir. get_enclosing_scope ( id)
714
- && let Some ( encl_def_id) = hir. opt_local_def_id ( encl_scope)
715
- && let Some ( ign_traits) = self . ignored_derived_traits . get ( & encl_def_id)
716
- {
717
- let traits_str = ign_traits
718
- . iter ( )
719
- . map ( |( trait_id, _) | format ! ( "`{}`" , self . tcx. item_name( * trait_id) ) )
720
- . collect :: < Vec < _ > > ( )
721
- . join ( " and " ) ;
722
- let plural_s = pluralize ! ( ign_traits. len( ) ) ;
723
- let article = if ign_traits. len ( ) > 1 { "" } else { "a " } ;
724
- let is_are = if ign_traits. len ( ) > 1 { "these are" } else { "this is" } ;
725
- let msg = format ! (
726
- "`{}` has {}derived impl{} for the trait{} {}, but {} \
727
- intentionally ignored during dead code analysis",
728
- self . tcx. item_name( encl_def_id. to_def_id( ) ) ,
729
- article,
730
- plural_s,
731
- plural_s,
732
- traits_str,
733
- is_are
734
- ) ;
735
- err. note ( & msg) ;
736
- }
737
- err. emit ( ) ;
738
- } ) ;
739
- }
806
+ self . warn_multiple_dead_codes ( & [ ( id, span, name) ] , participle, None ) ;
740
807
}
741
808
}
742
809
@@ -790,15 +857,40 @@ impl<'tcx> Visitor<'tcx> for DeadVisitor<'tcx> {
790
857
// This visitor should only visit a single module at a time.
791
858
fn visit_mod ( & mut self , _: & ' tcx hir:: Mod < ' tcx > , _: Span , _: hir:: HirId ) { }
792
859
860
+ fn visit_enum_def (
861
+ & mut self ,
862
+ enum_definition : & ' tcx hir:: EnumDef < ' tcx > ,
863
+ generics : & ' tcx hir:: Generics < ' tcx > ,
864
+ item_id : hir:: HirId ,
865
+ _: Span ,
866
+ ) {
867
+ intravisit:: walk_enum_def ( self , enum_definition, generics, item_id) ;
868
+ let dead_variants = enum_definition
869
+ . variants
870
+ . iter ( )
871
+ . filter_map ( |variant| {
872
+ if self . should_warn_about_variant ( & variant) {
873
+ Some ( DeadVariant {
874
+ hir_id : variant. id ,
875
+ span : variant. span ,
876
+ name : variant. ident . name ,
877
+ level : self . tcx . lint_level_at_node ( lint:: builtin:: DEAD_CODE , variant. id ) . 0 ,
878
+ } )
879
+ } else {
880
+ None
881
+ }
882
+ } )
883
+ . collect ( ) ;
884
+ self . warn_dead_fields_and_variants ( item_id, "constructed" , dead_variants)
885
+ }
886
+
793
887
fn visit_variant (
794
888
& mut self ,
795
889
variant : & ' tcx hir:: Variant < ' tcx > ,
796
890
g : & ' tcx hir:: Generics < ' tcx > ,
797
891
id : hir:: HirId ,
798
892
) {
799
- if self . should_warn_about_variant ( & variant) {
800
- self . warn_dead_code ( variant. id , variant. span , variant. ident . name , "constructed" ) ;
801
- } else {
893
+ if !self . should_warn_about_variant ( & variant) {
802
894
intravisit:: walk_variant ( self , variant, g, id) ;
803
895
}
804
896
}
@@ -810,11 +902,35 @@ impl<'tcx> Visitor<'tcx> for DeadVisitor<'tcx> {
810
902
intravisit:: walk_foreign_item ( self , fi) ;
811
903
}
812
904
813
- fn visit_field_def ( & mut self , field : & ' tcx hir:: FieldDef < ' tcx > ) {
814
- if self . should_warn_about_field ( & field) {
815
- self . warn_dead_code ( field. hir_id , field. span , field. ident . name , "read" ) ;
816
- }
817
- intravisit:: walk_field_def ( self , field) ;
905
+ fn visit_variant_data (
906
+ & mut self ,
907
+ def : & ' tcx hir:: VariantData < ' tcx > ,
908
+ _: Symbol ,
909
+ _: & hir:: Generics < ' _ > ,
910
+ id : hir:: HirId ,
911
+ _: rustc_span:: Span ,
912
+ ) {
913
+ intravisit:: walk_struct_def ( self , def) ;
914
+ let dead_fields = def
915
+ . fields ( )
916
+ . iter ( )
917
+ . filter_map ( |field| {
918
+ if self . should_warn_about_field ( & field) {
919
+ Some ( DeadVariant {
920
+ hir_id : field. hir_id ,
921
+ span : field. span ,
922
+ name : field. ident . name ,
923
+ level : self
924
+ . tcx
925
+ . lint_level_at_node ( lint:: builtin:: DEAD_CODE , field. hir_id )
926
+ . 0 ,
927
+ } )
928
+ } else {
929
+ None
930
+ }
931
+ } )
932
+ . collect ( ) ;
933
+ self . warn_dead_fields_and_variants ( id, "read" , dead_fields)
818
934
}
819
935
820
936
fn visit_impl_item ( & mut self , impl_item : & ' tcx hir:: ImplItem < ' tcx > ) {
@@ -867,12 +983,7 @@ impl<'tcx> Visitor<'tcx> for DeadVisitor<'tcx> {
867
983
868
984
fn check_mod_deathness ( tcx : TyCtxt < ' _ > , module : LocalDefId ) {
869
985
let ( live_symbols, ignored_derived_traits) = tcx. live_symbols_and_ignored_derived_traits ( ( ) ) ;
870
- let mut visitor = DeadVisitor {
871
- tcx,
872
- live_symbols,
873
- ignored_derived_traits,
874
- ignored_struct_def : FxHashSet :: default ( ) ,
875
- } ;
986
+ let mut visitor = DeadVisitor { tcx, live_symbols, ignored_derived_traits } ;
876
987
let ( module, _, module_id) = tcx. hir ( ) . get_module ( module) ;
877
988
// Do not use an ItemLikeVisitor since we may want to skip visiting some items
878
989
// when a surrounding one is warned against or `_`.
0 commit comments