1use std::mem;
7use std::ops::ControlFlow;
8
9use hir::def_id::{LocalDefIdMap, LocalDefIdSet};
10use rustc_abi::FieldIdx;
11use rustc_data_structures::fx::FxIndexSet;
12use rustc_errors::{ErrorGuaranteed, MultiSpan};
13use rustc_hir::def::{CtorOf, DefKind, Res};
14use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId};
15use rustc_hir::intravisit::{self, Visitor};
16use rustc_hir::{self as hir, Node, PatKind, QPath};
17use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
18use rustc_middle::middle::privacy::Level;
19use rustc_middle::query::Providers;
20use rustc_middle::ty::{self, AssocTag, TyCtxt};
21use rustc_middle::{bug, span_bug};
22use rustc_session::lint::builtin::DEAD_CODE;
23use rustc_session::lint::{self, LintExpectationId};
24use rustc_span::{Symbol, kw, sym};
25
26use crate::errors::{
27 ChangeFields, IgnoredDerivedImpls, MultipleDeadCodes, ParentInfo, UselessAssignment,
28};
29
30fn should_explore(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
34 match tcx.def_kind(def_id) {
35 DefKind::Mod
36 | DefKind::Struct
37 | DefKind::Union
38 | DefKind::Enum
39 | DefKind::Variant
40 | DefKind::Trait
41 | DefKind::TyAlias
42 | DefKind::ForeignTy
43 | DefKind::TraitAlias
44 | DefKind::AssocTy
45 | DefKind::Fn
46 | DefKind::Const
47 | DefKind::Static { .. }
48 | DefKind::AssocFn
49 | DefKind::AssocConst
50 | DefKind::Macro(_)
51 | DefKind::GlobalAsm
52 | DefKind::Impl { .. }
53 | DefKind::OpaqueTy
54 | DefKind::AnonConst
55 | DefKind::InlineConst
56 | DefKind::ExternCrate
57 | DefKind::Use
58 | DefKind::Ctor(..)
59 | DefKind::ForeignMod => true,
60
61 DefKind::TyParam
62 | DefKind::ConstParam
63 | DefKind::Field
64 | DefKind::LifetimeParam
65 | DefKind::Closure
66 | DefKind::SyntheticCoroutineBody => false,
67 }
68}
69
70#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
73enum ComesFromAllowExpect {
74 Yes,
75 No,
76}
77
78struct MarkSymbolVisitor<'tcx> {
79 worklist: Vec<(LocalDefId, ComesFromAllowExpect)>,
80 tcx: TyCtxt<'tcx>,
81 maybe_typeck_results: Option<&'tcx ty::TypeckResults<'tcx>>,
82 scanned: LocalDefIdSet,
83 live_symbols: LocalDefIdSet,
84 repr_unconditionally_treats_fields_as_live: bool,
85 repr_has_repr_simd: bool,
86 in_pat: bool,
87 ignore_variant_stack: Vec<DefId>,
88 ignored_derived_traits: LocalDefIdMap<FxIndexSet<DefId>>,
92}
93
94impl<'tcx> MarkSymbolVisitor<'tcx> {
95 #[track_caller]
99 fn typeck_results(&self) -> &'tcx ty::TypeckResults<'tcx> {
100 self.maybe_typeck_results
101 .expect("`MarkSymbolVisitor::typeck_results` called outside of body")
102 }
103
104 fn check_def_id(&mut self, def_id: DefId) {
105 if let Some(def_id) = def_id.as_local() {
106 if should_explore(self.tcx, def_id) {
107 self.worklist.push((def_id, ComesFromAllowExpect::No));
108 }
109 self.live_symbols.insert(def_id);
110 }
111 }
112
113 fn insert_def_id(&mut self, def_id: DefId) {
114 if let Some(def_id) = def_id.as_local() {
115 debug_assert!(!should_explore(self.tcx, def_id));
116 self.live_symbols.insert(def_id);
117 }
118 }
119
120 fn handle_res(&mut self, res: Res) {
121 match res {
122 Res::Def(
123 DefKind::Const | DefKind::AssocConst | DefKind::AssocTy | DefKind::TyAlias,
124 def_id,
125 ) => {
126 self.check_def_id(def_id);
127 }
128 _ if self.in_pat => {}
129 Res::PrimTy(..) | Res::SelfCtor(..) | Res::Local(..) => {}
130 Res::Def(DefKind::Ctor(CtorOf::Variant, ..), ctor_def_id) => {
131 let variant_id = self.tcx.parent(ctor_def_id);
132 let enum_id = self.tcx.parent(variant_id);
133 self.check_def_id(enum_id);
134 if !self.ignore_variant_stack.contains(&ctor_def_id) {
135 self.check_def_id(variant_id);
136 }
137 }
138 Res::Def(DefKind::Variant, variant_id) => {
139 let enum_id = self.tcx.parent(variant_id);
140 self.check_def_id(enum_id);
141 if !self.ignore_variant_stack.contains(&variant_id) {
142 self.check_def_id(variant_id);
143 }
144 }
145 Res::Def(_, def_id) => self.check_def_id(def_id),
146 Res::SelfTyParam { trait_: t } => self.check_def_id(t),
147 Res::SelfTyAlias { alias_to: i, .. } => self.check_def_id(i),
148 Res::ToolMod | Res::NonMacroAttr(..) | Res::Err => {}
149 }
150 }
151
152 fn lookup_and_handle_method(&mut self, id: hir::HirId) {
153 if let Some(def_id) = self.typeck_results().type_dependent_def_id(id) {
154 self.check_def_id(def_id);
155 } else {
156 assert!(
157 self.typeck_results().tainted_by_errors.is_some(),
158 "no type-dependent def for method"
159 );
160 }
161 }
162
163 fn handle_field_access(&mut self, lhs: &hir::Expr<'_>, hir_id: hir::HirId) {
164 match self.typeck_results().expr_ty_adjusted(lhs).kind() {
165 ty::Adt(def, _) => {
166 let index = self.typeck_results().field_index(hir_id);
167 self.insert_def_id(def.non_enum_variant().fields[index].did);
168 }
169 ty::Tuple(..) => {}
170 ty::Error(_) => {}
171 kind => span_bug!(lhs.span, "named field access on non-ADT: {kind:?}"),
172 }
173 }
174
175 fn handle_assign(&mut self, expr: &'tcx hir::Expr<'tcx>) {
176 if self
177 .typeck_results()
178 .expr_adjustments(expr)
179 .iter()
180 .any(|adj| matches!(adj.kind, ty::adjustment::Adjust::Deref(_)))
181 {
182 let _ = self.visit_expr(expr);
183 } else if let hir::ExprKind::Field(base, ..) = expr.kind {
184 self.handle_assign(base);
186 } else {
187 let _ = self.visit_expr(expr);
188 }
189 }
190
191 fn check_for_self_assign(&mut self, assign: &'tcx hir::Expr<'tcx>) {
192 fn check_for_self_assign_helper<'tcx>(
193 typeck_results: &'tcx ty::TypeckResults<'tcx>,
194 lhs: &'tcx hir::Expr<'tcx>,
195 rhs: &'tcx hir::Expr<'tcx>,
196 ) -> bool {
197 match (&lhs.kind, &rhs.kind) {
198 (hir::ExprKind::Path(qpath_l), hir::ExprKind::Path(qpath_r)) => {
199 if let (Res::Local(id_l), Res::Local(id_r)) = (
200 typeck_results.qpath_res(qpath_l, lhs.hir_id),
201 typeck_results.qpath_res(qpath_r, rhs.hir_id),
202 ) {
203 if id_l == id_r {
204 return true;
205 }
206 }
207 return false;
208 }
209 (hir::ExprKind::Field(lhs_l, ident_l), hir::ExprKind::Field(lhs_r, ident_r)) => {
210 if ident_l == ident_r {
211 return check_for_self_assign_helper(typeck_results, lhs_l, lhs_r);
212 }
213 return false;
214 }
215 _ => {
216 return false;
217 }
218 }
219 }
220
221 if let hir::ExprKind::Assign(lhs, rhs, _) = assign.kind
222 && check_for_self_assign_helper(self.typeck_results(), lhs, rhs)
223 && !assign.span.from_expansion()
224 {
225 let is_field_assign = matches!(lhs.kind, hir::ExprKind::Field(..));
226 self.tcx.emit_node_span_lint(
227 lint::builtin::DEAD_CODE,
228 assign.hir_id,
229 assign.span,
230 UselessAssignment { is_field_assign, ty: self.typeck_results().expr_ty(lhs) },
231 )
232 }
233 }
234
235 fn handle_field_pattern_match(
236 &mut self,
237 lhs: &hir::Pat<'_>,
238 res: Res,
239 pats: &[hir::PatField<'_>],
240 ) {
241 let variant = match self.typeck_results().node_type(lhs.hir_id).kind() {
242 ty::Adt(adt, _) => {
243 self.check_def_id(adt.did());
248 adt.variant_of_res(res)
249 }
250 _ => span_bug!(lhs.span, "non-ADT in struct pattern"),
251 };
252 for pat in pats {
253 if let PatKind::Wild = pat.pat.kind {
254 continue;
255 }
256 let index = self.typeck_results().field_index(pat.hir_id);
257 self.insert_def_id(variant.fields[index].did);
258 }
259 }
260
261 fn handle_tuple_field_pattern_match(
262 &mut self,
263 lhs: &hir::Pat<'_>,
264 res: Res,
265 pats: &[hir::Pat<'_>],
266 dotdot: hir::DotDotPos,
267 ) {
268 let variant = match self.typeck_results().node_type(lhs.hir_id).kind() {
269 ty::Adt(adt, _) => {
270 self.check_def_id(adt.did());
272 adt.variant_of_res(res)
273 }
274 _ => {
275 self.tcx.dcx().span_delayed_bug(lhs.span, "non-ADT in tuple struct pattern");
276 return;
277 }
278 };
279 let dotdot = dotdot.as_opt_usize().unwrap_or(pats.len());
280 let first_n = pats.iter().enumerate().take(dotdot);
281 let missing = variant.fields.len() - pats.len();
282 let last_n = pats.iter().enumerate().skip(dotdot).map(|(idx, pat)| (idx + missing, pat));
283 for (idx, pat) in first_n.chain(last_n) {
284 if let PatKind::Wild = pat.kind {
285 continue;
286 }
287 self.insert_def_id(variant.fields[FieldIdx::from_usize(idx)].did);
288 }
289 }
290
291 fn handle_offset_of(&mut self, expr: &'tcx hir::Expr<'tcx>) {
292 let data = self.typeck_results().offset_of_data();
293 let &(container, ref indices) =
294 data.get(expr.hir_id).expect("no offset_of_data for offset_of");
295
296 let body_did = self.typeck_results().hir_owner.to_def_id();
297 let typing_env = ty::TypingEnv::non_body_analysis(self.tcx, body_did);
298
299 let mut current_ty = container;
300
301 for &(variant, field) in indices {
302 match current_ty.kind() {
303 ty::Adt(def, args) => {
304 let field = &def.variant(variant).fields[field];
305
306 self.insert_def_id(field.did);
307 let field_ty = field.ty(self.tcx, args);
308
309 current_ty = self.tcx.normalize_erasing_regions(typing_env, field_ty);
310 }
311 ty::Tuple(tys) => {
314 current_ty =
315 self.tcx.normalize_erasing_regions(typing_env, tys[field.as_usize()]);
316 }
317 _ => span_bug!(expr.span, "named field access on non-ADT"),
318 }
319 }
320 }
321
322 fn mark_live_symbols(&mut self) -> <MarkSymbolVisitor<'tcx> as Visitor<'tcx>>::Result {
323 while let Some(work) = self.worklist.pop() {
324 let (mut id, comes_from_allow_expect) = work;
325
326 if let DefKind::Ctor(..) = self.tcx.def_kind(id) {
329 id = self.tcx.local_parent(id);
330 }
331
332 match comes_from_allow_expect {
354 ComesFromAllowExpect::Yes => {}
355 ComesFromAllowExpect::No => {
356 self.live_symbols.insert(id);
357 }
358 }
359
360 if !self.scanned.insert(id) {
361 continue;
362 }
363
364 if self.tcx.is_impl_trait_in_trait(id.to_def_id()) {
366 self.live_symbols.insert(id);
367 continue;
368 }
369
370 self.visit_node(self.tcx.hir_node_by_def_id(id))?;
371 }
372
373 ControlFlow::Continue(())
374 }
375
376 fn should_ignore_impl_item(&mut self, impl_item: &hir::ImplItem<'_>) -> bool {
380 if let hir::ImplItemImplKind::Trait { .. } = impl_item.impl_kind
381 && let impl_of = self.tcx.parent(impl_item.owner_id.to_def_id())
382 && self.tcx.is_automatically_derived(impl_of)
383 && let trait_ref = self.tcx.impl_trait_ref(impl_of).instantiate_identity()
384 && self.tcx.has_attr(trait_ref.def_id, sym::rustc_trivial_field_reads)
385 {
386 if let ty::Adt(adt_def, _) = trait_ref.self_ty().kind()
387 && let Some(adt_def_id) = adt_def.did().as_local()
388 {
389 self.ignored_derived_traits.entry(adt_def_id).or_default().insert(trait_ref.def_id);
390 }
391 return true;
392 }
393
394 false
395 }
396
397 fn visit_node(
398 &mut self,
399 node: Node<'tcx>,
400 ) -> <MarkSymbolVisitor<'tcx> as Visitor<'tcx>>::Result {
401 if let Node::ImplItem(impl_item) = node
402 && self.should_ignore_impl_item(impl_item)
403 {
404 return ControlFlow::Continue(());
405 }
406
407 let unconditionally_treated_fields_as_live =
408 self.repr_unconditionally_treats_fields_as_live;
409 let had_repr_simd = self.repr_has_repr_simd;
410 self.repr_unconditionally_treats_fields_as_live = false;
411 self.repr_has_repr_simd = false;
412 let walk_result = match node {
413 Node::Item(item) => match item.kind {
414 hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) => {
415 let def = self.tcx.adt_def(item.owner_id);
416 self.repr_unconditionally_treats_fields_as_live =
417 def.repr().c() || def.repr().transparent();
418 self.repr_has_repr_simd = def.repr().simd();
419
420 intravisit::walk_item(self, item)
421 }
422 hir::ItemKind::ForeignMod { .. } => ControlFlow::Continue(()),
423 hir::ItemKind::Trait(.., trait_item_refs) => {
424 for trait_item in trait_item_refs {
426 if matches!(self.tcx.def_kind(trait_item.owner_id), DefKind::AssocTy) {
427 self.check_def_id(trait_item.owner_id.to_def_id());
428 }
429 }
430 intravisit::walk_item(self, item)
431 }
432 _ => intravisit::walk_item(self, item),
433 },
434 Node::TraitItem(trait_item) => {
435 let trait_item_id = trait_item.owner_id.to_def_id();
437 if let Some(trait_id) = self.tcx.trait_of_assoc(trait_item_id) {
438 self.check_def_id(trait_id);
439 }
440 intravisit::walk_trait_item(self, trait_item)
441 }
442 Node::ImplItem(impl_item) => {
443 let item = self.tcx.local_parent(impl_item.owner_id.def_id);
444 if let hir::ImplItemImplKind::Inherent { .. } = impl_item.impl_kind {
445 let self_ty = self.tcx.type_of(item).instantiate_identity();
450 match *self_ty.kind() {
451 ty::Adt(def, _) => self.check_def_id(def.did()),
452 ty::Foreign(did) => self.check_def_id(did),
453 ty::Dynamic(data, ..) => {
454 if let Some(def_id) = data.principal_def_id() {
455 self.check_def_id(def_id)
456 }
457 }
458 _ => {}
459 }
460 }
461 intravisit::walk_impl_item(self, impl_item)
462 }
463 Node::ForeignItem(foreign_item) => intravisit::walk_foreign_item(self, foreign_item),
464 Node::OpaqueTy(opaq) => intravisit::walk_opaque_ty(self, opaq),
465 _ => ControlFlow::Continue(()),
466 };
467 self.repr_has_repr_simd = had_repr_simd;
468 self.repr_unconditionally_treats_fields_as_live = unconditionally_treated_fields_as_live;
469
470 walk_result
471 }
472
473 fn mark_as_used_if_union(&mut self, adt: ty::AdtDef<'tcx>, fields: &[hir::ExprField<'_>]) {
474 if adt.is_union() && adt.non_enum_variant().fields.len() > 1 && adt.did().is_local() {
475 for field in fields {
476 let index = self.typeck_results().field_index(field.hir_id);
477 self.insert_def_id(adt.non_enum_variant().fields[index].did);
478 }
479 }
480 }
481
482 fn check_impl_or_impl_item_live(&mut self, local_def_id: LocalDefId) -> bool {
487 let (impl_block_id, trait_def_id) = match self.tcx.def_kind(local_def_id) {
488 DefKind::AssocConst | DefKind::AssocTy | DefKind::AssocFn => {
490 let trait_item_id =
491 self.tcx.trait_item_of(local_def_id).and_then(|def_id| def_id.as_local());
492 (self.tcx.local_parent(local_def_id), trait_item_id)
493 }
494 DefKind::Impl { of_trait: true } => {
496 (local_def_id, self.tcx.impl_trait_id(local_def_id).as_local())
497 }
498 _ => bug!(),
499 };
500
501 if let Some(trait_def_id) = trait_def_id
502 && !self.live_symbols.contains(&trait_def_id)
503 {
504 return false;
505 }
506
507 if let ty::Adt(adt, _) = self.tcx.type_of(impl_block_id).instantiate_identity().kind()
509 && let Some(adt_def_id) = adt.did().as_local()
510 && !self.live_symbols.contains(&adt_def_id)
511 {
512 return false;
513 }
514
515 true
516 }
517}
518
519impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> {
520 type Result = ControlFlow<ErrorGuaranteed>;
521
522 fn visit_nested_body(&mut self, body: hir::BodyId) -> Self::Result {
523 let typeck_results = self.tcx.typeck_body(body);
524
525 if let Some(guar) = typeck_results.tainted_by_errors {
527 return ControlFlow::Break(guar);
528 }
529
530 let old_maybe_typeck_results = self.maybe_typeck_results.replace(typeck_results);
531 let body = self.tcx.hir_body(body);
532 let result = self.visit_body(body);
533 self.maybe_typeck_results = old_maybe_typeck_results;
534
535 result
536 }
537
538 fn visit_variant_data(&mut self, def: &'tcx hir::VariantData<'tcx>) -> Self::Result {
539 let tcx = self.tcx;
540 let unconditionally_treat_fields_as_live = self.repr_unconditionally_treats_fields_as_live;
541 let has_repr_simd = self.repr_has_repr_simd;
542 let effective_visibilities = &tcx.effective_visibilities(());
543 let live_fields = def.fields().iter().filter_map(|f| {
544 let def_id = f.def_id;
545 if unconditionally_treat_fields_as_live || (f.is_positional() && has_repr_simd) {
546 return Some(def_id);
547 }
548 if !effective_visibilities.is_reachable(f.hir_id.owner.def_id) {
549 return None;
550 }
551 if effective_visibilities.is_reachable(def_id) { Some(def_id) } else { None }
552 });
553 self.live_symbols.extend(live_fields);
554
555 intravisit::walk_struct_def(self, def)
556 }
557
558 fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Self::Result {
559 match expr.kind {
560 hir::ExprKind::Path(ref qpath @ QPath::TypeRelative(..)) => {
561 let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
562 self.handle_res(res);
563 }
564 hir::ExprKind::MethodCall(..) => {
565 self.lookup_and_handle_method(expr.hir_id);
566 }
567 hir::ExprKind::Field(ref lhs, ..) => {
568 if self.typeck_results().opt_field_index(expr.hir_id).is_some() {
569 self.handle_field_access(lhs, expr.hir_id);
570 } else {
571 self.tcx.dcx().span_delayed_bug(expr.span, "couldn't resolve index for field");
572 }
573 }
574 hir::ExprKind::Struct(qpath, fields, _) => {
575 let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
576 self.handle_res(res);
577 if let ty::Adt(adt, _) = self.typeck_results().expr_ty(expr).kind() {
578 self.mark_as_used_if_union(*adt, fields);
579 }
580 }
581 hir::ExprKind::Closure(cls) => {
582 self.insert_def_id(cls.def_id.to_def_id());
583 }
584 hir::ExprKind::OffsetOf(..) => {
585 self.handle_offset_of(expr);
586 }
587 hir::ExprKind::Assign(ref lhs, ..) => {
588 self.handle_assign(lhs);
589 self.check_for_self_assign(expr);
590 }
591 _ => (),
592 }
593
594 intravisit::walk_expr(self, expr)
595 }
596
597 fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) -> Self::Result {
598 let len = self.ignore_variant_stack.len();
602 self.ignore_variant_stack.extend(arm.pat.necessary_variants());
603 let result = intravisit::walk_arm(self, arm);
604 self.ignore_variant_stack.truncate(len);
605
606 result
607 }
608
609 fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) -> Self::Result {
610 self.in_pat = true;
611 match pat.kind {
612 PatKind::Struct(ref path, fields, _) => {
613 let res = self.typeck_results().qpath_res(path, pat.hir_id);
614 self.handle_field_pattern_match(pat, res, fields);
615 }
616 PatKind::TupleStruct(ref qpath, fields, dotdot) => {
617 let res = self.typeck_results().qpath_res(qpath, pat.hir_id);
618 self.handle_tuple_field_pattern_match(pat, res, fields, dotdot);
619 }
620 _ => (),
621 }
622
623 let result = intravisit::walk_pat(self, pat);
624 self.in_pat = false;
625
626 result
627 }
628
629 fn visit_pat_expr(&mut self, expr: &'tcx rustc_hir::PatExpr<'tcx>) -> Self::Result {
630 match &expr.kind {
631 rustc_hir::PatExprKind::Path(qpath) => {
632 if let ty::Adt(adt, _) = self.typeck_results().node_type(expr.hir_id).kind() {
634 self.check_def_id(adt.did());
635 }
636
637 let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
638 self.handle_res(res);
639 }
640 _ => {}
641 }
642 intravisit::walk_pat_expr(self, expr)
643 }
644
645 fn visit_path(&mut self, path: &hir::Path<'tcx>, _: hir::HirId) -> Self::Result {
646 self.handle_res(path.res);
647 intravisit::walk_path(self, path)
648 }
649
650 fn visit_anon_const(&mut self, c: &'tcx hir::AnonConst) -> Self::Result {
651 let in_pat = mem::replace(&mut self.in_pat, false);
654
655 self.live_symbols.insert(c.def_id);
656 let result = intravisit::walk_anon_const(self, c);
657
658 self.in_pat = in_pat;
659
660 result
661 }
662
663 fn visit_inline_const(&mut self, c: &'tcx hir::ConstBlock) -> Self::Result {
664 let in_pat = mem::replace(&mut self.in_pat, false);
667
668 self.live_symbols.insert(c.def_id);
669 let result = intravisit::walk_inline_const(self, c);
670
671 self.in_pat = in_pat;
672
673 result
674 }
675
676 fn visit_trait_ref(&mut self, t: &'tcx hir::TraitRef<'tcx>) -> Self::Result {
677 if let Some(trait_def_id) = t.path.res.opt_def_id()
678 && let Some(segment) = t.path.segments.last()
679 && let Some(args) = segment.args
680 {
681 for constraint in args.constraints {
682 if let Some(local_def_id) = self
683 .tcx
684 .associated_items(trait_def_id)
685 .find_by_ident_and_kind(
686 self.tcx,
687 constraint.ident,
688 AssocTag::Const,
689 trait_def_id,
690 )
691 .and_then(|item| item.def_id.as_local())
692 {
693 self.worklist.push((local_def_id, ComesFromAllowExpect::No));
694 }
695 }
696 }
697
698 intravisit::walk_trait_ref(self, t)
699 }
700}
701
702fn has_allow_dead_code_or_lang_attr(
703 tcx: TyCtxt<'_>,
704 def_id: LocalDefId,
705) -> Option<ComesFromAllowExpect> {
706 fn has_lang_attr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
707 tcx.has_attr(def_id, sym::lang)
708 || tcx.has_attr(def_id, sym::panic_handler)
710 }
711
712 fn has_allow_expect_dead_code(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
713 let hir_id = tcx.local_def_id_to_hir_id(def_id);
714 let lint_level = tcx.lint_level_at_node(lint::builtin::DEAD_CODE, hir_id).level;
715 matches!(lint_level, lint::Allow | lint::Expect)
716 }
717
718 fn has_used_like_attr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
719 tcx.def_kind(def_id).has_codegen_attrs() && {
720 let cg_attrs = tcx.codegen_fn_attrs(def_id);
721
722 cg_attrs.contains_extern_indicator()
725 || cg_attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER)
726 || cg_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER)
727 }
728 }
729
730 if has_allow_expect_dead_code(tcx, def_id) {
731 Some(ComesFromAllowExpect::Yes)
732 } else if has_used_like_attr(tcx, def_id) || has_lang_attr(tcx, def_id) {
733 Some(ComesFromAllowExpect::No)
734 } else {
735 None
736 }
737}
738
739fn maybe_record_as_seed<'tcx>(
755 tcx: TyCtxt<'tcx>,
756 owner_id: hir::OwnerId,
757 worklist: &mut Vec<(LocalDefId, ComesFromAllowExpect)>,
758 unsolved_items: &mut Vec<LocalDefId>,
759) {
760 let allow_dead_code = has_allow_dead_code_or_lang_attr(tcx, owner_id.def_id);
761 if let Some(comes_from_allow) = allow_dead_code {
762 worklist.push((owner_id.def_id, comes_from_allow));
763 }
764
765 match tcx.def_kind(owner_id) {
766 DefKind::Enum => {
767 if let Some(comes_from_allow) = allow_dead_code {
768 let adt = tcx.adt_def(owner_id);
769 worklist.extend(
770 adt.variants()
771 .iter()
772 .map(|variant| (variant.def_id.expect_local(), comes_from_allow)),
773 );
774 }
775 }
776 DefKind::AssocFn | DefKind::AssocConst | DefKind::AssocTy => {
777 if allow_dead_code.is_none() {
778 let parent = tcx.local_parent(owner_id.def_id);
779 match tcx.def_kind(parent) {
780 DefKind::Impl { of_trait: false } | DefKind::Trait => {}
781 DefKind::Impl { of_trait: true } => {
782 unsolved_items.push(owner_id.def_id);
788 }
789 _ => bug!(),
790 }
791 }
792 }
793 DefKind::Impl { of_trait: true } => {
794 if allow_dead_code.is_none() {
795 unsolved_items.push(owner_id.def_id);
796 }
797 }
798 DefKind::GlobalAsm => {
799 worklist.push((owner_id.def_id, ComesFromAllowExpect::No));
801 }
802 DefKind::Const => {
803 if tcx.item_name(owner_id.def_id) == kw::Underscore {
804 worklist.push((owner_id.def_id, ComesFromAllowExpect::No));
808 }
809 }
810 _ => {}
811 }
812}
813
814fn create_and_seed_worklist(
815 tcx: TyCtxt<'_>,
816) -> (Vec<(LocalDefId, ComesFromAllowExpect)>, Vec<LocalDefId>) {
817 let effective_visibilities = &tcx.effective_visibilities(());
818 let mut unsolved_impl_item = Vec::new();
819 let mut worklist = effective_visibilities
820 .iter()
821 .filter_map(|(&id, effective_vis)| {
822 effective_vis
823 .is_public_at_level(Level::Reachable)
824 .then_some(id)
825 .map(|id| (id, ComesFromAllowExpect::No))
826 })
827 .chain(
829 tcx.entry_fn(())
830 .and_then(|(def_id, _)| def_id.as_local().map(|id| (id, ComesFromAllowExpect::No))),
831 )
832 .collect::<Vec<_>>();
833
834 let crate_items = tcx.hir_crate_items(());
835 for id in crate_items.owners() {
836 maybe_record_as_seed(tcx, id, &mut worklist, &mut unsolved_impl_item);
837 }
838
839 (worklist, unsolved_impl_item)
840}
841
842fn live_symbols_and_ignored_derived_traits(
843 tcx: TyCtxt<'_>,
844 (): (),
845) -> Result<(LocalDefIdSet, LocalDefIdMap<FxIndexSet<DefId>>), ErrorGuaranteed> {
846 let (worklist, mut unsolved_items) = create_and_seed_worklist(tcx);
847 let mut symbol_visitor = MarkSymbolVisitor {
848 worklist,
849 tcx,
850 maybe_typeck_results: None,
851 scanned: Default::default(),
852 live_symbols: Default::default(),
853 repr_unconditionally_treats_fields_as_live: false,
854 repr_has_repr_simd: false,
855 in_pat: false,
856 ignore_variant_stack: vec![],
857 ignored_derived_traits: Default::default(),
858 };
859 if let ControlFlow::Break(guar) = symbol_visitor.mark_live_symbols() {
860 return Err(guar);
861 }
862
863 let mut items_to_check: Vec<_> = unsolved_items
866 .extract_if(.., |&mut local_def_id| {
867 symbol_visitor.check_impl_or_impl_item_live(local_def_id)
868 })
869 .collect();
870
871 while !items_to_check.is_empty() {
872 symbol_visitor
873 .worklist
874 .extend(items_to_check.drain(..).map(|id| (id, ComesFromAllowExpect::No)));
875 if let ControlFlow::Break(guar) = symbol_visitor.mark_live_symbols() {
876 return Err(guar);
877 }
878
879 items_to_check.extend(unsolved_items.extract_if(.., |&mut local_def_id| {
880 symbol_visitor.check_impl_or_impl_item_live(local_def_id)
881 }));
882 }
883
884 Ok((symbol_visitor.live_symbols, symbol_visitor.ignored_derived_traits))
885}
886
887struct DeadItem {
888 def_id: LocalDefId,
889 name: Symbol,
890 level: (lint::Level, Option<LintExpectationId>),
891}
892
893struct DeadVisitor<'tcx> {
894 tcx: TyCtxt<'tcx>,
895 live_symbols: &'tcx LocalDefIdSet,
896 ignored_derived_traits: &'tcx LocalDefIdMap<FxIndexSet<DefId>>,
897}
898
899enum ShouldWarnAboutField {
900 Yes,
901 No,
902}
903
904#[derive(Debug, Copy, Clone, PartialEq, Eq)]
905enum ReportOn {
906 TupleField,
908 NamedField,
910}
911
912impl<'tcx> DeadVisitor<'tcx> {
913 fn should_warn_about_field(&mut self, field: &ty::FieldDef) -> ShouldWarnAboutField {
914 if self.live_symbols.contains(&field.did.expect_local()) {
915 return ShouldWarnAboutField::No;
916 }
917 let field_type = self.tcx.type_of(field.did).instantiate_identity();
918 if field_type.is_phantom_data() {
919 return ShouldWarnAboutField::No;
920 }
921 let is_positional = field.name.as_str().starts_with(|c: char| c.is_ascii_digit());
922 if is_positional
923 && self
924 .tcx
925 .layout_of(
926 ty::TypingEnv::non_body_analysis(self.tcx, field.did)
927 .as_query_input(field_type),
928 )
929 .map_or(true, |layout| layout.is_zst())
930 {
931 return ShouldWarnAboutField::No;
932 }
933 ShouldWarnAboutField::Yes
934 }
935
936 fn def_lint_level(&self, id: LocalDefId) -> (lint::Level, Option<LintExpectationId>) {
937 let hir_id = self.tcx.local_def_id_to_hir_id(id);
938 let level = self.tcx.lint_level_at_node(DEAD_CODE, hir_id);
939 (level.level, level.lint_id)
940 }
941
942 fn lint_at_single_level(
949 &self,
950 dead_codes: &[&DeadItem],
951 participle: &str,
952 parent_item: Option<LocalDefId>,
953 report_on: ReportOn,
954 ) {
955 let Some(&first_item) = dead_codes.first() else { return };
956 let tcx = self.tcx;
957
958 let first_lint_level = first_item.level;
959 assert!(dead_codes.iter().skip(1).all(|item| item.level == first_lint_level));
960
961 let names: Vec<_> = dead_codes.iter().map(|item| item.name).collect();
962 let spans: Vec<_> = dead_codes
963 .iter()
964 .map(|item| {
965 let span = tcx.def_span(item.def_id);
966 let ident_span = tcx.def_ident_span(item.def_id);
967 ident_span.map(|s| s.with_ctxt(span.ctxt())).unwrap_or(span)
969 })
970 .collect();
971
972 let mut descr = tcx.def_descr(first_item.def_id.to_def_id());
973 if dead_codes.iter().any(|item| tcx.def_descr(item.def_id.to_def_id()) != descr) {
976 descr = "associated item"
977 }
978
979 let num = dead_codes.len();
980 let multiple = num > 6;
981 let name_list = names.into();
982
983 let parent_info = parent_item.map(|parent_item| {
984 let parent_descr = tcx.def_descr(parent_item.to_def_id());
985 let span = if let DefKind::Impl { .. } = tcx.def_kind(parent_item) {
986 tcx.def_span(parent_item)
987 } else {
988 tcx.def_ident_span(parent_item).unwrap()
989 };
990 ParentInfo { num, descr, parent_descr, span }
991 });
992
993 let mut encl_def_id = parent_item.unwrap_or(first_item.def_id);
994 if let DefKind::Variant = tcx.def_kind(encl_def_id) {
996 encl_def_id = tcx.local_parent(encl_def_id);
997 }
998
999 let ignored_derived_impls =
1000 self.ignored_derived_traits.get(&encl_def_id).map(|ign_traits| {
1001 let trait_list = ign_traits
1002 .iter()
1003 .map(|trait_id| self.tcx.item_name(*trait_id))
1004 .collect::<Vec<_>>();
1005 let trait_list_len = trait_list.len();
1006 IgnoredDerivedImpls {
1007 name: self.tcx.item_name(encl_def_id.to_def_id()),
1008 trait_list: trait_list.into(),
1009 trait_list_len,
1010 }
1011 });
1012
1013 let diag = match report_on {
1014 ReportOn::TupleField => {
1015 let tuple_fields = if let Some(parent_id) = parent_item
1016 && let node = tcx.hir_node_by_def_id(parent_id)
1017 && let hir::Node::Item(hir::Item {
1018 kind: hir::ItemKind::Struct(_, _, hir::VariantData::Tuple(fields, _, _)),
1019 ..
1020 }) = node
1021 {
1022 *fields
1023 } else {
1024 &[]
1025 };
1026
1027 let trailing_tuple_fields = if tuple_fields.len() >= dead_codes.len() {
1028 LocalDefIdSet::from_iter(
1029 tuple_fields
1030 .iter()
1031 .skip(tuple_fields.len() - dead_codes.len())
1032 .map(|f| f.def_id),
1033 )
1034 } else {
1035 LocalDefIdSet::default()
1036 };
1037
1038 let fields_suggestion =
1039 if dead_codes.iter().all(|dc| trailing_tuple_fields.contains(&dc.def_id)) {
1042 ChangeFields::Remove { num }
1043 } else {
1044 ChangeFields::ChangeToUnitTypeOrRemove { num, spans: spans.clone() }
1045 };
1046
1047 MultipleDeadCodes::UnusedTupleStructFields {
1048 multiple,
1049 num,
1050 descr,
1051 participle,
1052 name_list,
1053 change_fields_suggestion: fields_suggestion,
1054 parent_info,
1055 ignored_derived_impls,
1056 }
1057 }
1058 ReportOn::NamedField => {
1059 let enum_variants_with_same_name = dead_codes
1060 .iter()
1061 .filter_map(|dead_item| {
1062 if let DefKind::AssocFn | DefKind::AssocConst =
1063 tcx.def_kind(dead_item.def_id)
1064 && let impl_did = tcx.local_parent(dead_item.def_id)
1065 && let DefKind::Impl { of_trait: false } = tcx.def_kind(impl_did)
1066 && let ty::Adt(maybe_enum, _) =
1067 tcx.type_of(impl_did).instantiate_identity().kind()
1068 && maybe_enum.is_enum()
1069 && let Some(variant) =
1070 maybe_enum.variants().iter().find(|i| i.name == dead_item.name)
1071 {
1072 Some(crate::errors::EnumVariantSameName {
1073 dead_descr: tcx.def_descr(dead_item.def_id.to_def_id()),
1074 dead_name: dead_item.name,
1075 variant_span: tcx.def_span(variant.def_id),
1076 })
1077 } else {
1078 None
1079 }
1080 })
1081 .collect();
1082
1083 MultipleDeadCodes::DeadCodes {
1084 multiple,
1085 num,
1086 descr,
1087 participle,
1088 name_list,
1089 parent_info,
1090 ignored_derived_impls,
1091 enum_variants_with_same_name,
1092 }
1093 }
1094 };
1095
1096 let hir_id = tcx.local_def_id_to_hir_id(first_item.def_id);
1097 self.tcx.emit_node_span_lint(DEAD_CODE, hir_id, MultiSpan::from_spans(spans), diag);
1098 }
1099
1100 fn warn_multiple(
1101 &self,
1102 def_id: LocalDefId,
1103 participle: &str,
1104 dead_codes: Vec<DeadItem>,
1105 report_on: ReportOn,
1106 ) {
1107 let mut dead_codes = dead_codes
1108 .iter()
1109 .filter(|v| !v.name.as_str().starts_with('_'))
1110 .collect::<Vec<&DeadItem>>();
1111 if dead_codes.is_empty() {
1112 return;
1113 }
1114 dead_codes.sort_by_key(|v| v.level.0);
1116 for group in dead_codes.chunk_by(|a, b| a.level == b.level) {
1117 self.lint_at_single_level(&group, participle, Some(def_id), report_on);
1118 }
1119 }
1120
1121 fn warn_dead_code(&mut self, id: LocalDefId, participle: &str) {
1122 let item = DeadItem {
1123 def_id: id,
1124 name: self.tcx.item_name(id.to_def_id()),
1125 level: self.def_lint_level(id),
1126 };
1127 self.lint_at_single_level(&[&item], participle, None, ReportOn::NamedField);
1128 }
1129
1130 fn check_definition(&mut self, def_id: LocalDefId) {
1131 if self.is_live_code(def_id) {
1132 return;
1133 }
1134 match self.tcx.def_kind(def_id) {
1135 DefKind::AssocConst
1136 | DefKind::AssocTy
1137 | DefKind::AssocFn
1138 | DefKind::Fn
1139 | DefKind::Static { .. }
1140 | DefKind::Const
1141 | DefKind::TyAlias
1142 | DefKind::Enum
1143 | DefKind::Union
1144 | DefKind::ForeignTy
1145 | DefKind::Trait => self.warn_dead_code(def_id, "used"),
1146 DefKind::Struct => self.warn_dead_code(def_id, "constructed"),
1147 DefKind::Variant | DefKind::Field => bug!("should be handled specially"),
1148 _ => {}
1149 }
1150 }
1151
1152 fn is_live_code(&self, def_id: LocalDefId) -> bool {
1153 let Some(name) = self.tcx.opt_item_name(def_id.to_def_id()) else {
1156 return true;
1157 };
1158
1159 self.live_symbols.contains(&def_id) || name.as_str().starts_with('_')
1160 }
1161}
1162
1163fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalModDefId) {
1164 let Ok((live_symbols, ignored_derived_traits)) =
1165 tcx.live_symbols_and_ignored_derived_traits(()).as_ref()
1166 else {
1167 return;
1168 };
1169
1170 let mut visitor = DeadVisitor { tcx, live_symbols, ignored_derived_traits };
1171
1172 let module_items = tcx.hir_module_items(module);
1173
1174 for item in module_items.free_items() {
1175 let def_kind = tcx.def_kind(item.owner_id);
1176
1177 let mut dead_codes = Vec::new();
1178 if matches!(def_kind, DefKind::Impl { of_trait: false })
1184 || (def_kind == DefKind::Trait && live_symbols.contains(&item.owner_id.def_id))
1185 {
1186 for &def_id in tcx.associated_item_def_ids(item.owner_id.def_id) {
1187 if let Some(local_def_id) = def_id.as_local()
1188 && !visitor.is_live_code(local_def_id)
1189 {
1190 let name = tcx.item_name(def_id);
1191 let level = visitor.def_lint_level(local_def_id);
1192 dead_codes.push(DeadItem { def_id: local_def_id, name, level });
1193 }
1194 }
1195 }
1196 if !dead_codes.is_empty() {
1197 visitor.warn_multiple(item.owner_id.def_id, "used", dead_codes, ReportOn::NamedField);
1198 }
1199
1200 if !live_symbols.contains(&item.owner_id.def_id) {
1201 let parent = tcx.local_parent(item.owner_id.def_id);
1202 if parent != module.to_local_def_id() && !live_symbols.contains(&parent) {
1203 continue;
1205 }
1206 visitor.check_definition(item.owner_id.def_id);
1207 continue;
1208 }
1209
1210 if let DefKind::Struct | DefKind::Union | DefKind::Enum = def_kind {
1211 let adt = tcx.adt_def(item.owner_id);
1212 let mut dead_variants = Vec::new();
1213
1214 for variant in adt.variants() {
1215 let def_id = variant.def_id.expect_local();
1216 if !live_symbols.contains(&def_id) {
1217 let level = visitor.def_lint_level(def_id);
1219 dead_variants.push(DeadItem { def_id, name: variant.name, level });
1220 continue;
1221 }
1222
1223 let is_positional = variant.fields.raw.first().is_some_and(|field| {
1224 field.name.as_str().starts_with(|c: char| c.is_ascii_digit())
1225 });
1226 let report_on =
1227 if is_positional { ReportOn::TupleField } else { ReportOn::NamedField };
1228 let dead_fields = variant
1229 .fields
1230 .iter()
1231 .filter_map(|field| {
1232 let def_id = field.did.expect_local();
1233 if let ShouldWarnAboutField::Yes = visitor.should_warn_about_field(field) {
1234 let level = visitor.def_lint_level(def_id);
1235 Some(DeadItem { def_id, name: field.name, level })
1236 } else {
1237 None
1238 }
1239 })
1240 .collect();
1241 visitor.warn_multiple(def_id, "read", dead_fields, report_on);
1242 }
1243
1244 visitor.warn_multiple(
1245 item.owner_id.def_id,
1246 "constructed",
1247 dead_variants,
1248 ReportOn::NamedField,
1249 );
1250 }
1251 }
1252
1253 for foreign_item in module_items.foreign_items() {
1254 visitor.check_definition(foreign_item.owner_id.def_id);
1255 }
1256}
1257
1258pub(crate) fn provide(providers: &mut Providers) {
1259 *providers =
1260 Providers { live_symbols_and_ignored_derived_traits, check_mod_deathness, ..*providers };
1261}