1 // This implements the dead-code warning pass. It follows middle::reachable
2 // closely. The idea is that all reachable symbols are live, codes called
3 // from live codes are live, and everything else is dead.
5 use itertools
::Itertools
;
6 use rustc_data_structures
::fx
::{FxHashMap, FxHashSet}
;
7 use rustc_errors
::{pluralize, MultiSpan}
;
9 use rustc_hir
::def
::{CtorOf, DefKind, Res}
;
10 use rustc_hir
::def_id
::{DefId, LocalDefId}
;
11 use rustc_hir
::intravisit
::{self, Visitor}
;
12 use rustc_hir
::{Node, PatKind, TyKind}
;
13 use rustc_middle
::hir
::nested_filter
;
14 use rustc_middle
::middle
::codegen_fn_attrs
::CodegenFnAttrFlags
;
15 use rustc_middle
::middle
::privacy
;
16 use rustc_middle
::ty
::query
::Providers
;
17 use rustc_middle
::ty
::{self, DefIdTree, TyCtxt}
;
18 use rustc_session
::lint
;
19 use rustc_span
::symbol
::{sym, Symbol}
;
23 // Any local node that may call something in its body block should be
24 // explored. For example, if it's a live Node::Item that is a
25 // function, then we should explore its block to check for codes that
26 // may need to be marked as live.
27 fn should_explore(tcx
: TyCtxt
<'_
>, def_id
: LocalDefId
) -> bool
{
29 tcx
.hir().find_by_def_id(def_id
),
33 | Node
::ForeignItem(..)
41 struct MarkSymbolVisitor
<'tcx
> {
42 worklist
: Vec
<LocalDefId
>,
44 maybe_typeck_results
: Option
<&'tcx ty
::TypeckResults
<'tcx
>>,
45 live_symbols
: FxHashSet
<LocalDefId
>,
46 repr_has_repr_c
: bool
,
48 ignore_variant_stack
: Vec
<DefId
>,
49 // maps from tuple struct constructors to tuple struct items
50 struct_constructors
: FxHashMap
<LocalDefId
, LocalDefId
>,
51 // maps from ADTs to ignored derived traits (e.g. Debug and Clone)
52 // and the span of their respective impl (i.e., part of the derive
54 ignored_derived_traits
: FxHashMap
<LocalDefId
, Vec
<(DefId
, DefId
)>>,
57 impl<'tcx
> MarkSymbolVisitor
<'tcx
> {
58 /// Gets the type-checking results for the current body.
59 /// As this will ICE if called outside bodies, only call when working with
60 /// `Expr` or `Pat` nodes (they are guaranteed to be found only in bodies).
62 fn typeck_results(&self) -> &'tcx ty
::TypeckResults
<'tcx
> {
63 self.maybe_typeck_results
64 .expect("`MarkSymbolVisitor::typeck_results` called outside of body")
67 fn check_def_id(&mut self, def_id
: DefId
) {
68 if let Some(def_id
) = def_id
.as_local() {
69 if should_explore(self.tcx
, def_id
) || self.struct_constructors
.contains_key(&def_id
) {
70 self.worklist
.push(def_id
);
72 self.live_symbols
.insert(def_id
);
76 fn insert_def_id(&mut self, def_id
: DefId
) {
77 if let Some(def_id
) = def_id
.as_local() {
78 debug_assert
!(!should_explore(self.tcx
, def_id
));
79 self.live_symbols
.insert(def_id
);
83 fn handle_res(&mut self, res
: Res
) {
85 Res
::Def(DefKind
::Const
| DefKind
::AssocConst
| DefKind
::TyAlias
, _
) => {
86 self.check_def_id(res
.def_id());
88 _
if self.in_pat
=> {}
89 Res
::PrimTy(..) | Res
::SelfCtor(..) | Res
::Local(..) => {}
90 Res
::Def(DefKind
::Ctor(CtorOf
::Variant
, ..), ctor_def_id
) => {
91 let variant_id
= self.tcx
.parent(ctor_def_id
);
92 let enum_id
= self.tcx
.parent(variant_id
);
93 self.check_def_id(enum_id
);
94 if !self.ignore_variant_stack
.contains(&ctor_def_id
) {
95 self.check_def_id(variant_id
);
98 Res
::Def(DefKind
::Variant
, variant_id
) => {
99 let enum_id
= self.tcx
.parent(variant_id
);
100 self.check_def_id(enum_id
);
101 if !self.ignore_variant_stack
.contains(&variant_id
) {
102 self.check_def_id(variant_id
);
105 Res
::SelfTy { trait_: t, alias_to: i }
=> {
107 self.check_def_id(t
);
109 if let Some((i
, _
)) = i
{
110 self.check_def_id(i
);
113 Res
::ToolMod
| Res
::NonMacroAttr(..) | Res
::Err
=> {}
115 self.check_def_id(res
.def_id());
120 fn lookup_and_handle_method(&mut self, id
: hir
::HirId
) {
121 if let Some(def_id
) = self.typeck_results().type_dependent_def_id(id
) {
122 self.check_def_id(def_id
);
124 bug
!("no type-dependent def for method");
128 fn handle_field_access(&mut self, lhs
: &hir
::Expr
<'_
>, hir_id
: hir
::HirId
) {
129 match self.typeck_results().expr_ty_adjusted(lhs
).kind() {
131 let index
= self.tcx
.field_index(hir_id
, self.typeck_results());
132 self.insert_def_id(def
.non_enum_variant().fields
[index
].did
);
135 _
=> span_bug
!(lhs
.span
, "named field access on non-ADT"),
139 #[allow(dead_code)] // FIXME(81658): should be used + lint reinstated after #83171 relands.
140 fn handle_assign(&mut self, expr
: &'tcx hir
::Expr
<'tcx
>) {
143 .expr_adjustments(expr
)
145 .any(|adj
| matches
!(adj
.kind
, ty
::adjustment
::Adjust
::Deref(_
)))
147 self.visit_expr(expr
);
148 } else if let hir
::ExprKind
::Field(base
, ..) = expr
.kind
{
149 // Ignore write to field
150 self.handle_assign(base
);
152 self.visit_expr(expr
);
156 #[allow(dead_code)] // FIXME(81658): should be used + lint reinstated after #83171 relands.
157 fn check_for_self_assign(&mut self, assign
: &'tcx hir
::Expr
<'tcx
>) {
158 fn check_for_self_assign_helper
<'tcx
>(
159 typeck_results
: &'tcx ty
::TypeckResults
<'tcx
>,
160 lhs
: &'tcx hir
::Expr
<'tcx
>,
161 rhs
: &'tcx hir
::Expr
<'tcx
>,
163 match (&lhs
.kind
, &rhs
.kind
) {
164 (hir
::ExprKind
::Path(ref qpath_l
), hir
::ExprKind
::Path(ref qpath_r
)) => {
165 if let (Res
::Local(id_l
), Res
::Local(id_r
)) = (
166 typeck_results
.qpath_res(qpath_l
, lhs
.hir_id
),
167 typeck_results
.qpath_res(qpath_r
, rhs
.hir_id
),
175 (hir
::ExprKind
::Field(lhs_l
, ident_l
), hir
::ExprKind
::Field(lhs_r
, ident_r
)) => {
176 if ident_l
== ident_r
{
177 return check_for_self_assign_helper(typeck_results
, lhs_l
, lhs_r
);
187 if let hir
::ExprKind
::Assign(lhs
, rhs
, _
) = assign
.kind
188 && check_for_self_assign_helper(self.typeck_results(), lhs
, rhs
)
189 && !assign
.span
.from_expansion()
191 let is_field_assign
= matches
!(lhs
.kind
, hir
::ExprKind
::Field(..));
192 self.tcx
.struct_span_lint_hir(
193 lint
::builtin
::DEAD_CODE
,
198 "useless assignment of {} of type `{}` to itself",
199 if is_field_assign { "field" }
else { "variable" }
,
200 self.typeck_results().expr_ty(lhs
),
208 fn handle_field_pattern_match(
212 pats
: &[hir
::PatField
<'_
>],
214 let variant
= match self.typeck_results().node_type(lhs
.hir_id
).kind() {
215 ty
::Adt(adt
, _
) => adt
.variant_of_res(res
),
216 _
=> span_bug
!(lhs
.span
, "non-ADT in struct pattern"),
219 if let PatKind
::Wild
= pat
.pat
.kind
{
222 let index
= self.tcx
.field_index(pat
.hir_id
, self.typeck_results());
223 self.insert_def_id(variant
.fields
[index
].did
);
227 fn mark_live_symbols(&mut self) {
228 let mut scanned
= FxHashSet
::default();
229 while let Some(id
) = self.worklist
.pop() {
230 if !scanned
.insert(id
) {
234 // in the case of tuple struct constructors we want to check the item, not the generated
235 // tuple struct constructor function
236 let id
= self.struct_constructors
.get(&id
).copied().unwrap_or(id
);
238 if let Some(node
) = self.tcx
.hir().find_by_def_id(id
) {
239 self.live_symbols
.insert(id
);
240 self.visit_node(node
);
245 /// Automatically generated items marked with `rustc_trivial_field_reads`
246 /// will be ignored for the purposes of dead code analysis (see PR #85200
248 fn should_ignore_item(&mut self, def_id
: DefId
) -> bool
{
249 if let Some(impl_of
) = self.tcx
.impl_of_method(def_id
) {
250 if !self.tcx
.has_attr(impl_of
, sym
::automatically_derived
) {
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
)
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()
261 self.ignored_derived_traits
264 .push((trait_of
, impl_of
));
273 fn visit_node(&mut self, node
: Node
<'tcx
>) {
274 if let Node
::ImplItem(hir
::ImplItem { def_id, .. }
) = node
275 && self.should_ignore_item(def_id
.to_def_id())
280 let had_repr_c
= self.repr_has_repr_c
;
281 self.repr_has_repr_c
= false;
283 Node
::Item(item
) => match item
.kind
{
284 hir
::ItemKind
::Struct(..) | hir
::ItemKind
::Union(..) => {
285 let def
= self.tcx
.adt_def(item
.def_id
);
286 self.repr_has_repr_c
= def
.repr().c();
288 intravisit
::walk_item(self, &item
);
290 hir
::ItemKind
::Enum(..) => {
291 intravisit
::walk_item(self, &item
);
293 hir
::ItemKind
::ForeignMod { .. }
=> {}
295 intravisit
::walk_item(self, &item
);
298 Node
::TraitItem(trait_item
) => {
299 intravisit
::walk_trait_item(self, trait_item
);
301 Node
::ImplItem(impl_item
) => {
302 intravisit
::walk_impl_item(self, impl_item
);
304 Node
::ForeignItem(foreign_item
) => {
305 intravisit
::walk_foreign_item(self, &foreign_item
);
309 self.repr_has_repr_c
= had_repr_c
;
312 fn mark_as_used_if_union(&mut self, adt
: ty
::AdtDef
<'tcx
>, fields
: &[hir
::ExprField
<'_
>]) {
313 if adt
.is_union() && adt
.non_enum_variant().fields
.len() > 1 && adt
.did().is_local() {
314 for field
in fields
{
315 let index
= self.tcx
.field_index(field
.hir_id
, self.typeck_results());
316 self.insert_def_id(adt
.non_enum_variant().fields
[index
].did
);
322 impl<'tcx
> Visitor
<'tcx
> for MarkSymbolVisitor
<'tcx
> {
323 fn visit_nested_body(&mut self, body
: hir
::BodyId
) {
324 let old_maybe_typeck_results
=
325 self.maybe_typeck_results
.replace(self.tcx
.typeck_body(body
));
326 let body
= self.tcx
.hir().body(body
);
327 self.visit_body(body
);
328 self.maybe_typeck_results
= old_maybe_typeck_results
;
331 fn visit_variant_data(
333 def
: &'tcx hir
::VariantData
<'tcx
>,
335 _
: &hir
::Generics
<'_
>,
340 let has_repr_c
= self.repr_has_repr_c
;
341 let live_fields
= def
.fields().iter().filter_map(|f
| {
342 let def_id
= tcx
.hir().local_def_id(f
.hir_id
);
346 if !tcx
.visibility(f
.hir_id
.owner
).is_public() {
349 if tcx
.visibility(def_id
).is_public() { Some(def_id) }
else { None }
351 self.live_symbols
.extend(live_fields
);
353 intravisit
::walk_struct_def(self, def
);
356 fn visit_expr(&mut self, expr
: &'tcx hir
::Expr
<'tcx
>) {
358 hir
::ExprKind
::Path(ref qpath @ hir
::QPath
::TypeRelative(..)) => {
359 let res
= self.typeck_results().qpath_res(qpath
, expr
.hir_id
);
360 self.handle_res(res
);
362 hir
::ExprKind
::MethodCall(..) => {
363 self.lookup_and_handle_method(expr
.hir_id
);
365 hir
::ExprKind
::Field(ref lhs
, ..) => {
366 self.handle_field_access(&lhs
, expr
.hir_id
);
368 hir
::ExprKind
::Struct(ref qpath
, ref fields
, _
) => {
369 let res
= self.typeck_results().qpath_res(qpath
, expr
.hir_id
);
370 self.handle_res(res
);
371 if let ty
::Adt(adt
, _
) = self.typeck_results().expr_ty(expr
).kind() {
372 self.mark_as_used_if_union(*adt
, fields
);
378 intravisit
::walk_expr(self, expr
);
381 fn visit_arm(&mut self, arm
: &'tcx hir
::Arm
<'tcx
>) {
382 // Inside the body, ignore constructions of variants
383 // necessary for the pattern to match. Those construction sites
384 // can't be reached unless the variant is constructed elsewhere.
385 let len
= self.ignore_variant_stack
.len();
386 self.ignore_variant_stack
.extend(arm
.pat
.necessary_variants());
387 intravisit
::walk_arm(self, arm
);
388 self.ignore_variant_stack
.truncate(len
);
391 fn visit_pat(&mut self, pat
: &'tcx hir
::Pat
<'tcx
>) {
394 PatKind
::Struct(ref path
, ref fields
, _
) => {
395 let res
= self.typeck_results().qpath_res(path
, pat
.hir_id
);
396 self.handle_field_pattern_match(pat
, res
, fields
);
398 PatKind
::Path(ref qpath
) => {
399 let res
= self.typeck_results().qpath_res(qpath
, pat
.hir_id
);
400 self.handle_res(res
);
405 intravisit
::walk_pat(self, pat
);
409 fn visit_path(&mut self, path
: &'tcx hir
::Path
<'tcx
>, _
: hir
::HirId
) {
410 self.handle_res(path
.res
);
411 intravisit
::walk_path(self, path
);
414 fn visit_ty(&mut self, ty
: &'tcx hir
::Ty
<'tcx
>) {
415 if let TyKind
::OpaqueDef(item_id
, _
) = ty
.kind
{
416 let item
= self.tcx
.hir().item(item_id
);
417 intravisit
::walk_item(self, item
);
419 intravisit
::walk_ty(self, ty
);
422 fn visit_anon_const(&mut self, c
: &'tcx hir
::AnonConst
) {
423 // When inline const blocks are used in pattern position, paths
424 // referenced by it should be considered as used.
425 let in_pat
= mem
::replace(&mut self.in_pat
, false);
427 self.live_symbols
.insert(self.tcx
.hir().local_def_id(c
.hir_id
));
428 intravisit
::walk_anon_const(self, c
);
430 self.in_pat
= in_pat
;
434 fn has_allow_dead_code_or_lang_attr(tcx
: TyCtxt
<'_
>, id
: hir
::HirId
) -> bool
{
435 let attrs
= tcx
.hir().attrs(id
);
436 if tcx
.sess
.contains_name(attrs
, sym
::lang
) {
440 // Stable attribute for #[lang = "panic_impl"]
441 if tcx
.sess
.contains_name(attrs
, sym
::panic_handler
) {
445 // (To be) stable attribute for #[lang = "oom"]
446 if tcx
.sess
.contains_name(attrs
, sym
::alloc_error_handler
) {
450 let def_id
= tcx
.hir().local_def_id(id
);
451 if tcx
.def_kind(def_id
).has_codegen_attrs() {
452 let cg_attrs
= tcx
.codegen_fn_attrs(def_id
);
454 // #[used], #[no_mangle], #[export_name], etc also keeps the item alive
455 // forcefully, e.g., for placing it in a specific section.
456 if cg_attrs
.contains_extern_indicator()
457 || cg_attrs
.flags
.contains(CodegenFnAttrFlags
::USED
)
458 || cg_attrs
.flags
.contains(CodegenFnAttrFlags
::USED_LINKER
)
464 tcx
.lint_level_at_node(lint
::builtin
::DEAD_CODE
, id
).0 == lint
::Allow
467 // These check_* functions seeds items that
468 // 1) We want to explicitly consider as live:
469 // * Item annotated with #[allow(dead_code)]
470 // - This is done so that if we want to suppress warnings for a
471 // group of dead functions, we only have to annotate the "root".
472 // For example, if both `f` and `g` are dead and `f` calls `g`,
473 // then annotating `f` with `#[allow(dead_code)]` will suppress
474 // warning for both `f` and `g`.
475 // * Item annotated with #[lang=".."]
476 // - This is because lang items are always callable from elsewhere.
478 // 2) We are not sure to be live or not
479 // * Implementations of traits and trait methods
482 worklist
: &mut Vec
<LocalDefId
>,
483 struct_constructors
: &mut FxHashMap
<LocalDefId
, LocalDefId
>,
486 let allow_dead_code
= has_allow_dead_code_or_lang_attr(tcx
, id
.hir_id());
488 worklist
.push(id
.def_id
);
491 match tcx
.def_kind(id
.def_id
) {
493 let item
= tcx
.hir().item(id
);
494 if let hir
::ItemKind
::Enum(ref enum_def
, _
) = item
.kind
{
498 enum_def
.variants
.iter().map(|variant
| hir
.local_def_id(variant
.id
)),
502 for variant
in enum_def
.variants
{
503 if let Some(ctor_hir_id
) = variant
.data
.ctor_hir_id() {
505 .insert(hir
.local_def_id(ctor_hir_id
), hir
.local_def_id(variant
.id
));
511 let of_trait
= tcx
.impl_trait_ref(id
.def_id
);
513 if of_trait
.is_some() {
514 worklist
.push(id
.def_id
);
517 // get DefIds from another query
518 let local_def_ids
= tcx
519 .associated_item_def_ids(id
.def_id
)
521 .filter_map(|def_id
| def_id
.as_local());
523 // And we access the Map here to get HirId from LocalDefId
524 for id
in local_def_ids
{
525 if of_trait
.is_some()
526 || has_allow_dead_code_or_lang_attr(tcx
, tcx
.hir().local_def_id_to_hir_id(id
))
533 let item
= tcx
.hir().item(id
);
534 if let hir
::ItemKind
::Struct(ref variant_data
, _
) = item
.kind
535 && let Some(ctor_hir_id
) = variant_data
.ctor_hir_id()
537 struct_constructors
.insert(tcx
.hir().local_def_id(ctor_hir_id
), item
.def_id
);
540 DefKind
::GlobalAsm
=> {
541 // global_asm! is always live.
542 worklist
.push(id
.def_id
);
548 fn check_trait_item
<'tcx
>(tcx
: TyCtxt
<'tcx
>, worklist
: &mut Vec
<LocalDefId
>, id
: hir
::TraitItemId
) {
549 use hir
::TraitItemKind
::{Const, Fn}
;
550 if matches
!(tcx
.def_kind(id
.def_id
), DefKind
::AssocConst
| DefKind
::AssocFn
) {
551 let trait_item
= tcx
.hir().trait_item(id
);
552 if matches
!(trait_item
.kind
, Const(_
, Some(_
)) | Fn(_
, hir
::TraitFn
::Provided(_
)))
553 && has_allow_dead_code_or_lang_attr(tcx
, trait_item
.hir_id())
555 worklist
.push(trait_item
.def_id
);
560 fn check_foreign_item
<'tcx
>(
562 worklist
: &mut Vec
<LocalDefId
>,
563 id
: hir
::ForeignItemId
,
565 if matches
!(tcx
.def_kind(id
.def_id
), DefKind
::Static(_
) | DefKind
::Fn
)
566 && has_allow_dead_code_or_lang_attr(tcx
, id
.hir_id())
568 worklist
.push(id
.def_id
);
572 fn create_and_seed_worklist
<'tcx
>(
574 ) -> (Vec
<LocalDefId
>, FxHashMap
<LocalDefId
, LocalDefId
>) {
575 let access_levels
= &tcx
.privacy_access_levels(());
576 // see `MarkSymbolVisitor::struct_constructors`
577 let mut struct_constructors
= Default
::default();
578 let mut worklist
= access_levels
583 if level
>= privacy
::AccessLevel
::Reachable { Some(id) }
else { None }
587 .chain(tcx
.entry_fn(()).and_then(|(def_id
, _
)| def_id
.as_local()))
588 .collect
::<Vec
<_
>>();
590 let crate_items
= tcx
.hir_crate_items(());
591 for id
in crate_items
.items() {
592 check_item(tcx
, &mut worklist
, &mut struct_constructors
, id
);
595 for id
in crate_items
.trait_items() {
596 check_trait_item(tcx
, &mut worklist
, id
);
599 for id
in crate_items
.foreign_items() {
600 check_foreign_item(tcx
, &mut worklist
, id
);
603 (worklist
, struct_constructors
)
606 fn live_symbols_and_ignored_derived_traits
<'tcx
>(
609 ) -> (FxHashSet
<LocalDefId
>, FxHashMap
<LocalDefId
, Vec
<(DefId
, DefId
)>>) {
610 let (worklist
, struct_constructors
) = create_and_seed_worklist(tcx
);
611 let mut symbol_visitor
= MarkSymbolVisitor
{
614 maybe_typeck_results
: None
,
615 live_symbols
: Default
::default(),
616 repr_has_repr_c
: false,
618 ignore_variant_stack
: vec
![],
620 ignored_derived_traits
: FxHashMap
::default(),
622 symbol_visitor
.mark_live_symbols();
623 (symbol_visitor
.live_symbols
, symbol_visitor
.ignored_derived_traits
)
633 struct DeadVisitor
<'tcx
> {
635 live_symbols
: &'tcx FxHashSet
<LocalDefId
>,
636 ignored_derived_traits
: &'tcx FxHashMap
<LocalDefId
, Vec
<(DefId
, DefId
)>>,
639 impl<'tcx
> DeadVisitor
<'tcx
> {
640 fn should_warn_about_item(&mut self, item
: &hir
::Item
<'_
>) -> bool
{
641 let should_warn
= matches
!(
643 hir
::ItemKind
::Static(..)
644 | hir
::ItemKind
::Const(..)
645 | hir
::ItemKind
::Fn(..)
646 | hir
::ItemKind
::TyAlias(..)
647 | hir
::ItemKind
::Enum(..)
648 | hir
::ItemKind
::Struct(..)
649 | hir
::ItemKind
::Union(..)
651 should_warn
&& !self.symbol_is_live(item
.def_id
)
654 fn should_warn_about_field(&mut self, field
: &hir
::FieldDef
<'_
>) -> bool
{
655 let def_id
= self.tcx
.hir().local_def_id(field
.hir_id
);
656 let field_type
= self.tcx
.type_of(def_id
);
657 !field
.is_positional()
658 && !self.symbol_is_live(def_id
)
659 && !field_type
.is_phantom_data()
660 && !has_allow_dead_code_or_lang_attr(self.tcx
, field
.hir_id
)
663 fn should_warn_about_variant(&mut self, variant
: &hir
::Variant
<'_
>) -> bool
{
664 let def_id
= self.tcx
.hir().local_def_id(variant
.id
);
665 !self.symbol_is_live(def_id
) && !has_allow_dead_code_or_lang_attr(self.tcx
, variant
.id
)
668 fn should_warn_about_foreign_item(&mut self, fi
: &hir
::ForeignItem
<'_
>) -> bool
{
669 !self.symbol_is_live(fi
.def_id
) && !has_allow_dead_code_or_lang_attr(self.tcx
, fi
.hir_id())
672 // id := HIR id of an item's definition.
673 fn symbol_is_live(&mut self, def_id
: LocalDefId
) -> bool
{
674 if self.live_symbols
.contains(&def_id
) {
677 // If it's a type whose items are live, then it's live, too.
678 // This is done to handle the case where, for example, the static
679 // method of a private type is used, but the type itself is never
681 let inherent_impls
= self.tcx
.inherent_impls(def_id
);
682 for &impl_did
in inherent_impls
.iter() {
683 for item_did
in self.tcx
.associated_item_def_ids(impl_did
) {
684 if let Some(def_id
) = item_did
.as_local()
685 && self.live_symbols
.contains(&def_id
)
694 fn warn_multiple_dead_codes(
696 dead_codes
: &[(hir
::HirId
, Span
, Symbol
)],
698 parent_hir_id
: Option
<hir
::HirId
>,
700 if let Some((id
, _
, name
)) = dead_codes
.first()
701 && !name
.as_str().starts_with('_'
)
703 self.tcx
.struct_span_lint_hir(
704 lint
::builtin
::DEAD_CODE
,
706 MultiSpan
::from_spans(
707 dead_codes
.iter().map(|(_
, span
, _
)| *span
).collect(),
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
<_
>>()[..]
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(", "))
720 [] => unreachable
!(),
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
),
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
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());
738 "{descr}{s} in this {parent_descr}",
739 s
= pluralize
!(span_len
)
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
)
747 let traits_str
= ign_traits
749 .map(|(trait_id
, _
)| format
!("`{}`", self.tcx
.item_name(*trait_id
)))
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" }
;
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()),
773 fn warn_dead_fields_and_variants(
777 dead_codes
: Vec
<DeadVariant
>,
779 let mut dead_codes
= dead_codes
781 .filter(|v
| !v
.name
.as_str().starts_with('_'
))
783 .collect
::<Vec
<&DeadVariant
>>();
784 if dead_codes
.is_empty() {
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(
791 .map(|v
| (v
.hir_id
, v
.span
, v
.name
))
792 .collect
::<Vec
<(hir
::HirId
, Span
, Symbol
)>>(),
802 span
: rustc_span
::Span
,
806 self.warn_multiple_dead_codes(&[(id
, span
, name
)], participle
, None
);
810 impl<'tcx
> Visitor
<'tcx
> for DeadVisitor
<'tcx
> {
811 type NestedFilter
= nested_filter
::All
;
813 /// Walk nested items in place so that we don't report dead-code
814 /// on inner functions when the outer function is already getting
815 /// an error. We could do this also by checking the parents, but
816 /// this is how the code is setup and it seems harmless enough.
817 fn nested_visit_map(&mut self) -> Self::Map
{
821 fn visit_item(&mut self, item
: &'tcx hir
::Item
<'tcx
>) {
822 if self.should_warn_about_item(item
) {
823 // For most items, we want to highlight its identifier
824 let span
= match item
.kind
{
825 hir
::ItemKind
::Fn(..)
826 | hir
::ItemKind
::Mod(..)
827 | hir
::ItemKind
::Enum(..)
828 | hir
::ItemKind
::Struct(..)
829 | hir
::ItemKind
::Union(..)
830 | hir
::ItemKind
::Trait(..)
831 | hir
::ItemKind
::Impl { .. }
=> {
832 // FIXME(66095): Because item.span is annotated with things
833 // like expansion data, and ident.span isn't, we use the
834 // def_span method if it's part of a macro invocation
835 // (and thus has a source_callee set).
836 // We should probably annotate ident.span with the macro
837 // context, but that's a larger change.
838 if item
.span
.source_callee().is_some() {
839 self.tcx
.sess
.source_map().guess_head_span(item
.span
)
846 let participle
= match item
.kind
{
847 hir
::ItemKind
::Struct(..) => "constructed", // Issue #52325
850 self.warn_dead_code(item
.hir_id(), span
, item
.ident
.name
, participle
);
852 // Only continue if we didn't warn
853 intravisit
::walk_item(self, item
);
857 // This visitor should only visit a single module at a time.
858 fn visit_mod(&mut self, _
: &'tcx hir
::Mod
<'tcx
>, _
: Span
, _
: hir
::HirId
) {}
862 enum_definition
: &'tcx hir
::EnumDef
<'tcx
>,
863 generics
: &'tcx hir
::Generics
<'tcx
>,
867 intravisit
::walk_enum_def(self, enum_definition
, generics
, item_id
);
868 let dead_variants
= enum_definition
871 .filter_map(|variant
| {
872 if self.should_warn_about_variant(&variant
) {
876 name
: variant
.ident
.name
,
877 level
: self.tcx
.lint_level_at_node(lint
::builtin
::DEAD_CODE
, variant
.id
).0,
884 self.warn_dead_fields_and_variants(item_id
, "constructed", dead_variants
)
889 variant
: &'tcx hir
::Variant
<'tcx
>,
890 g
: &'tcx hir
::Generics
<'tcx
>,
893 if !self.should_warn_about_variant(&variant
) {
894 intravisit
::walk_variant(self, variant
, g
, id
);
898 fn visit_foreign_item(&mut self, fi
: &'tcx hir
::ForeignItem
<'tcx
>) {
899 if self.should_warn_about_foreign_item(fi
) {
900 self.warn_dead_code(fi
.hir_id(), fi
.span
, fi
.ident
.name
, "used");
902 intravisit
::walk_foreign_item(self, fi
);
905 fn visit_variant_data(
907 def
: &'tcx hir
::VariantData
<'tcx
>,
909 _
: &hir
::Generics
<'_
>,
913 intravisit
::walk_struct_def(self, def
);
914 let dead_fields
= def
917 .filter_map(|field
| {
918 if self.should_warn_about_field(&field
) {
920 hir_id
: field
.hir_id
,
922 name
: field
.ident
.name
,
925 .lint_level_at_node(lint
::builtin
::DEAD_CODE
, field
.hir_id
)
933 self.warn_dead_fields_and_variants(id
, "read", dead_fields
)
936 fn visit_impl_item(&mut self, impl_item
: &'tcx hir
::ImplItem
<'tcx
>) {
937 match impl_item
.kind
{
938 hir
::ImplItemKind
::Const(_
, body_id
) => {
939 if !self.symbol_is_live(impl_item
.def_id
) {
943 impl_item
.ident
.name
,
947 self.visit_nested_body(body_id
)
949 hir
::ImplItemKind
::Fn(_
, body_id
) => {
950 if !self.symbol_is_live(impl_item
.def_id
) {
951 // FIXME(66095): Because impl_item.span is annotated with things
952 // like expansion data, and ident.span isn't, we use the
953 // def_span method if it's part of a macro invocation
954 // (and thus has a source_callee set).
955 // We should probably annotate ident.span with the macro
956 // context, but that's a larger change.
957 let span
= if impl_item
.span
.source_callee().is_some() {
958 self.tcx
.sess
.source_map().guess_head_span(impl_item
.span
)
962 self.warn_dead_code(impl_item
.hir_id(), span
, impl_item
.ident
.name
, "used");
964 self.visit_nested_body(body_id
)
966 hir
::ImplItemKind
::TyAlias(..) => {}
970 // Overwrite so that we don't warn the trait item itself.
971 fn visit_trait_item(&mut self, trait_item
: &'tcx hir
::TraitItem
<'tcx
>) {
972 match trait_item
.kind
{
973 hir
::TraitItemKind
::Const(_
, Some(body_id
))
974 | hir
::TraitItemKind
::Fn(_
, hir
::TraitFn
::Provided(body_id
)) => {
975 self.visit_nested_body(body_id
)
977 hir
::TraitItemKind
::Const(_
, None
)
978 | hir
::TraitItemKind
::Fn(_
, hir
::TraitFn
::Required(_
))
979 | hir
::TraitItemKind
::Type(..) => {}
984 fn check_mod_deathness(tcx
: TyCtxt
<'_
>, module
: LocalDefId
) {
985 let (live_symbols
, ignored_derived_traits
) = tcx
.live_symbols_and_ignored_derived_traits(());
986 let mut visitor
= DeadVisitor { tcx, live_symbols, ignored_derived_traits }
;
987 let (module
, _
, module_id
) = tcx
.hir().get_module(module
);
988 // Do not use an ItemLikeVisitor since we may want to skip visiting some items
989 // when a surrounding one is warned against or `_`.
990 intravisit
::walk_mod(&mut visitor
, module
, module_id
);
993 pub(crate) fn provide(providers
: &mut Providers
) {
995 Providers { live_symbols_and_ignored_derived_traits, check_mod_deathness, ..*providers }
;