Skip to content

Commit e466d2a

Browse files
committed
Auto merge of #161571 - mu001999-contrib:dead-code-refactor, r=<try>
Refactor the `#[allow(dead_code)]` propagation for impl items of traits
2 parents da51146 + d32f000 commit e466d2a

4 files changed

Lines changed: 195 additions & 82 deletions

File tree

compiler/rustc_passes/src/dead.rs

Lines changed: 141 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};
@@ -113,6 +113,11 @@ struct WorkItem {
113113
own: ComesFromAllowExpect,
114114
}
115115

116+
enum ImplItemCheckResult {
117+
Live(ComesFromAllowExpect),
118+
Dead { require: LocalDefId },
119+
}
120+
116121
struct MarkSymbolVisitor<'tcx> {
117122
worklist: Vec<WorkItem>,
118123
tcx: TyCtxt<'tcx>,
@@ -128,6 +133,7 @@ struct MarkSymbolVisitor<'tcx> {
128133
// macro)
129134
ignored_derived_traits: LocalDefIdMap<FxIndexSet<DefId>>,
130135
propagated_comes_from_allow_expect: ComesFromAllowExpect,
136+
unsolved_items: Vec<LocalDefId>,
131137
}
132138

133139
impl<'tcx> MarkSymbolVisitor<'tcx> {
@@ -418,6 +424,8 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
418424

419425
if !self.scanned.insert((id, propagated)) {
420426
continue;
427+
} else if propagated == ComesFromAllowExpect::No {
428+
self.scanned.insert((id, ComesFromAllowExpect::Yes));
421429
}
422430

423431
// Avoid accessing the HIR for the synthesized associated type generated for RPITITs.
@@ -544,13 +552,17 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
544552
/// `local_def_id` points to an impl or an impl item,
545553
/// both impl and impl item that may be passed to this function are of a trait,
546554
/// and added into the unsolved_items during `create_and_seed_worklist`
547-
fn check_impl_or_impl_item_live(&mut self, local_def_id: LocalDefId) -> bool {
555+
fn check_impl_or_impl_item_live(
556+
&self,
557+
local_def_id: LocalDefId,
558+
defer_seeds_come_from_allow: bool,
559+
) -> ImplItemCheckResult {
548560
let (impl_block_id, trait_def_id) = match self.tcx.def_kind(local_def_id) {
549561
// assoc impl items of traits are live if the corresponding trait items are live
550562
DefKind::AssocConst { .. } | DefKind::AssocTy | DefKind::AssocFn => {
551-
let trait_item_id =
563+
let trait_def_id =
552564
self.tcx.trait_item_of(local_def_id).and_then(|def_id| def_id.as_local());
553-
(self.tcx.local_parent(local_def_id), trait_item_id)
565+
(self.tcx.local_parent(local_def_id), trait_def_id)
554566
}
555567
// impl items are live if the corresponding traits are live
556568
DefKind::Impl { of_trait: true } => {
@@ -559,10 +571,22 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
559571
_ => bug!(),
560572
};
561573

562-
if let Some(trait_def_id) = trait_def_id
563-
&& !self.live_symbols.contains(&trait_def_id)
564-
{
565-
return false;
574+
let mut trait_comes_from_allow = None;
575+
if let Some(trait_def_id) = trait_def_id {
576+
if defer_seeds_come_from_allow {
577+
if !self.live_symbols.contains(&trait_def_id) {
578+
return ImplItemCheckResult::Dead { require: trait_def_id };
579+
}
580+
} else {
581+
trait_comes_from_allow = has_allow_dead_code_or_lang_attr(self.tcx, trait_def_id);
582+
583+
if !self.live_symbols.contains(&trait_def_id) {
584+
return match trait_comes_from_allow {
585+
Some(comes_from_allow) => ImplItemCheckResult::Live(comes_from_allow),
586+
None => ImplItemCheckResult::Dead { require: trait_def_id },
587+
};
588+
}
589+
}
566590
}
567591

568592
// The impl or impl item is used if the corresponding trait or trait item is used and the ty is used.
@@ -571,10 +595,93 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
571595
&& let Some(adt_def_id) = adt.did().as_local()
572596
&& !self.live_symbols.contains(&adt_def_id)
573597
{
574-
return false;
598+
if defer_seeds_come_from_allow {
599+
return ImplItemCheckResult::Dead { require: adt_def_id };
600+
}
601+
602+
return match trait_comes_from_allow {
603+
Some(comes_from_allow) => ImplItemCheckResult::Live(comes_from_allow),
604+
None => ImplItemCheckResult::Dead { require: adt_def_id },
605+
};
606+
}
607+
608+
ImplItemCheckResult::Live(ComesFromAllowExpect::No)
609+
}
610+
611+
fn collect_live_items_from_unsolved_items(
612+
&mut self,
613+
defer_seeds_come_from_allow: bool,
614+
unsolved_items: Vec<LocalDefId>,
615+
unsolved_map: &mut FxHashMap<LocalDefId, Vec<LocalDefId>>,
616+
) -> Vec<(LocalDefId, ComesFromAllowExpect)> {
617+
let mut items_to_check = vec![];
618+
619+
for def_id in unsolved_items {
620+
match self.check_impl_or_impl_item_live(def_id, defer_seeds_come_from_allow) {
621+
ImplItemCheckResult::Live(comes_from_allow) => {
622+
items_to_check.push((def_id, comes_from_allow));
623+
}
624+
ImplItemCheckResult::Dead { require } => {
625+
unsolved_map.entry(require).or_default().push(def_id);
626+
}
627+
}
628+
}
629+
items_to_check
630+
}
631+
632+
#[expect(
633+
rustc::potential_query_instability,
634+
reason = "The order of the unsolved items is not important, so we can just collect them into a vector."
635+
)]
636+
fn mark_live_symbols_and_ignored_derived_traits(
637+
&mut self,
638+
defer_seeds_come_from_allow: bool,
639+
) -> Result<(), ErrorGuaranteed> {
640+
if let ControlFlow::Break(guar) = self.mark_live_symbols() {
641+
return Err(guar);
642+
}
643+
644+
// We have marked the primary seeds as live. We now need to process unsolved items from traits
645+
// and trait impls: add them to the work list if the trait or the implemented type is live.
646+
let unsolved_items = std::mem::take(&mut self.unsolved_items);
647+
let mut unsolved_map = FxHashMap::default();
648+
let mut items_to_check = self.collect_live_items_from_unsolved_items(
649+
defer_seeds_come_from_allow,
650+
unsolved_items,
651+
&mut unsolved_map,
652+
);
653+
654+
while !items_to_check.is_empty() {
655+
self.worklist.extend(items_to_check.into_iter().map(|(id, comes_from_allow)| {
656+
let own = if defer_seeds_come_from_allow {
657+
ComesFromAllowExpect::No
658+
} else {
659+
has_allow_dead_code_or_lang_attr(self.tcx, id)
660+
.unwrap_or(ComesFromAllowExpect::No)
661+
};
662+
663+
WorkItem { id, propagated: comes_from_allow, own }
664+
}));
665+
if let ControlFlow::Break(guar) = self.mark_live_symbols() {
666+
return Err(guar);
667+
}
668+
669+
let unsolved_items = unsolved_map
670+
.extract_if(|require, _| self.live_symbols.contains(require))
671+
.map(|(_, items)| items)
672+
.flatten()
673+
.collect();
674+
675+
items_to_check = self.collect_live_items_from_unsolved_items(
676+
defer_seeds_come_from_allow,
677+
unsolved_items,
678+
&mut unsolved_map,
679+
);
575680
}
576681

577-
true
682+
self.unsolved_items = unsolved_map.values().into_iter().cloned().flatten().collect();
683+
684+
Ok(())
578685
}
579686
}
580687

@@ -849,19 +956,6 @@ fn maybe_record_as_seed<'tcx>(
849956
match tcx.def_kind(parent) {
850957
DefKind::Impl { of_trait: false } | DefKind::Trait => {}
851958
DefKind::Impl { of_trait: true } => {
852-
if let Some(trait_item_def_id) =
853-
tcx.associated_item(owner_id.def_id).trait_item_def_id()
854-
&& let Some(trait_item_local_def_id) = trait_item_def_id.as_local()
855-
&& let Some(comes_from_allow) =
856-
has_allow_dead_code_or_lang_attr(tcx, trait_item_local_def_id)
857-
{
858-
push_into_worklist(WorkItem {
859-
id: owner_id.def_id,
860-
propagated: comes_from_allow,
861-
own: comes_from_allow,
862-
});
863-
}
864-
865959
// We only care about associated items of traits,
866960
// because they cannot be visited directly,
867961
// so we later mark them as live if their corresponding traits
@@ -873,22 +967,8 @@ fn maybe_record_as_seed<'tcx>(
873967
}
874968
}
875969
}
876-
DefKind::Impl { of_trait: true } => {
877-
if allow_dead_code.is_none() {
878-
if let Some(trait_def_id) =
879-
tcx.impl_trait_ref(owner_id.def_id).skip_binder().def_id.as_local()
880-
&& let Some(comes_from_allow) =
881-
has_allow_dead_code_or_lang_attr(tcx, trait_def_id)
882-
{
883-
push_into_worklist(WorkItem {
884-
id: owner_id.def_id,
885-
propagated: comes_from_allow,
886-
own: comes_from_allow,
887-
});
888-
}
889-
890-
unsolved_items.push(owner_id.def_id);
891-
}
970+
DefKind::Impl { of_trait: true } if allow_dead_code.is_none() => {
971+
unsolved_items.push(owner_id.def_id);
892972
}
893973
DefKind::GlobalAsm => {
894974
// global_asm! is always live.
@@ -914,15 +994,21 @@ fn maybe_record_as_seed<'tcx>(
914994
}
915995
}
916996

