about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_passes/Cargo.toml1
-rw-r--r--compiler/rustc_passes/src/dead.rs272
2 files changed, 200 insertions, 73 deletions
diff --git a/compiler/rustc_passes/Cargo.toml b/compiler/rustc_passes/Cargo.toml
index 787d37277c6..676812db59a 100644
--- a/compiler/rustc_passes/Cargo.toml
+++ b/compiler/rustc_passes/Cargo.toml
@@ -5,6 +5,7 @@ edition = "2021"
 
 [dependencies]
 tracing = "0.1"
+itertools = "0.10.1"
 rustc_middle = { path = "../rustc_middle" }
 rustc_attr = { path = "../rustc_attr" }
 rustc_data_structures = { path = "../rustc_data_structures" }
diff --git a/compiler/rustc_passes/src/dead.rs b/compiler/rustc_passes/src/dead.rs
index 519fb7ea264..01d93f6ff0c 100644
--- a/compiler/rustc_passes/src/dead.rs
+++ b/compiler/rustc_passes/src/dead.rs
@@ -2,8 +2,9 @@
 // closely. The idea is that all reachable symbols are live, codes called
 // from live codes are live, and everything else is dead.
 
+use itertools::Itertools;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
-use rustc_errors::pluralize;
+use rustc_errors::{pluralize, MultiSpan};
 use rustc_hir as hir;
 use rustc_hir::def::{CtorOf, DefKind, Res};
 use rustc_hir::def_id::{DefId, LocalDefId};
@@ -183,10 +184,10 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
             }
         }
 
-        if let hir::ExprKind::Assign(lhs, rhs, _) = assign.kind {
-            if check_for_self_assign_helper(self.typeck_results(), lhs, rhs)
+        if let hir::ExprKind::Assign(lhs, rhs, _) = assign.kind
+            && check_for_self_assign_helper(self.typeck_results(), lhs, rhs)
                 && !assign.span.from_expansion()
-            {
+        {
                 let is_field_assign = matches!(lhs.kind, hir::ExprKind::Field(..));
                 self.tcx.struct_span_lint_hir(
                     lint::builtin::DEAD_CODE,
@@ -201,7 +202,6 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
                         .emit();
                     },
                 )
-            }
         }
     }
 
@@ -251,19 +251,19 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
                 return false;
             }
 
