Skip to content

Commit 895dd41

Browse files
committed
Detect unused struct impls pub trait
1 parent ef32456 commit 895dd41

9 files changed

+129
-10
lines changed

compiler/rustc_passes/src/dead.rs

+94-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use rustc_hir::{Node, PatKind, TyKind};
1515
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
1616
use rustc_middle::middle::privacy::Level;
1717
use rustc_middle::query::Providers;
18-
use rustc_middle::ty::{self, TyCtxt, Visibility};
18+
use rustc_middle::ty::{self, TyCtxt};
1919
use rustc_session::lint;
2020
use rustc_session::lint::builtin::DEAD_CODE;
2121
use rustc_span::symbol::{sym, Symbol};
@@ -45,6 +45,18 @@ fn should_explore(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
4545
)
4646
}
4747

48+
fn ty_ref_to_pub_struct(tcx: TyCtxt<'_>, ty: &hir::Ty<'_>) -> bool {
49+
if let TyKind::Path(hir::QPath::Resolved(_, path)) = ty.kind
50+
&& let Res::Def(def_kind, def_id) = path.res
51+
&& def_id.is_local()
52+
&& matches!(def_kind, DefKind::Struct | DefKind::Enum | DefKind::Union)
53+
{
54+
tcx.visibility(def_id).is_public()
55+
} else {
56+
true
57+
}
58+
}
59+
4860
/// Determine if a work from the worklist is coming from the a `#[allow]`
4961
/// or a `#[expect]` of `dead_code`
5062
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
@@ -415,6 +427,13 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
415427
&& let ItemKind::Impl(impl_ref) =
416428
self.tcx.hir().expect_item(local_impl_id).kind
417429
{
430+
if self.tcx.visibility(trait_id).is_public()
431+
&& matches!(trait_item.kind, hir::TraitItemKind::Fn(..))
432+
&& !ty_ref_to_pub_struct(self.tcx, impl_ref.self_ty)
433+
{
434+
continue;
435+
}
436+
418437
// mark self_ty live
419438
intravisit::walk_ty(self, impl_ref.self_ty);
420439
if let Some(&impl_item_id) =
@@ -683,12 +702,23 @@ fn check_item<'tcx>(
683702
.iter()
684703
.filter_map(|def_id| def_id.as_local());
685704

705+
let ty_is_pub = ty_ref_to_pub_struct(tcx, tcx.hir().item(id).expect_impl().self_ty);
706+
686707
// And we access the Map here to get HirId from LocalDefId
687708
for id in local_def_ids {
709+
// check the function may construct Self
710+
let mut may_construct_self = true;
711+
if let Some(hir_id) = tcx.opt_local_def_id_to_hir_id(id)
712+
&& let Some(fn_sig) = tcx.hir().fn_sig_by_hir_id(hir_id)
713+
{
714+
may_construct_self =
715+
matches!(fn_sig.decl.implicit_self, hir::ImplicitSelfKind::None);
716+
}
717+
688718
// for impl trait blocks, mark associate functions live if the trait is public
689719
if of_trait
690720
&& (!matches!(tcx.def_kind(id), DefKind::AssocFn)
691-
|| tcx.local_visibility(id) == Visibility::Public)
721+
|| tcx.visibility(id).is_public() && (ty_is_pub || may_construct_self))
692722
{
693723
worklist.push((id, ComesFromAllowExpect::No));
694724
} else if let Some(comes_from_allow) = has_allow_dead_code_or_lang_attr(tcx, id) {
@@ -712,6 +742,49 @@ fn check_item<'tcx>(
712742
}
713743
}
714744

