Skip to content

Commit c42ac5f

Browse files
committed
Auto merge of rust-lang#161571 - mu001999-contrib:dead-code-refactor, r=chenyukang
Refactor the `#[allow(dead_code)]` propagation for impl items of traits Extracted from rust-lang#157885. This PR does the refactor and corrects the previous implementation. The following will fail currently ([play](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=3279902b2d2e6b0fe75c5af565b1cddd)): ```rust #![deny(dead_code)] #![deny(unfulfilled_lint_expectations)] #[allow(dead_code)] pub trait Tr { fn foo(&self); } struct Foo; impl Tr for Foo { fn foo(&self) { bar(); } } #[expect(dead_code)] fn bar() {} fn main() {} ``` After this PR, we could handle the `#[allow(dead_code)]` propagation correctly, and should get perf improvement.
2 parents d0f2ef5 + 93d03be commit c42ac5f

2 files changed

Lines changed: 169 additions & 82 deletions

File tree

compiler/rustc_passes/src/dead.rs

Lines changed: 146 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::sync::atomic::Ordering;
99

1010
use hir::def_id::{LocalDefIdMap, LocalDefIdSet};
1111
use rustc_abi::FieldIdx;
12-
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
12+
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
1313
use rustc_errors::{ErrorGuaranteed, MultiSpan};
1414
use rustc_hir::def::{CtorOf, DefKind, Res};
1515
use rustc_hir::def_id::{DefId, LocalDefId, LocalModId};
@@ -114,6 +114,11 @@ struct WorkItem {
114114
own: ComesFromAllowExpect,
115115
}
116116

117+
enum ImplItemCheckResult {
118+
Live(ComesFromAllowExpect),
119+
Dead { require: LocalDefId },
120+
}
121+
117122
struct MarkSymbolVisitor<'tcx> {
118123
worklist: Vec<WorkItem>,
119124
tcx: TyCtxt<'tcx>,
@@ -129,6 +134,7 @@ struct MarkSymbolVisitor<'tcx> {
129134
// macro)
130135
ignored_derived_traits: LocalDefIdMap<FxIndexSet<DefId>>,
131136
propagated_comes_from_allow_expect: ComesFromAllowExpect,
137+
unsolved_items: Vec<LocalDefId>,
132138
}
133139

