Auto merge of #144863 - cjgillot:live-or-dead, r=Urgau

Simplify dead code lint

This PR scratches a few itches I had when looking at that code.

The perf improvement comes from keeping the `scanned` set through several marking phases. This pretty much divides by 2 the number of HIR traversals.
This commit is contained in:
bors
2025-08-05 18:10:21 +00:00
3 changed files with 199 additions and 274 deletions

View File

@@ -1178,11 +1178,10 @@ rustc_queries! {
/// Return the live symbols in the crate for dead code check. /// Return the live symbols in the crate for dead code check.
/// ///
/// The second return value maps from ADTs to ignored derived traits (e.g. Debug and Clone) and /// The second return value maps from ADTs to ignored derived traits (e.g. Debug and Clone).
/// their respective impl (i.e., part of the derive macro)
query live_symbols_and_ignored_derived_traits(_: ()) -> &'tcx ( query live_symbols_and_ignored_derived_traits(_: ()) -> &'tcx (
LocalDefIdSet, LocalDefIdSet,
LocalDefIdMap<FxIndexSet<(DefId, DefId)>> LocalDefIdMap<FxIndexSet<DefId>>,
) { ) {
arena_cache arena_cache
desc { "finding live symbols in crate" } desc { "finding live symbols in crate" }

View File

@@ -5,7 +5,6 @@
use std::mem; use std::mem;
use hir::ItemKind;
use hir::def_id::{LocalDefIdMap, LocalDefIdSet}; use hir::def_id::{LocalDefIdMap, LocalDefIdSet};
use rustc_abi::FieldIdx; use rustc_abi::FieldIdx;
use rustc_data_structures::fx::FxIndexSet; use rustc_data_structures::fx::FxIndexSet;
@@ -14,7 +13,7 @@ use rustc_errors::MultiSpan;
use rustc_hir::def::{CtorOf, DefKind, Res}; use rustc_hir::def::{CtorOf, DefKind, Res};
use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId}; use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId};
use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{self as hir, ImplItem, ImplItemKind, Node, PatKind, QPath, TyKind}; use rustc_hir::{self as hir, Node, PatKind, QPath};
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
use rustc_middle::middle::privacy::Level; use rustc_middle::middle::privacy::Level;
use rustc_middle::query::Providers; use rustc_middle::query::Providers;
@@ -28,37 +27,43 @@ use crate::errors::{
ChangeFields, IgnoredDerivedImpls, MultipleDeadCodes, ParentInfo, UselessAssignment, ChangeFields, IgnoredDerivedImpls, MultipleDeadCodes, ParentInfo, UselessAssignment,
}; };
// Any local node that may call something in its body block should be /// Any local definition that may call something in its body block should be explored. For example,
// explored. For example, if it's a live Node::Item that is a /// if it's a live function, then we should explore its block to check for codes that may need to
// function, then we should explore its block to check for codes that /// be marked as live.
// may need to be marked as live.
fn should_explore(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { fn should_explore(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
matches!( match tcx.def_kind(def_id) {
tcx.hir_node_by_def_id(def_id), DefKind::Mod
Node::Item(..) | DefKind::Struct
| Node::ImplItem(..) | DefKind::Union
| Node::ForeignItem(..) | DefKind::Enum
| Node::TraitItem(..) | DefKind::Variant
| Node::Variant(..) | DefKind::Trait
| Node::AnonConst(..) | DefKind::TyAlias
| Node::OpaqueTy(..) | DefKind::ForeignTy
) | DefKind::TraitAlias
} | DefKind::AssocTy
| DefKind::Fn
| DefKind::Const
| DefKind::Static { .. }
| DefKind::AssocFn
| DefKind::AssocConst
| DefKind::Macro(_)
| DefKind::GlobalAsm
| DefKind::Impl { .. }
| DefKind::OpaqueTy
| DefKind::AnonConst
| DefKind::InlineConst
| DefKind::ExternCrate
| DefKind::Use
| DefKind::Ctor(..)
| DefKind::ForeignMod => true,
/// Returns the local def id of the ADT if the given ty refers to a local one. DefKind::TyParam
fn local_adt_def_of_ty<'tcx>(ty: &hir::Ty<'tcx>) -> Option<LocalDefId> { | DefKind::ConstParam
match ty.kind { | DefKind::Field
TyKind::Path(QPath::Resolved(_, path)) => { | DefKind::LifetimeParam
if let Res::Def(def_kind, def_id) = path.res | DefKind::Closure
&& let Some(local_def_id) = def_id.as_local() | DefKind::SyntheticCoroutineBody => false,
&& matches!(def_kind, DefKind::Struct | DefKind::Enum | DefKind::Union)
{
Some(local_def_id)
} else {
None
}
}
_ => None,
} }
} }
@@ -74,17 +79,16 @@ struct MarkSymbolVisitor<'tcx> {
worklist: Vec<(LocalDefId, ComesFromAllowExpect)>, worklist: Vec<(LocalDefId, ComesFromAllowExpect)>,
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
maybe_typeck_results: Option<&'tcx ty::TypeckResults<'tcx>>, maybe_typeck_results: Option<&'tcx ty::TypeckResults<'tcx>>,
scanned: UnordSet<(LocalDefId, ComesFromAllowExpect)>,
live_symbols: LocalDefIdSet, live_symbols: LocalDefIdSet,
repr_unconditionally_treats_fields_as_live: bool, repr_unconditionally_treats_fields_as_live: bool,
repr_has_repr_simd: bool, repr_has_repr_simd: bool,
in_pat: bool, in_pat: bool,
ignore_variant_stack: Vec<DefId>, ignore_variant_stack: Vec<DefId>,
// maps from tuple struct constructors to tuple struct items
struct_constructors: LocalDefIdMap<LocalDefId>,
// maps from ADTs to ignored derived traits (e.g. Debug and Clone) // maps from ADTs to ignored derived traits (e.g. Debug and Clone)
// and the span of their respective impl (i.e., part of the derive // and the span of their respective impl (i.e., part of the derive
// macro) // macro)
ignored_derived_traits: LocalDefIdMap<FxIndexSet<(DefId, DefId)>>, ignored_derived_traits: LocalDefIdMap<FxIndexSet<DefId>>,
} }
impl<'tcx> MarkSymbolVisitor<'tcx> { impl<'tcx> MarkSymbolVisitor<'tcx> {
@@ -99,7 +103,7 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
fn check_def_id(&mut self, def_id: DefId) { fn check_def_id(&mut self, def_id: DefId) {
if let Some(def_id) = def_id.as_local() { if let Some(def_id) = def_id.as_local() {
if should_explore(self.tcx, def_id) || self.struct_constructors.contains_key(&def_id) { if should_explore(self.tcx, def_id) {
self.worklist.push((def_id, ComesFromAllowExpect::No)); self.worklist.push((def_id, ComesFromAllowExpect::No));
} }
self.live_symbols.insert(def_id); self.live_symbols.insert(def_id);
@@ -318,13 +322,12 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
} }
fn mark_live_symbols(&mut self) { fn mark_live_symbols(&mut self) {
let mut scanned = UnordSet::default();
while let Some(work) = self.worklist.pop() { while let Some(work) = self.worklist.pop() {
if !scanned.insert(work) { if !self.scanned.insert(work) {
continue; continue;
} }
let (id, comes_from_allow_expect) = work; let (mut id, comes_from_allow_expect) = work;
// Avoid accessing the HIR for the synthesized associated type generated for RPITITs. // Avoid accessing the HIR for the synthesized associated type generated for RPITITs.
if self.tcx.is_impl_trait_in_trait(id.to_def_id()) { if self.tcx.is_impl_trait_in_trait(id.to_def_id()) {
@@ -332,9 +335,11 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
continue; continue;
} }
// in the case of tuple struct constructors we want to check the item, not the generated // in the case of tuple struct constructors we want to check the item,
// tuple struct constructor function // not the generated tuple struct constructor function
let id = self.struct_constructors.get(&id).copied().unwrap_or(id); if let DefKind::Ctor(..) = self.tcx.def_kind(id) {
id = self.tcx.local_parent(id);
}
// When using `#[allow]` or `#[expect]` of `dead_code`, we do a QOL improvement // When using `#[allow]` or `#[expect]` of `dead_code`, we do a QOL improvement
// by declaring fn calls, statics, ... within said items as live, as well as // by declaring fn calls, statics, ... within said items as live, as well as
@@ -380,10 +385,7 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
if let ty::Adt(adt_def, _) = trait_ref.self_ty().kind() if let ty::Adt(adt_def, _) = trait_ref.self_ty().kind()
&& let Some(adt_def_id) = adt_def.did().as_local() && let Some(adt_def_id) = adt_def.did().as_local()
{ {
self.ignored_derived_traits self.ignored_derived_traits.entry(adt_def_id).or_default().insert(trait_of);
.entry(adt_def_id)
.or_default()
.insert((trait_of, impl_of));
} }
return true; return true;
} }
@@ -478,24 +480,24 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
/// `local_def_id` points to an impl or an impl item, /// `local_def_id` points to an impl or an impl item,
/// both impl and impl item that may be passed to this function are of a trait, /// both impl and impl item that may be passed to this function are of a trait,
/// and added into the unsolved_items during `create_and_seed_worklist` /// and added into the unsolved_items during `create_and_seed_worklist`
fn check_impl_or_impl_item_live( fn check_impl_or_impl_item_live(&mut self, local_def_id: LocalDefId) -> bool {
&mut self, let (impl_block_id, trait_def_id) = match self.tcx.def_kind(local_def_id) {
impl_id: hir::ItemId,
local_def_id: LocalDefId,
) -> bool {
let trait_def_id = match self.tcx.def_kind(local_def_id) {
// assoc impl items of traits are live if the corresponding trait items are live // assoc impl items of traits are live if the corresponding trait items are live
DefKind::AssocConst | DefKind::AssocTy | DefKind::AssocFn => self DefKind::AssocConst | DefKind::AssocTy | DefKind::AssocFn => (
.tcx self.tcx.local_parent(local_def_id),
.associated_item(local_def_id) self.tcx
.trait_item_def_id .associated_item(local_def_id)
.and_then(|def_id| def_id.as_local()), .trait_item_def_id
.and_then(|def_id| def_id.as_local()),
),
// impl items are live if the corresponding traits are live // impl items are live if the corresponding traits are live
DefKind::Impl { of_trait: true } => self DefKind::Impl { of_trait: true } => (
.tcx local_def_id,
.impl_trait_ref(impl_id.owner_id.def_id) self.tcx
.and_then(|trait_ref| trait_ref.skip_binder().def_id.as_local()), .impl_trait_ref(local_def_id)
_ => None, .and_then(|trait_ref| trait_ref.skip_binder().def_id.as_local()),
),
_ => bug!(),
}; };
if let Some(trait_def_id) = trait_def_id if let Some(trait_def_id) = trait_def_id
@@ -505,9 +507,9 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
} }
// The impl or impl item is used if the corresponding trait or trait item is used and the ty is used. // The impl or impl item is used if the corresponding trait or trait item is used and the ty is used.
if let Some(local_def_id) = if let ty::Adt(adt, _) = self.tcx.type_of(impl_block_id).instantiate_identity().kind()
local_adt_def_of_ty(self.tcx.hir_item(impl_id).expect_impl().self_ty) && let Some(adt_def_id) = adt.did().as_local()
&& !self.live_symbols.contains(&local_def_id) && !self.live_symbols.contains(&adt_def_id)
{ {
return false; return false;
} }
@@ -714,139 +716,86 @@ fn has_allow_dead_code_or_lang_attr(
} }
} }
// These check_* functions seeds items that /// Examine the given definition and record it in the worklist if it should be considered live.
// 1) We want to explicitly consider as live: ///
// * Item annotated with #[allow(dead_code)] /// We want to explicitly consider as live:
// - This is done so that if we want to suppress warnings for a /// * Item annotated with #[allow(dead_code)]
// group of dead functions, we only have to annotate the "root". /// This is done so that if we want to suppress warnings for a
// For example, if both `f` and `g` are dead and `f` calls `g`, /// group of dead functions, we only have to annotate the "root".
// then annotating `f` with `#[allow(dead_code)]` will suppress /// For example, if both `f` and `g` are dead and `f` calls `g`,
// warning for both `f` and `g`. /// then annotating `f` with `#[allow(dead_code)]` will suppress
// * Item annotated with #[lang=".."] /// warning for both `f` and `g`.
// - This is because lang items are always callable from elsewhere. ///
// or /// * Item annotated with #[lang=".."]
// 2) We are not sure to be live or not /// Lang items are always callable from elsewhere.
// * Implementations of traits and trait methods ///
fn check_item<'tcx>( /// For trait methods and implementations of traits, we are not certain that the definitions are
/// live at this stage. We record them in `unsolved_items` for later examination.
fn maybe_record_as_seed<'tcx>(
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
owner_id: hir::OwnerId,
worklist: &mut Vec<(LocalDefId, ComesFromAllowExpect)>, worklist: &mut Vec<(LocalDefId, ComesFromAllowExpect)>,
struct_constructors: &mut LocalDefIdMap<LocalDefId>, unsolved_items: &mut Vec<LocalDefId>,
unsolved_items: &mut Vec<(hir::ItemId, LocalDefId)>,
id: hir::ItemId,
) { ) {
let allow_dead_code = has_allow_dead_code_or_lang_attr(tcx, id.owner_id.def_id); let allow_dead_code = has_allow_dead_code_or_lang_attr(tcx, owner_id.def_id);
if let Some(comes_from_allow) = allow_dead_code { if let Some(comes_from_allow) = allow_dead_code {
worklist.push((id.owner_id.def_id, comes_from_allow)); worklist.push((owner_id.def_id, comes_from_allow));
} }
match tcx.def_kind(id.owner_id) { match tcx.def_kind(owner_id) {
DefKind::Enum => { DefKind::Enum => {
let item = tcx.hir_item(id); if let Some(comes_from_allow) = allow_dead_code {
if let hir::ItemKind::Enum(_, _, ref enum_def) = item.kind { let adt = tcx.adt_def(owner_id);
if let Some(comes_from_allow) = allow_dead_code { worklist.extend(
worklist.extend( adt.variants()
enum_def.variants.iter().map(|variant| (variant.def_id, comes_from_allow)), .iter()
); .map(|variant| (variant.def_id.expect_local(), comes_from_allow)),
} );
}
for variant in enum_def.variants { }
if let Some(ctor_def_id) = variant.data.ctor_def_id() { DefKind::AssocFn | DefKind::AssocConst | DefKind::AssocTy => {
struct_constructors.insert(ctor_def_id, variant.def_id); if allow_dead_code.is_none() {
let parent = tcx.local_parent(owner_id.def_id);
match tcx.def_kind(parent) {
DefKind::Impl { of_trait: false } | DefKind::Trait => {}
DefKind::Impl { of_trait: true } => {
// We only care about associated items of traits,
// because they cannot be visited directly,
// so we later mark them as live if their corresponding traits
// or trait items and self types are both live,
// but inherent associated items can be visited and marked directly.
unsolved_items.push(owner_id.def_id);
} }
_ => bug!(),
} }
} }
} }
DefKind::Impl { of_trait } => { DefKind::Impl { of_trait: true } => {
if let Some(comes_from_allow) = if allow_dead_code.is_none() {
has_allow_dead_code_or_lang_attr(tcx, id.owner_id.def_id) unsolved_items.push(owner_id.def_id);
{
worklist.push((id.owner_id.def_id, comes_from_allow));
} else if of_trait {
unsolved_items.push((id, id.owner_id.def_id));
}
for def_id in tcx.associated_item_def_ids(id.owner_id) {
let local_def_id = def_id.expect_local();
if let Some(comes_from_allow) = has_allow_dead_code_or_lang_attr(tcx, local_def_id)
{
worklist.push((local_def_id, comes_from_allow));
} else if of_trait {
// We only care about associated items of traits,
// because they cannot be visited directly,
// so we later mark them as live if their corresponding traits
// or trait items and self types are both live,
// but inherent associated items can be visited and marked directly.
unsolved_items.push((id, local_def_id));
}
}
}
DefKind::Struct => {
let item = tcx.hir_item(id);
if let hir::ItemKind::Struct(_, _, ref variant_data) = item.kind
&& let Some(ctor_def_id) = variant_data.ctor_def_id()
{
struct_constructors.insert(ctor_def_id, item.owner_id.def_id);
} }
} }
DefKind::GlobalAsm => { DefKind::GlobalAsm => {
// global_asm! is always live. // global_asm! is always live.
worklist.push((id.owner_id.def_id, ComesFromAllowExpect::No)); worklist.push((owner_id.def_id, ComesFromAllowExpect::No));
} }
DefKind::Const => { DefKind::Const => {
let item = tcx.hir_item(id); if tcx.item_name(owner_id.def_id) == kw::Underscore {
if let hir::ItemKind::Const(ident, ..) = item.kind
&& ident.name == kw::Underscore
{
// `const _` is always live, as that syntax only exists for the side effects // `const _` is always live, as that syntax only exists for the side effects
// of type checking and evaluating the constant expression, and marking them // of type checking and evaluating the constant expression, and marking them
// as dead code would defeat that purpose. // as dead code would defeat that purpose.
worklist.push((id.owner_id.def_id, ComesFromAllowExpect::No)); worklist.push((owner_id.def_id, ComesFromAllowExpect::No));
} }
} }
_ => {} _ => {}
} }
} }
fn check_trait_item(
tcx: TyCtxt<'_>,
worklist: &mut Vec<(LocalDefId, ComesFromAllowExpect)>,
id: hir::TraitItemId,
) {
use hir::TraitItemKind::{Const, Fn, Type};
let trait_item = tcx.hir_trait_item(id);
if matches!(trait_item.kind, Const(_, Some(_)) | Type(_, Some(_)) | Fn(..))
&& let Some(comes_from_allow) =
has_allow_dead_code_or_lang_attr(tcx, trait_item.owner_id.def_id)
{
worklist.push((trait_item.owner_id.def_id, comes_from_allow));
}
}
fn check_foreign_item(
tcx: TyCtxt<'_>,
worklist: &mut Vec<(LocalDefId, ComesFromAllowExpect)>,
id: hir::ForeignItemId,
) {
if matches!(tcx.def_kind(id.owner_id), DefKind::Static { .. } | DefKind::Fn)
&& let Some(comes_from_allow) = has_allow_dead_code_or_lang_attr(tcx, id.owner_id.def_id)
{
worklist.push((id.owner_id.def_id, comes_from_allow));
}
}
fn create_and_seed_worklist( fn create_and_seed_worklist(
tcx: TyCtxt<'_>, tcx: TyCtxt<'_>,
) -> ( ) -> (Vec<(LocalDefId, ComesFromAllowExpect)>, Vec<LocalDefId>) {
Vec<(LocalDefId, ComesFromAllowExpect)>,
LocalDefIdMap<LocalDefId>,
Vec<(hir::ItemId, LocalDefId)>,
) {
let effective_visibilities = &tcx.effective_visibilities(()); let effective_visibilities = &tcx.effective_visibilities(());
// see `MarkSymbolVisitor::struct_constructors`
let mut unsolved_impl_item = Vec::new(); let mut unsolved_impl_item = Vec::new();
let mut struct_constructors = Default::default();
let mut worklist = effective_visibilities let mut worklist = effective_visibilities
.iter() .iter()
.filter_map(|(&id, effective_vis)| { .filter_map(|(&id, effective_vis)| {
@@ -863,54 +812,49 @@ fn create_and_seed_worklist(
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let crate_items = tcx.hir_crate_items(()); let crate_items = tcx.hir_crate_items(());
for id in crate_items.free_items() { for id in crate_items.owners() {
check_item(tcx, &mut worklist, &mut struct_constructors, &mut unsolved_impl_item, id); maybe_record_as_seed(tcx, id, &mut worklist, &mut unsolved_impl_item);
} }
for id in crate_items.trait_items() { (worklist, unsolved_impl_item)
check_trait_item(tcx, &mut worklist, id);
}
for id in crate_items.foreign_items() {
check_foreign_item(tcx, &mut worklist, id);
}
(worklist, struct_constructors, unsolved_impl_item)
} }
fn live_symbols_and_ignored_derived_traits( fn live_symbols_and_ignored_derived_traits(
tcx: TyCtxt<'_>, tcx: TyCtxt<'_>,
(): (), (): (),
) -> (LocalDefIdSet, LocalDefIdMap<FxIndexSet<(DefId, DefId)>>) { ) -> (LocalDefIdSet, LocalDefIdMap<FxIndexSet<DefId>>) {
let (worklist, struct_constructors, mut unsolved_items) = create_and_seed_worklist(tcx); let (worklist, mut unsolved_items) = create_and_seed_worklist(tcx);
let mut symbol_visitor = MarkSymbolVisitor { let mut symbol_visitor = MarkSymbolVisitor {
worklist, worklist,
tcx, tcx,
maybe_typeck_results: None, maybe_typeck_results: None,
scanned: Default::default(),
live_symbols: Default::default(), live_symbols: Default::default(),
repr_unconditionally_treats_fields_as_live: false, repr_unconditionally_treats_fields_as_live: false,
repr_has_repr_simd: false, repr_has_repr_simd: false,
in_pat: false, in_pat: false,
ignore_variant_stack: vec![], ignore_variant_stack: vec![],
struct_constructors,
ignored_derived_traits: Default::default(), ignored_derived_traits: Default::default(),
}; };
symbol_visitor.mark_live_symbols(); symbol_visitor.mark_live_symbols();
let mut items_to_check;
(items_to_check, unsolved_items) = // We have marked the primary seeds as live. We now need to process unsolved items from traits
unsolved_items.into_iter().partition(|&(impl_id, local_def_id)| { // and trait impls: add them to the work list if the trait or the implemented type is live.
symbol_visitor.check_impl_or_impl_item_live(impl_id, local_def_id) let mut items_to_check: Vec<_> = unsolved_items
}); .extract_if(.., |&mut local_def_id| {
symbol_visitor.check_impl_or_impl_item_live(local_def_id)
})
.collect();
while !items_to_check.is_empty() { while !items_to_check.is_empty() {
symbol_visitor.worklist = symbol_visitor
items_to_check.into_iter().map(|(_, id)| (id, ComesFromAllowExpect::No)).collect(); .worklist
.extend(items_to_check.drain(..).map(|id| (id, ComesFromAllowExpect::No)));
symbol_visitor.mark_live_symbols(); symbol_visitor.mark_live_symbols();
(items_to_check, unsolved_items) = items_to_check.extend(unsolved_items.extract_if(.., |&mut local_def_id| {
unsolved_items.into_iter().partition(|&(impl_id, local_def_id)| { symbol_visitor.check_impl_or_impl_item_live(local_def_id)
symbol_visitor.check_impl_or_impl_item_live(impl_id, local_def_id) }));
});
} }
(symbol_visitor.live_symbols, symbol_visitor.ignored_derived_traits) (symbol_visitor.live_symbols, symbol_visitor.ignored_derived_traits)
@@ -925,7 +869,7 @@ struct DeadItem {
struct DeadVisitor<'tcx> { struct DeadVisitor<'tcx> {
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
live_symbols: &'tcx LocalDefIdSet, live_symbols: &'tcx LocalDefIdSet,
ignored_derived_traits: &'tcx LocalDefIdMap<FxIndexSet<(DefId, DefId)>>, ignored_derived_traits: &'tcx LocalDefIdMap<FxIndexSet<DefId>>,
} }
enum ShouldWarnAboutField { enum ShouldWarnAboutField {
@@ -984,25 +928,7 @@ impl<'tcx> DeadVisitor<'tcx> {
parent_item: Option<LocalDefId>, parent_item: Option<LocalDefId>,
report_on: ReportOn, report_on: ReportOn,
) { ) {
fn get_parent_if_enum_variant<'tcx>( let Some(&first_item) = dead_codes.first() else { return };
tcx: TyCtxt<'tcx>,
may_variant: LocalDefId,
) -> LocalDefId {
if let Node::Variant(_) = tcx.hir_node_by_def_id(may_variant)
&& let Some(enum_did) = tcx.opt_parent(may_variant.to_def_id())
&& let Some(enum_local_id) = enum_did.as_local()
&& let Node::Item(item) = tcx.hir_node_by_def_id(enum_local_id)
&& let ItemKind::Enum(..) = item.kind
{
enum_local_id
} else {
may_variant
}
}
let Some(&first_item) = dead_codes.first() else {
return;
};
let tcx = self.tcx; let tcx = self.tcx;
let first_lint_level = first_item.level; let first_lint_level = first_item.level;
@@ -1011,81 +937,54 @@ impl<'tcx> DeadVisitor<'tcx> {
let names: Vec<_> = dead_codes.iter().map(|item| item.name).collect(); let names: Vec<_> = dead_codes.iter().map(|item| item.name).collect();
let spans: Vec<_> = dead_codes let spans: Vec<_> = dead_codes
.iter() .iter()
.map(|item| match tcx.def_ident_span(item.def_id) { .map(|item| {
Some(s) => s.with_ctxt(tcx.def_span(item.def_id).ctxt()), let span = tcx.def_span(item.def_id);
None => tcx.def_span(item.def_id), let ident_span = tcx.def_ident_span(item.def_id);
// FIXME(cjgillot) this SyntaxContext manipulation does not make any sense.
ident_span.map(|s| s.with_ctxt(span.ctxt())).unwrap_or(span)
}) })
.collect(); .collect();
let descr = tcx.def_descr(first_item.def_id.to_def_id()); let mut descr = tcx.def_descr(first_item.def_id.to_def_id());
// `impl` blocks are "batched" and (unlike other batching) might // `impl` blocks are "batched" and (unlike other batching) might
// contain different kinds of associated items. // contain different kinds of associated items.
let descr = if dead_codes.iter().any(|item| tcx.def_descr(item.def_id.to_def_id()) != descr) if dead_codes.iter().any(|item| tcx.def_descr(item.def_id.to_def_id()) != descr) {
{ descr = "associated item"
"associated item" }
} else {
descr
};
let num = dead_codes.len(); let num = dead_codes.len();
let multiple = num > 6; let multiple = num > 6;
let name_list = names.into(); let name_list = names.into();
let parent_info = if let Some(parent_item) = parent_item { let parent_info = parent_item.map(|parent_item| {
let parent_descr = tcx.def_descr(parent_item.to_def_id()); let parent_descr = tcx.def_descr(parent_item.to_def_id());
let span = if let DefKind::Impl { .. } = tcx.def_kind(parent_item) { let span = if let DefKind::Impl { .. } = tcx.def_kind(parent_item) {
tcx.def_span(parent_item) tcx.def_span(parent_item)
} else { } else {
tcx.def_ident_span(parent_item).unwrap() tcx.def_ident_span(parent_item).unwrap()
}; };
Some(ParentInfo { num, descr, parent_descr, span }) ParentInfo { num, descr, parent_descr, span }
} else { });
None
};
let encl_def_id = parent_item.unwrap_or(first_item.def_id); let mut encl_def_id = parent_item.unwrap_or(first_item.def_id);
// If parent of encl_def_id is an enum, use the parent ID instead. // `ignored_derived_traits` is computed for the enum, not for the variants.
let encl_def_id = get_parent_if_enum_variant(tcx, encl_def_id); if let DefKind::Variant = tcx.def_kind(encl_def_id) {
encl_def_id = tcx.local_parent(encl_def_id);
}
let ignored_derived_impls = let ignored_derived_impls =
if let Some(ign_traits) = self.ignored_derived_traits.get(&encl_def_id) { self.ignored_derived_traits.get(&encl_def_id).map(|ign_traits| {
let trait_list = ign_traits let trait_list = ign_traits
.iter() .iter()
.map(|(trait_id, _)| self.tcx.item_name(*trait_id)) .map(|trait_id| self.tcx.item_name(*trait_id))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let trait_list_len = trait_list.len(); let trait_list_len = trait_list.len();
Some(IgnoredDerivedImpls { IgnoredDerivedImpls {
name: self.tcx.item_name(encl_def_id.to_def_id()), name: self.tcx.item_name(encl_def_id.to_def_id()),
trait_list: trait_list.into(), trait_list: trait_list.into(),
trait_list_len, trait_list_len,
})
} else {
None
};
let enum_variants_with_same_name = dead_codes
.iter()
.filter_map(|dead_item| {
if let Node::ImplItem(ImplItem {
kind: ImplItemKind::Fn(..) | ImplItemKind::Const(..),
..
}) = tcx.hir_node_by_def_id(dead_item.def_id)
&& let Some(impl_did) = tcx.opt_parent(dead_item.def_id.to_def_id())
&& let DefKind::Impl { of_trait: false } = tcx.def_kind(impl_did)
&& let ty::Adt(maybe_enum, _) = tcx.type_of(impl_did).skip_binder().kind()
&& maybe_enum.is_enum()
&& let Some(variant) =
maybe_enum.variants().iter().find(|i| i.name == dead_item.name)
{
Some(crate::errors::EnumVariantSameName {
dead_descr: tcx.def_descr(dead_item.def_id.to_def_id()),
dead_name: dead_item.name,
variant_span: tcx.def_span(variant.def_id),
})
} else {
None
} }
}) });
.collect();
let diag = match report_on { let diag = match report_on {
ReportOn::TupleField => { ReportOn::TupleField => {
@@ -1132,16 +1031,42 @@ impl<'tcx> DeadVisitor<'tcx> {
ignored_derived_impls, ignored_derived_impls,
} }
} }
ReportOn::NamedField => MultipleDeadCodes::DeadCodes { ReportOn::NamedField => {
multiple, let enum_variants_with_same_name = dead_codes
num, .iter()
descr, .filter_map(|dead_item| {
participle, if let DefKind::AssocFn | DefKind::AssocConst =
name_list, tcx.def_kind(dead_item.def_id)
parent_info, && let impl_did = tcx.local_parent(dead_item.def_id)
ignored_derived_impls, && let DefKind::Impl { of_trait: false } = tcx.def_kind(impl_did)
enum_variants_with_same_name, && let ty::Adt(maybe_enum, _) =
}, tcx.type_of(impl_did).instantiate_identity().kind()
&& maybe_enum.is_enum()
&& let Some(variant) =
maybe_enum.variants().iter().find(|i| i.name == dead_item.name)
{
Some(crate::errors::EnumVariantSameName {
dead_descr: tcx.def_descr(dead_item.def_id.to_def_id()),
dead_name: dead_item.name,
variant_span: tcx.def_span(variant.def_id),
})
} else {
None
}
})
.collect();
MultipleDeadCodes::DeadCodes {
multiple,
num,
descr,
participle,
name_list,
parent_info,
ignored_derived_impls,
enum_variants_with_same_name,
}
}
}; };
let hir_id = tcx.local_def_id_to_hir_id(first_item.def_id); let hir_id = tcx.local_def_id_to_hir_id(first_item.def_id);

View File

@@ -8,6 +8,7 @@
#![allow(internal_features)] #![allow(internal_features)]
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
#![doc(rust_logo)] #![doc(rust_logo)]
#![feature(if_let_guard)]
#![feature(map_try_insert)] #![feature(map_try_insert)]
#![feature(rustdoc_internals)] #![feature(rustdoc_internals)]
// tidy-alphabetical-end // tidy-alphabetical-end