rustc_passes/
dead.rs

1// This implements the dead-code warning pass.
2// All reachable symbols are live, code called from live code is live, code with certain lint
3// expectations such as `#[expect(unused)]` and `#[expect(dead_code)]` is live, and everything else
4// is dead.
5
6use std::mem;
7
8use hir::ItemKind;
9use hir::def_id::{LocalDefIdMap, LocalDefIdSet};
10use rustc_abi::FieldIdx;
11use rustc_data_structures::fx::FxIndexSet;
12use rustc_data_structures::unord::UnordSet;
13use rustc_errors::MultiSpan;
14use rustc_hir::def::{CtorOf, DefKind, Res};
15use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId};
16use rustc_hir::intravisit::{self, Visitor};
17use rustc_hir::{self as hir, ImplItem, ImplItemKind, Node, PatKind, QPath, TyKind};
18use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
19use rustc_middle::middle::privacy::Level;
20use rustc_middle::query::Providers;
21use rustc_middle::ty::{self, TyCtxt};
22use rustc_middle::{bug, span_bug};
23use rustc_session::lint::builtin::DEAD_CODE;
24use rustc_session::lint::{self, LintExpectationId};
25use rustc_span::{Symbol, kw, sym};
26
27use crate::errors::{
28    ChangeFields, IgnoredDerivedImpls, MultipleDeadCodes, ParentInfo, UselessAssignment,
29};
30
31// Any local node that may call something in its body block should be
32// explored. For example, if it's a live Node::Item that is a
33// function, then we should explore its block to check for codes that
34// may need to be marked as live.
35fn should_explore(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
36    matches!(
37        tcx.hir_node_by_def_id(def_id),
38        Node::Item(..)
39            | Node::ImplItem(..)
40            | Node::ForeignItem(..)
41            | Node::TraitItem(..)
42            | Node::Variant(..)
43            | Node::AnonConst(..)
44            | Node::OpaqueTy(..)
45    )
46}
47
48/// Returns the local def id of the ADT if the given ty refers to a local one.
49fn local_adt_def_of_ty<'tcx>(ty: &hir::Ty<'tcx>) -> Option<LocalDefId> {
50    match ty.kind {
51        TyKind::Path(QPath::Resolved(_, path)) => {
52            if let Res::Def(def_kind, def_id) = path.res
53                && let Some(local_def_id) = def_id.as_local()
54                && matches!(def_kind, DefKind::Struct | DefKind::Enum | DefKind::Union)
55            {
56                Some(local_def_id)
57            } else {
58                None
59            }
60        }
61        _ => None,
62    }
63}
64
65/// Determine if a work from the worklist is coming from a `#[allow]`
66/// or a `#[expect]` of `dead_code`
67#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
68enum ComesFromAllowExpect {
69    Yes,
70    No,
71}
72
73struct MarkSymbolVisitor<'tcx> {
74    worklist: Vec<(LocalDefId, ComesFromAllowExpect)>,
75    tcx: TyCtxt<'tcx>,
76    maybe_typeck_results: Option<&'tcx ty::TypeckResults<'tcx>>,
77    live_symbols: LocalDefIdSet,
78    repr_unconditionally_treats_fields_as_live: bool,
79    repr_has_repr_simd: bool,
80    in_pat: bool,
81    ignore_variant_stack: Vec<DefId>,
82    // maps from tuple struct constructors to tuple struct items
83    struct_constructors: LocalDefIdMap<LocalDefId>,
84    // maps from ADTs to ignored derived traits (e.g. Debug and Clone)
85    // and the span of their respective impl (i.e., part of the derive
86    // macro)
87    ignored_derived_traits: LocalDefIdMap<FxIndexSet<(DefId, DefId)>>,
88}
89
90impl<'tcx> MarkSymbolVisitor<'tcx> {
91    /// Gets the type-checking results for the current body.
92    /// As this will ICE if called outside bodies, only call when working with
93    /// `Expr` or `Pat` nodes (they are guaranteed to be found only in bodies).
94    #[track_caller]
95    fn typeck_results(&self) -> &'tcx ty::TypeckResults<'tcx> {
96        self.maybe_typeck_results
97            .expect("`MarkSymbolVisitor::typeck_results` called outside of body")
98    }
99
100    fn check_def_id(&mut self, def_id: DefId) {
101        if let Some(def_id) = def_id.as_local() {
102            if should_explore(self.tcx, def_id) || self.struct_constructors.contains_key(&def_id) {
103                self.worklist.push((def_id, ComesFromAllowExpect::No));
104            }
105            self.live_symbols.insert(def_id);
106        }
107    }
108
109    fn insert_def_id(&mut self, def_id: DefId) {
110        if let Some(def_id) = def_id.as_local() {
111            debug_assert!(!should_explore(self.tcx, def_id));
112            self.live_symbols.insert(def_id);
113        }
114    }
115
116    fn handle_res(&mut self, res: Res) {
117        match res {
118            Res::Def(DefKind::Const | DefKind::AssocConst | DefKind::TyAlias, def_id) => {
119                self.check_def_id(def_id);
120            }
121            _ if self.in_pat => {}
122            Res::PrimTy(..) | Res::SelfCtor(..) | Res::Local(..) => {}
123            Res::Def(DefKind::Ctor(CtorOf::Variant, ..), ctor_def_id) => {
124                let variant_id = self.tcx.parent(ctor_def_id);
125                let enum_id = self.tcx.parent(variant_id);
126                self.check_def_id(enum_id);
127                if !self.ignore_variant_stack.contains(&ctor_def_id) {
128                    self.check_def_id(variant_id);
129                }
130            }
131            Res::Def(DefKind::Variant, variant_id) => {
132                let enum_id = self.tcx.parent(variant_id);
133                self.check_def_id(enum_id);
134                if !self.ignore_variant_stack.contains(&variant_id) {
135                    self.check_def_id(variant_id);
136                }
137            }
138            Res::Def(_, def_id) => self.check_def_id(def_id),
139            Res::SelfTyParam { trait_: t } => self.check_def_id(t),
140            Res::SelfTyAlias { alias_to: i, .. } => self.check_def_id(i),
141            Res::ToolMod | Res::NonMacroAttr(..) | Res::Err => {}
142        }
143    }
144
145    fn lookup_and_handle_method(&mut self, id: hir::HirId) {
146        if let Some(def_id) = self.typeck_results().type_dependent_def_id(id) {
147            self.check_def_id(def_id);
148        } else {
149            assert!(
150                self.typeck_results().tainted_by_errors.is_some(),
151                "no type-dependent def for method"
152            );
153        }
154    }
155
156    fn handle_field_access(&mut self, lhs: &hir::Expr<'_>, hir_id: hir::HirId) {
157        match self.typeck_results().expr_ty_adjusted(lhs).kind() {
158            ty::Adt(def, _) => {
159                let index = self.typeck_results().field_index(hir_id);
160                self.insert_def_id(def.non_enum_variant().fields[index].did);
161            }
162            ty::Tuple(..) => {}
163            ty::Error(_) => {}
164            kind => span_bug!(lhs.span, "named field access on non-ADT: {kind:?}"),
165        }
166    }
167
168    #[allow(dead_code)] // FIXME(81658): should be used + lint reinstated after #83171 relands.
169    fn handle_assign(&mut self, expr: &'tcx hir::Expr<'tcx>) {
170        if self
171            .typeck_results()
172            .expr_adjustments(expr)
173            .iter()
174            .any(|adj| matches!(adj.kind, ty::adjustment::Adjust::Deref(_)))
175        {
176            self.visit_expr(expr);
177        } else if let hir::ExprKind::Field(base, ..) = expr.kind {
178            // Ignore write to field
179            self.handle_assign(base);
180        } else {
181            self.visit_expr(expr);
182        }
183    }
184
185    #[allow(dead_code)] // FIXME(81658): should be used + lint reinstated after #83171 relands.
186    fn check_for_self_assign(&mut self, assign: &'tcx hir::Expr<'tcx>) {
187        fn check_for_self_assign_helper<'tcx>(
188            typeck_results: &'tcx ty::TypeckResults<'tcx>,
189            lhs: &'tcx hir::Expr<'tcx>,
190            rhs: &'tcx hir::Expr<'tcx>,
191        ) -> bool {
192            match (&lhs.kind, &rhs.kind) {
193                (hir::ExprKind::Path(qpath_l), hir::ExprKind::Path(qpath_r)) => {
194                    if let (Res::Local(id_l), Res::Local(id_r)) = (
195                        typeck_results.qpath_res(qpath_l, lhs.hir_id),
196                        typeck_results.qpath_res(qpath_r, rhs.hir_id),
197                    ) {
198                        if id_l == id_r {
199                            return true;
200                        }
201                    }
202                    return false;
203                }
204                (hir::ExprKind::Field(lhs_l, ident_l), hir::ExprKind::Field(lhs_r, ident_r)) => {
205                    if ident_l == ident_r {
206                        return check_for_self_assign_helper(typeck_results, lhs_l, lhs_r);
207                    }
208                    return false;
209                }
210                _ => {
211                    return false;
212                }
213            }
214        }
215
216        if let hir::ExprKind::Assign(lhs, rhs, _) = assign.kind
217            && check_for_self_assign_helper(self.typeck_results(), lhs, rhs)
218            && !assign.span.from_expansion()
219        {
220            let is_field_assign = matches!(lhs.kind, hir::ExprKind::Field(..));
221            self.tcx.emit_node_span_lint(
222                lint::builtin::DEAD_CODE,
223                assign.hir_id,
224                assign.span,
225                UselessAssignment { is_field_assign, ty: self.typeck_results().expr_ty(lhs) },
226            )
227        }
228    }
229
230    fn handle_field_pattern_match(
231        &mut self,
232        lhs: &hir::Pat<'_>,
233        res: Res,
234        pats: &[hir::PatField<'_>],
235    ) {
236        let variant = match self.typeck_results().node_type(lhs.hir_id).kind() {
237            ty::Adt(adt, _) => {
238                // Marks the ADT live if its variant appears as the pattern,
239                // considering cases when we have `let T(x) = foo()` and `fn foo<T>() -> T;`,
240                // we will lose the liveness info of `T` cause we cannot mark it live when visiting `foo`.
241                // Related issue: https://github.com/rust-lang/rust/issues/120770
242                self.check_def_id(adt.did());
243                adt.variant_of_res(res)
244            }
245            _ => span_bug!(lhs.span, "non-ADT in struct pattern"),
246        };
247        for pat in pats {
248            if let PatKind::Wild = pat.pat.kind {
249                continue;
250            }
251            let index = self.typeck_results().field_index(pat.hir_id);
252            self.insert_def_id(variant.fields[index].did);
253        }
254    }
255
256    fn handle_tuple_field_pattern_match(
257        &mut self,
258        lhs: &hir::Pat<'_>,
259        res: Res,
260        pats: &[hir::Pat<'_>],
261        dotdot: hir::DotDotPos,
262    ) {
263        let variant = match self.typeck_results().node_type(lhs.hir_id).kind() {
264            ty::Adt(adt, _) => {
265                // Marks the ADT live if its variant appears as the pattern
266                self.check_def_id(adt.did());
267                adt.variant_of_res(res)
268            }
269            _ => {
270                self.tcx.dcx().span_delayed_bug(lhs.span, "non-ADT in tuple struct pattern");
271                return;
272            }
273        };
274        let dotdot = dotdot.as_opt_usize().unwrap_or(pats.len());
275        let first_n = pats.iter().enumerate().take(dotdot);
276        let missing = variant.fields.len() - pats.len();
277        let last_n = pats.iter().enumerate().skip(dotdot).map(|(idx, pat)| (idx + missing, pat));
278        for (idx, pat) in first_n.chain(last_n) {
279            if let PatKind::Wild = pat.kind {
280                continue;
281            }
282            self.insert_def_id(variant.fields[FieldIdx::from_usize(idx)].did);
283        }
284    }
285
286    fn handle_offset_of(&mut self, expr: &'tcx hir::Expr<'tcx>) {
287        let data = self.typeck_results().offset_of_data();
288        let &(container, ref indices) =
289            data.get(expr.hir_id).expect("no offset_of_data for offset_of");
290
291        let body_did = self.typeck_results().hir_owner.to_def_id();
292        let typing_env = ty::TypingEnv::non_body_analysis(self.tcx, body_did);
293
294        let mut current_ty = container;
295
296        for &(variant, field) in indices {
297            match current_ty.kind() {
298                ty::Adt(def, args) => {
299                    let field = &def.variant(variant).fields[field];
300
301                    self.insert_def_id(field.did);
302                    let field_ty = field.ty(self.tcx, args);
303
304                    current_ty = self.tcx.normalize_erasing_regions(typing_env, field_ty);
305                }
306                // we don't need to mark tuple fields as live,
307                // but we may need to mark subfields
308                ty::Tuple(tys) => {
309                    current_ty =
310                        self.tcx.normalize_erasing_regions(typing_env, tys[field.as_usize()]);
311                }
312                _ => span_bug!(expr.span, "named field access on non-ADT"),
313            }
314        }
315    }
316
317    fn mark_live_symbols(&mut self) {
318        let mut scanned = UnordSet::default();
319        while let Some(work) = self.worklist.pop() {
320            if !scanned.insert(work) {
321                continue;
322            }
323
324            let (id, comes_from_allow_expect) = work;
325
326            // Avoid accessing the HIR for the synthesized associated type generated for RPITITs.
327            if self.tcx.is_impl_trait_in_trait(id.to_def_id()) {
328                self.live_symbols.insert(id);
329                continue;
330            }
331
332            // in the case of tuple struct constructors we want to check the item, not the generated
333            // tuple struct constructor function
334            let id = self.struct_constructors.get(&id).copied().unwrap_or(id);
335
336            // When using `#[allow]` or `#[expect]` of `dead_code`, we do a QOL improvement
337            // by declaring fn calls, statics, ... within said items as live, as well as
338            // the item itself, although technically this is not the case.
339            //
340            // This means that the lint for said items will never be fired.
341            //
342            // This doesn't make any difference for the item declared with `#[allow]`, as
343            // the lint firing will be a nop, as it will be silenced by the `#[allow]` of
344            // the item.
345            //
346            // However, for `#[expect]`, the presence or absence of the lint is relevant,
347            // so we don't add it to the list of live symbols when it comes from a
348            // `#[expect]`. This means that we will correctly report an item as live or not
349            // for the `#[expect]` case.
350            //
351            // Note that an item can and will be duplicated on the worklist with different
352            // `ComesFromAllowExpect`, particularly if it was added from the
353            // `effective_visibilities` query or from the `#[allow]`/`#[expect]` checks,
354            // this "duplication" is essential as otherwise a function with `#[expect]`
355            // called from a `pub fn` may be falsely reported as not live, falsely
356            // triggering the `unfulfilled_lint_expectations` lint.
357            if comes_from_allow_expect != ComesFromAllowExpect::Yes {
358                self.live_symbols.insert(id);
359            }
360            self.visit_node(self.tcx.hir_node_by_def_id(id));
361        }
362    }
363
364    /// Automatically generated items marked with `rustc_trivial_field_reads`
365    /// will be ignored for the purposes of dead code analysis (see PR #85200
366    /// for discussion).
367    fn should_ignore_item(&mut self, def_id: DefId) -> bool {
368        if let Some(impl_of) = self.tcx.impl_of_method(def_id) {
369            if !self.tcx.is_automatically_derived(impl_of) {
370                return false;
371            }
372
373            if let Some(trait_of) = self.tcx.trait_id_of_impl(impl_of)
374                && self.tcx.has_attr(trait_of, sym::rustc_trivial_field_reads)
375            {
376                let trait_ref = self.tcx.impl_trait_ref(impl_of).unwrap().instantiate_identity();
377                if let ty::Adt(adt_def, _) = trait_ref.self_ty().kind()
378                    && let Some(adt_def_id) = adt_def.did().as_local()
379                {
380                    self.ignored_derived_traits
381                        .entry(adt_def_id)
382                        .or_default()
383                        .insert((trait_of, impl_of));
384                }
385                return true;
386            }
387        }
388
389        false
390    }
391
392    fn visit_node(&mut self, node: Node<'tcx>) {
393        if let Node::ImplItem(hir::ImplItem { owner_id, .. }) = node
394            && self.should_ignore_item(owner_id.to_def_id())
395        {
396            return;
397        }
398
399        let unconditionally_treated_fields_as_live =
400            self.repr_unconditionally_treats_fields_as_live;
401        let had_repr_simd = self.repr_has_repr_simd;
402        self.repr_unconditionally_treats_fields_as_live = false;
403        self.repr_has_repr_simd = false;
404        match node {
405            Node::Item(item) => match item.kind {
406                hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) => {
407                    let def = self.tcx.adt_def(item.owner_id);
408                    self.repr_unconditionally_treats_fields_as_live =
409                        def.repr().c() || def.repr().transparent();
410                    self.repr_has_repr_simd = def.repr().simd();
411
412                    intravisit::walk_item(self, item)
413                }
414                hir::ItemKind::ForeignMod { .. } => {}
415                hir::ItemKind::Trait(.., trait_item_refs) => {
416                    // mark assoc ty live if the trait is live
417                    for trait_item in trait_item_refs {
418                        if matches!(trait_item.kind, hir::AssocItemKind::Type) {
419                            self.check_def_id(trait_item.id.owner_id.to_def_id());
420                        }
421                    }
422                    intravisit::walk_item(self, item)
423                }
424                _ => intravisit::walk_item(self, item),
425            },
426            Node::TraitItem(trait_item) => {
427                // mark the trait live
428                let trait_item_id = trait_item.owner_id.to_def_id();
429                if let Some(trait_id) = self.tcx.trait_of_item(trait_item_id) {
430                    self.check_def_id(trait_id);
431                }
432                intravisit::walk_trait_item(self, trait_item);
433            }
434            Node::ImplItem(impl_item) => {
435                let item = self.tcx.local_parent(impl_item.owner_id.def_id);
436                if self.tcx.impl_trait_ref(item).is_none() {
437                    //// If it's a type whose items are live, then it's live, too.
438                    //// This is done to handle the case where, for example, the static
439                    //// method of a private type is used, but the type itself is never
440                    //// called directly.
441                    let self_ty = self.tcx.type_of(item).instantiate_identity();
442                    match *self_ty.kind() {
443                        ty::Adt(def, _) => self.check_def_id(def.did()),
444                        ty::Foreign(did) => self.check_def_id(did),
445                        ty::Dynamic(data, ..) => {
446                            if let Some(def_id) = data.principal_def_id() {
447                                self.check_def_id(def_id)
448                            }
449                        }
450                        _ => {}
451                    }
452                }
453                intravisit::walk_impl_item(self, impl_item);
454            }
455            Node::ForeignItem(foreign_item) => {
456                intravisit::walk_foreign_item(self, foreign_item);
457            }
458            Node::OpaqueTy(opaq) => intravisit::walk_opaque_ty(self, opaq),
459            _ => {}
460        }
461        self.repr_has_repr_simd = had_repr_simd;
462        self.repr_unconditionally_treats_fields_as_live = unconditionally_treated_fields_as_live;
463    }
464
465    fn mark_as_used_if_union(&mut self, adt: ty::AdtDef<'tcx>, fields: &[hir::ExprField<'_>]) {
466        if adt.is_union() && adt.non_enum_variant().fields.len() > 1 && adt.did().is_local() {
467            for field in fields {
468                let index = self.typeck_results().field_index(field.hir_id);
469                self.insert_def_id(adt.non_enum_variant().fields[index].did);
470            }
471        }
472    }
473
474    /// Returns whether `local_def_id` is potentially alive or not.
475    /// `local_def_id` points to an impl or an impl item,
476    /// both impl and impl item that may be passed to this function are of a trait,
477    /// and added into the unsolved_items during `create_and_seed_worklist`
478    fn check_impl_or_impl_item_live(
479        &mut self,
480        impl_id: hir::ItemId,
481        local_def_id: LocalDefId,
482    ) -> bool {
483        let trait_def_id = match self.tcx.def_kind(local_def_id) {
484            // assoc impl items of traits are live if the corresponding trait items are live
485            DefKind::AssocFn => self
486                .tcx
487                .associated_item(local_def_id)
488                .trait_item_def_id
489                .and_then(|def_id| def_id.as_local()),
490            // impl items are live if the corresponding traits are live
491            DefKind::Impl { of_trait: true } => self
492                .tcx
493                .impl_trait_ref(impl_id.owner_id.def_id)
494                .and_then(|trait_ref| trait_ref.skip_binder().def_id.as_local()),
495            _ => None,
496        };
497
498        if let Some(trait_def_id) = trait_def_id
499            && !self.live_symbols.contains(&trait_def_id)
500        {
501            return false;
502        }
503
504        // The impl or impl item is used if the corresponding trait or trait item is used and the ty is used.
505        if let Some(local_def_id) =
506            local_adt_def_of_ty(self.tcx.hir_item(impl_id).expect_impl().self_ty)
507            && !self.live_symbols.contains(&local_def_id)
508        {
509            return false;
510        }
511
512        true
513    }
514}
515
516impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> {
517    fn visit_nested_body(&mut self, body: hir::BodyId) {
518        let old_maybe_typeck_results =
519            self.maybe_typeck_results.replace(self.tcx.typeck_body(body));
520        let body = self.tcx.hir_body(body);
521        self.visit_body(body);
522        self.maybe_typeck_results = old_maybe_typeck_results;
523    }
524
525    fn visit_variant_data(&mut self, def: &'tcx hir::VariantData<'tcx>) {
526        let tcx = self.tcx;
527        let unconditionally_treat_fields_as_live = self.repr_unconditionally_treats_fields_as_live;
528        let has_repr_simd = self.repr_has_repr_simd;
529        let effective_visibilities = &tcx.effective_visibilities(());
530        let live_fields = def.fields().iter().filter_map(|f| {
531            let def_id = f.def_id;
532            if unconditionally_treat_fields_as_live || (f.is_positional() && has_repr_simd) {
533                return Some(def_id);
534            }
535            if !effective_visibilities.is_reachable(f.hir_id.owner.def_id) {
536                return None;
537            }
538            if effective_visibilities.is_reachable(def_id) { Some(def_id) } else { None }
539        });
540        self.live_symbols.extend(live_fields);
541
542        intravisit::walk_struct_def(self, def);
543    }
544
545    fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
546        match expr.kind {
547            hir::ExprKind::Path(ref qpath @ QPath::TypeRelative(..)) => {
548                let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
549                self.handle_res(res);
550            }
551            hir::ExprKind::MethodCall(..) => {
552                self.lookup_and_handle_method(expr.hir_id);
553            }
554            hir::ExprKind::Field(ref lhs, ..) => {
555                if self.typeck_results().opt_field_index(expr.hir_id).is_some() {
556                    self.handle_field_access(lhs, expr.hir_id);
557                } else {
558                    self.tcx.dcx().span_delayed_bug(expr.span, "couldn't resolve index for field");
559                }
560            }
561            hir::ExprKind::Struct(qpath, fields, _) => {
562                let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
563                self.handle_res(res);
564                if let ty::Adt(adt, _) = self.typeck_results().expr_ty(expr).kind() {
565                    self.mark_as_used_if_union(*adt, fields);
566                }
567            }
568            hir::ExprKind::Closure(cls) => {
569                self.insert_def_id(cls.def_id.to_def_id());
570            }
571            hir::ExprKind::OffsetOf(..) => {
572                self.handle_offset_of(expr);
573            }
574            _ => (),
575        }
576
577        intravisit::walk_expr(self, expr);
578    }
579
580    fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) {
581        // Inside the body, ignore constructions of variants
582        // necessary for the pattern to match. Those construction sites
583        // can't be reached unless the variant is constructed elsewhere.
584        let len = self.ignore_variant_stack.len();
585        self.ignore_variant_stack.extend(arm.pat.necessary_variants());
586        intravisit::walk_arm(self, arm);
587        self.ignore_variant_stack.truncate(len);
588    }
589
590    fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {
591        self.in_pat = true;
592        match pat.kind {
593            PatKind::Struct(ref path, fields, _) => {
594                let res = self.typeck_results().qpath_res(path, pat.hir_id);
595                self.handle_field_pattern_match(pat, res, fields);
596            }
597            PatKind::TupleStruct(ref qpath, fields, dotdot) => {
598                let res = self.typeck_results().qpath_res(qpath, pat.hir_id);
599                self.handle_tuple_field_pattern_match(pat, res, fields, dotdot);
600            }
601            _ => (),
602        }
603
604        intravisit::walk_pat(self, pat);
605        self.in_pat = false;
606    }
607
608    fn visit_pat_expr(&mut self, expr: &'tcx rustc_hir::PatExpr<'tcx>) {
609        match &expr.kind {
610            rustc_hir::PatExprKind::Path(qpath) => {
611                // mark the type of variant live when meeting E::V in expr
612                if let ty::Adt(adt, _) = self.typeck_results().node_type(expr.hir_id).kind() {
613                    self.check_def_id(adt.did());
614                }
615
616                let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
617                self.handle_res(res);
618            }
619            _ => {}
620        }
621        intravisit::walk_pat_expr(self, expr);
622    }
623
624    fn visit_path(&mut self, path: &hir::Path<'tcx>, _: hir::HirId) {
625        self.handle_res(path.res);
626        intravisit::walk_path(self, path);
627    }
628
629    fn visit_anon_const(&mut self, c: &'tcx hir::AnonConst) {
630        // When inline const blocks are used in pattern position, paths
631        // referenced by it should be considered as used.
632        let in_pat = mem::replace(&mut self.in_pat, false);
633
634        self.live_symbols.insert(c.def_id);
635        intravisit::walk_anon_const(self, c);
636
637        self.in_pat = in_pat;
638    }
639
640    fn visit_inline_const(&mut self, c: &'tcx hir::ConstBlock) {
641        // When inline const blocks are used in pattern position, paths
642        // referenced by it should be considered as used.
643        let in_pat = mem::replace(&mut self.in_pat, false);
644
645        self.live_symbols.insert(c.def_id);
646        intravisit::walk_inline_const(self, c);
647
648        self.in_pat = in_pat;
649    }
650}
651
652fn has_allow_dead_code_or_lang_attr(
653    tcx: TyCtxt<'_>,
654    def_id: LocalDefId,
655) -> Option<ComesFromAllowExpect> {
656    fn has_lang_attr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
657        tcx.has_attr(def_id, sym::lang)
658            // Stable attribute for #[lang = "panic_impl"]
659            || tcx.has_attr(def_id, sym::panic_handler)
660    }
661
662    fn has_allow_expect_dead_code(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
663        let hir_id = tcx.local_def_id_to_hir_id(def_id);
664        let lint_level = tcx.lint_level_at_node(lint::builtin::DEAD_CODE, hir_id).level;
665        matches!(lint_level, lint::Allow | lint::Expect)
666    }
667
668    fn has_used_like_attr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
669        tcx.def_kind(def_id).has_codegen_attrs() && {
670            let cg_attrs = tcx.codegen_fn_attrs(def_id);
671
672            // #[used], #[no_mangle], #[export_name], etc also keeps the item alive
673            // forcefully, e.g., for placing it in a specific section.
674            cg_attrs.contains_extern_indicator()
675                || cg_attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER)
676                || cg_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER)
677        }
678    }
679
680    if has_allow_expect_dead_code(tcx, def_id) {
681        Some(ComesFromAllowExpect::Yes)
682    } else if has_used_like_attr(tcx, def_id) || has_lang_attr(tcx, def_id) {
683        Some(ComesFromAllowExpect::No)
684    } else {
685        None
686    }
687}
688
689// These check_* functions seeds items that
690//   1) We want to explicitly consider as live:
691//     * Item annotated with #[allow(dead_code)]
692//         - This is done so that if we want to suppress warnings for a
693//           group of dead functions, we only have to annotate the "root".
694//           For example, if both `f` and `g` are dead and `f` calls `g`,
695//           then annotating `f` with `#[allow(dead_code)]` will suppress
696//           warning for both `f` and `g`.
697//     * Item annotated with #[lang=".."]
698//         - This is because lang items are always callable from elsewhere.
699//   or
700//   2) We are not sure to be live or not
701//     * Implementations of traits and trait methods
702fn check_item<'tcx>(
703    tcx: TyCtxt<'tcx>,
704    worklist: &mut Vec<(LocalDefId, ComesFromAllowExpect)>,
705    struct_constructors: &mut LocalDefIdMap<LocalDefId>,
706    unsolved_items: &mut Vec<(hir::ItemId, LocalDefId)>,
707    id: hir::ItemId,
708) {
709    let allow_dead_code = has_allow_dead_code_or_lang_attr(tcx, id.owner_id.def_id);
710    if let Some(comes_from_allow) = allow_dead_code {
711        worklist.push((id.owner_id.def_id, comes_from_allow));
712    }
713
714    match tcx.def_kind(id.owner_id) {
715        DefKind::Enum => {
716            let item = tcx.hir_item(id);
717            if let hir::ItemKind::Enum(_, _, ref enum_def) = item.kind {
718                if let Some(comes_from_allow) = allow_dead_code {
719                    worklist.extend(
720                        enum_def.variants.iter().map(|variant| (variant.def_id, comes_from_allow)),
721                    );
722                }
723
724                for variant in enum_def.variants {
725                    if let Some(ctor_def_id) = variant.data.ctor_def_id() {
726                        struct_constructors.insert(ctor_def_id, variant.def_id);
727                    }
728                }
729            }
730        }
731        DefKind::Impl { of_trait } => {
732            if let Some(comes_from_allow) =
733                has_allow_dead_code_or_lang_attr(tcx, id.owner_id.def_id)
734            {
735                worklist.push((id.owner_id.def_id, comes_from_allow));
736            } else if of_trait {
737                unsolved_items.push((id, id.owner_id.def_id));
738            }
739
740            for def_id in tcx.associated_item_def_ids(id.owner_id) {
741                let local_def_id = def_id.expect_local();
742
743                if let Some(comes_from_allow) = has_allow_dead_code_or_lang_attr(tcx, local_def_id)
744                {
745                    worklist.push((local_def_id, comes_from_allow));
746                } else if of_trait {
747                    // FIXME: This condition can be removed
748                    // if we support dead check for assoc consts and tys.
749                    if !matches!(tcx.def_kind(local_def_id), DefKind::AssocFn) {
750                        worklist.push((local_def_id, ComesFromAllowExpect::No));
751                    } else {
752                        // We only care about associated items of traits,
753                        // because they cannot be visited directly,
754                        // so we later mark them as live if their corresponding traits
755                        // or trait items and self types are both live,
756                        // but inherent associated items can be visited and marked directly.
757                        unsolved_items.push((id, local_def_id));
758                    }
759                }
760            }
761        }
762        DefKind::Struct => {
763            let item = tcx.hir_item(id);
764            if let hir::ItemKind::Struct(_, _, ref variant_data) = item.kind
765                && let Some(ctor_def_id) = variant_data.ctor_def_id()
766            {
767                struct_constructors.insert(ctor_def_id, item.owner_id.def_id);
768            }
769        }
770        DefKind::GlobalAsm => {
771            // global_asm! is always live.
772            worklist.push((id.owner_id.def_id, ComesFromAllowExpect::No));
773        }
774        DefKind::Const => {
775            let item = tcx.hir_item(id);
776            if let hir::ItemKind::Const(ident, ..) = item.kind
777                && ident.name == kw::Underscore
778            {
779                // `const _` is always live, as that syntax only exists for the side effects
780                // of type checking and evaluating the constant expression, and marking them
781                // as dead code would defeat that purpose.
782                worklist.push((id.owner_id.def_id, ComesFromAllowExpect::No));
783            }
784        }
785        _ => {}
786    }
787}
788
789fn check_trait_item(
790    tcx: TyCtxt<'_>,
791    worklist: &mut Vec<(LocalDefId, ComesFromAllowExpect)>,
792    id: hir::TraitItemId,
793) {
794    use hir::TraitItemKind::{Const, Fn};
795    if matches!(tcx.def_kind(id.owner_id), DefKind::AssocConst | DefKind::AssocFn) {
796        let trait_item = tcx.hir_trait_item(id);
797        if matches!(trait_item.kind, Const(_, Some(_)) | Fn(..))
798            && let Some(comes_from_allow) =
799                has_allow_dead_code_or_lang_attr(tcx, trait_item.owner_id.def_id)
800        {
801            worklist.push((trait_item.owner_id.def_id, comes_from_allow));
802        }
803    }
804}
805
806fn check_foreign_item(
807    tcx: TyCtxt<'_>,
808    worklist: &mut Vec<(LocalDefId, ComesFromAllowExpect)>,
809    id: hir::ForeignItemId,
810) {
811    if matches!(tcx.def_kind(id.owner_id), DefKind::Static { .. } | DefKind::Fn)
812        && let Some(comes_from_allow) = has_allow_dead_code_or_lang_attr(tcx, id.owner_id.def_id)
813    {
814        worklist.push((id.owner_id.def_id, comes_from_allow));
815    }
816}
817
818fn create_and_seed_worklist(
819    tcx: TyCtxt<'_>,
820) -> (
821    Vec<(LocalDefId, ComesFromAllowExpect)>,
822    LocalDefIdMap<LocalDefId>,
823    Vec<(hir::ItemId, LocalDefId)>,
824) {
825    let effective_visibilities = &tcx.effective_visibilities(());
826    // see `MarkSymbolVisitor::struct_constructors`
827    let mut unsolved_impl_item = Vec::new();
828    let mut struct_constructors = Default::default();
829    let mut worklist = effective_visibilities
830        .iter()
831        .filter_map(|(&id, effective_vis)| {
832            effective_vis
833                .is_public_at_level(Level::Reachable)
834                .then_some(id)
835                .map(|id| (id, ComesFromAllowExpect::No))
836        })
837        // Seed entry point
838        .chain(
839            tcx.entry_fn(())
840                .and_then(|(def_id, _)| def_id.as_local().map(|id| (id, ComesFromAllowExpect::No))),
841        )
842        .collect::<Vec<_>>();
843
844    let crate_items = tcx.hir_crate_items(());
845    for id in crate_items.free_items() {
846        check_item(tcx, &mut worklist, &mut struct_constructors, &mut unsolved_impl_item, id);
847    }
848
849    for id in crate_items.trait_items() {
850        check_trait_item(tcx, &mut worklist, id);
851    }
852
853    for id in crate_items.foreign_items() {
854        check_foreign_item(tcx, &mut worklist, id);
855    }
856
857    (worklist, struct_constructors, unsolved_impl_item)
858}
859
860fn live_symbols_and_ignored_derived_traits(
861    tcx: TyCtxt<'_>,
862    (): (),
863) -> (LocalDefIdSet, LocalDefIdMap<FxIndexSet<(DefId, DefId)>>) {
864    let (worklist, struct_constructors, mut unsolved_items) = create_and_seed_worklist(tcx);
865    let mut symbol_visitor = MarkSymbolVisitor {
866        worklist,
867        tcx,
868        maybe_typeck_results: None,
869        live_symbols: Default::default(),
870        repr_unconditionally_treats_fields_as_live: false,
871        repr_has_repr_simd: false,
872        in_pat: false,
873        ignore_variant_stack: vec![],
874        struct_constructors,
875        ignored_derived_traits: Default::default(),
876    };
877    symbol_visitor.mark_live_symbols();
878    let mut items_to_check;
879    (items_to_check, unsolved_items) =
880        unsolved_items.into_iter().partition(|&(impl_id, local_def_id)| {
881            symbol_visitor.check_impl_or_impl_item_live(impl_id, local_def_id)
882        });
883
884    while !items_to_check.is_empty() {
885        symbol_visitor.worklist =
886            items_to_check.into_iter().map(|(_, id)| (id, ComesFromAllowExpect::No)).collect();
887        symbol_visitor.mark_live_symbols();
888
889        (items_to_check, unsolved_items) =
890            unsolved_items.into_iter().partition(|&(impl_id, local_def_id)| {
891                symbol_visitor.check_impl_or_impl_item_live(impl_id, local_def_id)
892            });
893    }
894
895    (symbol_visitor.live_symbols, symbol_visitor.ignored_derived_traits)
896}
897
898struct DeadItem {
899    def_id: LocalDefId,
900    name: Symbol,
901    level: (lint::Level, Option<LintExpectationId>),
902}
903
904struct DeadVisitor<'tcx> {
905    tcx: TyCtxt<'tcx>,
906    live_symbols: &'tcx LocalDefIdSet,
907    ignored_derived_traits: &'tcx LocalDefIdMap<FxIndexSet<(DefId, DefId)>>,
908}
909
910enum ShouldWarnAboutField {
911    Yes,
912    No,
913}
914
915#[derive(Debug, Copy, Clone, PartialEq, Eq)]
916enum ReportOn {
917    /// Report on something that hasn't got a proper name to refer to
918    TupleField,
919    /// Report on something that has got a name, which could be a field but also a method
920    NamedField,
921}
922
923impl<'tcx> DeadVisitor<'tcx> {
924    fn should_warn_about_field(&mut self, field: &ty::FieldDef) -> ShouldWarnAboutField {
925        if self.live_symbols.contains(&field.did.expect_local()) {
926            return ShouldWarnAboutField::No;
927        }
928        let field_type = self.tcx.type_of(field.did).instantiate_identity();
929        if field_type.is_phantom_data() {
930            return ShouldWarnAboutField::No;
931        }
932        let is_positional = field.name.as_str().starts_with(|c: char| c.is_ascii_digit());
933        if is_positional
934            && self
935                .tcx
936                .layout_of(
937                    ty::TypingEnv::non_body_analysis(self.tcx, field.did)
938                        .as_query_input(field_type),
939                )
940                .map_or(true, |layout| layout.is_zst())
941        {
942            return ShouldWarnAboutField::No;
943        }
944        ShouldWarnAboutField::Yes
945    }
946
947    fn def_lint_level(&self, id: LocalDefId) -> (lint::Level, Option<LintExpectationId>) {
948        let hir_id = self.tcx.local_def_id_to_hir_id(id);
949        let level = self.tcx.lint_level_at_node(DEAD_CODE, hir_id);
950        (level.level, level.lint_id)
951    }
952
953    // # Panics
954    // All `dead_codes` must have the same lint level, otherwise we will intentionally ICE.
955    // This is because we emit a multi-spanned lint using the lint level of the `dead_codes`'s
956    // first local def id.
957    // Prefer calling `Self.warn_dead_code` or `Self.warn_dead_code_grouped_by_lint_level`
958    // since those methods group by lint level before calling this method.
959    fn lint_at_single_level(
960        &self,
961        dead_codes: &[&DeadItem],
962        participle: &str,
963        parent_item: Option<LocalDefId>,
964        report_on: ReportOn,
965    ) {
966        fn get_parent_if_enum_variant<'tcx>(
967            tcx: TyCtxt<'tcx>,
968            may_variant: LocalDefId,
969        ) -> LocalDefId {
970            if let Node::Variant(_) = tcx.hir_node_by_def_id(may_variant)
971                && let Some(enum_did) = tcx.opt_parent(may_variant.to_def_id())
972                && let Some(enum_local_id) = enum_did.as_local()
973                && let Node::Item(item) = tcx.hir_node_by_def_id(enum_local_id)
974                && let ItemKind::Enum(..) = item.kind
975            {
976                enum_local_id
977            } else {
978                may_variant
979            }
980        }
981
982        let Some(&first_item) = dead_codes.first() else {
983            return;
984        };
985        let tcx = self.tcx;
986
987        let first_lint_level = first_item.level;
988        assert!(dead_codes.iter().skip(1).all(|item| item.level == first_lint_level));
989
990        let names: Vec<_> = dead_codes.iter().map(|item| item.name).collect();
991        let spans: Vec<_> = dead_codes
992            .iter()
993            .map(|item| match tcx.def_ident_span(item.def_id) {
994                Some(s) => s.with_ctxt(tcx.def_span(item.def_id).ctxt()),
995                None => tcx.def_span(item.def_id),
996            })
997            .collect();
998
999        let descr = tcx.def_descr(first_item.def_id.to_def_id());
1000        // `impl` blocks are "batched" and (unlike other batching) might
1001        // contain different kinds of associated items.
1002        let descr = if dead_codes.iter().any(|item| tcx.def_descr(item.def_id.to_def_id()) != descr)
1003        {
1004            "associated item"
1005        } else {
1006            descr
1007        };
1008        let num = dead_codes.len();
1009        let multiple = num > 6;
1010        let name_list = names.into();
1011
1012        let parent_info = if let Some(parent_item) = parent_item {
1013            let parent_descr = tcx.def_descr(parent_item.to_def_id());
1014            let span = if let DefKind::Impl { .. } = tcx.def_kind(parent_item) {
1015                tcx.def_span(parent_item)
1016            } else {
1017                tcx.def_ident_span(parent_item).unwrap()
1018            };
1019            Some(ParentInfo { num, descr, parent_descr, span })
1020        } else {
1021            None
1022        };
1023
1024        let encl_def_id = parent_item.unwrap_or(first_item.def_id);
1025        // If parent of encl_def_id is an enum, use the parent ID instead.
1026        let encl_def_id = get_parent_if_enum_variant(tcx, encl_def_id);
1027
1028        let ignored_derived_impls =
1029            if let Some(ign_traits) = self.ignored_derived_traits.get(&encl_def_id) {
1030                let trait_list = ign_traits
1031                    .iter()
1032                    .map(|(trait_id, _)| self.tcx.item_name(*trait_id))
1033                    .collect::<Vec<_>>();
1034                let trait_list_len = trait_list.len();
1035                Some(IgnoredDerivedImpls {
1036                    name: self.tcx.item_name(encl_def_id.to_def_id()),
1037                    trait_list: trait_list.into(),
1038                    trait_list_len,
1039                })
1040            } else {
1041                None
1042            };
1043
1044        let enum_variants_with_same_name = dead_codes
1045            .iter()
1046            .filter_map(|dead_item| {
1047                if let Node::ImplItem(ImplItem {
1048                    kind: ImplItemKind::Fn(..) | ImplItemKind::Const(..),
1049                    ..
1050                }) = tcx.hir_node_by_def_id(dead_item.def_id)
1051                    && let Some(impl_did) = tcx.opt_parent(dead_item.def_id.to_def_id())
1052                    && let DefKind::Impl { of_trait: false } = tcx.def_kind(impl_did)
1053                    && let ty::Adt(maybe_enum, _) = tcx.type_of(impl_did).skip_binder().kind()
1054                    && maybe_enum.is_enum()
1055                    && let Some(variant) =
1056                        maybe_enum.variants().iter().find(|i| i.name == dead_item.name)
1057                {
1058                    Some(crate::errors::EnumVariantSameName {
1059                        dead_descr: tcx.def_descr(dead_item.def_id.to_def_id()),
1060                        dead_name: dead_item.name,
1061                        variant_span: tcx.def_span(variant.def_id),
1062                    })
1063                } else {
1064                    None
1065                }
1066            })
1067            .collect();
1068
1069        let diag = match report_on {
1070            ReportOn::TupleField => {
1071                let tuple_fields = if let Some(parent_id) = parent_item
1072                    && let node = tcx.hir_node_by_def_id(parent_id)
1073                    && let hir::Node::Item(hir::Item {
1074                        kind: hir::ItemKind::Struct(_, _, hir::VariantData::Tuple(fields, _, _)),
1075                        ..
1076                    }) = node
1077                {
1078                    *fields
1079                } else {
1080                    &[]
1081                };
1082
1083                let trailing_tuple_fields = if tuple_fields.len() >= dead_codes.len() {
1084                    LocalDefIdSet::from_iter(
1085                        tuple_fields
1086                            .iter()
1087                            .skip(tuple_fields.len() - dead_codes.len())
1088                            .map(|f| f.def_id),
1089                    )
1090                } else {
1091                    LocalDefIdSet::default()
1092                };
1093
1094                let fields_suggestion =
1095                    // Suggest removal if all tuple fields are at the end.
1096                    // Otherwise suggest removal or changing to unit type
1097                    if dead_codes.iter().all(|dc| trailing_tuple_fields.contains(&dc.def_id)) {
1098                        ChangeFields::Remove { num }
1099                    } else {
1100                        ChangeFields::ChangeToUnitTypeOrRemove { num, spans: spans.clone() }
1101                    };
1102
1103                MultipleDeadCodes::UnusedTupleStructFields {
1104                    multiple,
1105                    num,
1106                    descr,
1107                    participle,
1108                    name_list,
1109                    change_fields_suggestion: fields_suggestion,
1110                    parent_info,
1111                    ignored_derived_impls,
1112                }
1113            }
1114            ReportOn::NamedField => MultipleDeadCodes::DeadCodes {
1115                multiple,
1116                num,
1117                descr,
1118                participle,
1119                name_list,
1120                parent_info,
1121                ignored_derived_impls,
1122                enum_variants_with_same_name,
1123            },
1124        };
1125
1126        let hir_id = tcx.local_def_id_to_hir_id(first_item.def_id);
1127        self.tcx.emit_node_span_lint(DEAD_CODE, hir_id, MultiSpan::from_spans(spans), diag);
1128    }
1129
1130    fn warn_multiple(
1131        &self,
1132        def_id: LocalDefId,
1133        participle: &str,
1134        dead_codes: Vec<DeadItem>,
1135        report_on: ReportOn,
1136    ) {
1137        let mut dead_codes = dead_codes
1138            .iter()
1139            .filter(|v| !v.name.as_str().starts_with('_'))
1140            .collect::<Vec<&DeadItem>>();
1141        if dead_codes.is_empty() {
1142            return;
1143        }
1144        // FIXME: `dead_codes` should probably be morally equivalent to `IndexMap<(Level, LintExpectationId), (DefId, Symbol)>`
1145        dead_codes.sort_by_key(|v| v.level.0);
1146        for group in dead_codes.chunk_by(|a, b| a.level == b.level) {
1147            self.lint_at_single_level(&group, participle, Some(def_id), report_on);
1148        }
1149    }
1150
1151    fn warn_dead_code(&mut self, id: LocalDefId, participle: &str) {
1152        let item = DeadItem {
1153            def_id: id,
1154            name: self.tcx.item_name(id.to_def_id()),
1155            level: self.def_lint_level(id),
1156        };
1157        self.lint_at_single_level(&[&item], participle, None, ReportOn::NamedField);
1158    }
1159
1160    fn check_definition(&mut self, def_id: LocalDefId) {
1161        if self.is_live_code(def_id) {
1162            return;
1163        }
1164        match self.tcx.def_kind(def_id) {
1165            DefKind::AssocConst
1166            | DefKind::AssocFn
1167            | DefKind::Fn
1168            | DefKind::Static { .. }
1169            | DefKind::Const
1170            | DefKind::TyAlias
1171            | DefKind::Enum
1172            | DefKind::Union
1173            | DefKind::ForeignTy
1174            | DefKind::Trait => self.warn_dead_code(def_id, "used"),
1175            DefKind::Struct => self.warn_dead_code(def_id, "constructed"),
1176            DefKind::Variant | DefKind::Field => bug!("should be handled specially"),
1177            _ => {}
1178        }
1179    }
1180
1181    fn is_live_code(&self, def_id: LocalDefId) -> bool {
1182        // if we cannot get a name for the item, then we just assume that it is
1183        // live. I mean, we can't really emit a lint.
1184        let Some(name) = self.tcx.opt_item_name(def_id.to_def_id()) else {
1185            return true;
1186        };
1187
1188        self.live_symbols.contains(&def_id) || name.as_str().starts_with('_')
1189    }
1190}
1191
1192fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalModDefId) {
1193    let (live_symbols, ignored_derived_traits) = tcx.live_symbols_and_ignored_derived_traits(());
1194    let mut visitor = DeadVisitor { tcx, live_symbols, ignored_derived_traits };
1195
1196    let module_items = tcx.hir_module_items(module);
1197
1198    for item in module_items.free_items() {
1199        let def_kind = tcx.def_kind(item.owner_id);
1200
1201        let mut dead_codes = Vec::new();
1202        // Only diagnose unused assoc items in inherent impl and used trait,
1203        // for unused assoc items in impls of trait,
1204        // we have diagnosed them in the trait if they are unused,
1205        // for unused assoc items in unused trait,
1206        // we have diagnosed the unused trait.
1207        if matches!(def_kind, DefKind::Impl { of_trait: false })
1208            || (def_kind == DefKind::Trait && live_symbols.contains(&item.owner_id.def_id))
1209        {
1210            for &def_id in tcx.associated_item_def_ids(item.owner_id.def_id) {
1211                if let Some(local_def_id) = def_id.as_local()
1212                    && !visitor.is_live_code(local_def_id)
1213                {
1214                    let name = tcx.item_name(def_id);
1215                    let level = visitor.def_lint_level(local_def_id);
1216                    dead_codes.push(DeadItem { def_id: local_def_id, name, level });
1217                }
1218            }
1219        }
1220        if !dead_codes.is_empty() {
1221            visitor.warn_multiple(item.owner_id.def_id, "used", dead_codes, ReportOn::NamedField);
1222        }
1223
1224        if !live_symbols.contains(&item.owner_id.def_id) {
1225            let parent = tcx.local_parent(item.owner_id.def_id);
1226            if parent != module.to_local_def_id() && !live_symbols.contains(&parent) {
1227                // We already have diagnosed something.
1228                continue;
1229            }
1230            visitor.check_definition(item.owner_id.def_id);
1231            continue;
1232        }
1233
1234        if let DefKind::Struct | DefKind::Union | DefKind::Enum = def_kind {
1235            let adt = tcx.adt_def(item.owner_id);
1236            let mut dead_variants = Vec::new();
1237
1238            for variant in adt.variants() {
1239                let def_id = variant.def_id.expect_local();
1240                if !live_symbols.contains(&def_id) {
1241                    // Record to group diagnostics.
1242                    let level = visitor.def_lint_level(def_id);
1243                    dead_variants.push(DeadItem { def_id, name: variant.name, level });
1244                    continue;
1245                }
1246
1247                let is_positional = variant.fields.raw.first().is_some_and(|field| {
1248                    field.name.as_str().starts_with(|c: char| c.is_ascii_digit())
1249                });
1250                let report_on =
1251                    if is_positional { ReportOn::TupleField } else { ReportOn::NamedField };
1252                let dead_fields = variant
1253                    .fields
1254                    .iter()
1255                    .filter_map(|field| {
1256                        let def_id = field.did.expect_local();
1257                        if let ShouldWarnAboutField::Yes = visitor.should_warn_about_field(field) {
1258                            let level = visitor.def_lint_level(def_id);
1259                            Some(DeadItem { def_id, name: field.name, level })
1260                        } else {
1261                            None
1262                        }
1263                    })
1264                    .collect();
1265                visitor.warn_multiple(def_id, "read", dead_fields, report_on);
1266            }
1267
1268            visitor.warn_multiple(
1269                item.owner_id.def_id,
1270                "constructed",
1271                dead_variants,
1272                ReportOn::NamedField,
1273            );
1274        }
1275    }
1276
1277    for foreign_item in module_items.foreign_items() {
1278        visitor.check_definition(foreign_item.owner_id.def_id);
1279    }
1280}
1281
1282pub(crate) fn provide(providers: &mut Providers) {
1283    *providers =
1284        Providers { live_symbols_and_ignored_derived_traits, check_mod_deathness, ..*providers };
1285}