1use 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
31fn 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
48fn 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#[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 struct_constructors: LocalDefIdMap<LocalDefId>,
84 ignored_derived_traits: LocalDefIdMap<FxIndexSet<(DefId, DefId)>>,
88}
89
90impl<'tcx> MarkSymbolVisitor<'tcx> {
91 #[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)] 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 self.handle_assign(base);
180 } else {
181 self.visit_expr(expr);
182 }
183 }
184
185 #[allow(dead_code)] 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 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 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 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 if self.tcx.is_impl_trait_in_trait(id.to_def_id()) {
328 self.live_symbols.insert(id);
329 continue;
330 }
331
332 let id = self.struct_constructors.get(&id).copied().unwrap_or(id);
335
336 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 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 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 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 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 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 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 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 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 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 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 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 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 || 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 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
689fn 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 if !matches!(tcx.def_kind(local_def_id), DefKind::AssocFn) {
750 worklist.push((local_def_id, ComesFromAllowExpect::No));
751 } else {
752 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 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 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 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 .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 TupleField,
919 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 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 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 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 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 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 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 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 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 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}