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, 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, 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, _) => adt.variant_of_res(res),
238 _ => span_bug!(lhs.span, "non-ADT in struct pattern"),
239 };
240 for pat in pats {
241 if let PatKind::Wild = pat.pat.kind {
242 continue;
243 }
244 let index = self.typeck_results().field_index(pat.hir_id);
245 self.insert_def_id(variant.fields[index].did);
246 }
247 }
248
249 fn handle_tuple_field_pattern_match(
250 &mut self,
251 lhs: &hir::Pat<'_>,
252 res: Res,
253 pats: &[hir::Pat<'_>],
254 dotdot: hir::DotDotPos,
255 ) {
256 let variant = match self.typeck_results().node_type(lhs.hir_id).kind() {
257 ty::Adt(adt, _) => adt.variant_of_res(res),
258 _ => {
259 self.tcx.dcx().span_delayed_bug(lhs.span, "non-ADT in tuple struct pattern");
260 return;
261 }
262 };
263 let dotdot = dotdot.as_opt_usize().unwrap_or(pats.len());
264 let first_n = pats.iter().enumerate().take(dotdot);
265 let missing = variant.fields.len() - pats.len();
266 let last_n = pats.iter().enumerate().skip(dotdot).map(|(idx, pat)| (idx + missing, pat));
267 for (idx, pat) in first_n.chain(last_n) {
268 if let PatKind::Wild = pat.kind {
269 continue;
270 }
271 self.insert_def_id(variant.fields[FieldIdx::from_usize(idx)].did);
272 }
273 }
274
275 fn handle_offset_of(&mut self, expr: &'tcx hir::Expr<'tcx>) {
276 let data = self.typeck_results().offset_of_data();
277 let &(container, ref indices) =
278 data.get(expr.hir_id).expect("no offset_of_data for offset_of");
279
280 let body_did = self.typeck_results().hir_owner.to_def_id();
281 let typing_env = ty::TypingEnv::non_body_analysis(self.tcx, body_did);
282
283 let mut current_ty = container;
284
285 for &(variant, field) in indices {
286 match current_ty.kind() {
287 ty::Adt(def, args) => {
288 let field = &def.variant(variant).fields[field];
289
290 self.insert_def_id(field.did);
291 let field_ty = field.ty(self.tcx, args);
292
293 current_ty = self.tcx.normalize_erasing_regions(typing_env, field_ty);
294 }
295 ty::Tuple(tys) => {
298 current_ty =
299 self.tcx.normalize_erasing_regions(typing_env, tys[field.as_usize()]);
300 }
301 _ => span_bug!(expr.span, "named field access on non-ADT"),
302 }
303 }
304 }
305
306 fn mark_live_symbols(&mut self) {
307 let mut scanned = UnordSet::default();
308 while let Some(work) = self.worklist.pop() {
309 if !scanned.insert(work) {
310 continue;
311 }
312
313 let (id, comes_from_allow_expect) = work;
314
315 if self.tcx.is_impl_trait_in_trait(id.to_def_id()) {
317 self.live_symbols.insert(id);
318 continue;
319 }
320
321 let id = self.struct_constructors.get(&id).copied().unwrap_or(id);
324
325 if comes_from_allow_expect != ComesFromAllowExpect::Yes {
347 self.live_symbols.insert(id);
348 }
349 self.visit_node(self.tcx.hir_node_by_def_id(id));
350 }
351 }
352
353 fn should_ignore_item(&mut self, def_id: DefId) -> bool {
357 if let Some(impl_of) = self.tcx.impl_of_method(def_id) {
358 if !self.tcx.is_automatically_derived(impl_of) {
359 return false;
360 }
361
362 if let Some(local_impl_of) = impl_of.as_local()
365 && let Some(local_def_id) = def_id.as_local()
366 && let Some(fn_sig) =
367 self.tcx.hir_fn_sig_by_hir_id(self.tcx.local_def_id_to_hir_id(local_def_id))
368 && matches!(fn_sig.decl.implicit_self, hir::ImplicitSelfKind::None)
369 && let TyKind::Path(QPath::Resolved(_, path)) =
370 self.tcx.hir_expect_item(local_impl_of).expect_impl().self_ty.kind
371 && let Res::Def(def_kind, did) = path.res
372 {
373 match def_kind {
374 DefKind::Struct | DefKind::Union if self.tcx.visibility(did).is_public() => {
378 return false;
379 }
380 DefKind::Enum => return false,
383 _ => (),
384 };
385 }
386
387 if let Some(trait_of) = self.tcx.trait_id_of_impl(impl_of)
388 && self.tcx.has_attr(trait_of, sym::rustc_trivial_field_reads)
389 {
390 let trait_ref = self.tcx.impl_trait_ref(impl_of).unwrap().instantiate_identity();
391 if let ty::Adt(adt_def, _) = trait_ref.self_ty().kind()
392 && let Some(adt_def_id) = adt_def.did().as_local()
393 {
394 self.ignored_derived_traits
395 .entry(adt_def_id)
396 .or_default()
397 .insert((trait_of, impl_of));
398 }
399 return true;
400 }
401 }
402
403 false
404 }
405
406 fn visit_node(&mut self, node: Node<'tcx>) {
407 if let Node::ImplItem(hir::ImplItem { owner_id, .. }) = node
408 && self.should_ignore_item(owner_id.to_def_id())
409 {
410 return;
411 }
412
413 let unconditionally_treated_fields_as_live =
414 self.repr_unconditionally_treats_fields_as_live;
415 let had_repr_simd = self.repr_has_repr_simd;
416 self.repr_unconditionally_treats_fields_as_live = false;
417 self.repr_has_repr_simd = false;
418 match node {
419 Node::Item(item) => match item.kind {
420 hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) => {
421 let def = self.tcx.adt_def(item.owner_id);
422 self.repr_unconditionally_treats_fields_as_live =
423 def.repr().c() || def.repr().transparent();
424 self.repr_has_repr_simd = def.repr().simd();
425
426 intravisit::walk_item(self, item)
427 }
428 hir::ItemKind::ForeignMod { .. } => {}
429 hir::ItemKind::Trait(.., trait_item_refs) => {
430 for trait_item in trait_item_refs {
432 if matches!(trait_item.kind, hir::AssocItemKind::Type) {
433 self.check_def_id(trait_item.id.owner_id.to_def_id());
434 }
435 }
436 intravisit::walk_item(self, item)
437 }
438 _ => intravisit::walk_item(self, item),
439 },
440 Node::TraitItem(trait_item) => {
441 let trait_item_id = trait_item.owner_id.to_def_id();
443 if let Some(trait_id) = self.tcx.trait_of_item(trait_item_id) {
444 self.check_def_id(trait_id);
445 }
446 intravisit::walk_trait_item(self, trait_item);
447 }
448 Node::ImplItem(impl_item) => {
449 let item = self.tcx.local_parent(impl_item.owner_id.def_id);
450 if self.tcx.impl_trait_ref(item).is_none() {
451 let self_ty = self.tcx.type_of(item).instantiate_identity();
456 match *self_ty.kind() {
457 ty::Adt(def, _) => self.check_def_id(def.did()),
458 ty::Foreign(did) => self.check_def_id(did),
459 ty::Dynamic(data, ..) => {
460 if let Some(def_id) = data.principal_def_id() {
461 self.check_def_id(def_id)
462 }
463 }
464 _ => {}
465 }
466 }
467 intravisit::walk_impl_item(self, impl_item);
468 }
469 Node::ForeignItem(foreign_item) => {
470 intravisit::walk_foreign_item(self, foreign_item);
471 }
472 Node::OpaqueTy(opaq) => intravisit::walk_opaque_ty(self, opaq),
473 _ => {}
474 }
475 self.repr_has_repr_simd = had_repr_simd;
476 self.repr_unconditionally_treats_fields_as_live = unconditionally_treated_fields_as_live;
477 }
478
479 fn mark_as_used_if_union(&mut self, adt: ty::AdtDef<'tcx>, fields: &[hir::ExprField<'_>]) {
480 if adt.is_union() && adt.non_enum_variant().fields.len() > 1 && adt.did().is_local() {
481 for field in fields {
482 let index = self.typeck_results().field_index(field.hir_id);
483 self.insert_def_id(adt.non_enum_variant().fields[index].did);
484 }
485 }
486 }
487
488 fn check_impl_or_impl_item_live(
493 &mut self,
494 impl_id: hir::ItemId,
495 local_def_id: LocalDefId,
496 ) -> bool {
497 if self.should_ignore_item(local_def_id.to_def_id()) {
498 return false;
499 }
500
501 let trait_def_id = match self.tcx.def_kind(local_def_id) {
502 DefKind::AssocFn => self.tcx.associated_item(local_def_id).trait_item_def_id,
504 DefKind::Impl { of_trait: true } => self
506 .tcx
507 .impl_trait_ref(impl_id.owner_id.def_id)
508 .and_then(|trait_ref| Some(trait_ref.skip_binder().def_id)),
509 _ => None,
510 };
511
512 if let Some(trait_def_id) = trait_def_id {
513 if let Some(trait_def_id) = trait_def_id.as_local()
514 && !self.live_symbols.contains(&trait_def_id)
515 {
516 return false;
517 }
518
519 if let Some(fn_sig) =
523 self.tcx.hir_fn_sig_by_hir_id(self.tcx.local_def_id_to_hir_id(local_def_id))
524 && matches!(fn_sig.decl.implicit_self, hir::ImplicitSelfKind::None)
525 && self.tcx.visibility(trait_def_id).is_public()
526 {
527 return true;
528 }
529 }
530
531 if let Some(local_def_id) =
533 local_adt_def_of_ty(self.tcx.hir_item(impl_id).expect_impl().self_ty)
534 && !self.live_symbols.contains(&local_def_id)
535 {
536 return false;
537 }
538
539 true
540 }
541}
542
543impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> {
544 fn visit_nested_body(&mut self, body: hir::BodyId) {
545 let old_maybe_typeck_results =
546 self.maybe_typeck_results.replace(self.tcx.typeck_body(body));
547 let body = self.tcx.hir_body(body);
548 self.visit_body(body);
549 self.maybe_typeck_results = old_maybe_typeck_results;
550 }
551
552 fn visit_variant_data(&mut self, def: &'tcx hir::VariantData<'tcx>) {
553 let tcx = self.tcx;
554 let unconditionally_treat_fields_as_live = self.repr_unconditionally_treats_fields_as_live;
555 let has_repr_simd = self.repr_has_repr_simd;
556 let effective_visibilities = &tcx.effective_visibilities(());
557 let live_fields = def.fields().iter().filter_map(|f| {
558 let def_id = f.def_id;
559 if unconditionally_treat_fields_as_live || (f.is_positional() && has_repr_simd) {
560 return Some(def_id);
561 }
562 if !effective_visibilities.is_reachable(f.hir_id.owner.def_id) {
563 return None;
564 }
565 if effective_visibilities.is_reachable(def_id) { Some(def_id) } else { None }
566 });
567 self.live_symbols.extend(live_fields);
568
569 intravisit::walk_struct_def(self, def);
570 }
571
572 fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
573 match expr.kind {
574 hir::ExprKind::Path(ref qpath @ QPath::TypeRelative(..)) => {
575 let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
576 self.handle_res(res);
577 }
578 hir::ExprKind::MethodCall(..) => {
579 self.lookup_and_handle_method(expr.hir_id);
580 }
581 hir::ExprKind::Field(ref lhs, ..) => {
582 if self.typeck_results().opt_field_index(expr.hir_id).is_some() {
583 self.handle_field_access(lhs, expr.hir_id);
584 } else {
585 self.tcx.dcx().span_delayed_bug(expr.span, "couldn't resolve index for field");
586 }
587 }
588 hir::ExprKind::Struct(qpath, fields, _) => {
589 let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
590 self.handle_res(res);
591 if let ty::Adt(adt, _) = self.typeck_results().expr_ty(expr).kind() {
592 self.mark_as_used_if_union(*adt, fields);
593 }
594 }
595 hir::ExprKind::Closure(cls) => {
596 self.insert_def_id(cls.def_id.to_def_id());
597 }
598 hir::ExprKind::OffsetOf(..) => {
599 self.handle_offset_of(expr);
600 }
601 _ => (),
602 }
603
604 intravisit::walk_expr(self, expr);
605 }
606
607 fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) {
608 let len = self.ignore_variant_stack.len();
612 self.ignore_variant_stack.extend(arm.pat.necessary_variants());
613 intravisit::walk_arm(self, arm);
614 self.ignore_variant_stack.truncate(len);
615 }
616
617 fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {
618 self.in_pat = true;
619 match pat.kind {
620 PatKind::Struct(ref path, fields, _) => {
621 let res = self.typeck_results().qpath_res(path, pat.hir_id);
622 self.handle_field_pattern_match(pat, res, fields);
623 }
624 PatKind::TupleStruct(ref qpath, fields, dotdot) => {
625 let res = self.typeck_results().qpath_res(qpath, pat.hir_id);
626 self.handle_tuple_field_pattern_match(pat, res, fields, dotdot);
627 }
628 _ => (),
629 }
630
631 intravisit::walk_pat(self, pat);
632 self.in_pat = false;
633 }
634
635 fn visit_pat_expr(&mut self, expr: &'tcx rustc_hir::PatExpr<'tcx>) {
636 match &expr.kind {
637 rustc_hir::PatExprKind::Path(qpath) => {
638 let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
639 self.handle_res(res);
640 }
641 _ => {}
642 }
643 intravisit::walk_pat_expr(self, expr);
644 }
645
646 fn visit_path(&mut self, path: &hir::Path<'tcx>, _: hir::HirId) {
647 self.handle_res(path.res);
648 intravisit::walk_path(self, path);
649 }
650
651 fn visit_anon_const(&mut self, c: &'tcx hir::AnonConst) {
652 let in_pat = mem::replace(&mut self.in_pat, false);
655
656 self.live_symbols.insert(c.def_id);
657 intravisit::walk_anon_const(self, c);
658
659 self.in_pat = in_pat;
660 }
661
662 fn visit_inline_const(&mut self, c: &'tcx hir::ConstBlock) {
663 let in_pat = mem::replace(&mut self.in_pat, false);
666
667 self.live_symbols.insert(c.def_id);
668 intravisit::walk_inline_const(self, c);
669
670 self.in_pat = in_pat;
671 }
672}
673
674fn has_allow_dead_code_or_lang_attr(
675 tcx: TyCtxt<'_>,
676 def_id: LocalDefId,
677) -> Option<ComesFromAllowExpect> {
678 fn has_lang_attr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
679 tcx.has_attr(def_id, sym::lang)
680 || tcx.has_attr(def_id, sym::panic_handler)
682 }
683
684 fn has_allow_expect_dead_code(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
685 let hir_id = tcx.local_def_id_to_hir_id(def_id);
686 let lint_level = tcx.lint_level_at_node(lint::builtin::DEAD_CODE, hir_id).level;
687 matches!(lint_level, lint::Allow | lint::Expect)
688 }
689
690 fn has_used_like_attr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
691 tcx.def_kind(def_id).has_codegen_attrs() && {
692 let cg_attrs = tcx.codegen_fn_attrs(def_id);
693
694 cg_attrs.contains_extern_indicator()
697 || cg_attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER)
698 || cg_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER)
699 }
700 }
701
702 if has_allow_expect_dead_code(tcx, def_id) {
703 Some(ComesFromAllowExpect::Yes)
704 } else if has_used_like_attr(tcx, def_id) || has_lang_attr(tcx, def_id) {
705 Some(ComesFromAllowExpect::No)
706 } else {
707 None
708 }
709}
710
711fn check_item<'tcx>(
725 tcx: TyCtxt<'tcx>,
726 worklist: &mut Vec<(LocalDefId, ComesFromAllowExpect)>,
727 struct_constructors: &mut LocalDefIdMap<LocalDefId>,
728 unsolved_items: &mut Vec<(hir::ItemId, LocalDefId)>,
729 id: hir::ItemId,
730) {
731 let allow_dead_code = has_allow_dead_code_or_lang_attr(tcx, id.owner_id.def_id);
732 if let Some(comes_from_allow) = allow_dead_code {
733 worklist.push((id.owner_id.def_id, comes_from_allow));
734 }
735
736 match tcx.def_kind(id.owner_id) {
737 DefKind::Enum => {
738 let item = tcx.hir_item(id);
739 if let hir::ItemKind::Enum(_, _, ref enum_def) = item.kind {
740 if let Some(comes_from_allow) = allow_dead_code {
741 worklist.extend(
742 enum_def.variants.iter().map(|variant| (variant.def_id, comes_from_allow)),
743 );
744 }
745
746 for variant in enum_def.variants {
747 if let Some(ctor_def_id) = variant.data.ctor_def_id() {
748 struct_constructors.insert(ctor_def_id, variant.def_id);
749 }
750 }
751 }
752 }
753 DefKind::Impl { of_trait } => {
754 if let Some(comes_from_allow) =
755 has_allow_dead_code_or_lang_attr(tcx, id.owner_id.def_id)
756 {
757 worklist.push((id.owner_id.def_id, comes_from_allow));
758 } else if of_trait {
759 unsolved_items.push((id, id.owner_id.def_id));
760 }
761
762 for def_id in tcx.associated_item_def_ids(id.owner_id) {
763 let local_def_id = def_id.expect_local();
764
765 if let Some(comes_from_allow) = has_allow_dead_code_or_lang_attr(tcx, local_def_id)
766 {
767 worklist.push((local_def_id, comes_from_allow));
768 } else if of_trait {
769 if !matches!(tcx.def_kind(local_def_id), DefKind::AssocFn) {
772 worklist.push((local_def_id, ComesFromAllowExpect::No));
773 } else {
774 unsolved_items.push((id, local_def_id));
780 }
781 }
782 }
783 }
784 DefKind::Struct => {
785 let item = tcx.hir_item(id);
786 if let hir::ItemKind::Struct(_, _, ref variant_data) = item.kind
787 && let Some(ctor_def_id) = variant_data.ctor_def_id()
788 {
789 struct_constructors.insert(ctor_def_id, item.owner_id.def_id);
790 }
791 }
792 DefKind::GlobalAsm => {
793 worklist.push((id.owner_id.def_id, ComesFromAllowExpect::No));
795 }
796 _ => {}
797 }
798}
799
800fn check_trait_item(
801 tcx: TyCtxt<'_>,
802 worklist: &mut Vec<(LocalDefId, ComesFromAllowExpect)>,
803 id: hir::TraitItemId,
804) {
805 use hir::TraitItemKind::{Const, Fn};
806 if matches!(tcx.def_kind(id.owner_id), DefKind::AssocConst | DefKind::AssocFn) {
807 let trait_item = tcx.hir_trait_item(id);
808 if matches!(trait_item.kind, Const(_, Some(_)) | Fn(..))
809 && let Some(comes_from_allow) =
810 has_allow_dead_code_or_lang_attr(tcx, trait_item.owner_id.def_id)
811 {
812 worklist.push((trait_item.owner_id.def_id, comes_from_allow));
813 }
814 }
815}
816
817fn check_foreign_item(
818 tcx: TyCtxt<'_>,
819 worklist: &mut Vec<(LocalDefId, ComesFromAllowExpect)>,
820 id: hir::ForeignItemId,
821) {
822 if matches!(tcx.def_kind(id.owner_id), DefKind::Static { .. } | DefKind::Fn)
823 && let Some(comes_from_allow) = has_allow_dead_code_or_lang_attr(tcx, id.owner_id.def_id)
824 {
825 worklist.push((id.owner_id.def_id, comes_from_allow));
826 }
827}
828
829fn create_and_seed_worklist(
830 tcx: TyCtxt<'_>,
831) -> (
832 Vec<(LocalDefId, ComesFromAllowExpect)>,
833 LocalDefIdMap<LocalDefId>,
834 Vec<(hir::ItemId, LocalDefId)>,
835) {
836 let effective_visibilities = &tcx.effective_visibilities(());
837 let mut unsolved_impl_item = Vec::new();
839 let mut struct_constructors = Default::default();
840 let mut worklist = effective_visibilities
841 .iter()
842 .filter_map(|(&id, effective_vis)| {
843 effective_vis
844 .is_public_at_level(Level::Reachable)
845 .then_some(id)
846 .map(|id| (id, ComesFromAllowExpect::No))
847 })
848 .chain(
850 tcx.entry_fn(())
851 .and_then(|(def_id, _)| def_id.as_local().map(|id| (id, ComesFromAllowExpect::No))),
852 )
853 .collect::<Vec<_>>();
854
855 let crate_items = tcx.hir_crate_items(());
856 for id in crate_items.free_items() {
857 check_item(tcx, &mut worklist, &mut struct_constructors, &mut unsolved_impl_item, id);
858 }
859
860 for id in crate_items.trait_items() {
861 check_trait_item(tcx, &mut worklist, id);
862 }
863
864 for id in crate_items.foreign_items() {
865 check_foreign_item(tcx, &mut worklist, id);
866 }
867
868 (worklist, struct_constructors, unsolved_impl_item)
869}
870
871fn live_symbols_and_ignored_derived_traits(
872 tcx: TyCtxt<'_>,
873 (): (),
874) -> (LocalDefIdSet, LocalDefIdMap<FxIndexSet<(DefId, DefId)>>) {
875 let (worklist, struct_constructors, mut unsolved_items) = create_and_seed_worklist(tcx);
876 let mut symbol_visitor = MarkSymbolVisitor {
877 worklist,
878 tcx,
879 maybe_typeck_results: None,
880 live_symbols: Default::default(),
881 repr_unconditionally_treats_fields_as_live: false,
882 repr_has_repr_simd: false,
883 in_pat: false,
884 ignore_variant_stack: vec![],
885 struct_constructors,
886 ignored_derived_traits: Default::default(),
887 };
888 symbol_visitor.mark_live_symbols();
889 let mut items_to_check;
890 (items_to_check, unsolved_items) =
891 unsolved_items.into_iter().partition(|&(impl_id, local_def_id)| {
892 symbol_visitor.check_impl_or_impl_item_live(impl_id, local_def_id)
893 });
894
895 while !items_to_check.is_empty() {
896 symbol_visitor.worklist =
897 items_to_check.into_iter().map(|(_, id)| (id, ComesFromAllowExpect::No)).collect();
898 symbol_visitor.mark_live_symbols();
899
900 (items_to_check, unsolved_items) =
901 unsolved_items.into_iter().partition(|&(impl_id, local_def_id)| {
902 symbol_visitor.check_impl_or_impl_item_live(impl_id, local_def_id)
903 });
904 }
905
906 (symbol_visitor.live_symbols, symbol_visitor.ignored_derived_traits)
907}
908
909struct DeadItem {
910 def_id: LocalDefId,
911 name: Symbol,
912 level: (lint::Level, Option<LintExpectationId>),
913}
914
915struct DeadVisitor<'tcx> {
916 tcx: TyCtxt<'tcx>,
917 live_symbols: &'tcx LocalDefIdSet,
918 ignored_derived_traits: &'tcx LocalDefIdMap<FxIndexSet<(DefId, DefId)>>,
919}
920
921enum ShouldWarnAboutField {
922 Yes,
923 No,
924}
925
926#[derive(Debug, Copy, Clone, PartialEq, Eq)]
927enum ReportOn {
928 TupleField,
929 NamedField,
930}
931
932impl<'tcx> DeadVisitor<'tcx> {
933 fn should_warn_about_field(&mut self, field: &ty::FieldDef) -> ShouldWarnAboutField {
934 if self.live_symbols.contains(&field.did.expect_local()) {
935 return ShouldWarnAboutField::No;
936 }
937 let field_type = self.tcx.type_of(field.did).instantiate_identity();
938 if field_type.is_phantom_data() {
939 return ShouldWarnAboutField::No;
940 }
941 let is_positional = field.name.as_str().starts_with(|c: char| c.is_ascii_digit());
942 if is_positional
943 && self
944 .tcx
945 .layout_of(
946 ty::TypingEnv::non_body_analysis(self.tcx, field.did)
947 .as_query_input(field_type),
948 )
949 .map_or(true, |layout| layout.is_zst())
950 {
951 return ShouldWarnAboutField::No;
952 }
953 ShouldWarnAboutField::Yes
954 }
955
956 fn def_lint_level(&self, id: LocalDefId) -> (lint::Level, Option<LintExpectationId>) {
957 let hir_id = self.tcx.local_def_id_to_hir_id(id);
958 let level = self.tcx.lint_level_at_node(DEAD_CODE, hir_id);
959 (level.level, level.lint_id)
960 }
961
962 fn lint_at_single_level(
969 &self,
970 dead_codes: &[&DeadItem],
971 participle: &str,
972 parent_item: Option<LocalDefId>,
973 report_on: ReportOn,
974 ) {
975 fn get_parent_if_enum_variant<'tcx>(
976 tcx: TyCtxt<'tcx>,
977 may_variant: LocalDefId,
978 ) -> LocalDefId {
979 if let Node::Variant(_) = tcx.hir_node_by_def_id(may_variant)
980 && let Some(enum_did) = tcx.opt_parent(may_variant.to_def_id())
981 && let Some(enum_local_id) = enum_did.as_local()
982 && let Node::Item(item) = tcx.hir_node_by_def_id(enum_local_id)
983 && let ItemKind::Enum(..) = item.kind
984 {
985 enum_local_id
986 } else {
987 may_variant
988 }
989 }
990
991 let Some(&first_item) = dead_codes.first() else {
992 return;
993 };
994 let tcx = self.tcx;
995
996 let first_lint_level = first_item.level;
997 assert!(dead_codes.iter().skip(1).all(|item| item.level == first_lint_level));
998
999 let names: Vec<_> = dead_codes.iter().map(|item| item.name).collect();
1000 let spans: Vec<_> = dead_codes
1001 .iter()
1002 .map(|item| match tcx.def_ident_span(item.def_id) {
1003 Some(s) => s.with_ctxt(tcx.def_span(item.def_id).ctxt()),
1004 None => tcx.def_span(item.def_id),
1005 })
1006 .collect();
1007
1008 let descr = tcx.def_descr(first_item.def_id.to_def_id());
1009 let descr = if dead_codes.iter().any(|item| tcx.def_descr(item.def_id.to_def_id()) != descr)
1012 {
1013 "associated item"
1014 } else {
1015 descr
1016 };
1017 let num = dead_codes.len();
1018 let multiple = num > 6;
1019 let name_list = names.into();
1020
1021 let parent_info = if let Some(parent_item) = parent_item {
1022 let parent_descr = tcx.def_descr(parent_item.to_def_id());
1023 let span = if let DefKind::Impl { .. } = tcx.def_kind(parent_item) {
1024 tcx.def_span(parent_item)
1025 } else {
1026 tcx.def_ident_span(parent_item).unwrap()
1027 };
1028 Some(ParentInfo { num, descr, parent_descr, span })
1029 } else {
1030 None
1031 };
1032
1033 let encl_def_id = parent_item.unwrap_or(first_item.def_id);
1034 let encl_def_id = get_parent_if_enum_variant(tcx, encl_def_id);
1036
1037 let ignored_derived_impls =
1038 if let Some(ign_traits) = self.ignored_derived_traits.get(&encl_def_id) {
1039 let trait_list = ign_traits
1040 .iter()
1041 .map(|(trait_id, _)| self.tcx.item_name(*trait_id))
1042 .collect::<Vec<_>>();
1043 let trait_list_len = trait_list.len();
1044 Some(IgnoredDerivedImpls {
1045 name: self.tcx.item_name(encl_def_id.to_def_id()),
1046 trait_list: trait_list.into(),
1047 trait_list_len,
1048 })
1049 } else {
1050 None
1051 };
1052
1053 let diag = match report_on {
1054 ReportOn::TupleField => {
1055 let tuple_fields = if let Some(parent_id) = parent_item
1056 && let node = tcx.hir_node_by_def_id(parent_id)
1057 && let hir::Node::Item(hir::Item {
1058 kind: hir::ItemKind::Struct(_, _, hir::VariantData::Tuple(fields, _, _)),
1059 ..
1060 }) = node
1061 {
1062 *fields
1063 } else {
1064 &[]
1065 };
1066
1067 let trailing_tuple_fields = if tuple_fields.len() >= dead_codes.len() {
1068 LocalDefIdSet::from_iter(
1069 tuple_fields
1070 .iter()
1071 .skip(tuple_fields.len() - dead_codes.len())
1072 .map(|f| f.def_id),
1073 )
1074 } else {
1075 LocalDefIdSet::default()
1076 };
1077
1078 let fields_suggestion =
1079 if dead_codes.iter().all(|dc| trailing_tuple_fields.contains(&dc.def_id)) {
1082 ChangeFields::Remove { num }
1083 } else {
1084 ChangeFields::ChangeToUnitTypeOrRemove { num, spans: spans.clone() }
1085 };
1086
1087 MultipleDeadCodes::UnusedTupleStructFields {
1088 multiple,
1089 num,
1090 descr,
1091 participle,
1092 name_list,
1093 change_fields_suggestion: fields_suggestion,
1094 parent_info,
1095 ignored_derived_impls,
1096 }
1097 }
1098 ReportOn::NamedField => MultipleDeadCodes::DeadCodes {
1099 multiple,
1100 num,
1101 descr,
1102 participle,
1103 name_list,
1104 parent_info,
1105 ignored_derived_impls,
1106 },
1107 };
1108
1109 let hir_id = tcx.local_def_id_to_hir_id(first_item.def_id);
1110 self.tcx.emit_node_span_lint(DEAD_CODE, hir_id, MultiSpan::from_spans(spans), diag);
1111 }
1112
1113 fn warn_multiple(
1114 &self,
1115 def_id: LocalDefId,
1116 participle: &str,
1117 dead_codes: Vec<DeadItem>,
1118 report_on: ReportOn,
1119 ) {
1120 let mut dead_codes = dead_codes
1121 .iter()
1122 .filter(|v| !v.name.as_str().starts_with('_'))
1123 .collect::<Vec<&DeadItem>>();
1124 if dead_codes.is_empty() {
1125 return;
1126 }
1127 dead_codes.sort_by_key(|v| v.level.0);
1129 for group in dead_codes.chunk_by(|a, b| a.level == b.level) {
1130 self.lint_at_single_level(&group, participle, Some(def_id), report_on);
1131 }
1132 }
1133
1134 fn warn_dead_code(&mut self, id: LocalDefId, participle: &str) {
1135 let item = DeadItem {
1136 def_id: id,
1137 name: self.tcx.item_name(id.to_def_id()),
1138 level: self.def_lint_level(id),
1139 };
1140 self.lint_at_single_level(&[&item], participle, None, ReportOn::NamedField);
1141 }
1142
1143 fn check_definition(&mut self, def_id: LocalDefId) {
1144 if self.is_live_code(def_id) {
1145 return;
1146 }
1147 match self.tcx.def_kind(def_id) {
1148 DefKind::AssocConst
1149 | DefKind::AssocFn
1150 | DefKind::Fn
1151 | DefKind::Static { .. }
1152 | DefKind::Const
1153 | DefKind::TyAlias
1154 | DefKind::Enum
1155 | DefKind::Union
1156 | DefKind::ForeignTy
1157 | DefKind::Trait => self.warn_dead_code(def_id, "used"),
1158 DefKind::Struct => self.warn_dead_code(def_id, "constructed"),
1159 DefKind::Variant | DefKind::Field => bug!("should be handled specially"),
1160 _ => {}
1161 }
1162 }
1163
1164 fn is_live_code(&self, def_id: LocalDefId) -> bool {
1165 let Some(name) = self.tcx.opt_item_name(def_id.to_def_id()) else {
1168 return true;
1169 };
1170
1171 self.live_symbols.contains(&def_id) || name.as_str().starts_with('_')
1172 }
1173}
1174
1175fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalModDefId) {
1176 let (live_symbols, ignored_derived_traits) = tcx.live_symbols_and_ignored_derived_traits(());
1177 let mut visitor = DeadVisitor { tcx, live_symbols, ignored_derived_traits };
1178
1179 let module_items = tcx.hir_module_items(module);
1180
1181 for item in module_items.free_items() {
1182 let def_kind = tcx.def_kind(item.owner_id);
1183
1184 let mut dead_codes = Vec::new();
1185 if matches!(def_kind, DefKind::Impl { of_trait: false })
1191 || (def_kind == DefKind::Trait && live_symbols.contains(&item.owner_id.def_id))
1192 {
1193 for &def_id in tcx.associated_item_def_ids(item.owner_id.def_id) {
1194 if let Some(local_def_id) = def_id.as_local()
1195 && !visitor.is_live_code(local_def_id)
1196 {
1197 let name = tcx.item_name(def_id);
1198 let level = visitor.def_lint_level(local_def_id);
1199 dead_codes.push(DeadItem { def_id: local_def_id, name, level });
1200 }
1201 }
1202 }
1203 if !dead_codes.is_empty() {
1204 visitor.warn_multiple(item.owner_id.def_id, "used", dead_codes, ReportOn::NamedField);
1205 }
1206
1207 if !live_symbols.contains(&item.owner_id.def_id) {
1208 let parent = tcx.local_parent(item.owner_id.def_id);
1209 if parent != module.to_local_def_id() && !live_symbols.contains(&parent) {
1210 continue;
1212 }
1213 visitor.check_definition(item.owner_id.def_id);
1214 continue;
1215 }
1216
1217 if let DefKind::Struct | DefKind::Union | DefKind::Enum = def_kind {
1218 let adt = tcx.adt_def(item.owner_id);
1219 let mut dead_variants = Vec::new();
1220
1221 for variant in adt.variants() {
1222 let def_id = variant.def_id.expect_local();
1223 if !live_symbols.contains(&def_id) {
1224 let level = visitor.def_lint_level(def_id);
1226 dead_variants.push(DeadItem { def_id, name: variant.name, level });
1227 continue;
1228 }
1229
1230 let is_positional = variant.fields.raw.first().is_some_and(|field| {
1231 field.name.as_str().starts_with(|c: char| c.is_ascii_digit())
1232 });
1233 let report_on =
1234 if is_positional { ReportOn::TupleField } else { ReportOn::NamedField };
1235 let dead_fields = variant
1236 .fields
1237 .iter()
1238 .filter_map(|field| {
1239 let def_id = field.did.expect_local();
1240 if let ShouldWarnAboutField::Yes = visitor.should_warn_about_field(field) {
1241 let level = visitor.def_lint_level(def_id);
1242 Some(DeadItem { def_id, name: field.name, level })
1243 } else {
1244 None
1245 }
1246 })
1247 .collect();
1248 visitor.warn_multiple(def_id, "read", dead_fields, report_on);
1249 }
1250
1251 visitor.warn_multiple(
1252 item.owner_id.def_id,
1253 "constructed",
1254 dead_variants,
1255 ReportOn::NamedField,
1256 );
1257 }
1258 }
1259
1260 for foreign_item in module_items.foreign_items() {
1261 visitor.check_definition(foreign_item.owner_id.def_id);
1262 }
1263}
1264
1265pub(crate) fn provide(providers: &mut Providers) {
1266 *providers =
1267 Providers { live_symbols_and_ignored_derived_traits, check_mod_deathness, ..*providers };
1268}