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:
@@ -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" }
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user