997+
#[derive(Default)]
998+
struct DeferredSeeds {
999+
pub_reachables: Vec<WorkItem>,
1000+
come_from_allow: Vec<WorkItem>,
1001+
}
1002+
9171003
struct SeedWorklists {
9181004
worklist: Vec<WorkItem>,
919-
deferred_seeds: Vec<WorkItem>,
1005+
deferred_seeds: DeferredSeeds,
9201006
unsolved_items: Vec<LocalDefId>,
9211007
}
9221008

9231009
fn create_and_seed_worklist(tcx: TyCtxt<'_>) -> SeedWorklists {
9241010
let mut unsolved_items = Vec::new();
925-
let mut deferred_seeds = Vec::new();
1011+
let mut deferred_seeds = DeferredSeeds::default();
9261012
let mut worklist = Vec::new();
9271013

9281014
if let Some((def_id, _)) = tcx.entry_fn(())
@@ -952,7 +1038,7 @@ fn create_and_seed_worklist(tcx: TyCtxt<'_>) -> SeedWorklists {
9521038

9531039
for (id, effective_vis) in tcx.effective_visibilities(()).iter() {
9541040
if effective_vis.is_public_at_level(Level::Reachable) {
955-
deferred_seeds.push(WorkItem {
1041+
deferred_seeds.pub_reachables.push(WorkItem {
9561042
id: *id,
9571043
propagated: ComesFromAllowExpect::No,
9581044
own: ComesFromAllowExpect::No,
@@ -961,7 +1047,7 @@ fn create_and_seed_worklist(tcx: TyCtxt<'_>) -> SeedWorklists {
9611047
}
9621048

9631049
let mut push_into_worklist = |work_item: WorkItem| match work_item.own {
964-
ComesFromAllowExpect::Yes => deferred_seeds.push(work_item),
1050+
ComesFromAllowExpect::Yes => deferred_seeds.come_from_allow.push(work_item),
9651051
ComesFromAllowExpect::No => worklist.push(work_item),
9661052
};
9671053
let crate_items = tcx.hir_crate_items(());
@@ -976,8 +1062,7 @@ fn live_symbols_and_ignored_derived_traits(
9761062
tcx: TyCtxt<'_>,
9771063
(): (),
9781064
) -> Result<DeadCodeLivenessSummary, ErrorGuaranteed> {
979-
let SeedWorklists { worklist, deferred_seeds, mut unsolved_items } =
980-
create_and_seed_worklist(tcx);
1065+
let SeedWorklists { worklist, deferred_seeds, unsolved_items } = create_and_seed_worklist(tcx);
9811066
let mut symbol_visitor = MarkSymbolVisitor {
9821067
worklist,
9831068
tcx,
@@ -990,15 +1075,23 @@ fn live_symbols_and_ignored_derived_traits(
9901075
ignore_variant_stack: vec![],
9911076
ignored_derived_traits: Default::default(),
9921077
propagated_comes_from_allow_expect: ComesFromAllowExpect::No,
1078+
unsolved_items,
9931079
};
994-
mark_live_symbols_and_ignored_derived_traits(&mut symbol_visitor, &mut unsolved_items)?;
1080+
symbol_visitor.mark_live_symbols_and_ignored_derived_traits(true)?;
9951081
let pre_deferred_seeding = DeadCodeLivenessSnapshot {
9961082
live_symbols: symbol_visitor.live_symbols.clone(),
9971083
ignored_derived_traits: symbol_visitor.ignored_derived_traits.clone(),
9981084
};
9991085

1000-
symbol_visitor.worklist.extend(deferred_seeds);
1001-
mark_live_symbols_and_ignored_derived_traits(&mut symbol_visitor, &mut unsolved_items)?;
1086+
if !deferred_seeds.pub_reachables.is_empty() {
1087+
symbol_visitor.worklist.extend(deferred_seeds.pub_reachables);
1088+
symbol_visitor.mark_live_symbols_and_ignored_derived_traits(true)?;
1089+
}
1090+
1091+
if !deferred_seeds.come_from_allow.is_empty() {
1092+
symbol_visitor.worklist.extend(deferred_seeds.come_from_allow);
1093+
symbol_visitor.mark_live_symbols_and_ignored_derived_traits(false)?;
1094+
}
10021095

10031096
Ok(DeadCodeLivenessSummary {
10041097
pre_deferred_seeding,
@@ -1009,40 +1102,6 @@ fn live_symbols_and_ignored_derived_traits(
10091102
})
10101103
}
10111104

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

0 commit comments

Comments
 (0)