Skip to content

Commit cc7aa4d

Browse files
committed
Detect pub structs never constructed and unused associated constants in traits
1 parent 21e6de7 commit cc7aa4d

32 files changed

+258
-89
lines changed

compiler/rustc_passes/src/dead.rs

+79-29
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};
18+
use rustc_middle::ty::{self, AssocItemContainer, TyCtxt};
1919
use rustc_middle::{bug, span_bug};
2020
use rustc_session::lint;
2121
use rustc_session::lint::builtin::DEAD_CODE;
@@ -44,16 +44,50 @@ fn should_explore(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
4444
)
4545
}
4646

47-
fn ty_ref_to_pub_struct(tcx: TyCtxt<'_>, ty: &hir::Ty<'_>) -> bool {
47+
fn struct_all_fields_are_public(tcx: TyCtxt<'_>, id: DefId) -> bool {
48+
// treat PhantomData and positional ZST as public,
49+
// we don't want to lint types which only have them,
50+
// cause it's a common way to use such types to check things like well-formedness
51+
tcx.adt_def(id).all_fields().all(|field| {
52+
let field_type = tcx.type_of(field.did).instantiate_identity();
53+
if field_type.is_phantom_data() {
54+
return true;
55+
}
56+
let is_positional = field.name.as_str().starts_with(|c: char| c.is_ascii_digit());
57+
if is_positional
58+
&& tcx
59+
.layout_of(tcx.param_env(field.did).and(field_type))
60+
.map_or(true, |layout| layout.is_zst())
61+
{
62+
return true;
63+
}
64+
field.vis.is_public()
65+
})
66+
}
67+
68+
/// check struct is public or not
69+
fn ty_ref_to_pub_struct(
70+
tcx: TyCtxt<'_>,
71+
ty: &hir::Ty<'_>,
72+
) -> (bool, /* public and all fields are public */ bool) {
4873
if let TyKind::Path(hir::QPath::Resolved(_, path)) = ty.kind
4974
&& let Res::Def(def_kind, def_id) = path.res
5075
&& def_id.is_local()
51-
&& matches!(def_kind, DefKind::Struct | DefKind::Enum | DefKind::Union)
5276
{
53-
tcx.visibility(def_id).is_public()
54-
} else {
55-
true
77+
return match def_kind {
78+
DefKind::Enum | DefKind::Union => {
79+
let ty_is_public = tcx.visibility(def_id).is_public();
80+
(ty_is_public, ty_is_public)
81+
}
82+
DefKind::Struct => {
83+
let ty_is_public = tcx.visibility(def_id).is_public();
84+
(ty_is_public, ty_is_public && struct_all_fields_are_public(tcx, def_id))
85+
}
86+
_ => (true, true),
87+
};
5688
}
89+
90+
(true, true)
5791
}
5892

5993
/// Determine if a work from the worklist is coming from the a `#[allow]`
@@ -426,10 +460,11 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
426460
self.tcx.hir().expect_item(local_impl_id).kind
427461
{
428462
if matches!(trait_item.kind, hir::TraitItemKind::Fn(..))
429-
&& !ty_ref_to_pub_struct(self.tcx, impl_ref.self_ty)
463+
&& !ty_ref_to_pub_struct(self.tcx, impl_ref.self_ty).1
430464
{
431-
// skip methods of private ty,
432-
// they would be solved in `solve_rest_impl_items`
465+
// skip impl-items of non pure pub ty,
466+
// cause we don't know the ty is constructed or not,
467+
// check these later in `solve_rest_impl_items`
433468
continue;
434469
}
435470

@@ -510,22 +545,21 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
510545
&& let Some(local_def_id) = def_id.as_local()
511546
&& matches!(def_kind, DefKind::Struct | DefKind::Enum | DefKind::Union)
512547
{
513-
if self.tcx.visibility(impl_item_id).is_public() {
514-
// for the public method, we don't know the trait item is used or not,
515-
// so we mark the method live if the self is used
516-
return self.live_symbols.contains(&local_def_id);
517-
}
518-
519548
if let Some(trait_item_id) = self.tcx.associated_item(impl_item_id).trait_item_def_id
520549
&& let Some(local_id) = trait_item_id.as_local()
521550
{
522-
// for the private method, we can know the trait item is used or not,
551+
// for the local impl item, we can know the trait item is used or not,
523552
// so we mark the method live if the self is used and the trait item is used
524-
return self.live_symbols.contains(&local_id)
525-
&& self.live_symbols.contains(&local_def_id);
553+
self.live_symbols.contains(&local_id) && self.live_symbols.contains(&local_def_id)
554+
} else {
555+
// for the foreign method and inherent pub method,
556+
// we don't know the trait item or the method is used or not,
557+
// so we mark the method live if the self is used
558+
self.live_symbols.contains(&local_def_id)
526559
}
560+
} else {
561+
false
527562
}
528-
false
529563
}
530564
}
531565

