From b04d643ef1a48ebff145735e6933c39c67195a46 Mon Sep 17 00:00:00 2001 From: mu001999 Date: Wed, 5 Nov 2025 23:05:32 +0800 Subject: [PATCH] Lint unused associated types --- compiler/rustc_passes/src/dead.rs | 111 ++++++++++++++---- .../ui/associated-type-bounds/union-bounds.rs | 2 +- .../ui/associated-types/impl-wf-cycle-5.fixed | 2 +- tests/ui/associated-types/impl-wf-cycle-5.rs | 2 +- .../ui/associated-types/impl-wf-cycle-6.fixed | 2 +- tests/ui/associated-types/impl-wf-cycle-6.rs | 2 +- .../generic-associated-types/collections.rs | 2 +- tests/ui/lint/dead-code/unused-assoc-ty.rs | 71 +++++++++++ .../ui/lint/dead-code/unused-assoc-ty.stderr | 17 +++ .../lint/dead-code/used-assoc-ty-in-struct.rs | 16 +++ .../lint/dead-code/used-assoc-ty-on-const.rs | 19 +++ .../lint/dead-code/used-assoc-ty-on-impl.rs | 23 ++++ .../lint/dead-code/used-inherent-assoc-ty.rs | 12 ++ tests/ui/sanitizer/cfi/sized-associated-ty.rs | 2 +- tests/ui/traits/issue-38033.rs | 3 +- tests/ui/traits/issue-38033.stderr | 8 +- 16 files changed, 261 insertions(+), 33 deletions(-) create mode 100644 tests/ui/lint/dead-code/unused-assoc-ty.rs create mode 100644 tests/ui/lint/dead-code/unused-assoc-ty.stderr create mode 100644 tests/ui/lint/dead-code/used-assoc-ty-in-struct.rs create mode 100644 tests/ui/lint/dead-code/used-assoc-ty-on-const.rs create mode 100644 tests/ui/lint/dead-code/used-assoc-ty-on-impl.rs create mode 100644 tests/ui/lint/dead-code/used-inherent-assoc-ty.rs diff --git a/compiler/rustc_passes/src/dead.rs b/compiler/rustc_passes/src/dead.rs index 6d34587684bb3..741097507843e 100644 --- a/compiler/rustc_passes/src/dead.rs +++ b/compiler/rustc_passes/src/dead.rs @@ -17,7 +17,7 @@ use rustc_hir::{self as hir, Node, PatKind, QPath}; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::middle::privacy::Level; use rustc_middle::query::Providers; -use rustc_middle::ty::{self, AssocTag, TyCtxt}; +use rustc_middle::ty::{self, AssocTag, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor}; use rustc_middle::{bug, span_bug}; use rustc_session::lint::builtin::DEAD_CODE; use rustc_session::lint::{self, LintExpectationId}; @@ -110,6 +110,56 @@ impl<'tcx> MarkSymbolVisitor<'tcx> { } } + fn check_assoc_ty(&mut self, def_id: LocalDefId) { + let def_kind = self.tcx.def_kind(def_id); + + match def_kind { + // Check `T::Ty` used in types of inputs and output + DefKind::Fn | DefKind::AssocFn => { + let fn_sig = self.tcx.fn_sig(def_id).instantiate_identity(); + for ty in fn_sig.inputs().skip_binder() { + self.visit_middle_ty(ty.clone()); + } + self.visit_middle_ty(fn_sig.output().skip_binder().clone()); + } + // Check `T::Ty` used in assoc type + DefKind::AssocTy => { + if matches!(self.tcx.def_kind(self.tcx.local_parent(def_id)), DefKind::Impl { .. }) + || matches!( + self.tcx.hir_expect_trait_item(def_id).kind, + hir::TraitItemKind::Type(_, Some(_)) + ) + { + self.visit_middle_ty(self.tcx.type_of(def_id).instantiate_identity()); + } + } + // Check `T::Ty` used in assoc const's type + DefKind::AssocConst => { + self.visit_middle_ty(self.tcx.type_of(def_id).instantiate_identity()); + } + _ => (), + } + + if matches!( + def_kind, + DefKind::Struct + | DefKind::Union + | DefKind::Enum + | DefKind::Fn + | DefKind::Const + | DefKind::Trait + | DefKind::Impl { .. } + | DefKind::AssocFn + | DefKind::AssocTy + | DefKind::AssocConst + ) { + let preds = self.tcx.predicates_of(def_id).instantiate_identity(self.tcx); + for pred in preds.iter() { + >>::visit_predicate(self, pred.0.as_predicate()); + } + } + } + fn insert_def_id(&mut self, def_id: DefId) { if let Some(def_id) = def_id.as_local() { debug_assert!(!should_explore(self.tcx, def_id)); @@ -119,12 +169,6 @@ impl<'tcx> MarkSymbolVisitor<'tcx> { fn handle_res(&mut self, res: Res) { match res { - Res::Def( - DefKind::Const | DefKind::AssocConst | DefKind::AssocTy | DefKind::TyAlias, - def_id, - ) => { - self.check_def_id(def_id); - } Res::PrimTy(..) | Res::SelfCtor(..) | Res::Local(..) => {} Res::Def(DefKind::Ctor(CtorOf::Variant, ..), ctor_def_id) => { // Using a variant in patterns should not make the variant live, @@ -376,6 +420,7 @@ impl<'tcx> MarkSymbolVisitor<'tcx> { continue; } + self.check_assoc_ty(id); self.visit_node(self.tcx.hir_node_by_def_id(id))?; } @@ -429,15 +474,6 @@ impl<'tcx> MarkSymbolVisitor<'tcx> { intravisit::walk_item(self, item) } hir::ItemKind::ForeignMod { .. } => ControlFlow::Continue(()), - hir::ItemKind::Trait(.., trait_item_refs) => { - // mark assoc ty live if the trait is live - for trait_item in trait_item_refs { - if matches!(self.tcx.def_kind(trait_item.owner_id), DefKind::AssocTy) { - self.check_def_id(trait_item.owner_id.to_def_id()); - } - } - intravisit::walk_item(self, item) - } _ => intravisit::walk_item(self, item), }, Node::TraitItem(trait_item) => { @@ -523,6 +559,10 @@ impl<'tcx> MarkSymbolVisitor<'tcx> { true } + + fn visit_middle_ty(&mut self, ty: Ty<'tcx>) { + >>::visit_ty(self, ty); + } } impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> { @@ -600,6 +640,9 @@ impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> { _ => (), } + // Check `T::Ty` used in the type of `expr` + self.visit_middle_ty(self.typeck_results().expr_ty(expr)); + intravisit::walk_expr(self, expr) } @@ -687,16 +730,16 @@ impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> { && let Some(segment) = t.path.segments.last() && let Some(args) = segment.args { + // Mark assoc items used if they are constrained in the trait ref for constraint in args.constraints { if let Some(local_def_id) = self .tcx .associated_items(trait_def_id) - .find_by_ident_and_kind( - self.tcx, - constraint.ident, - AssocTag::Const, - trait_def_id, - ) + .filter_by_name_unhygienic(constraint.ident.name) + .filter(|item| matches!(item.as_tag(), AssocTag::Const | AssocTag::Type)) + .find(|item| { + self.tcx.hygienic_eq(constraint.ident, item.ident(self.tcx), trait_def_id) + }) .and_then(|item| item.def_id.as_local()) { self.worklist.push((local_def_id, ComesFromAllowExpect::No)); @@ -706,6 +749,30 @@ impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> { intravisit::walk_trait_ref(self, t) } + + fn visit_field_def(&mut self, field_def: &'tcx hir::FieldDef<'tcx>) -> Self::Result { + // Check `T::Ty` used in field's type, marks assoc types live whether the field is used or not + // Consider that there would be three situations: + // 1. `field` is used, it's good + // 2. `field` is not used but marked like `#[allow(dead_code)]`, + // it's annoying to mark the assoc type `#[allow(dead_code)]` again + // 3. `field` is not used and will be linted, + // the assoc type will be linted after removing the unused field + self.visit_middle_ty(self.tcx.type_of(field_def.def_id).instantiate_identity()); + intravisit::walk_field_def(self, field_def) + } +} + +impl<'tcx> TypeVisitor> for MarkSymbolVisitor<'tcx> { + fn visit_ty(&mut self, ty: Ty<'tcx>) { + match ty.kind() { + ty::Alias(_, alias) => { + self.check_def_id(alias.def_id); + } + _ => (), + } + ty.super_visit_with(self); + } } fn has_allow_dead_code_or_lang_attr( diff --git a/tests/ui/associated-type-bounds/union-bounds.rs b/tests/ui/associated-type-bounds/union-bounds.rs index b9b92a96fb009..be726eee6e8d2 100644 --- a/tests/ui/associated-type-bounds/union-bounds.rs +++ b/tests/ui/associated-type-bounds/union-bounds.rs @@ -4,7 +4,7 @@ trait Tr1: Copy { type As1: Copy; } trait Tr2: Copy { type As2: Copy; } -trait Tr3: Copy { type As3: Copy; } +trait Tr3: Copy { #[allow(dead_code)] type As3: Copy; } trait Tr4<'a>: Copy { type As4: Copy; } trait Tr5: Copy { type As5: Copy; } diff --git a/tests/ui/associated-types/impl-wf-cycle-5.fixed b/tests/ui/associated-types/impl-wf-cycle-5.fixed index 1c2c0811a5006..62f2db590c2ba 100644 --- a/tests/ui/associated-types/impl-wf-cycle-5.fixed +++ b/tests/ui/associated-types/impl-wf-cycle-5.fixed @@ -11,7 +11,7 @@ impl Fiz for bool {} trait Grault { type A; - type B; + #[allow(dead_code)] type B; } impl Grault for () { diff --git a/tests/ui/associated-types/impl-wf-cycle-5.rs b/tests/ui/associated-types/impl-wf-cycle-5.rs index 88a0b762c37bf..9cc2e34a64a28 100644 --- a/tests/ui/associated-types/impl-wf-cycle-5.rs +++ b/tests/ui/associated-types/impl-wf-cycle-5.rs @@ -11,7 +11,7 @@ impl Fiz for bool {} trait Grault { type A; - type B; + #[allow(dead_code)] type B; } impl Grault for () { diff --git a/tests/ui/associated-types/impl-wf-cycle-6.fixed b/tests/ui/associated-types/impl-wf-cycle-6.fixed index ce98b9c2f02a5..997f9a5b6c77a 100644 --- a/tests/ui/associated-types/impl-wf-cycle-6.fixed +++ b/tests/ui/associated-types/impl-wf-cycle-6.fixed @@ -11,7 +11,7 @@ impl Fiz for bool {} trait Grault { type A; - type B; + #[allow(dead_code)] type B; } impl Grault for () { diff --git a/tests/ui/associated-types/impl-wf-cycle-6.rs b/tests/ui/associated-types/impl-wf-cycle-6.rs index a05ffcd6b4c3e..9be8a60325996 100644 --- a/tests/ui/associated-types/impl-wf-cycle-6.rs +++ b/tests/ui/associated-types/impl-wf-cycle-6.rs @@ -11,7 +11,7 @@ impl Fiz for bool {} trait Grault { type A; - type B; + #[allow(dead_code)] type B; } impl Grault for () { diff --git a/tests/ui/generic-associated-types/collections.rs b/tests/ui/generic-associated-types/collections.rs index 7239d226927df..33803bb664530 100644 --- a/tests/ui/generic-associated-types/collections.rs +++ b/tests/ui/generic-associated-types/collections.rs @@ -10,7 +10,7 @@ trait Collection { type Iter<'iter>: Iterator where T: 'iter, Self: 'iter; type Family: CollectionFamily; // Test associated type defaults with parameters - type Sibling: Collection = + #[allow(dead_code)] type Sibling: Collection = <>::Family as CollectionFamily>::Member; fn empty() -> Self; diff --git a/tests/ui/lint/dead-code/unused-assoc-ty.rs b/tests/ui/lint/dead-code/unused-assoc-ty.rs new file mode 100644 index 0000000000000..f90f1b275add6 --- /dev/null +++ b/tests/ui/lint/dead-code/unused-assoc-ty.rs @@ -0,0 +1,71 @@ +#![deny(dead_code)] + +trait Tr { + type A; + type B; + type C: Default; + type D; + type E; + type F; + type G; + type H; //~ ERROR associated type `H` is never used +} + +impl Tr for i32 { + type A = Self; + type B = Self; + type C = Self; + type D = Self; + type E = Self; + type F = Self; + type G = Self; + type H = Self; +} + +trait Tr2 { + type A; +} + +impl Tr2 for i32 { + type A = Self; +} + +struct Foo { + _x: T::A, +} + +struct Bar { + _x: T +} + +impl Bar +where + T: Tr2 +{} + +type Baz = ::G; + +struct FooBaz { + _x: Baz, +} + +fn foo(t: impl Tr) -> impl Tr +where + T::B: Copy +{ + let _a: T::C = Default::default(); + baz::(); + t +} +fn bar() {} +fn baz() {} + +fn main() { + foo::(42); + bar::>(); + let _d: ::D = Default::default(); + baz::<::E>(); + baz::>(); + baz::>(); + baz::>(); +} diff --git a/tests/ui/lint/dead-code/unused-assoc-ty.stderr b/tests/ui/lint/dead-code/unused-assoc-ty.stderr new file mode 100644 index 0000000000000..a027615ad1fbb --- /dev/null +++ b/tests/ui/lint/dead-code/unused-assoc-ty.stderr @@ -0,0 +1,17 @@ +error: associated type `H` is never used + --> $DIR/unused-assoc-ty.rs:11:10 + | +LL | trait Tr { + | -- associated type in this trait +... +LL | type H; + | ^ + | +note: the lint level is defined here + --> $DIR/unused-assoc-ty.rs:1:9 + | +LL | #![deny(dead_code)] + | ^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/lint/dead-code/used-assoc-ty-in-struct.rs b/tests/ui/lint/dead-code/used-assoc-ty-in-struct.rs new file mode 100644 index 0000000000000..3512a4c9669b8 --- /dev/null +++ b/tests/ui/lint/dead-code/used-assoc-ty-in-struct.rs @@ -0,0 +1,16 @@ +//@ check-pass + +#![deny(dead_code)] + +trait Trait { type Ty; } + +impl Trait for () { type Ty = (); } + +pub struct Wrap(Inner<()>); +struct Inner(T::Ty); // <- use of QPath::TypeRelative `Ty` in a non-body only + +impl Wrap { + pub fn live(self) { _ = self.0; } +} + +fn main() {} diff --git a/tests/ui/lint/dead-code/used-assoc-ty-on-const.rs b/tests/ui/lint/dead-code/used-assoc-ty-on-const.rs new file mode 100644 index 0000000000000..bc37dde405db3 --- /dev/null +++ b/tests/ui/lint/dead-code/used-assoc-ty-on-const.rs @@ -0,0 +1,19 @@ +//@ check-pass + +#![deny(dead_code)] + +pub struct A; + +trait B { + type Assoc; +} + +impl B for A { + type Assoc = A; +} + +const X: ::Assoc = A; + +fn main() { + let _x: A = X; +} diff --git a/tests/ui/lint/dead-code/used-assoc-ty-on-impl.rs b/tests/ui/lint/dead-code/used-assoc-ty-on-impl.rs new file mode 100644 index 0000000000000..3359f6fcc6eb1 --- /dev/null +++ b/tests/ui/lint/dead-code/used-assoc-ty-on-impl.rs @@ -0,0 +1,23 @@ +//@ check-pass + +#![deny(dead_code)] + +pub struct A; + +trait B { + type Assoc; +} + +impl B for A { + type Assoc = A; +} + +trait C {} + +impl C for ::Assoc {} + +fn foo() {} + +fn main() { + foo::(); +} diff --git a/tests/ui/lint/dead-code/used-inherent-assoc-ty.rs b/tests/ui/lint/dead-code/used-inherent-assoc-ty.rs new file mode 100644 index 0000000000000..3ff3b85151d7c --- /dev/null +++ b/tests/ui/lint/dead-code/used-inherent-assoc-ty.rs @@ -0,0 +1,12 @@ +//@ check-pass + +#![feature(inherent_associated_types)] +#![allow(incomplete_features)] +#[deny(dead_code)] + +fn main() { + let _: Struct::Item = (); +} + +struct Struct; +impl Struct { type Item = (); } diff --git a/tests/ui/sanitizer/cfi/sized-associated-ty.rs b/tests/ui/sanitizer/cfi/sized-associated-ty.rs index da8c385c6fc8b..3038546ab68d0 100644 --- a/tests/ui/sanitizer/cfi/sized-associated-ty.rs +++ b/tests/ui/sanitizer/cfi/sized-associated-ty.rs @@ -15,7 +15,7 @@ //@ run-pass trait Foo { - type Bar<'a> + #[allow(dead_code)] type Bar<'a> where Self: Sized; diff --git a/tests/ui/traits/issue-38033.rs b/tests/ui/traits/issue-38033.rs index f3525bd13c4fa..801ded51f791f 100644 --- a/tests/ui/traits/issue-38033.rs +++ b/tests/ui/traits/issue-38033.rs @@ -16,10 +16,11 @@ trait Future { trait IntoFuture { type Future: Future; + //~^ WARN associated items `Future` and `into_future` are never used type Item; type Error; - fn into_future(self) -> Self::Future; //~ WARN method `into_future` is never used + fn into_future(self) -> Self::Future; } impl IntoFuture for F { diff --git a/tests/ui/traits/issue-38033.stderr b/tests/ui/traits/issue-38033.stderr index fb713c564cf14..6d26b1ca8e9f5 100644 --- a/tests/ui/traits/issue-38033.stderr +++ b/tests/ui/traits/issue-38033.stderr @@ -1,8 +1,10 @@ -warning: method `into_future` is never used - --> $DIR/issue-38033.rs:22:8 +warning: associated items `Future` and `into_future` are never used + --> $DIR/issue-38033.rs:18:10 | LL | trait IntoFuture { - | ---------- method in this trait + | ---------- associated items in this trait +LL | type Future: Future; + | ^^^^^^ ... LL | fn into_future(self) -> Self::Future; | ^^^^^^^^^^^