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 } ;
@@ -183,10 +184,10 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
183184 }
184185 }
185186
186- if let hir:: ExprKind :: Assign ( lhs, rhs, _) = assign. kind {
187- if check_for_self_assign_helper ( self . typeck_results ( ) , lhs, rhs)
187+ if let hir:: ExprKind :: Assign ( lhs, rhs, _) = assign. kind
188+ && check_for_self_assign_helper ( self . typeck_results ( ) , lhs, rhs)
188189 && !assign. span . from_expansion ( )
189- {
190+ {
190191 let is_field_assign = matches ! ( lhs. kind, hir:: ExprKind :: Field ( ..) ) ;
191192 self . tcx . struct_span_lint_hir (
192193 lint:: builtin:: DEAD_CODE ,
@@ -201,7 +202,6 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
201202 . emit ( ) ;
202203 } ,
203204 )
204- }
205205 }
206206 }
207207
@@ -251,33 +251,30 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
251251 return false ;
252252 }
253253
254- if let Some ( trait_of) = self . tcx . trait_id_of_impl ( impl_of) {
255- if self . tcx . has_attr ( trait_of, sym:: rustc_trivial_field_reads) {
256- let trait_ref = self . tcx . impl_trait_ref ( impl_of) . unwrap ( ) ;
257- if let ty:: Adt ( adt_def, _) = trait_ref. self_ty ( ) . kind ( ) {
258- if let Some ( adt_def_id) = adt_def. did ( ) . as_local ( ) {
259- self . ignored_derived_traits
260- . entry ( adt_def_id)
261- . or_default ( )
262- . push ( ( trait_of, impl_of) ) ;
263- }
264- }
265- return true ;
254+ if let Some ( trait_of) = self . tcx . trait_id_of_impl ( impl_of)
255+ && self . tcx . has_attr ( trait_of, sym:: rustc_trivial_field_reads)
256+ {
257+ let trait_ref = self . tcx . impl_trait_ref ( impl_of) . unwrap ( ) ;
258+ if let ty:: Adt ( adt_def, _) = trait_ref. self_ty ( ) . kind ( )
259+ && let Some ( adt_def_id) = adt_def. did ( ) . as_local ( )
260+ {
261+ self . ignored_derived_traits
262+ . entry ( adt_def_id)
263+ . or_default ( )
264+ . push ( ( trait_of, impl_of) ) ;
266265 }
266+ return true ;
267267 }
268268 }
269269
270270 return false ;
271271 }
272272
273273 fn visit_node ( & mut self , node : Node < ' tcx > ) {
274- if let Some ( item_def_id) = match node {
275- Node :: ImplItem ( hir:: ImplItem { def_id, .. } ) => Some ( def_id. to_def_id ( ) ) ,
276- _ => None ,
277- } {
278- if self . should_ignore_item ( item_def_id) {
279- return ;
280- }
274+ if let Node :: ImplItem ( hir:: ImplItem { def_id, .. } ) = node
275+ && self . should_ignore_item ( def_id. to_def_id ( ) )
276+ {
277+ return ;
281278 }
282279
283280 let had_repr_c = self . repr_has_repr_c ;
@@ -534,10 +531,10 @@ fn check_item<'tcx>(
534531 }
535532 DefKind :: Struct => {
536533 let item = tcx. hir ( ) . item ( id) ;
537- if let hir:: ItemKind :: Struct ( ref variant_data, _) = item. kind {
538- if let Some ( ctor_hir_id) = variant_data. ctor_hir_id ( ) {
539- struct_constructors . insert ( tcx . hir ( ) . local_def_id ( ctor_hir_id ) , item . def_id ) ;
540- }
534+ if let hir:: ItemKind :: Struct ( ref variant_data, _) = item. kind
535+ && let Some ( ctor_hir_id) = variant_data. ctor_hir_id ( )
536+ {
537+ struct_constructors . insert ( tcx . hir ( ) . local_def_id ( ctor_hir_id ) , item . def_id ) ;
541538 }
542539 }
543540 DefKind :: GlobalAsm => {
@@ -626,6 +623,13 @@ fn live_symbols_and_ignored_derived_traits<'tcx>(
626623 ( symbol_visitor. live_symbols , symbol_visitor. ignored_derived_traits )
627624}
628625
626+ struct DeadVariant {
627+ hir_id : hir:: HirId ,
628+ span : Span ,
629+ name : Symbol ,
630+ level : lint:: Level ,
631+ }
632+
629633struct DeadVisitor < ' tcx > {
630634 tcx : TyCtxt < ' tcx > ,
631635 live_symbols : & ' tcx FxHashSet < LocalDefId > ,
@@ -677,56 +681,129 @@ impl<'tcx> DeadVisitor<'tcx> {
677681 let inherent_impls = self . tcx . inherent_impls ( def_id) ;
678682 for & impl_did in inherent_impls. iter ( ) {
679683 for item_did in self . tcx . associated_item_def_ids ( impl_did) {
680- if let Some ( def_id) = item_did. as_local ( ) {
681- if self . live_symbols . contains ( & def_id) {
682- return true ;
683- }
684+ if let Some ( def_id) = item_did. as_local ( )
685+ && self . live_symbols . contains ( & def_id)
686+ {
687+ return true ;
684688 }
685689 }
686690 }
687691 false
688692 }
689693
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+
690799 fn warn_dead_code (
691800 & mut self ,
692801 id : hir:: HirId ,
693802 span : rustc_span:: Span ,
694803 name : Symbol ,
695804 participle : & str ,
696805 ) {
697- if !name. as_str ( ) . starts_with ( '_' ) {
698- self . tcx . struct_span_lint_hir ( lint:: builtin:: DEAD_CODE , id, span, |lint| {
699- let def_id = self . tcx . hir ( ) . local_def_id ( id) ;
700- let descr = self . tcx . def_kind ( def_id) . descr ( def_id. to_def_id ( ) ) ;
701- let mut err = lint. build ( & format ! ( "{} is never {}: `{}`" , descr, participle, name) ) ;
702- let hir = self . tcx . hir ( ) ;
703- if let Some ( encl_scope) = hir. get_enclosing_scope ( id)
704- && let Some ( encl_def_id) = hir. opt_local_def_id ( encl_scope)
705- && let Some ( ign_traits) = self . ignored_derived_traits . get ( & encl_def_id)
706- {
707- let traits_str = ign_traits
708- . iter ( )
709- . map ( |( trait_id, _) | format ! ( "`{}`" , self . tcx. item_name( * trait_id) ) )
710- . collect :: < Vec < _ > > ( )
711- . join ( " and " ) ;
712- let plural_s = pluralize ! ( ign_traits. len( ) ) ;
713- let article = if ign_traits. len ( ) > 1 { "" } else { "a " } ;
714- let is_are = if ign_traits. len ( ) > 1 { "these are" } else { "this is" } ;
715- let msg = format ! (
716- "`{}` has {}derived impl{} for the trait{} {}, but {} \
717- intentionally ignored during dead code analysis",
718- self . tcx. item_name( encl_def_id. to_def_id( ) ) ,
719- article,
720- plural_s,
721- plural_s,
722- traits_str,
723- is_are
724- ) ;
725- err. note ( & msg) ;
726- }
727- err. emit ( ) ;
728- } ) ;
729- }
806+ self . warn_multiple_dead_codes ( & [ ( id, span, name) ] , participle, None ) ;
730807 }
731808}
732809
@@ -780,15 +857,40 @@ impl<'tcx> Visitor<'tcx> for DeadVisitor<'tcx> {
780857 // This visitor should only visit a single module at a time.
781858 fn visit_mod ( & mut self , _: & ' tcx hir:: Mod < ' tcx > , _: Span , _: hir:: HirId ) { }
782859
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+
783887 fn visit_variant (
784888 & mut self ,
785889 variant : & ' tcx hir:: Variant < ' tcx > ,
786890 g : & ' tcx hir:: Generics < ' tcx > ,
787891 id : hir:: HirId ,
788892 ) {
789- if self . should_warn_about_variant ( & variant) {
790- self . warn_dead_code ( variant. id , variant. span , variant. ident . name , "constructed" ) ;
791- } else {
893+ if !self . should_warn_about_variant ( & variant) {
792894 intravisit:: walk_variant ( self , variant, g, id) ;
793895 }
794896 }
@@ -800,11 +902,35 @@ impl<'tcx> Visitor<'tcx> for DeadVisitor<'tcx> {
800902 intravisit:: walk_foreign_item ( self , fi) ;
801903 }
802904
803- fn visit_field_def ( & mut self , field : & ' tcx hir:: FieldDef < ' tcx > ) {
804- if self . should_warn_about_field ( & field) {
805- self . warn_dead_code ( field. hir_id , field. span , field. ident . name , "read" ) ;
806- }
807- 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)
808934 }
809935
810936 fn visit_impl_item ( & mut self , impl_item : & ' tcx hir:: ImplItem < ' tcx > ) {
0 commit comments