@@ -747,7 +781,8 @@ fn check_item<'tcx>(
747781
.iter()
748782
.filter_map(|def_id| def_id.as_local());
749783

750-
let ty_is_pub = ty_ref_to_pub_struct(tcx, tcx.hir().item(id).expect_impl().self_ty);
784+
let self_ty = tcx.hir().item(id).expect_impl().self_ty;
785+
let (ty_is_pub, ty_is_pure_pub) = ty_ref_to_pub_struct(tcx, self_ty);
751786

752787
// And we access the Map here to get HirId from LocalDefId
753788
for local_def_id in local_def_ids {
@@ -763,18 +798,20 @@ fn check_item<'tcx>(
763798
// for trait impl blocks,
764799
// mark the method live if the self_ty is public,
765800
// or the method is public and may construct self
766-
if of_trait
767-
&& (!matches!(tcx.def_kind(local_def_id), DefKind::AssocFn)
768-
|| tcx.visibility(local_def_id).is_public()
769-
&& (ty_is_pub || may_construct_self))
801+
if of_trait && matches!(tcx.def_kind(local_def_id), DefKind::AssocTy)
802+
|| tcx.visibility(local_def_id).is_public()
803+
&& (ty_is_pure_pub || may_construct_self)
770804
{
805+
// if the impl item is public,
806+
// and the ty may be constructed or can be constructed in foreign crates,
807+
// mark the impl item live
771808
worklist.push((local_def_id, ComesFromAllowExpect::No));
772809
} else if let Some(comes_from_allow) =
773810
has_allow_dead_code_or_lang_attr(tcx, local_def_id)
774811
{
775812
worklist.push((local_def_id, comes_from_allow));
776-
} else if of_trait {
777-
// private method || public method not constructs self
813+
} else if of_trait || tcx.visibility(local_def_id).is_public() && ty_is_pub {
814+
// private impl items of traits || public impl items not constructs self
778815
unsolved_impl_items.push((id, local_def_id));
779816
}
780817
}
@@ -841,6 +878,14 @@ fn create_and_seed_worklist(
841878
effective_vis
842879
.is_public_at_level(Level::Reachable)
843880
.then_some(id)
881+
.filter(|&id|
882+
// checks impls, impl-items and pub structs with all public fields later
883+
match tcx.def_kind(id) {
884+
DefKind::Impl { .. } => false,
885+
DefKind::AssocConst | DefKind::AssocFn => !matches!(tcx.associated_item(id).container, AssocItemContainer::ImplContainer),
886+
DefKind::Struct => struct_all_fields_are_public(tcx, id.to_def_id()),
887+
_ => true
888+
})
844889
.map(|id| (id, ComesFromAllowExpect::No))
845890
})
846891
// Seed entry point
@@ -1113,10 +1158,15 @@ fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalModDefId) {
11131158
|| (def_kind == DefKind::Trait && live_symbols.contains(&item.owner_id.def_id))
11141159
{
11151160
for &def_id in tcx.associated_item_def_ids(item.owner_id.def_id) {
1116-
// We have diagnosed unused methods in traits
1161+
// We have diagnosed unused assoc consts and fns in traits
11171162
if matches!(def_kind, DefKind::Impl { of_trait: true })
1118-
&& tcx.def_kind(def_id) == DefKind::AssocFn
1119-
|| def_kind == DefKind::Trait && tcx.def_kind(def_id) != DefKind::AssocFn
1163+
&& matches!(tcx.def_kind(def_id), DefKind::AssocConst | DefKind::AssocFn)
1164+
// skip unused public inherent methods,
1165+
// cause we have diagnosed unconstructed struct
1166+
|| matches!(def_kind, DefKind::Impl { of_trait: false })
1167+
&& tcx.visibility(def_id).is_public()
1168+
&& ty_ref_to_pub_struct(tcx, tcx.hir().item(item).expect_impl().self_ty).0
1169+
|| def_kind == DefKind::Trait && tcx.def_kind(def_id) == DefKind::AssocTy
11201170
{
11211171
continue;
11221172
}

tests/codegen-units/item-collection/generic-impl.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,17 @@ impl<T> Struct<T> {
2424
}
2525
}
2626

27-
pub struct LifeTimeOnly<'a> {
27+
pub struct _LifeTimeOnly<'a> {
2828
_a: &'a u32
2929
}
3030

31-
impl<'a> LifeTimeOnly<'a> {
31+
impl<'a> _LifeTimeOnly<'a> {
3232

33-
//~ MONO_ITEM fn LifeTimeOnly::<'_>::foo
33+
//~ MONO_ITEM fn _LifeTimeOnly::<'_>::foo
3434
pub fn foo(&self) {}
35-
//~ MONO_ITEM fn LifeTimeOnly::<'_>::bar
35+
//~ MONO_ITEM fn _LifeTimeOnly::<'_>::bar
3636
pub fn bar(&'a self) {}
37-
//~ MONO_ITEM fn LifeTimeOnly::<'_>::baz
37+
//~ MONO_ITEM fn _LifeTimeOnly::<'_>::baz
3838
pub fn baz<'b>(&'b self) {}
3939

4040
pub fn non_instantiated<T>(&self) {}

tests/codegen-units/item-collection/overloaded-operators.rs

+12-12
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55

66
use std::ops::{Index, IndexMut, Add, Deref};
77

8-
pub struct Indexable {
8+
pub struct _Indexable {
99
data: [u8; 3]
1010
}
1111

12-
impl Index<usize> for Indexable {
12+
impl Index<usize> for _Indexable {
1313
type Output = u8;
1414

15-
//~ MONO_ITEM fn <Indexable as std::ops::Index<usize>>::index
15+
//~ MONO_ITEM fn <_Indexable as std::ops::Index<usize>>::index
1616
fn index(&self, index: usize) -> &Self::Output {
1717
if index >= 3 {
1818
&self.data[0]
@@ -22,8 +22,8 @@ impl Index<usize> for Indexable {
2222
}
2323
}
2424

25-
impl IndexMut<usize> for Indexable {
26-
//~ MONO_ITEM fn <Indexable as std::ops::IndexMut<usize>>::index_mut
25+
impl IndexMut<usize> for _Indexable {
26+
//~ MONO_ITEM fn <_Indexable as std::ops::IndexMut<usize>>::index_mut
2727
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
2828
if index >= 3 {
2929
&mut self.data[0]
@@ -34,25 +34,25 @@ impl IndexMut<usize> for Indexable {
3434
}
3535

3636

37-
//~ MONO_ITEM fn <Equatable as std::cmp::PartialEq>::eq
38-
//~ MONO_ITEM fn <Equatable as std::cmp::PartialEq>::ne
37+
//~ MONO_ITEM fn <_Equatable as std::cmp::PartialEq>::eq
38+
//~ MONO_ITEM fn <_Equatable as std::cmp::PartialEq>::ne
3939
#[derive(PartialEq)]
40-
pub struct Equatable(u32);
40+
pub struct _Equatable(u32);
4141

4242

43-
impl Add<u32> for Equatable {
43+
impl Add<u32> for _Equatable {
4444
type Output = u32;
4545

46-
//~ MONO_ITEM fn <Equatable as std::ops::Add<u32>>::add
46+
//~ MONO_ITEM fn <_Equatable as std::ops::Add<u32>>::add
4747
fn add(self, rhs: u32) -> u32 {
4848
self.0 + rhs
4949
}
5050
}
5151

52-
impl Deref for Equatable {
52+
impl Deref for _Equatable {
5353
type Target = u32;
5454

55-
//~ MONO_ITEM fn <Equatable as std::ops::Deref>::deref
55+
//~ MONO_ITEM fn <_Equatable as std::ops::Deref>::deref
5656
fn deref(&self) -> &Self::Target {
5757
&self.0
5858
}

tests/ui/coherence/re-rebalance-coherence.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
extern crate re_rebalance_coherence_lib as lib;
55
use lib::*;
66

7+
#[allow(dead_code)]
78
struct Oracle;
89
impl Backend for Oracle {}
910
impl<'a, T:'a, Tab> QueryFragment<Oracle> for BatchInsert<'a, T, Tab> {}

tests/ui/const-generics/defaults/repr-c-issue-82792.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
//@ run-pass
44

5+
#[allow(dead_code)]
56
#[repr(C)]
67
pub struct Loaf<T: Sized, const N: usize = 1> {
78
head: [T; N],

tests/ui/const-generics/generic_const_exprs/associated-consts.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ impl BlockCipher for BarCipher {
1616
const BLOCK_SIZE: usize = 32;
1717
}
1818

19-
pub struct Block<C>(#[allow(dead_code)] C);
19+
#[allow(dead_code)]
20+
pub struct Block<C>(C);
2021

2122
pub fn test<C: BlockCipher, const M: usize>()
2223
where

tests/ui/const-generics/transparent-maybeunit-array-wrapper.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use std::mem::MaybeUninit;
88

9+
#[allow(dead_code)]
910
#[repr(transparent)]
1011
pub struct MaybeUninitWrapper<const N: usize>(MaybeUninit<[u64; N]>);
1112

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#![forbid(dead_code)]
22

33
#[derive(Debug)]
4-
pub struct Whatever {
4+
pub struct Whatever { //~ ERROR struct `Whatever` is never constructed
55
pub field0: (),
6-
field1: (), //~ ERROR fields `field1`, `field2`, `field3`, and `field4` are never read
6+
field1: (),
77
field2: (),
88
field3: (),
99
field4: (),

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

+3-13
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,9 @@
1-
error: fields `field1`, `field2`, `field3`, and `field4` are never read
2-
--> $DIR/clone-debug-dead-code-in-the-same-struct.rs:6:5
1+
error: struct `Whatever` is never constructed
2+
--> $DIR/clone-debug-dead-code-in-the-same-struct.rs:4:12
33
|
44
LL | pub struct Whatever {
5-
| -------- fields in this struct
6-
LL | pub field0: (),
7-
LL | field1: (),
8-
| ^^^^^^
9-
LL | field2: (),
10-
| ^^^^^^
11-
LL | field3: (),
12-
| ^^^^^^
13-
LL | field4: (),
14-
| ^^^^^^
5+
| ^^^^^^^^
156
|
16-
= note: `Whatever` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
177
note: the lint level is defined here
188
--> $DIR/clone-debug-dead-code-in-the-same-struct.rs:1:11
199
|

tests/ui/issues/issue-5708.rs

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ pub trait MyTrait<T> {
4444
fn dummy(&self, t: T) -> T { panic!() }
4545
}
4646

47+
#[allow(dead_code)]
4748
pub struct MyContainer<'a, T:'a> {
4849
foos: Vec<&'a (dyn MyTrait<T>+'a)> ,
4950
}

tests/ui/lint/dead-code/lint-dead-code-1.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,10 @@ struct SemiUsedStruct;
4646
impl SemiUsedStruct {
4747
fn la_la_la() {}
4848
}
49-
struct StructUsedAsField;
49+
struct StructUsedAsField; //~ ERROR struct `StructUsedAsField` is never constructed
5050
pub struct StructUsedInEnum;
5151
struct StructUsedInGeneric;
52-
pub struct PubStruct2 {
53-
#[allow(dead_code)]
52+
pub struct PubStruct2 { //~ ERROR struct `PubStruct2` is never constructed
5453
struct_used_as_field: *const StructUsedAsField
5554
}
5655

0 commit comments

Comments
 (0)