Skip to content

Commit 3d829a0

Browse files
committed
Auto merge of #97853 - TaKO8Ki:emit-only-one-note-per-unused-struct-field, r=estebank
Collapse multiple dead code warnings into a single diagnostic closes #97643
2 parents a09c668 + 094d8cc commit 3d829a0

File tree

76 files changed

+606
-319
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+606
-319
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -4287,6 +4287,7 @@ dependencies = [
42874287
name = "rustc_passes"
42884288
version = "0.0.0"
42894289
dependencies = [
4290+
"itertools",
42904291
"rustc_ast",
42914292
"rustc_ast_pretty",
42924293
"rustc_attr",

compiler/rustc_passes/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ edition = "2021"
55

66
[dependencies]
77
tracing = "0.1"
8+
itertools = "0.10.1"
89
rustc_middle = { path = "../rustc_middle" }
910
rustc_attr = { path = "../rustc_attr" }
1011
rustc_data_structures = { path = "../rustc_data_structures" }

compiler/rustc_passes/src/dead.rs

+199-73
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
// closely. The idea is that all reachable symbols are live, codes called
33
// from live codes are live, and everything else is dead.
44

5+
use itertools::Itertools;
56
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
6-
use rustc_errors::pluralize;
7+
use rustc_errors::{pluralize, MultiSpan};
78
use rustc_hir as hir;
89
use rustc_hir::def::{CtorOf, DefKind, Res};
910
use rustc_hir::def_id::{DefId, LocalDefId};
@@ -183,10 +184,10 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
183184
}
184185
}
185186

186-
if let hir::ExprKind::Assign(lhs, rhs, _) = assign.kind {
187-
if check_for_self_assign_helper(self.typeck_results(), lhs, rhs)
187+
if let hir::ExprKind::Assign(lhs, rhs, _) = assign.kind
188+
&& check_for_self_assign_helper(self.typeck_results(), lhs, rhs)
188189
&& !assign.span.from_expansion()
189-
{
190+
{
190191
let is_field_assign = matches!(lhs.kind, hir::ExprKind::Field(..));
191192
self.tcx.struct_span_lint_hir(
192193
lint::builtin::DEAD_CODE,
@@ -201,7 +202,6 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
201202
.emit();
202203
},
203204
)
204-
}
205205
}
206206
}
207207

@@ -251,33 +251,30 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
251251
return false;
252252
}
253253

254-
if let Some(trait_of) = self.tcx.trait_id_of_impl(impl_of) {
255-
if self.tcx.has_attr(trait_of, sym::rustc_trivial_field_reads) {
256-
let trait_ref = self.tcx.impl_trait_ref(impl_of).unwrap();
257-
if let ty::Adt(adt_def, _) = trait_ref.self_ty().kind() {
258-
if let Some(adt_def_id) = adt_def.did().as_local() {
259-
self.ignored_derived_traits
260-
.entry(adt_def_id)
261-
.or_default()
262-
.push((trait_of, impl_of));
263-
}
264-
}
265-
return true;
254+
if let Some(trait_of) = self.tcx.trait_id_of_impl(impl_of)
255+
&& self.tcx.has_attr(trait_of, sym::rustc_trivial_field_reads)
256+
{
257+
let trait_ref = self.tcx.impl_trait_ref(impl_of).unwrap();
258+
if let ty::Adt(adt_def, _) = trait_ref.self_ty().kind()
259+
&& let Some(adt_def_id) = adt_def.did().as_local()
260+
{
261+
self.ignored_derived_traits
262+
.entry(adt_def_id)
263+
.or_default()
264+
.push((trait_of, impl_of));
266265
}
266+
return true;
267267
}
268268
}
269269

270270
return false;
271271
}
272272

