Rollup merge of #142485 - mu001999-contrib:dead-code/adt-pattern, r=petrochenkov

Marks ADT live if it appears in pattern

Marks ADT live if it appears in pattern, it implies the construction of the ADT.
1. Then we can detect unused private ADTs impl `Default`, without special logics for `Default` and other std traits.
2. We can also remove `rustc_trivial_field_reads` on `Default`, and the logic in `should_ignore_item` (introduced by rust-lang/rust#126302).

Fixes rust-lang/rust#120770

Extracted from rust-lang/rust#128637.
r? `@petrochenkov`
This commit is contained in:
Trevor Gross 2025-06-20 23:25:55 -04:00 committed by GitHub
commit c93fac7d64
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 161 additions and 67 deletions

View file

@ -234,7 +234,14 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
pats: &[hir::PatField<'_>],
) {
let variant = match self.typeck_results().node_type(lhs.hir_id).kind() {
ty::Adt(adt, _) => adt.variant_of_res(res),
ty::Adt(adt, _) => {
// Marks the ADT live if its variant appears as the pattern,
// considering cases when we have `let T(x) = foo()` and `fn foo<T>() -> T;`,
// we will lose the liveness info of `T` cause we cannot mark it live when visiting `foo`.
// Related issue: https://github.com/rust-lang/rust/issues/120770
self.check_def_id(adt.did());
adt.variant_of_res(res)
}
_ => span_bug!(lhs.span, "non-ADT in struct pattern"),
};
for pat in pats {
@ -254,7 +261,11 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
dotdot: hir::DotDotPos,
) {
let variant = match self.typeck_results().node_type(lhs.hir_id).kind() {
ty::Adt(adt, _) => adt.variant_of_res(res),
ty::Adt(adt, _) => {
// Marks the ADT live if its variant appears as the pattern
self.check_def_id(adt.did());
adt.variant_of_res(res)
}
_ => {
self.tcx.dcx().span_delayed_bug(lhs.span, "non-ADT in tuple struct pattern");
return;
@ -359,31 +370,6 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
return false;
}
// don't ignore impls for Enums and pub Structs whose methods don't have self receiver,
// cause external crate may call such methods to construct values of these types
if let Some(local_impl_of) = impl_of.as_local()
&& let Some(local_def_id) = def_id.as_local()
&& let Some(fn_sig) =
self.tcx.hir_fn_sig_by_hir_id(self.tcx.local_def_id_to_hir_id(local_def_id))
&& matches!(fn_sig.decl.implicit_self, hir::ImplicitSelfKind::None)
&& let TyKind::Path(QPath::Resolved(_, path)) =
self.tcx.hir_expect_item(local_impl_of).expect_impl().self_ty.kind
&& let Res::Def(def_kind, did) = path.res
{
match def_kind {
// for example, #[derive(Default)] pub struct T(i32);
// external crate can call T::default() to construct T,
// so that don't ignore impl Default for pub Enum and Structs
DefKind::Struct | DefKind::Union if self.tcx.visibility(did).is_public() => {
return false;
}
// don't ignore impl Default for Enums,
// cause we don't know which variant is constructed
DefKind::Enum => return false,
_ => (),
};
}
if let Some(trait_of) = self.tcx.trait_id_of_impl(impl_of)
&& self.tcx.has_attr(trait_of, sym::rustc_trivial_field_reads)
{
@ -494,38 +480,25 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
impl_id: hir::ItemId,
local_def_id: LocalDefId,
) -> bool {
if self.should_ignore_item(local_def_id.to_def_id()) {
return false;
}
let trait_def_id = match self.tcx.def_kind(local_def_id) {
// assoc impl items of traits are live if the corresponding trait items are live
DefKind::AssocFn => self.tcx.associated_item(local_def_id).trait_item_def_id,
DefKind::AssocFn => self
.tcx
.associated_item(local_def_id)
.trait_item_def_id
.and_then(|def_id| def_id.as_local()),
// impl items are live if the corresponding traits are live
DefKind::Impl { of_trait: true } => self
.tcx
.impl_trait_ref(impl_id.owner_id.def_id)
.and_then(|trait_ref| Some(trait_ref.skip_binder().def_id)),
.and_then(|trait_ref| trait_ref.skip_binder().def_id.as_local()),
_ => None,
};
if let Some(trait_def_id) = trait_def_id {
if let Some(trait_def_id) = trait_def_id.as_local()
&& !self.live_symbols.contains(&trait_def_id)
{
return false;
}
// FIXME: legacy logic to check whether the function may construct `Self`,
// this can be removed after supporting marking ADTs appearing in patterns
// as live, then we can check private impls of public traits directly
if let Some(fn_sig) =
self.tcx.hir_fn_sig_by_hir_id(self.tcx.local_def_id_to_hir_id(local_def_id))
&& matches!(fn_sig.decl.implicit_self, hir::ImplicitSelfKind::None)
&& self.tcx.visibility(trait_def_id).is_public()
{
return true;
}
if let Some(trait_def_id) = trait_def_id
&& !self.live_symbols.contains(&trait_def_id)
{
return false;
}
// The impl or impl item is used if the corresponding trait or trait item is used and the ty is used.
@ -635,6 +608,11 @@ impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> {
fn visit_pat_expr(&mut self, expr: &'tcx rustc_hir::PatExpr<'tcx>) {
match &expr.kind {
rustc_hir::PatExprKind::Path(qpath) => {
// mark the type of variant live when meeting E::V in expr
if let ty::Adt(adt, _) = self.typeck_results().node_type(expr.hir_id).kind() {
self.check_def_id(adt.did());
}
let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
self.handle_res(res);
}

View file

@ -103,7 +103,6 @@ use crate::ascii::Char as AsciiChar;
/// ```
#[rustc_diagnostic_item = "Default"]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_trivial_field_reads]
pub trait Default: Sized {
/// Returns the "default value" for a type.
///

View file

@ -3,6 +3,8 @@
//@ compile-flags: -Znext-solver
//@ check-pass
#![allow(dead_code)]
pub trait Future {
type Error;
fn poll() -> Self::Error;

View file

@ -9,7 +9,7 @@ pub trait Foo {
[(); Self::ASSOC_C]:;
}
struct Bar<const N: &'static ()>;
struct Bar<const N: &'static ()>; //~ WARN struct `Bar` is never constructed
impl<const N: &'static ()> Foo for Bar<N> {
const ASSOC_C: usize = 3;

View file

@ -0,0 +1,10 @@
warning: struct `Bar` is never constructed
--> $DIR/issue-86535-2.rs:12:8
|
LL | struct Bar<const N: &'static ()>;
| ^^^
|
= note: `#[warn(dead_code)]` on by default
warning: 1 warning emitted

View file

@ -2,7 +2,7 @@
#![feature(adt_const_params, unsized_const_params, generic_const_exprs)]
#![allow(incomplete_features, unused_variables)]
struct F<const S: &'static str>;
struct F<const S: &'static str>; //~ WARN struct `F` is never constructed
impl<const S: &'static str> X for F<{ S }> {
const W: usize = 3;

View file

@ -0,0 +1,10 @@
warning: struct `F` is never constructed
--> $DIR/issue-86535.rs:5:8
|
LL | struct F<const S: &'static str>;
| ^
|
= note: `#[warn(dead_code)]` on by default
warning: 1 warning emitted

View file

@ -40,7 +40,7 @@ LL | struct D { f: () }
| |
| field in this struct
|
= note: `D` has derived impls for the traits `Debug` and `Clone`, but these are intentionally ignored during dead code analysis
= note: `D` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis
error: field `f` is never read
--> $DIR/clone-debug-dead-code.rs:21:12

View file

@ -1,5 +1,6 @@
//@ run-rustfix
#![allow(dead_code)]
struct S<T>(T);
struct S2;

View file

@ -1,5 +1,6 @@
//@ run-rustfix
#![allow(dead_code)]
struct S<T>(T);
struct S2;

View file

@ -1,23 +1,23 @@
error: unexpected `impl` keyword
--> $DIR/extra-impl-in-trait-impl.rs:6:18
--> $DIR/extra-impl-in-trait-impl.rs:7:18
|
LL | impl<T: Default> impl Default for S<T> {
| ^^^^^ help: remove the extra `impl`
|
note: this is parsed as an `impl Trait` type, but a trait is expected at this position
--> $DIR/extra-impl-in-trait-impl.rs:6:18
--> $DIR/extra-impl-in-trait-impl.rs:7:18
|
LL | impl<T: Default> impl Default for S<T> {
| ^^^^^^^^^^^^
error: unexpected `impl` keyword
--> $DIR/extra-impl-in-trait-impl.rs:12:6
--> $DIR/extra-impl-in-trait-impl.rs:13:6
|
LL | impl impl Default for S2 {
| ^^^^^ help: remove the extra `impl`
|
note: this is parsed as an `impl Trait` type, but a trait is expected at this position
--> $DIR/extra-impl-in-trait-impl.rs:12:6
--> $DIR/extra-impl-in-trait-impl.rs:13:6
|
LL | impl impl Default for S2 {
| ^^^^^^^^^^^^

View file

@ -29,8 +29,6 @@ error: struct `UnusedStruct` is never constructed
|
LL | struct UnusedStruct;
| ^^^^^^^^^^^^
|
= note: `UnusedStruct` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
error: aborting due to 4 previous errors

View file

@ -4,8 +4,8 @@
#![deny(dead_code)]
#[allow(dead_code)]
struct Foo {
#[allow(dead_code)]
inner: u32,
}

View file

@ -0,0 +1,37 @@
#![deny(dead_code)]
struct Foo(u8); //~ ERROR struct `Foo` is never constructed
enum Bar { //~ ERROR enum `Bar` is never used
Var1(u8),
Var2(u8),
}
pub trait Tr1 {
fn f1() -> Self;
}
impl Tr1 for Foo {
fn f1() -> Foo {
let f = Foo(0);
let Foo(tag) = f;
Foo(tag)
}
}
impl Tr1 for Bar {
fn f1() -> Bar {
let b = Bar::Var1(0);
let b = if let Bar::Var1(_) = b {
Bar::Var1(0)
} else {
Bar::Var2(0)
};
match b {
Bar::Var1(_) => Bar::Var2(0),
Bar::Var2(_) => Bar::Var1(0),
}
}
}
fn main() {}

View file

@ -0,0 +1,20 @@
error: struct `Foo` is never constructed
--> $DIR/lint-unused-adt-appeared-in-pattern.rs:3:8
|
LL | struct Foo(u8);
| ^^^
|
note: the lint level is defined here
--> $DIR/lint-unused-adt-appeared-in-pattern.rs:1:9
|
LL | #![deny(dead_code)]
| ^^^^^^^^^
error: enum `Bar` is never used
--> $DIR/lint-unused-adt-appeared-in-pattern.rs:5:6
|
LL | enum Bar {
| ^^^
error: aborting due to 2 previous errors

View file

@ -56,8 +56,6 @@ warning: struct `Foo` is never constructed
|
LL | struct Foo(usize, #[allow(unused)] usize);
| ^^^
|
= note: `Foo` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
error: aborting due to 2 previous errors; 2 warnings emitted

View file

@ -0,0 +1,32 @@
//@ check-pass
#![deny(dead_code)]
#[repr(u8)]
#[derive(Copy, Clone, Debug)]
pub enum RecordField {
Target = 1,
Level,
Module,
File,
Line,
NumArgs,
}
unsafe trait Pod {}
#[repr(transparent)]
struct RecordFieldWrapper(RecordField);
unsafe impl Pod for RecordFieldWrapper {}
fn try_read<T: Pod>(buf: &[u8]) -> T {
unsafe { std::ptr::read_unaligned(buf.as_ptr() as *const T) }
}
pub fn foo(buf: &[u8]) -> RecordField {
let RecordFieldWrapper(tag) = try_read(buf);
tag
}
fn main() {}

View file

@ -2,7 +2,7 @@
struct T1; //~ ERROR struct `T1` is never constructed
pub struct T2(i32); //~ ERROR field `0` is never read
struct T3;
struct T3; //~ ERROR struct `T3` is never constructed
trait Trait1 { //~ ERROR trait `Trait1` is never used
const UNUSED: i32;

View file

@ -20,11 +20,17 @@ LL | pub struct T2(i32);
|
= help: consider removing this field
error: struct `T3` is never constructed
--> $DIR/unused-adt-impl-pub-trait-with-assoc-const.rs:5:8
|
LL | struct T3;
| ^^
error: trait `Trait1` is never used
--> $DIR/unused-adt-impl-pub-trait-with-assoc-const.rs:7:7
|
LL | trait Trait1 {
| ^^^^^^
error: aborting due to 3 previous errors
error: aborting due to 4 previous errors

View file

@ -22,4 +22,5 @@ pub struct T2 {
fn main() {
let _x: Used = Default::default();
let _e: E = Default::default();
}

View file

@ -4,7 +4,6 @@ error: struct `T` is never constructed
LL | struct T;
| ^
|
= note: `T` has a derived impl for the trait `Default`, but this is intentionally ignored during dead code analysis
note: the lint level is defined here
--> $DIR/unused-struct-derive-default.rs:1:9
|

View file

@ -1,5 +1,6 @@
//@ run-rustfix
#[allow(dead_code)]
struct Foo;
impl From<i32> for Foo {

View file

@ -1,5 +1,6 @@
//@ run-rustfix
#[allow(dead_code)]
struct Foo;
fn From<i32> for Foo {

View file

@ -1,5 +1,5 @@
error: you might have meant to write `impl` instead of `fn`
--> $DIR/issue-105366.rs:5:1
--> $DIR/issue-105366.rs:6:1
|
LL | fn From<i32> for Foo {
| ^^