134140
impl<'tcx> MarkSymbolVisitor<'tcx> {
@@ -419,6 +425,11 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
419425

420426
if !self.scanned.insert((id, propagated)) {
421427
continue;
428+
} else if propagated == ComesFromAllowExpect::No {
429+
// If the item is not coming from an `#[allow]` or `#[expect]`,
430+
// we also mark it as scanned with `ComesFromAllowExpect::Yes`
431+
// to avoid re-scanning it in the future.
432+
self.scanned.insert((id, ComesFromAllowExpect::Yes));
422433
}
423434

424435
// Avoid accessing the HIR for the synthesized associated type generated for RPITITs.
@@ -545,13 +556,17 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
545556
/// `local_def_id` points to an impl or an impl item,
546557
/// both impl and impl item that may be passed to this function are of a trait,
547558
/// and added into the unsolved_items during `create_and_seed_worklist`
548-
fn check_impl_or_impl_item_live(&mut self, local_def_id: LocalDefId) -> bool {
559+
fn check_impl_or_impl_item_live(
560+
&self,
561+
local_def_id: LocalDefId,
562+
defer_seeds_come_from_allow: bool,
563+
) -> ImplItemCheckResult {
549564
let (impl_block_id, trait_def_id) = match self.tcx.def_kind(local_def_id) {
550565
// assoc impl items of traits are live if the corresponding trait items are live
551566
DefKind::AssocConst { .. } | DefKind::AssocTy | DefKind::AssocFn => {
552-
let trait_item_id =
567+
let trait_def_id =
553568
self.tcx.trait_item_of(local_def_id).and_then(|def_id| def_id.as_local());
554-
(self.tcx.local_parent(local_def_id), trait_item_id)
569+
(self.tcx.local_parent(local_def_id), trait_def_id)
555570
}
556571
// impl items are live if the corresponding traits are live
557572
DefKind::Impl { of_trait: true } => {
@@ -560,10 +575,22 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
560575
_ => bug!(),
561576
};
562577

563-
if let Some(trait_def_id) = trait_def_id
564-
&& !self.live_symbols.contains(&trait_def_id)
565-
{
566-
return false;
578+
let mut trait_comes_from_allow = None;
579+
if let Some(trait_def_id) = trait_def_id {
580+
if defer_seeds_come_from_allow {
581+
if !self.live_symbols.contains(&trait_def_id) {
582+
return ImplItemCheckResult::Dead { require: trait_def_id };
583+
}
584+
} else {
585+
trait_comes_from_allow = has_allow_dead_code_or_lang_attr(self.tcx, trait_def_id);
586+
587+
if !self.live_symbols.contains(&trait_def_id) {
588+
return match trait_comes_from_allow {
589+
Some(comes_from_allow) => ImplItemCheckResult::Live(comes_from_allow),
590+
None => ImplItemCheckResult::Dead { require: trait_def_id },
591+
};
592+
}
593+
}
567594
}
568595

569596
// The impl or impl item is used if the corresponding trait or trait item is used and the ty is used.
@@ -572,10 +599,95 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
572599
&& let Some(adt_def_id) = adt.did().as_local()
573600
&& !self.live_symbols.contains(&adt_def_id)
574601
{
575-
return false;
602+
if defer_seeds_come_from_allow {
603+
return ImplItemCheckResult::Dead { require: adt_def_id };
604+
} else {
605+
let comes_from_allow = trait_comes_from_allow
606+
.or_else(|| has_allow_dead_code_or_lang_attr(self.tcx, adt_def_id));
607+
608+
return match comes_from_allow {
609+
Some(comes_from_allow) => ImplItemCheckResult::Live(comes_from_allow),
610+
None => ImplItemCheckResult::Dead { require: adt_def_id },
611+
};
612+
}
576613
}
577614

578-
true
615+
ImplItemCheckResult::Live(ComesFromAllowExpect::No)
616+
}
617+
618+
fn collect_live_items_from_unsolved_items(
619+
&mut self,
620+
defer_seeds_come_from_allow: bool,
621+
unsolved_items: Vec<LocalDefId>,
622+
unsolved_map: &mut FxHashMap<LocalDefId, Vec<LocalDefId>>,
623+
) -> Vec<(LocalDefId, ComesFromAllowExpect)> {
624+
let mut items_to_check = vec![];
625+
626+
for def_id in unsolved_items {
627+
match self.check_impl_or_impl_item_live(def_id, defer_seeds_come_from_allow) {
628+
ImplItemCheckResult::Live(comes_from_allow) => {
629+
items_to_check.push((def_id, comes_from_allow));
630+
}
631+
ImplItemCheckResult::Dead { require } => {
632+
unsolved_map.entry(require).or_default().push(def_id);
633+
}
634+
}
635+
}
636+
items_to_check
637+
}
638+
639+
#[expect(
640+
rustc::potential_query_instability,
641+
reason = "The order of the unsolved items is not important, so we can just collect them into a vector."
642+
)]
643+
fn mark_live_symbols_and_ignored_derived_traits(
644+
&mut self,
645+
defer_seeds_come_from_allow: bool,
646+
) -> Result<(), ErrorGuaranteed> {
647+
if let ControlFlow::Break(guar) = self.mark_live_symbols() {
648+
return Err(guar);
649+
}
650+
651+
// We have marked the primary seeds as live. We now need to process unsolved items from traits
652+
// and trait impls: add them to the work list if the trait or the implemented type is live.
653+
let unsolved_items = std::mem::take(&mut self.unsolved_items);
654+
let mut unsolved_map = FxHashMap::default();
655+
let mut items_to_check = self.collect_live_items_from_unsolved_items(
656+
defer_seeds_come_from_allow,
657+
unsolved_items,
658+
&mut unsolved_map,
659+
);
660+
661+
while !items_to_check.is_empty() {
662+
self.worklist.extend(items_to_check.into_iter().map(|(id, comes_from_allow)| {
663+
let own = if defer_seeds_come_from_allow {
664+
ComesFromAllowExpect::No
665+
} else {
666+
has_allow_dead_code_or_lang_attr(self.tcx, id)
667+
.unwrap_or(ComesFromAllowExpect::No)
668+
};
669+
670+
WorkItem { id, propagated: comes_from_allow, own }
671+
}));
672+
if let ControlFlow::Break(guar) = self.mark_live_symbols() {
673+
return Err(guar);
674+
}
675+
676+
let unsolved_items = unsolved_map
677+
.extract_if(|require, _| self.live_symbols.contains(require))
678+
.flat_map(|(_, items)| items)
679+
.collect();
680+
681+
items_to_check = self.collect_live_items_from_unsolved_items(
682+
defer_seeds_come_from_allow,
683+
unsolved_items,
684+
&mut unsolved_map,
685+
);
686+
}
687+
688+
self.unsolved_items = unsolved_map.into_values().flatten().collect();
689+
690+
Ok(())
579691
}
580692
}
581693

@@ -850,19 +962,6 @@ fn maybe_record_as_seed<'tcx>(
850962
match tcx.def_kind(parent) {
851963
DefKind::Impl { of_trait: false } | DefKind::Trait => {}
852964
DefKind::Impl { of_trait: true } => {
853-
if let Some(trait_item_def_id) =
854-
tcx.associated_item(owner_id.def_id).trait_item_def_id()
855-
&& let Some(trait_item_local_def_id) = trait_item_def_id.as_local()
856-
&& let Some(comes_from_allow) =
857-
has_allow_dead_code_or_lang_attr(tcx, trait_item_local_def_id)
858-
{
859-
push_into_worklist(WorkItem {
860-
id: owner_id.def_id,
861-
propagated: comes_from_allow,
862-
own: comes_from_allow,
863-
});
864-
}
865-
866965
// We only care about associated items of traits,
867966
// because they cannot be visited directly,
868967
// so we later mark them as live if their corresponding traits
@@ -874,22 +973,8 @@ fn maybe_record_as_seed<'tcx>(
874973
}
875974
}
876975
}
877-
DefKind::Impl { of_trait: true } => {
878-
if allow_dead_code.is_none() {
879-
if let Some(trait_def_id) =
880-
tcx.impl_trait_ref(owner_id.def_id).skip_binder().def_id.as_local()
881-
&& let Some(comes_from_allow) =
882-
has_allow_dead_code_or_lang_attr(tcx, trait_def_id)
883-
{
884-
push_into_worklist(WorkItem {
885-
id: owner_id.def_id,
886-
propagated: comes_from_allow,
887-
own: comes_from_allow,
888-
});
889-
}
890-
891-
unsolved_items.push(owner_id.def_id);
892-
}
976+
DefKind::Impl { of_trait: true } if allow_dead_code.is_none() => {
977+
unsolved_items.push(owner_id.def_id);
893978
}
894979
DefKind::GlobalAsm => {
895980
// global_asm! is always live.
@@ -915,15 +1000,21 @@ fn maybe_record_as_seed<'tcx>(
9151000
}
9161001
}
9171002

