22// closely. The idea is that all reachable symbols are live, codes called
33// from live codes are live, and everything else is dead.
44
5+ use itertools:: Itertools ;
56use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
6- use rustc_errors:: pluralize;
7+ use rustc_errors:: { pluralize, MultiSpan } ;
78use rustc_hir as hir;
89use rustc_hir:: def:: { CtorOf , DefKind , Res } ;
910use rustc_hir:: def_id:: { DefId , LocalDefId } ;
@@ -164,9 +165,10 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
164165 if let ( Res :: Local ( id_l) , Res :: Local ( id_r) ) = (
165166 typeck_results. qpath_res ( qpath_l, lhs. hir_id ) ,
166167 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+ }
170172 }
171173 return false ;
172174 }
@@ -269,13 +271,10 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
269271 }
270272
271273 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 ;
279278 }
280279
281280 let had_repr_c = self . repr_has_repr_c ;
@@ -624,11 +623,17 @@ fn live_symbols_and_ignored_derived_traits<'tcx>(
624623 ( symbol_visitor. live_symbols , symbol_visitor. ignored_derived_traits )
625624}
626625
626+ struct DeadVariant {
627+ hir_id : hir:: HirId ,
628+ span : Span ,
629+ name : Symbol ,
630+ level : lint:: Level ,
631+ }
632+
627633struct DeadVisitor < ' tcx > {
628634 tcx : TyCtxt < ' tcx > ,
629635 live_symbols : & ' tcx FxHashSet < LocalDefId > ,
630636 ignored_derived_traits : & ' tcx FxHashMap < LocalDefId , Vec < ( DefId , DefId ) > > ,
631- ignored_struct_def : FxHashSet < LocalDefId > ,
632637}
633638
634639impl < ' tcx > DeadVisitor < ' tcx > {
@@ -686,57 +691,119 @@ impl<'tcx> DeadVisitor<'tcx> {
686691 false
687692 }
688693
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+
689799 fn warn_dead_code (
690800 & mut self ,
691801 id : hir:: HirId ,
692802 span : rustc_span:: Span ,
693803 name : Symbol ,
694804 participle : & str ,
695805 ) {
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 ) ;
740807 }
741808}
742809
@@ -790,15 +857,40 @@ impl<'tcx> Visitor<'tcx> for DeadVisitor<'tcx> {
790857 // This visitor should only visit a single module at a time.
791858 fn visit_mod ( & mut self , _: & ' tcx hir:: Mod < ' tcx > , _: Span , _: hir:: HirId ) { }
792859
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+
793887 fn visit_variant (
794888 & mut self ,
795889 variant : & ' tcx hir:: Variant < ' tcx > ,
796890 g : & ' tcx hir:: Generics < ' tcx > ,
797891 id : hir:: HirId ,
798892 ) {
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) {
802894 intravisit:: walk_variant ( self , variant, g, id) ;
803895 }
804896 }
@@ -810,11 +902,35 @@ impl<'tcx> Visitor<'tcx> for DeadVisitor<'tcx> {
810902 intravisit:: walk_foreign_item ( self , fi) ;
811903 }
812904
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)
818934 }
819935
820936 fn visit_impl_item ( & mut self , impl_item : & ' tcx hir:: ImplItem < ' tcx > ) {
@@ -867,12 +983,7 @@ impl<'tcx> Visitor<'tcx> for DeadVisitor<'tcx> {
867983
868984fn check_mod_deathness ( tcx : TyCtxt < ' _ > , module : LocalDefId ) {
869985 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 } ;
876987 let ( module, _, module_id) = tcx. hir ( ) . get_module ( module) ;
877988 // Do not use an ItemLikeVisitor since we may want to skip visiting some items
878989 // when a surrounding one is warned against or `_`.
0 commit comments