Skip to content

Commit bd1f09d

Browse files
committed
Annotate dead code lint with notes about ignored derived impls
1 parent 2e2c86e commit bd1f09d

File tree

4 files changed

+74
-6
lines changed

4 files changed

+74
-6
lines changed

compiler/rustc_passes/src/dead.rs

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// from live codes are live, and everything else is dead.
44

55
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
6+
use rustc_errors::pluralize;
67
use rustc_hir as hir;
78
use rustc_hir::def::{CtorOf, DefKind, Res};
89
use rustc_hir::def_id::{DefId, LocalDefId};
@@ -47,6 +48,8 @@ struct MarkSymbolVisitor<'tcx> {
4748
ignore_variant_stack: Vec<DefId>,
4849
// maps from tuple struct constructors to tuple struct items
4950
struct_constructors: FxHashMap<LocalDefId, LocalDefId>,
51+
// maps from ADTs to ignored derived traits (e.g. Debug and Clone)
52+
ignored_derived_traits: FxHashMap<DefId, Vec<DefId>>,
5053
}
5154

5255
impl<'tcx> MarkSymbolVisitor<'tcx> {
@@ -242,14 +245,22 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
242245
/// Automatically generated items marked with `rustc_trivial_field_reads`
243246
/// will be ignored for the purposes of dead code analysis (see PR #85200
244247
/// for discussion).
245-
fn should_ignore_item(&self, def_id: DefId) -> bool {
248+
fn should_ignore_item(&mut self, def_id: DefId) -> bool {
246249
if let Some(impl_of) = self.tcx.impl_of_method(def_id) {
247250
if !self.tcx.has_attr(impl_of, sym::automatically_derived) {
248251
return false;
249252
}
250253

251254
if let Some(trait_of) = self.tcx.trait_id_of_impl(impl_of) {
252255
if self.tcx.has_attr(trait_of, sym::rustc_trivial_field_reads) {
256+
let trait_ref = self.tcx.impl_trait_ref(impl_of).unwrap();
257+
if let ty::Adt(adt_def, _) = trait_ref.self_ty().kind() {
258+
if let Some(v) = self.ignored_derived_traits.get_mut(&adt_def.did) {
259+
v.push(trait_of);
260+
} else {
261+
self.ignored_derived_traits.insert(adt_def.did, vec![trait_of]);
262+
}
263+
}
253264
return true;
254265
}
255266
}
@@ -577,7 +588,7 @@ fn create_and_seed_worklist<'tcx>(
577588
fn find_live<'tcx>(
578589
tcx: TyCtxt<'tcx>,
579590
access_levels: &privacy::AccessLevels,
580-
) -> FxHashSet<LocalDefId> {
591+
) -> (FxHashSet<LocalDefId>, FxHashMap<DefId, Vec<DefId>>) {
581592
let (worklist, struct_constructors) = create_and_seed_worklist(tcx, access_levels);
582593
let mut symbol_visitor = MarkSymbolVisitor {
583594
worklist,
@@ -590,14 +601,16 @@ fn find_live<'tcx>(
590601
pub_visibility: false,
591602
ignore_variant_stack: vec![],
592603
struct_constructors,
604+
ignored_derived_traits: FxHashMap::default(),
593605
};
594606
symbol_visitor.mark_live_symbols();
595-
symbol_visitor.live_symbols
607+
(symbol_visitor.live_symbols, symbol_visitor.ignored_derived_traits)
596608
}
597609

598610
struct DeadVisitor<'tcx> {
599611
tcx: TyCtxt<'tcx>,
600612
live_symbols: FxHashSet<LocalDefId>,
613+
ignored_derived_traits: FxHashMap<DefId, Vec<DefId>>,
601614
}
602615

603616
impl<'tcx> DeadVisitor<'tcx> {
@@ -666,7 +679,34 @@ impl<'tcx> DeadVisitor<'tcx> {
666679
self.tcx.struct_span_lint_hir(lint::builtin::DEAD_CODE, id, span, |lint| {
667680
let def_id = self.tcx.hir().local_def_id(id);
668681
let descr = self.tcx.def_kind(def_id).descr(def_id.to_def_id());
669-
lint.build(&format!("{} is never {}: `{}`", descr, participle, name)).emit()
682+
let mut err = lint.build(&format!("{} is never {}: `{}`", descr, participle, name));
683+
let hir = self.tcx.hir();
684+
if let Some(encl_scope) = hir.get_enclosing_scope(id) {
685+
if let Some(encl_def_id) = hir.opt_local_def_id(encl_scope) {
686+
if let Some(ign_traits) = self.ignored_derived_traits.get(&encl_def_id.to_def_id()) {
687+
let traits_str = ign_traits
688+
.iter()
689+
.map(|t| format!("`{}`", self.tcx.item_name(*t))).collect::<Vec<_>>()
690+
.join(" and ");
691+
let plural_s = pluralize!(ign_traits.len());
692+
let article = if ign_traits.len() > 1 { "" } else { "a " };
693+
let is_are = if ign_traits.len() > 1 { "these are" } else { "this is" };
694+
let msg = format!("`{}` has {}derived impl{} for the trait{} {}, but {} ignored during dead code analysis",
695+
self.tcx.item_name(encl_def_id.to_def_id()),
696+
article,
697+
plural_s,
698+
plural_s,
699+
traits_str,
700+
is_are);
701+
if let Some(span) = self.tcx.def_ident_span(encl_def_id) {
702+
err.span_note(span, &msg);
703+
} else {
704+
err.note(&msg);
705+
}
706+
}
707+
}
708+
}
709+
err.emit();
670710
});
671711
}
672712
}
@@ -796,7 +836,7 @@ impl<'tcx> Visitor<'tcx> for DeadVisitor<'tcx> {
796836

797837
pub fn check_crate(tcx: TyCtxt<'_>) {
798838
let access_levels = &tcx.privacy_access_levels(());
799-
let live_symbols = find_live(tcx, access_levels);
800-
let mut visitor = DeadVisitor { tcx, live_symbols };
839+
let (live_symbols, ignored_derived_traits) = find_live(tcx, access_levels);
840+
let mut visitor = DeadVisitor { tcx, live_symbols, ignored_derived_traits };
801841
tcx.hir().walk_toplevel_module(&mut visitor);
802842
}

src/test/ui/derive-uninhabited-enum-38885.stderr

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ LL | Void(Void),
55
| ^^^^^^^^^^
66
|
77
= note: `-W dead-code` implied by `-W unused`
8+
note: `Foo` has a derived impl for the trait `Debug`, but this is ignored during dead code analysis
9+
--> $DIR/derive-uninhabited-enum-38885.rs:11:6
10+
|
11+
LL | enum Foo {
12+
| ^^^
813

914
warning: 1 warning emitted
1015

src/test/ui/derives/clone-debug-dead-code.stderr

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,36 @@ error: field is never read: `f`
1515
|
1616
LL | struct B { f: () }
1717
| ^^^^^
18+
|
19+
note: `B` has a derived impl for the trait `Clone`, but this is ignored during dead code analysis
20+
--> $DIR/clone-debug-dead-code.rs:10:8
21+
|
22+
LL | struct B { f: () }
23+
| ^
1824

1925
error: field is never read: `f`
2026
--> $DIR/clone-debug-dead-code.rs:14:12
2127
|
2228
LL | struct C { f: () }
2329
| ^^^^^
30+
|
31+
note: `C` has a derived impl for the trait `Debug`, but this is ignored during dead code analysis
32+
--> $DIR/clone-debug-dead-code.rs:14:8
33+
|
34+
LL | struct C { f: () }
35+
| ^
2436

2537
error: field is never read: `f`
2638
--> $DIR/clone-debug-dead-code.rs:18:12
2739
|
2840
LL | struct D { f: () }
2941
| ^^^^^
42+
|
43+
note: `D` has derived impls for the traits `Clone` and `Debug`, but these are ignored during dead code analysis
44+
--> $DIR/clone-debug-dead-code.rs:18:8
45+
|
46+
LL | struct D { f: () }
47+
| ^
3048

3149
error: field is never read: `f`
3250
--> $DIR/clone-debug-dead-code.rs:21:12

src/test/ui/lint/dead-code/unused-variant.stderr

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ note: the lint level is defined here
99
|
1010
LL | #![deny(dead_code)]
1111
| ^^^^^^^^^
12+
note: `Enum` has a derived impl for the trait `Clone`, but this is ignored during dead code analysis
13+
--> $DIR/unused-variant.rs:4:6
14+
|
15+
LL | enum Enum {
16+
| ^^^^
1217

1318
error: aborting due to previous error
1419

0 commit comments

Comments
 (0)