1003+
#[derive(Default)]
1004+
struct DeferredSeeds {
1005+
pub_reachables: Vec<WorkItem>,
1006+
come_from_allow: Vec<WorkItem>,
1007+
}
1008+
9181009
struct SeedWorklists {
9191010
worklist: Vec<WorkItem>,
920-
deferred_seeds: Vec<WorkItem>,
1011+
deferred_seeds: DeferredSeeds,
9211012
unsolved_items: Vec<LocalDefId>,
9221013
}
9231014

9241015
fn create_and_seed_worklist(tcx: TyCtxt<'_>) -> SeedWorklists {
9251016
let mut unsolved_items = Vec::new();
926-
let mut deferred_seeds = Vec::new();
1017+
let mut deferred_seeds = DeferredSeeds::default();
9271018
let mut worklist = Vec::new();
9281019

9291020
if let Some((def_id, _)) = tcx.entry_fn(())
@@ -953,7 +1044,7 @@ fn create_and_seed_worklist(tcx: TyCtxt<'_>) -> SeedWorklists {
9531044

9541045
for (id, effective_vis) in tcx.effective_visibilities(()).iter() {
9551046
if effective_vis.is_public_at_level(Level::Reachable) {
956-
deferred_seeds.push(WorkItem {
1047+
deferred_seeds.pub_reachables.push(WorkItem {
9571048
id: *id,
9581049
propagated: ComesFromAllowExpect::No,
9591050
own: ComesFromAllowExpect::No,
@@ -962,7 +1053,7 @@ fn create_and_seed_worklist(tcx: TyCtxt<'_>) -> SeedWorklists {
9621053
}
9631054

9641055
let mut push_into_worklist = |work_item: WorkItem| match work_item.own {
965-
ComesFromAllowExpect::Yes => deferred_seeds.push(work_item),
1056+
ComesFromAllowExpect::Yes => deferred_seeds.come_from_allow.push(work_item),
9661057
ComesFromAllowExpect::No => worklist.push(work_item),
9671058
};
9681059
let crate_items = tcx.hir_crate_items(());
@@ -977,8 +1068,7 @@ fn live_symbols_and_ignored_derived_traits(
9771068
tcx: TyCtxt<'_>,
9781069
(): (),
9791070
) -> Result<DeadCodeLivenessSummary, ErrorGuaranteed> {
980-
let SeedWorklists { worklist, deferred_seeds, mut unsolved_items } =
981-
create_and_seed_worklist(tcx);
1071+
let SeedWorklists { worklist, deferred_seeds, unsolved_items } = create_and_seed_worklist(tcx);
9821072
let mut symbol_visitor = MarkSymbolVisitor {
9831073
worklist,
9841074
tcx,
@@ -991,15 +1081,23 @@ fn live_symbols_and_ignored_derived_traits(
9911081
ignore_variant_stack: vec![],
9921082
ignored_derived_traits: Default::default(),
9931083
propagated_comes_from_allow_expect: ComesFromAllowExpect::No,
1084+
unsolved_items,
9941085
};
995-
mark_live_symbols_and_ignored_derived_traits(&mut symbol_visitor, &mut unsolved_items)?;
1086+
symbol_visitor.mark_live_symbols_and_ignored_derived_traits(true)?;
9961087
let pre_deferred_seeding = DeadCodeLivenessSnapshot {
9971088
live_symbols: symbol_visitor.live_symbols.clone(),
9981089
ignored_derived_traits: symbol_visitor.ignored_derived_traits.clone(),
9991090
};
10001091

1001-
symbol_visitor.worklist.extend(deferred_seeds);
1002-
mark_live_symbols_and_ignored_derived_traits(&mut symbol_visitor, &mut unsolved_items)?;
1092+
if !deferred_seeds.pub_reachables.is_empty() {
1093+
symbol_visitor.worklist.extend(deferred_seeds.pub_reachables);
1094+
symbol_visitor.mark_live_symbols_and_ignored_derived_traits(true)?;
1095+
}
1096+
1097+
if !deferred_seeds.come_from_allow.is_empty() {
1098+
symbol_visitor.worklist.extend(deferred_seeds.come_from_allow);
1099+
symbol_visitor.mark_live_symbols_and_ignored_derived_traits(false)?;
1100+
}
10031101

10041102
Ok(DeadCodeLivenessSummary {
10051103
pre_deferred_seeding,
@@ -1010,40 +1108,6 @@ fn live_symbols_and_ignored_derived_traits(
10101108
})
10111109
}
10121110

1013-
fn mark_live_symbols_and_ignored_derived_traits(
1014-
symbol_visitor: &mut MarkSymbolVisitor<'_>,
1015-
unsolved_items: &mut Vec<LocalDefId>,
1016-
) -> Result<(), ErrorGuaranteed> {
1017-
if let ControlFlow::Break(guar) = symbol_visitor.mark_live_symbols() {
1018-
return Err(guar);
1019-
}
1020-
1021-
// We have marked the primary seeds as live. We now need to process unsolved items from traits
1022-
// and trait impls: add them to the work list if the trait or the implemented type is live.
1023-
let mut items_to_check: Vec<_> = unsolved_items
1024-
.extract_if(.., |&mut local_def_id| {
1025-
symbol_visitor.check_impl_or_impl_item_live(local_def_id)
1026-
})
1027-
.collect();
1028-
1029-
while !items_to_check.is_empty() {
1030-
symbol_visitor.worklist.extend(items_to_check.drain(..).map(|id| WorkItem {
1031-
id,
1032-
propagated: ComesFromAllowExpect::No,
1033-
own: ComesFromAllowExpect::No,
1034-
}));
1035-
if let ControlFlow::Break(guar) = symbol_visitor.mark_live_symbols() {
1036-
return Err(guar);
1037-
}
1038-
1039-
items_to_check.extend(unsolved_items.extract_if(.., |&mut local_def_id| {
1040-
symbol_visitor.check_impl_or_impl_item_live(local_def_id)
1041-
}));
1042-
}
1043-
1044-
Ok(())
1045-
}
1046-
10471111
struct DeadItem {
10481112
def_id: LocalDefId,
10491113
name: Symbol,
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//@ check-pass
2+
3+
#![deny(dead_code)]
4+
#![deny(unfulfilled_lint_expectations)]
5+
6+
#[allow(dead_code)]
7+
pub trait Tr {
8+
fn foo(&self);
9+
}
10+
11+
#[expect(dead_code)]
12+
struct Foo;
13+
14+
impl Tr for Foo {
15+
fn foo(&self) {
16+
bar();
17+
}
18+
}
19+
20+
#[expect(dead_code)]
21+
fn bar() {}
22+
23+
fn main() {}

0 commit comments

Comments
 (0)