-            if let Some(trait_of) = self.tcx.trait_id_of_impl(impl_of) {
-                if self.tcx.has_attr(trait_of, sym::rustc_trivial_field_reads) {
-                    let trait_ref = self.tcx.impl_trait_ref(impl_of).unwrap();
-                    if let ty::Adt(adt_def, _) = trait_ref.self_ty().kind() {
-                        if let Some(adt_def_id) = adt_def.did().as_local() {
-                            self.ignored_derived_traits
-                                .entry(adt_def_id)
-                                .or_default()
-                                .push((trait_of, impl_of));
-                        }
-                    }
-                    return true;
+            if let Some(trait_of) = self.tcx.trait_id_of_impl(impl_of)
+                && self.tcx.has_attr(trait_of, sym::rustc_trivial_field_reads)
+            {
+                let trait_ref = self.tcx.impl_trait_ref(impl_of).unwrap();
+                if let ty::Adt(adt_def, _) = trait_ref.self_ty().kind()
+                    && let Some(adt_def_id) = adt_def.did().as_local()
+                {
+                    self.ignored_derived_traits
+                        .entry(adt_def_id)
+                        .or_default()
+                        .push((trait_of, impl_of));
                 }
+                return true;
             }
         }
 
@@ -271,13 +271,10 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
     }
 
     fn visit_node(&mut self, node: Node<'tcx>) {
-        if let Some(item_def_id) = match node {
-            Node::ImplItem(hir::ImplItem { def_id, .. }) => Some(def_id.to_def_id()),
-            _ => None,
-        } {
-            if self.should_ignore_item(item_def_id) {
-                return;
-            }
+        if let Node::ImplItem(hir::ImplItem { def_id, .. }) = node
+            && self.should_ignore_item(def_id.to_def_id())
+        {
+            return;
         }
 
         let had_repr_c = self.repr_has_repr_c;
@@ -534,10 +531,10 @@ fn check_item<'tcx>(
         }
         DefKind::Struct => {
             let item = tcx.hir().item(id);
-            if let hir::ItemKind::Struct(ref variant_data, _) = item.kind {
-                if let Some(ctor_hir_id) = variant_data.ctor_hir_id() {
-                    struct_constructors.insert(tcx.hir().local_def_id(ctor_hir_id), item.def_id);
-                }
+            if let hir::ItemKind::Struct(ref variant_data, _) = item.kind
+                && let Some(ctor_hir_id) = variant_data.ctor_hir_id()
+            {
+                struct_constructors.insert(tcx.hir().local_def_id(ctor_hir_id), item.def_id);
             }
         }
         DefKind::GlobalAsm => {
@@ -626,6 +623,13 @@ fn live_symbols_and_ignored_derived_traits<'tcx>(
     (symbol_visitor.live_symbols, symbol_visitor.ignored_derived_traits)
 }
 
+struct DeadVariant {
+    hir_id: hir::HirId,
+    span: Span,
+    name: Symbol,
+    level: lint::Level,
+}
+
 struct DeadVisitor<'tcx> {
     tcx: TyCtxt<'tcx>,
     live_symbols: &'tcx FxHashSet<LocalDefId>,
@@ -677,16 +681,121 @@ impl<'tcx> DeadVisitor<'tcx> {
         let inherent_impls = self.tcx.inherent_impls(def_id);
         for &impl_did in inherent_impls.iter() {
             for item_did in self.tcx.associated_item_def_ids(impl_did) {
-                if let Some(def_id) = item_did.as_local() {
-                    if self.live_symbols.contains(&def_id) {
-                        return true;
-                    }
+                if let Some(def_id) = item_did.as_local()
+                    && self.live_symbols.contains(&def_id)
+                {
+                    return true;
                 }
             }
         }
         false
     }
 
+    fn warn_multiple_dead_codes(
+        &self,
+        dead_codes: &[(hir::HirId, Span, Symbol)],
+        participle: &str,
+        parent_hir_id: Option<hir::HirId>,
+    ) {
+        if let Some((id, _, name)) = dead_codes.first()
+            && !name.as_str().starts_with('_')
+        {
+            self.tcx.struct_span_lint_hir(
+                lint::builtin::DEAD_CODE,
+                *id,
+                MultiSpan::from_spans(
+                    dead_codes.iter().map(|(_, span, _)| *span).collect(),
+                ),
+                |lint| {
+                    let def_id = self.tcx.hir().local_def_id(*id);
+                    let descr = self.tcx.def_kind(def_id).descr(def_id.to_def_id());
+                    let span_len = dead_codes.len();
+                    let names = match &dead_codes.iter().map(|(_, _, n)| n.to_string()).collect::<Vec<_>>()[..]
+                    {
+                        _ if span_len > 6 => String::new(),
+                        [name] => format!("`{name}` "),
+                        [names @ .., last] => {
+                            format!("{} and `{last}` ", names.iter().map(|name| format!("`{name}`")).join(", "))
+                        }
+                        [] => unreachable!(),
+                    };
+                    let mut err = lint.build(&format!(
+                        "{these}{descr}{s} {names}{are} never {participle}",
+                        these = if span_len > 6 { "multiple " } else { "" },
+                        s = pluralize!(span_len),
+                        are = pluralize!("is", span_len),
+                    ));
+                    let hir = self.tcx.hir();
+                    if let Some(parent_hir_id) = parent_hir_id
+                        && let Some(parent_node) = hir.find(parent_hir_id)
+                        && let Node::Item(item) = parent_node
+                    {
+                        let def_id = self.tcx.hir().local_def_id(parent_hir_id);
+                        let parent_descr = self.tcx.def_kind(def_id).descr(def_id.to_def_id());
+                        err.span_label(
+                            item.ident.span,
+                            format!(
+                                "{descr}{s} in this {parent_descr}",
+                                s = pluralize!(span_len)
+                            ),
+                        );
+                    }
+                    if let Some(encl_scope) = hir.get_enclosing_scope(*id)
+                        && let Some(encl_def_id) = hir.opt_local_def_id(encl_scope)
+                        && let Some(ign_traits) = self.ignored_derived_traits.get(&encl_def_id)
+                    {
+                        let traits_str = ign_traits
+                            .iter()
+                            .map(|(trait_id, _)| format!("`{}`", self.tcx.item_name(*trait_id)))
+                            .collect::<Vec<_>>()
+                            .join(" and ");
+                        let plural_s = pluralize!(ign_traits.len());
+                        let article = if ign_traits.len() > 1 { "" } else { "a " };
+                        let is_are = if ign_traits.len() > 1 { "these are" } else { "this is" };
+                        let msg = format!(
+                            "`{}` has {}derived impl{} for the trait{} {}, but {} \
+                            intentionally ignored during dead code analysis",
+                            self.tcx.item_name(encl_def_id.to_def_id()),
+                            article,
+                            plural_s,
+                            plural_s,
+                            traits_str,
+                            is_are
+                        );
+                        err.note(&msg);
+                    }
+                        err.emit();
+                    },
+            );
+        }
+    }
+
+    fn warn_dead_fields_and_variants(
+        &self,
+        hir_id: hir::HirId,
+        participle: &str,
+        dead_codes: Vec<DeadVariant>,
+    ) {
+        let mut dead_codes = dead_codes
+            .iter()
+            .filter(|v| !v.name.as_str().starts_with('_'))
+            .map(|v| v)
+            .collect::<Vec<&DeadVariant>>();
+        if dead_codes.is_empty() {
+            return;
+        }
+        dead_codes.sort_by_key(|v| v.level);
+        for (_, group) in &dead_codes.into_iter().group_by(|v| v.level) {
+            self.warn_multiple_dead_codes(
+                &group
+                    .map(|v| (v.hir_id, v.span, v.name))
+                    .collect::<Vec<(hir::HirId, Span, Symbol)>>(),
+                participle,
+                Some(hir_id),
+            );
+        }
+    }
+
     fn warn_dead_code(
         &mut self,
         id: hir::HirId,
@@ -694,39 +803,7 @@ impl<'tcx> DeadVisitor<'tcx> {
         name: Symbol,
         participle: &str,
     ) {
-        if !name.as_str().starts_with('_') {
-            self.tcx.struct_span_lint_hir(lint::builtin::DEAD_CODE, id, span, |lint| {
-                let def_id = self.tcx.hir().local_def_id(id);
-                let descr = self.tcx.def_kind(def_id).descr(def_id.to_def_id());
-                let mut err = lint.build(&format!("{} is never {}: `{}`", descr, participle, name));
-                let hir = self.tcx.hir();
-                if let Some(encl_scope) = hir.get_enclosing_scope(id)
-                    && let Some(encl_def_id) = hir.opt_local_def_id(encl_scope)
-                    && let Some(ign_traits) = self.ignored_derived_traits.get(&encl_def_id)
-                {
-                    let traits_str = ign_traits
-                        .iter()
-                        .map(|(trait_id, _)| format!("`{}`", self.tcx.item_name(*trait_id)))
-                        .collect::<Vec<_>>()
-                        .join(" and ");
-                    let plural_s = pluralize!(ign_traits.len());
-                    let article = if ign_traits.len() > 1 { "" } else { "a " };
-                    let is_are = if ign_traits.len() > 1 { "these are" } else { "this is" };
-                    let msg = format!(
-                        "`{}` has {}derived impl{} for the trait{} {}, but {} \
-                        intentionally ignored during dead code analysis",
-                        self.tcx.item_name(encl_def_id.to_def_id()),
-                        article,
-                        plural_s,
-                        plural_s,
-                        traits_str,
-                        is_are
-                    );
-                    err.note(&msg);
-                }
-                err.emit();
-            });
-        }
+        self.warn_multiple_dead_codes(&[(id, span, name)], participle, None);
     }
 }
 
@@ -780,15 +857,40 @@ impl<'tcx> Visitor<'tcx> for DeadVisitor<'tcx> {
     // This visitor should only visit a single module at a time.
     fn visit_mod(&mut self, _: &'tcx hir::Mod<'tcx>, _: Span, _: hir::HirId) {}
 
+    fn visit_enum_def(
+        &mut self,
+        enum_definition: &'tcx hir::EnumDef<'tcx>,
+        generics: &'tcx hir::Generics<'tcx>,
+        item_id: hir::HirId,
+        _: Span,
+    ) {
+        intravisit::walk_enum_def(self, enum_definition, generics, item_id);
+        let dead_variants = enum_definition
+            .variants
+            .iter()
+            .filter_map(|variant| {
+                if self.should_warn_about_variant(&variant) {
+                    Some(DeadVariant {
+                        hir_id: variant.id,
+                        span: variant.span,
+                        name: variant.ident.name,
+                        level: self.tcx.lint_level_at_node(lint::builtin::DEAD_CODE, variant.id).0,
+                    })
+                } else {
+                    None
+                }
+            })
+            .collect();
+        self.warn_dead_fields_and_variants(item_id, "constructed", dead_variants)
+    }
+
     fn visit_variant(
         &mut self,
         variant: &'tcx hir::Variant<'tcx>,
         g: &'tcx hir::Generics<'tcx>,
         id: hir::HirId,
     ) {
-        if self.should_warn_about_variant(&variant) {
-            self.warn_dead_code(variant.id, variant.span, variant.ident.name, "constructed");
-        } else {
+        if !self.should_warn_about_variant(&variant) {
             intravisit::walk_variant(self, variant, g, id);
         }
     }
@@ -800,11 +902,35 @@ impl<'tcx> Visitor<'tcx> for DeadVisitor<'tcx> {
         intravisit::walk_foreign_item(self, fi);
     }
 
-    fn visit_field_def(&mut self, field: &'tcx hir::FieldDef<'tcx>) {
-        if self.should_warn_about_field(&field) {
-            self.warn_dead_code(field.hir_id, field.span, field.ident.name, "read");
-        }
-        intravisit::walk_field_def(self, field);
+    fn visit_variant_data(
+        &mut self,
+        def: &'tcx hir::VariantData<'tcx>,
+        _: Symbol,
+        _: &hir::Generics<'_>,
+        id: hir::HirId,
+        _: rustc_span::Span,
+    ) {
+        intravisit::walk_struct_def(self, def);
+        let dead_fields = def
+            .fields()
+            .iter()
+            .filter_map(|field| {
+                if self.should_warn_about_field(&field) {
+                    Some(DeadVariant {
+                        hir_id: field.hir_id,
+                        span: field.span,
+                        name: field.ident.name,
+                        level: self
+                            .tcx
+                            .lint_level_at_node(lint::builtin::DEAD_CODE, field.hir_id)
+                            .0,
+                    })
+                } else {
+                    None
+                }
+            })
+            .collect();
+        self.warn_dead_fields_and_variants(id, "read", dead_fields)
     }
 
     fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {