Skip to content

Commit 9a70585

Browse files
committed
Make traits / trait methods detected by the dead code lint
1 parent b2e73e9 commit 9a70585

File tree

3 files changed

+135
-3
lines changed

3 files changed

+135
-3
lines changed

compiler/rustc_passes/src/dead.rs

+58-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// is dead.
55

66
use hir::def_id::{LocalDefIdMap, LocalDefIdSet};
7+
use hir::AssocItemKind;
78
use itertools::Itertools;
89
use rustc_data_structures::unord::UnordSet;
910
use rustc_errors::MultiSpan;
@@ -379,6 +380,17 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
379380
_ => intravisit::walk_item(self, item),
380381
},
381382
Node::TraitItem(trait_item) => {
383+
// FIXME: could we get all impl terms for the trait item?
384+
// then we can add them into worklist when the trait item is live,
385+
// so that we won't lose any lost chain in the impl terms.
386+
// this also allows us to lint such code:
387+
// ```rust
388+
// struct UnusedStruct; //~ WARN
389+
// trait UnusedTrait { //~ WARN
390+
// fn foo() {}
391+
// }
392+
// impl UnusedTrait for UnusedStruct { fn foo() {} }
393+
// ```
382394
intravisit::walk_trait_item(self, trait_item);
383395
}
384396
Node::ImplItem(impl_item) => {
@@ -627,7 +639,20 @@ fn check_item<'tcx>(
627639
}
628640
}
629641
DefKind::Impl { of_trait } => {
630-
if of_trait {
642+
// lints unused struct and traits after 2024, e.g.,
643+
// ```rust
644+
// #[derive(Debug)]
645+
// struct Unused; //~ WARN
646+
//
647+
// trait Foo {} //~ WARN
648+
// impl Foo for () {}
649+
// ```
650+
// but we still cannot lint unused traits whose impl blocks have methods:
651+
// ```rust
652+
// trait Foo { fn foo(); }
653+
// impl Foo for () { fn foo() {} }
654+
// ```
655+
if of_trait && !tcx.hir().item(id).span.source_callsite().at_least_rust_2024() {
631656
worklist.push((id.owner_id.def_id, ComesFromAllowExpect::No));
632657
}
633658

@@ -640,6 +665,8 @@ fn check_item<'tcx>(
640665
// And we access the Map here to get HirId from LocalDefId
641666
for id in local_def_ids {
642667
if of_trait {
668+
// FIXME: not push impl item into worklist by default,
669+
// pushed when corresponding trait items are reachable.
643670
worklist.push((id, ComesFromAllowExpect::No));
644671
} else if let Some(comes_from_allow) = has_allow_dead_code_or_lang_attr(tcx, id) {
645672
worklist.push((id, comes_from_allow));
@@ -939,6 +966,13 @@ impl<'tcx> DeadVisitor<'tcx> {
939966
| DefKind::Enum
940967
| DefKind::Union
941968
| DefKind::ForeignTy => self.warn_dead_code(def_id, "used"),
969+
DefKind::Trait => {
970+
if let Some(Node::Item(item)) = self.tcx.hir().find_by_def_id(def_id)
971+
&& item.span.at_least_rust_2024()
972+
{
973+
self.warn_dead_code(def_id, "used")
974+
}
975+
}
942976
DefKind::Struct => self.warn_dead_code(def_id, "constructed"),
943977
DefKind::Variant | DefKind::Field => bug!("should be handled specially"),
944978
_ => {}
@@ -967,7 +1001,24 @@ fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalModDefId) {
9671001
let mut dead_items = Vec::new();
9681002
for item in impl_item.items {
9691003
let def_id = item.id.owner_id.def_id;
970-
if !visitor.is_live_code(def_id) {
1004+
let is_dead_code = if !visitor.is_live_code(def_id) {
1005+
true
1006+
} else if item.span.edition().at_least_rust_2024() // temporary
1007+
&& let AssocItemKind::Fn { .. } = item.kind
1008+
&& let Some(local_def_id) = item.trait_item_def_id.and_then(DefId::as_local)
1009+
&& !visitor.is_live_code(local_def_id)
1010+
&& has_allow_dead_code_or_lang_attr(tcx, local_def_id).is_none()
1011+
{
1012+
// lint methods in impl if we are sure the corresponding methods in trait are dead,
1013+
// but the chain of dead code within the methods in impl would be lost.
1014+
1015+
// FIXME: the better way is to mark trait items and corresponding impl items active,
1016+
// then the rests are dead, which requires the above FIXME at line 383
1017+
true
1018+
} else {
1019+
false
1020+
};
1021+
if is_dead_code {
9711022
let name = tcx.item_name(def_id.to_def_id());
9721023
let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
9731024
let level = tcx.lint_level_at_node(lint::builtin::DEAD_CODE, hir_id).0;
@@ -1041,7 +1092,11 @@ fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalModDefId) {
10411092
visitor.check_definition(foreign_item.owner_id.def_id);
10421093
}
10431094

1044-
// We do not warn trait items.
1095+
for trait_item in module_items.trait_items() {
1096+
if tcx.hir().trait_item(trait_item).span.at_least_rust_2024() {
1097+
visitor.check_definition(trait_item.owner_id.def_id);
1098+
}
1099+
}
10451100
}
10461101

10471102
pub(crate) fn provide(providers: &mut Providers) {
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// edition: 2024
2+
// compile-flags: -Zunstable-options
3+
#![deny(dead_code)]
4+
5+
enum Category {
6+
Dead, //~ ERROR variant `Dead` is never constructed
7+
Used,
8+
}
9+
10+
trait Demo {
11+
fn this_is_unused(&self) -> Category { //~ ERROR method `this_is_unused` is never used
12+
Category::Dead
13+
}
14+
}
15+
16+
impl Demo for () {
17+
fn this_is_unused(&self) -> Category { //~ ERROR method `this_is_unused` is never used
18+
Category::Used
19+
}
20+
}
21+
22+
impl UnusedTrait for () {}
23+
24+
trait UnusedTrait {} //~ ERROR trait `UnusedTrait` is never used
25+
26+
mod private {
27+
#[derive(Debug)]
28+
struct UnusedStruct; //~ ERROR struct `UnusedStruct` is never constructed
29+
}
30+
31+
fn main() {
32+
let _c = Category::Used;
33+
}
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
error: variant `Dead` is never constructed
2+
--> $DIR/issue-41883.rs:6:5
3+
|
4+
LL | enum Category {
5+
| -------- variant in this enum
6+
LL | Dead,
7+
| ^^^^
8+
|
9+
note: the lint level is defined here
10+
--> $DIR/issue-41883.rs:3:9
11+
|
12+
LL | #![deny(dead_code)]
13+
| ^^^^^^^^^
14+
15+
error: method `this_is_unused` is never used
16+
--> $DIR/issue-41883.rs:17:8
17+
|
18+
LL | impl Demo for () {
19+
| ---------------- method in this implementation
20+
LL | fn this_is_unused(&self) -> Category {
21+
| ^^^^^^^^^^^^^^
22+
23+
error: trait `UnusedTrait` is never used
24+
--> $DIR/issue-41883.rs:24:7
25+
|
26+
LL | trait UnusedTrait {}
27+
| ^^^^^^^^^^^
28+
29+
error: method `this_is_unused` is never used
30+
--> $DIR/issue-41883.rs:11:8
31+
|
32+
LL | fn this_is_unused(&self) -> Category {
33+
| ^^^^^^^^^^^^^^
34+
35+
error: struct `UnusedStruct` is never constructed
36+
--> $DIR/issue-41883.rs:28:12
37+
|
38+
LL | struct UnusedStruct;
39+
| ^^^^^^^^^^^^
40+
|
41+
= note: `UnusedStruct` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
42+
43+
error: aborting due to 5 previous errors
44+

0 commit comments

Comments
 (0)