Skip to content

Commit 3d0eaf7

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

File tree

5 files changed

+69
-84
lines changed

5 files changed

+69
-84
lines changed

compiler/rustc_passes/src/dead.rs

+43-63
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// is dead.
55

66
use hir::def_id::{LocalDefIdMap, LocalDefIdSet};
7-
use hir::AssocItemKind;
7+
use hir::ItemKind;
88
use itertools::Itertools;
99
use rustc_data_structures::unord::UnordSet;
1010
use rustc_errors::MultiSpan;
@@ -16,7 +16,7 @@ use rustc_hir::{Node, PatKind, TyKind};
1616
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
1717
use rustc_middle::middle::privacy::Level;
1818
use rustc_middle::query::Providers;
19-
use rustc_middle::ty::{self, TyCtxt};
19+
use rustc_middle::ty::{self, TyCtxt, Visibility};
2020
use rustc_session::lint;
2121
use rustc_span::symbol::{sym, Symbol};
2222
use rustc_target::abi::FieldIdx;
@@ -377,20 +377,42 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
377377
intravisit::walk_item(self, item)
378378
}
379379
hir::ItemKind::ForeignMod { .. } => {}
380+
hir::ItemKind::Trait(..) => {
381+
// mark dependent traits live
382+
for impl_def_id in self.tcx.all_impls(item.owner_id.to_def_id()) {
383+
if let Some(local_def_id) = impl_def_id.as_local()
384+
&& let ItemKind::Impl(impl_ref) =
385+
self.tcx.hir().expect_item(local_def_id).kind
386+
{
387+
intravisit::walk_generics(self, impl_ref.generics);
388+
}
389+
}
390+
391+
intravisit::walk_item(self, item)
392+
}
380393
_ => intravisit::walk_item(self, item),
381394
},
382395
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-
// ```
396+
// mark corresponing ImplTerm live
397+
let def_id = trait_item.owner_id.to_def_id();
398+
if let Some(trait_def_id) = self.tcx.trait_of_item(def_id) {
399+
// for assoc fn without self, mark the trait live
400+
self.check_def_id(trait_def_id);
401+
402+
for impl_def in self.tcx.all_impls(trait_def_id) {
403+
if let Some(impl_def_id) = impl_def.as_local()
404+
&& let ItemKind::Impl(impl_ref) =
405+
self.tcx.hir().expect_item(impl_def_id).kind
406+
{
407+
self.check_def_id(impl_def);
408+
for impl_item in impl_ref.items {
409+
if Some(def_id) == impl_item.trait_item_def_id {
410+
self.check_def_id(impl_item.id.owner_id.to_def_id());
411+
}
412+
}
413+
}
414+
}
415+
}
394416
intravisit::walk_trait_item(self, trait_item);
395417
}
396418
Node::ImplItem(impl_item) => {
@@ -639,23 +661,6 @@ fn check_item<'tcx>(
639661
}
640662
}
641663
DefKind::Impl { 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() {
656-
worklist.push((id.owner_id.def_id, ComesFromAllowExpect::No));
657-
}
658-
659664
// get DefIds from another query
660665
let local_def_ids = tcx
661666
.associated_item_def_ids(id.owner_id)
@@ -664,9 +669,9 @@ fn check_item<'tcx>(
664669

665670
// And we access the Map here to get HirId from LocalDefId
666671
for id in local_def_ids {
667-
if of_trait {
668-
// FIXME: not push impl item into worklist by default,
669-
// pushed when corresponding trait items are reachable.
672+
if tcx.local_visibility(id) == Visibility::Public
673+
|| of_trait && !matches!(tcx.def_kind(id), DefKind::AssocFn)
674+
{
670675
worklist.push((id, ComesFromAllowExpect::No));
671676
} else if let Some(comes_from_allow) = has_allow_dead_code_or_lang_attr(tcx, id) {
672677
worklist.push((id, comes_from_allow));
@@ -697,7 +702,7 @@ fn check_trait_item(
697702
use hir::TraitItemKind::{Const, Fn};
698703
if matches!(tcx.def_kind(id.owner_id), DefKind::AssocConst | DefKind::AssocFn) {
699704
let trait_item = tcx.hir().trait_item(id);
700-
if matches!(trait_item.kind, Const(_, Some(_)) | Fn(_, hir::TraitFn::Provided(_)))
705+
if matches!(trait_item.kind, Const(_, Some(_)) | Fn(..))
701706
&& let Some(comes_from_allow) =
702707
has_allow_dead_code_or_lang_attr(tcx, trait_item.owner_id.def_id)
703708
{
@@ -965,14 +970,8 @@ impl<'tcx> DeadVisitor<'tcx> {
965970
| DefKind::TyAlias
966971
| DefKind::Enum
967972
| DefKind::Union
968-
| 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-
}
973+
| DefKind::ForeignTy
974+
| DefKind::Trait => self.warn_dead_code(def_id, "used"),
976975
DefKind::Struct => self.warn_dead_code(def_id, "constructed"),
977976
DefKind::Variant | DefKind::Field => bug!("should be handled specially"),
978977
_ => {}
@@ -1001,24 +1000,7 @@ fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalModDefId) {
10011000
let mut dead_items = Vec::new();
10021001
for item in impl_item.items {
10031002
let def_id = item.id.owner_id.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 {
1003+
if !visitor.is_live_code(def_id) {
10221004
let name = tcx.item_name(def_id.to_def_id());
10231005
let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
10241006
let level = tcx.lint_level_at_node(lint::builtin::DEAD_CODE, hir_id).0;
@@ -1093,9 +1075,7 @@ fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalModDefId) {
10931075
}
10941076

10951077
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-
}
1078+
visitor.check_definition(trait_item.owner_id.def_id);
10991079
}
11001080
}
11011081

library/core/src/fmt/num.rs

+2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ trait DisplayInt:
1515
fn zero() -> Self;
1616
fn from_u8(u: u8) -> Self;
1717
fn to_u8(&self) -> u8;
18+
#[allow(dead_code)]
1819
fn to_u16(&self) -> u16;
20+
#[allow(dead_code)]
1921
fn to_u32(&self) -> u32;
2022
fn to_u64(&self) -> u64;
2123
fn to_u128(&self) -> u128;

library/std/src/error.rs

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mod private {
1717
// implementations, since that can enable unsound downcasting.
1818
#[unstable(feature = "error_type_id", issue = "60784")]
1919
#[derive(Debug)]
20+
#[allow(dead_code)]
2021
pub struct Internal;
2122
}
2223

tests/ui/lint/dead-code/issue-41883.rs

+4-8
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,24 @@
1-
// edition: 2024
2-
// compile-flags: -Zunstable-options
31
#![deny(dead_code)]
42

53
enum Category {
64
Dead, //~ ERROR variant `Dead` is never constructed
75
Used,
86
}
97

10-
trait Demo {
8+
trait UnusedTrait { //~ ERROR trait `UnusedTrait` is never used
119
fn this_is_unused(&self) -> Category { //~ ERROR method `this_is_unused` is never used
1210
Category::Dead
1311
}
1412
}
1513

16-
impl Demo for () {
14+
struct UnusedStruct; //~ ERROR struct `UnusedStruct` is never constructed
15+
16+
impl UnusedTrait for UnusedStruct {
1717
fn this_is_unused(&self) -> Category { //~ ERROR method `this_is_unused` is never used
1818
Category::Used
1919
}
2020
}
2121

22-
impl UnusedTrait for () {}
23-
24-
trait UnusedTrait {} //~ ERROR trait `UnusedTrait` is never used
25-
2622
mod private {
2723
#[derive(Debug)]
2824
struct UnusedStruct; //~ ERROR struct `UnusedStruct` is never constructed
+19-13
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,50 @@
11
error: variant `Dead` is never constructed
2-
--> $DIR/issue-41883.rs:6:5
2+
--> $DIR/issue-41883.rs:4:5
33
|
44
LL | enum Category {
55
| -------- variant in this enum
66
LL | Dead,
77
| ^^^^
88
|
99
note: the lint level is defined here
10-
--> $DIR/issue-41883.rs:3:9
10+
--> $DIR/issue-41883.rs:1:9
1111
|
1212
LL | #![deny(dead_code)]
1313
| ^^^^^^^^^
1414

15+
error: trait `UnusedTrait` is never used
16+
--> $DIR/issue-41883.rs:8:7
17+
|
18+
LL | trait UnusedTrait {
19+
| ^^^^^^^^^^^
20+
21+
error: struct `UnusedStruct` is never constructed
22+
--> $DIR/issue-41883.rs:14:8
23+
|
24+
LL | struct UnusedStruct;
25+
| ^^^^^^^^^^^^
26+
1527
error: method `this_is_unused` is never used
1628
--> $DIR/issue-41883.rs:17:8
1729
|
18-
LL | impl Demo for () {
19-
| ---------------- method in this implementation
30+
LL | impl UnusedTrait for UnusedStruct {
31+
| --------------------------------- method in this implementation
2032
LL | fn this_is_unused(&self) -> Category {
2133
| ^^^^^^^^^^^^^^
2234

23-
error: trait `UnusedTrait` is never used
24-
--> $DIR/issue-41883.rs:24:7
25-
|
26-
LL | trait UnusedTrait {}
27-
| ^^^^^^^^^^^
28-
2935
error: method `this_is_unused` is never used
30-
--> $DIR/issue-41883.rs:11:8
36+
--> $DIR/issue-41883.rs:9:8
3137
|
3238
LL | fn this_is_unused(&self) -> Category {
3339
| ^^^^^^^^^^^^^^
3440

3541
error: struct `UnusedStruct` is never constructed
36-
--> $DIR/issue-41883.rs:28:12
42+
--> $DIR/issue-41883.rs:24:12
3743
|
3844
LL | struct UnusedStruct;
3945
| ^^^^^^^^^^^^
4046
|
4147
= note: `UnusedStruct` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
4248

43-
error: aborting due to 5 previous errors
49+
error: aborting due to 6 previous errors
4450

0 commit comments

Comments
 (0)