diff --git a/compiler/rustc_passes/src/dead.rs b/compiler/rustc_passes/src/dead.rs index a3106856a6701..5b6d8f43f9cac 100644 --- a/compiler/rustc_passes/src/dead.rs +++ b/compiler/rustc_passes/src/dead.rs @@ -15,7 +15,7 @@ use rustc_hir::{Node, PatKind, TyKind}; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::middle::privacy::Level; use rustc_middle::query::Providers; -use rustc_middle::ty::{self, TyCtxt, Visibility}; +use rustc_middle::ty::{self, TyCtxt}; use rustc_session::lint; use rustc_session::lint::builtin::DEAD_CODE; use rustc_span::symbol::{sym, Symbol}; @@ -45,6 +45,18 @@ fn should_explore(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { ) } +fn ty_ref_to_pub_struct(tcx: TyCtxt<'_>, ty: &hir::Ty<'_>) -> bool { + if let TyKind::Path(hir::QPath::Resolved(_, path)) = ty.kind + && let Res::Def(def_kind, def_id) = path.res + && def_id.is_local() + && matches!(def_kind, DefKind::Struct | DefKind::Enum | DefKind::Union) + { + tcx.visibility(def_id).is_public() + } else { + true + } +} + /// Determine if a work from the worklist is coming from the a `#[allow]` /// or a `#[expect]` of `dead_code` #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] @@ -415,6 +427,13 @@ impl<'tcx> MarkSymbolVisitor<'tcx> { && let ItemKind::Impl(impl_ref) = self.tcx.hir().expect_item(local_impl_id).kind { + if self.tcx.visibility(trait_id).is_public() + && matches!(trait_item.kind, hir::TraitItemKind::Fn(..)) + && !ty_ref_to_pub_struct(self.tcx, impl_ref.self_ty) + { + continue; + } + // mark self_ty live intravisit::walk_ty(self, impl_ref.self_ty); if let Some(&impl_item_id) = @@ -683,12 +702,23 @@ fn check_item<'tcx>( .iter() .filter_map(|def_id| def_id.as_local()); + let ty_is_pub = ty_ref_to_pub_struct(tcx, tcx.hir().item(id).expect_impl().self_ty); + // And we access the Map here to get HirId from LocalDefId for id in local_def_ids { + // check the function may construct Self + let mut may_construct_self = true; + if let Some(hir_id) = tcx.opt_local_def_id_to_hir_id(id) + && let Some(fn_sig) = tcx.hir().fn_sig_by_hir_id(hir_id) + { + may_construct_self = + matches!(fn_sig.decl.implicit_self, hir::ImplicitSelfKind::None); + } + // for impl trait blocks, mark associate functions live if the trait is public if of_trait && (!matches!(tcx.def_kind(id), DefKind::AssocFn) - || tcx.local_visibility(id) == Visibility::Public) + || tcx.visibility(id).is_public() && (ty_is_pub || may_construct_self)) { worklist.push((id, ComesFromAllowExpect::No)); } else if let Some(comes_from_allow) = has_allow_dead_code_or_lang_attr(tcx, id) { @@ -712,6 +742,49 @@ fn check_item<'tcx>( } } +fn check_pub_impl_fns_of_constructed_self<'tcx>( + tcx: TyCtxt<'tcx>, + worklist: &mut Vec<(LocalDefId, ComesFromAllowExpect)>, + id: hir::ItemId, + live_symbols: &UnordSet, +) { + let allow_dead_code = has_allow_dead_code_or_lang_attr(tcx, id.owner_id.def_id); + if let Some(comes_from_allow) = allow_dead_code { + worklist.push((id.owner_id.def_id, comes_from_allow)); + } + + match tcx.def_kind(id.owner_id) { + DefKind::Impl { of_trait: true } => { + let mut self_is_constructed = false; + if let TyKind::Path(hir::QPath::Resolved(_, path)) = + tcx.hir().item(id).expect_impl().self_ty.kind + && let Res::Def(def_kind, def_id) = path.res + && let Some(local_def_id) = def_id.as_local() + && matches!(def_kind, DefKind::Struct | DefKind::Enum | DefKind::Union) + { + self_is_constructed = live_symbols.contains(&local_def_id); + } + + // get DefIds from another query + let local_def_ids = tcx + .associated_item_def_ids(id.owner_id) + .iter() + .filter_map(|def_id| def_id.as_local()); + + // And we access the Map here to get HirId from LocalDefId + for id in local_def_ids { + // for impl trait blocks, mark associate functions live if the trait is public + if !matches!(tcx.def_kind(id), DefKind::AssocFn) + || (tcx.visibility(id).is_public() && self_is_constructed) + { + worklist.push((id, ComesFromAllowExpect::No)); + } + } + } + _ => {} + } +} + fn check_trait_item( tcx: TyCtxt<'_>, worklist: &mut Vec<(LocalDefId, ComesFromAllowExpect)>, @@ -778,6 +851,20 @@ fn create_and_seed_worklist( (worklist, struct_constructors) } +fn create_worklist_for_pub_impl_fns_of_constructed_self( + tcx: TyCtxt<'_>, + live_symbols: &UnordSet, +) -> Vec<(LocalDefId, ComesFromAllowExpect)> { + let mut worklist = Vec::new(); + + let crate_items = tcx.hir_crate_items(()); + for id in crate_items.items() { + check_pub_impl_fns_of_constructed_self(tcx, &mut worklist, id, live_symbols); + } + + worklist +} + fn live_symbols_and_ignored_derived_traits( tcx: TyCtxt<'_>, (): (), @@ -796,6 +883,11 @@ fn live_symbols_and_ignored_derived_traits( ignored_derived_traits: Default::default(), }; symbol_visitor.mark_live_symbols(); + + symbol_visitor.worklist = + create_worklist_for_pub_impl_fns_of_constructed_self(tcx, &symbol_visitor.live_symbols); + symbol_visitor.mark_live_symbols(); + (symbol_visitor.live_symbols, symbol_visitor.ignored_derived_traits) } diff --git a/tests/ui/derives/clone-debug-dead-code-in-the-same-struct.stderr b/tests/ui/derives/clone-debug-dead-code-in-the-same-struct.stderr index e9b757b6bae72..aae5ea8e48cad 100644 --- a/tests/ui/derives/clone-debug-dead-code-in-the-same-struct.stderr +++ b/tests/ui/derives/clone-debug-dead-code-in-the-same-struct.stderr @@ -13,7 +13,7 @@ LL | field3: (), LL | field4: (), | ^^^^^^ | - = note: `Whatever` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis + = note: `Whatever` has derived impls for the traits `Debug` and `Debug`, but these are intentionally ignored during dead code analysis note: the lint level is defined here --> $DIR/clone-debug-dead-code-in-the-same-struct.rs:1:11 | diff --git a/tests/ui/issues/issue-33187.rs b/tests/ui/issues/issue-33187.rs index 6a039527b3b37..6ae7e150c51be 100644 --- a/tests/ui/issues/issue-33187.rs +++ b/tests/ui/issues/issue-33187.rs @@ -1,4 +1,4 @@ -//@ run-pass +//@ check-pass struct Foo(::Data); diff --git a/tests/ui/lint/dead-code/issue-41883.stderr b/tests/ui/lint/dead-code/issue-41883.stderr index cf079e4dda33a..47ccef9a53069 100644 --- a/tests/ui/lint/dead-code/issue-41883.stderr +++ b/tests/ui/lint/dead-code/issue-41883.stderr @@ -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 diff --git a/tests/ui/lint/dead-code/multiple-dead-codes-in-the-same-struct.stderr b/tests/ui/lint/dead-code/multiple-dead-codes-in-the-same-struct.stderr index b992005318f2e..25a7d96cb8978 100644 --- a/tests/ui/lint/dead-code/multiple-dead-codes-in-the-same-struct.stderr +++ b/tests/ui/lint/dead-code/multiple-dead-codes-in-the-same-struct.stderr @@ -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 diff --git a/tests/ui/lint/dead-code/unused-adt-impls-pub-trait.rs b/tests/ui/lint/dead-code/unused-adt-impls-pub-trait.rs new file mode 100644 index 0000000000000..10d6c728c2d9b --- /dev/null +++ b/tests/ui/lint/dead-code/unused-adt-impls-pub-trait.rs @@ -0,0 +1,17 @@ +#![deny(dead_code)] + +enum Foo {} //~ ERROR enum `Foo` is never used + +impl Clone for Foo { + fn clone(&self) -> Foo { loop {} } +} + +pub trait PubTrait { + fn unused_method(&self) -> Self; +} + +impl PubTrait for Foo { + fn unused_method(&self) -> Foo { loop {} } +} + +fn main() {} diff --git a/tests/ui/lint/dead-code/unused-adt-impls-pub-trait.stderr b/tests/ui/lint/dead-code/unused-adt-impls-pub-trait.stderr new file mode 100644 index 0000000000000..f0d0cc50f7d04 --- /dev/null +++ b/tests/ui/lint/dead-code/unused-adt-impls-pub-trait.stderr @@ -0,0 +1,14 @@ +error: enum `Foo` is never used + --> $DIR/unused-adt-impls-pub-trait.rs:3:6 + | +LL | enum Foo {} + | ^^^ + | +note: the lint level is defined here + --> $DIR/unused-adt-impls-pub-trait.rs:1:9 + | +LL | #![deny(dead_code)] + | ^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/rust-2018/uniform-paths/issue-55779.rs b/tests/ui/rust-2018/uniform-paths/issue-55779.rs index 246b8dd82c5e3..f90054e6b1d60 100644 --- a/tests/ui/rust-2018/uniform-paths/issue-55779.rs +++ b/tests/ui/rust-2018/uniform-paths/issue-55779.rs @@ -1,4 +1,4 @@ -//@ run-pass +//@ check-pass //@ edition:2018 //@ aux-crate:issue_55779_extern_trait=issue-55779-extern-trait.rs diff --git a/tests/ui/traits/augmented-assignments-trait.rs b/tests/ui/traits/augmented-assignments-trait.rs index 37fe42ce1c60c..a0f8b39eb7a3a 100644 --- a/tests/ui/traits/augmented-assignments-trait.rs +++ b/tests/ui/traits/augmented-assignments-trait.rs @@ -1,4 +1,4 @@ -//@ run-pass +//@ check-pass use std::ops::AddAssign; struct Int(#[allow(dead_code)] i32);