273273
fn visit_node(&mut self, node: Node<'tcx>) {
274-
if let Some(item_def_id) = match node {
275-
Node::ImplItem(hir::ImplItem { def_id, .. }) => Some(def_id.to_def_id()),
276-
_ => None,
277-
} {
278-
if self.should_ignore_item(item_def_id) {
279-
return;
280-
}
274+
if let Node::ImplItem(hir::ImplItem { def_id, .. }) = node
275+
&& self.should_ignore_item(def_id.to_def_id())
276+
{
277+
return;
281278
}
282279

283280
let had_repr_c = self.repr_has_repr_c;
@@ -534,10 +531,10 @@ fn check_item<'tcx>(
534531
}
535532
DefKind::Struct => {
536533
let item = tcx.hir().item(id);
537-
if let hir::ItemKind::Struct(ref variant_data, _) = item.kind {
538-
if let Some(ctor_hir_id) = variant_data.ctor_hir_id() {
539-
struct_constructors.insert(tcx.hir().local_def_id(ctor_hir_id), item.def_id);
540-
}
534+
if let hir::ItemKind::Struct(ref variant_data, _) = item.kind
535+
&& let Some(ctor_hir_id) = variant_data.ctor_hir_id()
536+
{
537+
struct_constructors.insert(tcx.hir().local_def_id(ctor_hir_id), item.def_id);
541538
}
542539
}
543540
DefKind::GlobalAsm => {
@@ -626,6 +623,13 @@ fn live_symbols_and_ignored_derived_traits<'tcx>(
626623
(symbol_visitor.live_symbols, symbol_visitor.ignored_derived_traits)
627624
}
628625

626+
struct DeadVariant {
627+
hir_id: hir::HirId,
628+
span: Span,
629+
name: Symbol,
630+
level: lint::Level,
631+
}
632+
629633
struct DeadVisitor<'tcx> {
630634
tcx: TyCtxt<'tcx>,
631635
live_symbols: &'tcx FxHashSet<LocalDefId>,
@@ -677,56 +681,129 @@ impl<'tcx> DeadVisitor<'tcx> {
677681
let inherent_impls = self.tcx.inherent_impls(def_id);
678682
for &impl_did in inherent_impls.iter() {
679683
for item_did in self.tcx.associated_item_def_ids(impl_did) {
680-
if let Some(def_id) = item_did.as_local() {
681-
if self.live_symbols.contains(&def_id) {
682-
return true;
683-
}
684+
if let Some(def_id) = item_did.as_local()
685+
&& self.live_symbols.contains(&def_id)
686+
{
687+
return true;
684688
}
685689
}
686690
}
687691
false
688692
}
689693

694+
fn warn_multiple_dead_codes(
695+
&self,
696+
dead_codes: &[(hir::HirId, Span, Symbol)],
697+
participle: &str,
698+
parent_hir_id: Option<hir::HirId>,
699+
) {
700+
if let Some((id, _, name)) = dead_codes.first()
701+
&& !name.as_str().starts_with('_')
702+
{
703+
self.tcx.struct_span_lint_hir(
704+
lint::builtin::DEAD_CODE,
705+
*id,
706+
MultiSpan::from_spans(
707+
dead_codes.iter().map(|(_, span, _)| *span).collect(),
708+
),
709+
|lint| {
710+
let def_id = self.tcx.hir().local_def_id(*id);
711+
let descr = self.tcx.def_kind(def_id).descr(def_id.to_def_id());
712+
let span_len = dead_codes.len();
713+
let names = match &dead_codes.iter().map(|(_, _, n)| n.to_string()).collect::<Vec<_>>()[..]
714+
{
715+
_ if span_len > 6 => String::new(),
716+
[name] => format!("`{name}` "),
717+
[names @ .., last] => {
718+
format!("{} and `{last}` ", names.iter().map(|name| format!("`{name}`")).join(", "))
719+
}
720+
[] => unreachable!(),
721+
};
722+
let mut err = lint.build(&format!(
723+
"{these}{descr}{s} {names}{are} never {participle}",
724+
these = if span_len > 6 { "multiple " } else { "" },
725+
s = pluralize!(span_len),
726+
are = pluralize!("is", span_len),
727+
));
728+
let hir = self.tcx.hir();
729+
if let Some(parent_hir_id) = parent_hir_id
730+
&& let Some(parent_node) = hir.find(parent_hir_id)
731+
&& let Node::Item(item) = parent_node
732+
{
733+
let def_id = self.tcx.hir().local_def_id(parent_hir_id);
734+
let parent_descr = self.tcx.def_kind(def_id).descr(def_id.to_def_id());
735+
err.span_label(
736+
item.ident.span,
737+
format!(
738+
"{descr}{s} in this {parent_descr}",
739+
s = pluralize!(span_len)
740+
),
741+
);
742+
}
743+
if let Some(encl_scope) = hir.get_enclosing_scope(*id)
744+
&& let Some(encl_def_id) = hir.opt_local_def_id(encl_scope)
745+
&& let Some(ign_traits) = self.ignored_derived_traits.get(&encl_def_id)
746+
{
747+
let traits_str = ign_traits
748+
.iter()
749+
.map(|(trait_id, _)| format!("`{}`", self.tcx.item_name(*trait_id)))
750+
.collect::<Vec<_>>()
751+
.join(" and ");
752+
let plural_s = pluralize!(ign_traits.len());
753+
let article = if ign_traits.len() > 1 { "" } else { "a " };
754+
let is_are = if ign_traits.len() > 1 { "these are" } else { "this is" };
755+
let msg = format!(
756+
"`{}` has {}derived impl{} for the trait{} {}, but {} \
757+
intentionally ignored during dead code analysis",
758+
self.tcx.item_name(encl_def_id.to_def_id()),
759+
article,
760+
plural_s,
761+
plural_s,
762+
traits_str,
763+
is_are
764+
);
765+
err.note(&msg);
766+
}
767+
err.emit();
768+
},
769+
);
770+
}
771+
}
772+
773+
fn warn_dead_fields_and_variants(
774+
&self,
775+
hir_id: hir::HirId,
776+
participle: &str,
777+
dead_codes: Vec<DeadVariant>,
778+
) {
779+
let mut dead_codes = dead_codes
780+
.iter()
781+
.filter(|v| !v.name.as_str().starts_with('_'))
782+
.map(|v| v)
783+
.collect::<Vec<&DeadVariant>>();
784+
if dead_codes.is_empty() {
785+
return;
786+
}
787+
dead_codes.sort_by_key(|v| v.level);
788+
for (_, group) in &dead_codes.into_iter().group_by(|v| v.level) {
789+
self.warn_multiple_dead_codes(
790+
&group
791+
.map(|v| (v.hir_id, v.span, v.name))
792+
.collect::<Vec<(hir::HirId, Span, Symbol)>>(),
793+
participle,
794+
Some(hir_id),
795+
);
796+
}
797+
}
798+
690799
fn warn_dead_code(
691800
&mut self,
692801
id: hir::HirId,
693802
span: rustc_span::Span,
694803
name: Symbol,
695804
participle: &str,
696805
) {
697-
if !name.as_str().starts_with('_') {
698-
self.tcx.struct_span_lint_hir(lint::builtin::DEAD_CODE, id, span, |lint| {
699-
let def_id = self.tcx.hir().local_def_id(id);
700-
let descr = self.tcx.def_kind(def_id).descr(def_id.to_def_id());
701-
let mut err = lint.build(&format!("{} is never {}: `{}`", descr, participle, name));
702-
let hir = self.tcx.hir();
703-
if let Some(encl_scope) = hir.get_enclosing_scope(id)
704-
&& let Some(encl_def_id) = hir.opt_local_def_id(encl_scope)
705-
&& let Some(ign_traits) = self.ignored_derived_traits.get(&encl_def_id)
706-
{
707-
let traits_str = ign_traits
708-
.iter()
709-
.map(|(trait_id, _)| format!("`{}`", self.tcx.item_name(*trait_id)))
710-
.collect::<Vec<_>>()
711-
.join(" and ");
712-
let plural_s = pluralize!(ign_traits.len());
713-
let article = if ign_traits.len() > 1 { "" } else { "a " };
714-
let is_are = if ign_traits.len() > 1 { "these are" } else { "this is" };
715-
let msg = format!(
716-
"`{}` has {}derived impl{} for the trait{} {}, but {} \
717-
intentionally ignored during dead code analysis",
718-
self.tcx.item_name(encl_def_id.to_def_id()),
719-
article,
720-
plural_s,
721-
plural_s,
722-
traits_str,
723-
is_are
724-
);
725-
err.note(&msg);
726-
}
727-
err.emit();
728-
});
729-
}
806+
self.warn_multiple_dead_codes(&[(id, span, name)], participle, None);
730807
}
731808
}
732809

@@ -780,15 +857,40 @@ impl<'tcx> Visitor<'tcx> for DeadVisitor<'tcx> {
780857
// This visitor should only visit a single module at a time.
781858
fn visit_mod(&mut self, _: &'tcx hir::Mod<'tcx>, _: Span, _: hir::HirId) {}
782859

860+
fn visit_enum_def(
861+
&mut self,
862+
enum_definition: &'tcx hir::EnumDef<'tcx>,
863+
generics: &'tcx hir::Generics<'tcx>,
864+
item_id: hir::HirId,
865+
_: Span,
866+
) {
867+
intravisit::walk_enum_def(self, enum_definition, generics, item_id);
868+
let dead_variants = enum_definition
869+
.variants
870+
.iter()
871+
.filter_map(|variant| {
872+
if self.should_warn_about_variant(&variant) {
873+
Some(DeadVariant {
874+
hir_id: variant.id,
875+
span: variant.span,
876+
name: variant.ident.name,
877+
level: self.tcx.lint_level_at_node(lint::builtin::DEAD_CODE, variant.id).0,
878+
})
879+
} else {
880+
None
881+
}
882+
})
883+
.collect();
884+
self.warn_dead_fields_and_variants(item_id, "constructed", dead_variants)
885+
}
886+
783887
fn visit_variant(
784888
&mut self,
785889
variant: &'tcx hir::Variant<'tcx>,
786890
g: &'tcx hir::Generics<'tcx>,
787891
id: hir::HirId,
788892
) {
789-
if self.should_warn_about_variant(&variant) {
790-
self.warn_dead_code(variant.id, variant.span, variant.ident.name, "constructed");
791-
} else {
893+
if !self.should_warn_about_variant(&variant) {
792894
intravisit::walk_variant(self, variant, g, id);
793895
}
794896
}
@@ -800,11 +902,35 @@ impl<'tcx> Visitor<'tcx> for DeadVisitor<'tcx> {
800902
intravisit::walk_foreign_item(self, fi);
801903
}
802904

803-
fn visit_field_def(&mut self, field: &'tcx hir::FieldDef<'tcx>) {
804-
if self.should_warn_about_field(&field) {
805-
self.warn_dead_code(field.hir_id, field.span, field.ident.name, "read");
806-
}
807-
intravisit::walk_field_def(self, field);
905+
fn visit_variant_data(
906+
&mut self,
907+
def: &'tcx hir::VariantData<'tcx>,
908+
_: Symbol,
909+
_: &hir::Generics<'_>,
910+
id: hir::HirId,
911+
_: rustc_span::Span,
912+
) {
913+
intravisit::walk_struct_def(self, def);
914+
let dead_fields = def
915+
.fields()
916+
.iter()
917+
.filter_map(|field| {
918+
if self.should_warn_about_field(&field) {
919+
Some(DeadVariant {
920+
hir_id: field.hir_id,
921+
span: field.span,
922+
name: field.ident.name,
923+
level: self
924+
.tcx
925+
.lint_level_at_node(lint::builtin::DEAD_CODE, field.hir_id)
926+
.0,
927+
})
928+
} else {
929+
None
930+
}
931+
})
932+
.collect();
933+
self.warn_dead_fields_and_variants(id, "read", dead_fields)
808934
}
809935

810936
fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {

src/test/rustdoc-ui/display-output.stdout

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ warning: unused variable: `x`
2424
LL | fn foo(x: &dyn std::fmt::Display) {}
2525
| ^ help: if this is intentional, prefix it with an underscore: `_x`
2626

27-
warning: function is never used: `foo`
27+
warning: function `foo` is never used
2828
--> $DIR/display-output.rs:13:4
2929
|
3030
LL | fn foo(x: &dyn std::fmt::Display) {}

src/test/ui/associated-consts/associated-const-dead-code.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ struct MyFoo;
44

55
impl MyFoo {
66
const BAR: u32 = 1;
7-
//~^ ERROR associated constant is never used: `BAR`
7+
//~^ ERROR associated constant `BAR` is never used
88
}
99

1010
fn main() {

0 commit comments

Comments
 (0)