745+
fn check_pub_impl_fns_of_constructed_self<'tcx>(
746+
tcx: TyCtxt<'tcx>,
747+
worklist: &mut Vec<(LocalDefId, ComesFromAllowExpect)>,
748+
id: hir::ItemId,
749+
live_symbols: &UnordSet<LocalDefId>,
750+
) {
751+
let allow_dead_code = has_allow_dead_code_or_lang_attr(tcx, id.owner_id.def_id);
752+
if let Some(comes_from_allow) = allow_dead_code {
753+
worklist.push((id.owner_id.def_id, comes_from_allow));
754+
}
755+
756+
match tcx.def_kind(id.owner_id) {
757+
DefKind::Impl { of_trait: true } => {
758+
let mut self_is_constructed = false;
759+
if let TyKind::Path(hir::QPath::Resolved(_, path)) =
760+
tcx.hir().item(id).expect_impl().self_ty.kind
761+
&& let Res::Def(def_kind, def_id) = path.res
762+
&& let Some(local_def_id) = def_id.as_local()
763+
&& matches!(def_kind, DefKind::Struct | DefKind::Enum | DefKind::Union)
764+
{
765+
self_is_constructed = live_symbols.contains(&local_def_id);
766+
}
767+
768+
// get DefIds from another query
769+
let local_def_ids = tcx
770+
.associated_item_def_ids(id.owner_id)
771+
.iter()
772+
.filter_map(|def_id| def_id.as_local());
773+
774+
// And we access the Map here to get HirId from LocalDefId
775+
for id in local_def_ids {
776+
// for impl trait blocks, mark associate functions live if the trait is public
777+
if !matches!(tcx.def_kind(id), DefKind::AssocFn)
778+
|| (tcx.visibility(id).is_public() && self_is_constructed)
779+
{
780+
worklist.push((id, ComesFromAllowExpect::No));
781+
}
782+
}
783+
}
784+
_ => {}
785+
}
786+
}
787+
715788
fn check_trait_item(
716789
tcx: TyCtxt<'_>,
717790
worklist: &mut Vec<(LocalDefId, ComesFromAllowExpect)>,
@@ -778,6 +851,20 @@ fn create_and_seed_worklist(
778851
(worklist, struct_constructors)
779852
}
780853

854+
fn create_worklist_for_pub_impl_fns_of_constructed_self(
855+
tcx: TyCtxt<'_>,
856+
live_symbols: &UnordSet<LocalDefId>,
857+
) -> Vec<(LocalDefId, ComesFromAllowExpect)> {
858+
let mut worklist = Vec::new();
859+
860+
let crate_items = tcx.hir_crate_items(());
861+
for id in crate_items.items() {
862+
check_pub_impl_fns_of_constructed_self(tcx, &mut worklist, id, live_symbols);
863+
}
864+
865+
worklist
866+
}
867+
781868
fn live_symbols_and_ignored_derived_traits(
782869
tcx: TyCtxt<'_>,
783870
(): (),
@@ -796,6 +883,11 @@ fn live_symbols_and_ignored_derived_traits(
796883
ignored_derived_traits: Default::default(),
797884
};
798885
symbol_visitor.mark_live_symbols();
886+
887+
symbol_visitor.worklist =
888+
create_worklist_for_pub_impl_fns_of_constructed_self(tcx, &symbol_visitor.live_symbols);
889+
symbol_visitor.mark_live_symbols();
890+
799891
(symbol_visitor.live_symbols, symbol_visitor.ignored_derived_traits)
800892
}
801893

tests/ui/derives/clone-debug-dead-code-in-the-same-struct.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ LL | field3: (),
1313
LL | field4: (),
1414
| ^^^^^^
1515
|
16-
= note: `Whatever` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
16+
= note: `Whatever` has derived impls for the traits `Debug` and `Debug`, but these are intentionally ignored during dead code analysis
1717
note: the lint level is defined here
1818
--> $DIR/clone-debug-dead-code-in-the-same-struct.rs:1:11
1919
|

tests/ui/issues/issue-33187.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//@ run-pass
1+
//@ check-pass
22

33
struct Foo<A: Repr>(<A as Repr>::Data);
44

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

-2
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ error: struct `UnusedStruct` is never constructed
2929
|
3030
LL | struct UnusedStruct;
3131
| ^^^^^^^^^^^^
32-
|
33-
= note: `UnusedStruct` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
3432

3533
error: aborting due to 4 previous errors
3634

tests/ui/lint/dead-code/multiple-dead-codes-in-the-same-struct.stderr

-2
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ warning: struct `Foo` is never constructed
5656
|
5757
LL | struct Foo(usize, #[allow(unused)] usize);
5858
| ^^^
59-
|
60-
= note: `Foo` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
6159

6260
error: aborting due to 2 previous errors; 2 warnings emitted
6361

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#![deny(dead_code)]
2+
3+
enum Foo {} //~ ERROR enum `Foo` is never used
4+
5+
impl Clone for Foo {
6+
fn clone(&self) -> Foo { loop {} }
7+
}
8+
9+
pub trait PubTrait {
10+
fn unused_method(&self) -> Self;
11+
}
12+
13+
impl PubTrait for Foo {
14+
fn unused_method(&self) -> Foo { loop {} }
15+
}
16+
17+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: enum `Foo` is never used
2+
--> $DIR/unused-adt-impls-pub-trait.rs:3:6
3+
|
4+
LL | enum Foo {}
5+
| ^^^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/unused-adt-impls-pub-trait.rs:1:9
9+
|
10+
LL | #![deny(dead_code)]
11+
| ^^^^^^^^^
12+
13+
error: aborting due to 1 previous error
14+

tests/ui/rust-2018/uniform-paths/issue-55779.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//@ run-pass
1+
//@ check-pass
22
//@ edition:2018
33
//@ aux-crate:issue_55779_extern_trait=issue-55779-extern-trait.rs
44

tests/ui/traits/augmented-assignments-trait.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//@ run-pass
1+
//@ check-pass
22
use std::ops::AddAssign;
33

44
struct Int(#[allow(dead_code)] i32);

0 commit comments

Comments
 (0)