1use std::mem;
7
8use hir::def_id::{LocalDefIdMap, LocalDefIdSet};
9use rustc_abi::FieldIdx;
10use rustc_data_structures::fx::FxIndexSet;
11use rustc_data_structures::unord::UnordSet;
12use rustc_errors::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: UnordSet<(LocalDefId, ComesFromAllowExpect)>,
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 self.visit_expr(expr);
183 } else if let hir::ExprKind::Field(base, ..) = expr.kind {
184 self.handle_assign(base);
186 } else {
187 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) {
323 while let Some(work) = self.worklist.pop() {
324 if !self.scanned.insert(work) {
325 continue;
326 }
327
328 let (mut id, comes_from_allow_expect) = work;
329
330 if self.tcx.is_impl_trait_in_trait(id.to_def_id()) {
332 self.live_symbols.insert(id);
333 continue;
334 }
335
336 if let DefKind::Ctor(..) = self.tcx.def_kind(id) {
339 id = self.tcx.local_parent(id);
340 }
341
342 if comes_from_allow_expect != ComesFromAllowExpect::Yes {
364 self.live_symbols.insert(id);
365 }
366 self.visit_node(self.tcx.hir_node_by_def_id(id));
367 }
368 }
369
370 fn should_ignore_item(&mut self, def_id: DefId) -> bool {
374 if let Some(impl_of) = self.tcx.trait_impl_of_assoc(def_id) {
375 if !self.tcx.is_automatically_derived(impl_of) {
376 return false;
377 }
378
379 if let Some(trait_of) = self.tcx.trait_id_of_impl(impl_of)
380 && self.tcx.has_attr(trait_of, sym::rustc_trivial_field_reads)
381 {
382 let trait_ref = self.tcx.impl_trait_ref(impl_of).unwrap().instantiate_identity();
383 if let ty::Adt(adt_def, _) = trait_ref.self_ty().kind()
384 && let Some(adt_def_id) = adt_def.did().as_local()
385 {
386 self.ignored_derived_traits.entry(adt_def_id).or_default().insert(trait_of);
387 }
388 return true;
389 }
390 }
391
392 false
393 }
394
395 fn visit_node(&mut self, node: Node<'tcx>) {
396 if let Node::ImplItem(hir::ImplItem { owner_id, .. }) = node
397 && self.should_ignore_item(owner_id.to_def_id())
398 {
399 return;
400 }
401
402 let unconditionally_treated_fields_as_live =
403 self.repr_unconditionally_treats_fields_as_live;
404 let had_repr_simd = self.repr_has_repr_simd;
405 self.repr_unconditionally_treats_fields_as_live = false;
406 self.repr_has_repr_simd = false;
407 match node {
408 Node::Item(item) => match item.kind {
409 hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) => {
410 let def = self.tcx.adt_def(item.owner_id);
411 self.repr_unconditionally_treats_fields_as_live =
412 def.repr().c() || def.repr().transparent();
413 self.repr_has_repr_simd = def.repr().simd();
414
415 intravisit::walk_item(self, item)
416 }
417 hir::ItemKind::ForeignMod { .. } => {}
418 hir::ItemKind::Trait(.., trait_item_refs) => {
419 for trait_item in trait_item_refs {
421 if matches!(self.tcx.def_kind(trait_item.owner_id), DefKind::AssocTy) {
422 self.check_def_id(trait_item.owner_id.to_def_id());
423 }
424 }
425 intravisit::walk_item(self, item)
426 }
427 _ => intravisit::walk_item(self, item),
428 },
429 Node::TraitItem(trait_item) => {
430 let trait_item_id = trait_item.owner_id.to_def_id();
432 if let Some(trait_id) = self.tcx.trait_of_assoc(trait_item_id) {
433 self.check_def_id(trait_id);
434 }
435 intravisit::walk_trait_item(self, trait_item);
436 }
437 Node::ImplItem(impl_item) => {
438 let item = self.tcx.local_parent(impl_item.owner_id.def_id);
439 if self.tcx.impl_trait_ref(item).is_none() {
440 let self_ty = self.tcx.type_of(item).instantiate_identity();
445 match *self_ty.kind() {
446 ty::Adt(def, _) => self.check_def_id(def.did()),
447 ty::Foreign(did) => self.check_def_id(did),
448 ty::Dynamic(data, ..) => {
449 if let Some(def_id) = data.principal_def_id() {
450 self.check_def_id(def_id)
451 }
452 }
453 _ => {}
454 }
455 }
456 intravisit::walk_impl_item(self, impl_item);
457 }
458 Node::ForeignItem(foreign_item) => {
459 intravisit::walk_foreign_item(self, foreign_item);
460 }
461 Node::OpaqueTy(opaq) => intravisit::walk_opaque_ty(self, opaq),
462 _ => {}
463 }
464 self.repr_has_repr_simd = had_repr_simd;
465 self.repr_unconditionally_treats_fields_as_live = unconditionally_treated_fields_as_live;
466 }
467
468 fn mark_as_used_if_union(&mut self, adt: ty::AdtDef<'tcx>, fields: &[hir::ExprField<'_>]) {
469 if adt.is_union() && adt.non_enum_variant().fields.len() > 1 && adt.did().is_local() {
470 for field in fields {
471 let index = self.typeck_results().field_index(field.hir_id);
472 self.insert_def_id(adt.non_enum_variant().fields[index].did);
473 }
474 }
475 }
476
477 fn check_impl_or_impl_item_live(&mut self, local_def_id: LocalDefId) -> bool {
482 let (impl_block_id, trait_def_id) = match self.tcx.def_kind(local_def_id) {
483 DefKind::AssocConst | DefKind::AssocTy | DefKind::AssocFn => (
485 self.tcx.local_parent(local_def_id),
486 self.tcx
487 .associated_item(local_def_id)
488 .trait_item_def_id
489 .and_then(|def_id| def_id.as_local()),
490 ),
491 DefKind::Impl { of_trait: true } => (
493 local_def_id,
494 self.tcx
495 .impl_trait_ref(local_def_id)
496 .and_then(|trait_ref| trait_ref.skip_binder().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 fn visit_nested_body(&mut self, body: hir::BodyId) {
521 let old_maybe_typeck_results =
522 self.maybe_typeck_results.replace(self.tcx.typeck_body(body));
523 let body = self.tcx.hir_body(body);
524 self.visit_body(body);
525 self.maybe_typeck_results = old_maybe_typeck_results;
526 }
527
528 fn visit_variant_data(&mut self, def: &'tcx hir::VariantData<'tcx>) {
529 let tcx = self.tcx;
530 let unconditionally_treat_fields_as_live = self.repr_unconditionally_treats_fields_as_live;
531 let has_repr_simd = self.repr_has_repr_simd;
532 let effective_visibilities = &tcx.effective_visibilities(());
533 let live_fields = def.fields().iter().filter_map(|f| {
534 let def_id = f.def_id;
535 if unconditionally_treat_fields_as_live || (f.is_positional() && has_repr_simd) {
536 return Some(def_id);
537 }
538 if !effective_visibilities.is_reachable(f.hir_id.owner.def_id) {
539 return None;
540 }
541 if effective_visibilities.is_reachable(def_id) { Some(def_id) } else { None }
542 });
543 self.live_symbols.extend(live_fields);
544
545 intravisit::walk_struct_def(self, def);
546 }
547
548 fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
549 match expr.kind {
550 hir::ExprKind::Path(ref qpath @ QPath::TypeRelative(..)) => {
551 let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
552 self.handle_res(res);
553 }
554 hir::ExprKind::MethodCall(..) => {
555 self.lookup_and_handle_method(expr.hir_id);
556 }
557 hir::ExprKind::Field(ref lhs, ..) => {
558 if self.typeck_results().opt_field_index(expr.hir_id).is_some() {
559 self.handle_field_access(lhs, expr.hir_id);
560 } else {
561 self.tcx.dcx().span_delayed_bug(expr.span, "couldn't resolve index for field");
562 }
563 }
564 hir::ExprKind::Struct(qpath, fields, _) => {
565 let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
566 self.handle_res(res);
567 if let ty::Adt(adt, _) = self.typeck_results().expr_ty(expr).kind() {
568 self.mark_as_used_if_union(*adt, fields);
569 }
570 }
571 hir::ExprKind::Closure(cls) => {
572 self.insert_def_id(cls.def_id.to_def_id());
573 }
574 hir::ExprKind::OffsetOf(..) => {
575 self.handle_offset_of(expr);
576 }
577 hir::ExprKind::Assign(ref lhs, ..) => {
578 self.handle_assign(lhs);
579 self.check_for_self_assign(expr);
580 }
581 _ => (),
582 }
583
584 intravisit::walk_expr(self, expr);
585 }
586
587 fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) {
588 let len = self.ignore_variant_stack.len();
592 self.ignore_variant_stack.extend(arm.pat.necessary_variants());
593 intravisit::walk_arm(self, arm);
594 self.ignore_variant_stack.truncate(len);
595 }
596
597 fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {
598 self.in_pat = true;
599 match pat.kind {
600 PatKind::Struct(ref path, fields, _) => {
601 let res = self.typeck_results().qpath_res(path, pat.hir_id);
602 self.handle_field_pattern_match(pat, res, fields);
603 }
604 PatKind::TupleStruct(ref qpath, fields, dotdot) => {
605 let res = self.typeck_results().qpath_res(qpath, pat.hir_id);
606 self.handle_tuple_field_pattern_match(pat, res, fields, dotdot);
607 }
608 _ => (),
609 }
610
611 intravisit::walk_pat(self, pat);
612 self.in_pat = false;
613 }
614
615 fn visit_pat_expr(&mut self, expr: &'tcx rustc_hir::PatExpr<'tcx>) {
616 match &expr.kind {
617 rustc_hir::PatExprKind::Path(qpath) => {
618 if let ty::Adt(adt, _) = self.typeck_results().node_type(expr.hir_id).kind() {
620 self.check_def_id(adt.did());
621 }
622
623 let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
624 self.handle_res(res);
625 }
626 _ => {}
627 }
628 intravisit::walk_pat_expr(self, expr);
629 }
630
631 fn visit_path(&mut self, path: &hir::Path<'tcx>, _: hir::HirId) {
632 self.handle_res(path.res);
633 intravisit::walk_path(self, path);
634 }
635
636 fn visit_anon_const(&mut self, c: &'tcx hir::AnonConst) {
637 let in_pat = mem::replace(&mut self.in_pat, false);
640
641 self.live_symbols.insert(c.def_id);
642 intravisit::walk_anon_const(self, c);
643
644 self.in_pat = in_pat;
645 }
646
647 fn visit_inline_const(&mut self, c: &'tcx hir::ConstBlock) {
648 let in_pat = mem::replace(&mut self.in_pat, false);
651
652 self.live_symbols.insert(c.def_id);
653 intravisit::walk_inline_const(self, c);
654
655 self.in_pat = in_pat;
656 }
657
658 fn visit_trait_ref(&mut self, t: &'tcx hir::TraitRef<'tcx>) {
659 if let Some(trait_def_id) = t.path.res.opt_def_id()
660 && let Some(segment) = t.path.segments.last()
661 && let Some(args) = segment.args
662 {
663 for constraint in args.constraints {
664 if let Some(local_def_id) = self
665 .tcx
666 .associated_items(trait_def_id)
667 .find_by_ident_and_kind(
668 self.tcx,
669 constraint.ident,
670 AssocTag::Const,
671 trait_def_id,
672 )
673 .and_then(|item| item.def_id.as_local())
674 {
675 self.worklist.push((local_def_id, ComesFromAllowExpect::No));
676 }
677 }
678 }
679
680 intravisit::walk_trait_ref(self, t);
681 }
682}
683
684fn has_allow_dead_code_or_lang_attr(
685 tcx: TyCtxt<'_>,
686 def_id: LocalDefId,
687) -> Option<ComesFromAllowExpect> {
688 fn has_lang_attr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
689 tcx.has_attr(def_id, sym::lang)
690 || tcx.has_attr(def_id, sym::panic_handler)
692 }
693
694 fn has_allow_expect_dead_code(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
695 let hir_id = tcx.local_def_id_to_hir_id(def_id);
696 let lint_level = tcx.lint_level_at_node(lint::builtin::DEAD_CODE, hir_id).level;
697 matches!(lint_level, lint::Allow | lint::Expect)
698 }
699
700 fn has_used_like_attr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
701 tcx.def_kind(def_id).has_codegen_attrs() && {
702 let cg_attrs = tcx.codegen_fn_attrs(def_id);
703
704 cg_attrs.contains_extern_indicator(tcx, def_id.into())
707 || cg_attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER)
708 || cg_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER)
709 }
710 }
711
712 if has_allow_expect_dead_code(tcx, def_id) {
713 Some(ComesFromAllowExpect::Yes)
714 } else if has_used_like_attr(tcx, def_id) || has_lang_attr(tcx, def_id) {
715 Some(ComesFromAllowExpect::No)
716 } else {
717 None
718 }
719}
720
721fn maybe_record_as_seed<'tcx>(
737 tcx: TyCtxt<'tcx>,
738 owner_id: hir::OwnerId,
739 worklist: &mut Vec<(LocalDefId, ComesFromAllowExpect)>,
740 unsolved_items: &mut Vec<LocalDefId>,
741) {
742 let allow_dead_code = has_allow_dead_code_or_lang_attr(tcx, owner_id.def_id);
743 if let Some(comes_from_allow) = allow_dead_code {
744 worklist.push((owner_id.def_id, comes_from_allow));
745 }
746
747 match tcx.def_kind(owner_id) {
748 DefKind::Enum => {
749 if let Some(comes_from_allow) = allow_dead_code {
750 let adt = tcx.adt_def(owner_id);
751 worklist.extend(
752 adt.variants()
753 .iter()
754 .map(|variant| (variant.def_id.expect_local(), comes_from_allow)),
755 );
756 }
757 }
758 DefKind::AssocFn | DefKind::AssocConst | DefKind::AssocTy => {
759 if allow_dead_code.is_none() {
760 let parent = tcx.local_parent(owner_id.def_id);
761 match tcx.def_kind(parent) {
762 DefKind::Impl { of_trait: false } | DefKind::Trait => {}
763 DefKind::Impl { of_trait: true } => {
764 unsolved_items.push(owner_id.def_id);
770 }
771 _ => bug!(),
772 }
773 }
774 }
775 DefKind::Impl { of_trait: true } => {
776 if allow_dead_code.is_none() {
777 unsolved_items.push(owner_id.def_id);
778 }
779 }
780 DefKind::GlobalAsm => {
781 worklist.push((owner_id.def_id, ComesFromAllowExpect::No));
783 }
784 DefKind::Const => {
785 if tcx.item_name(owner_id.def_id) == kw::Underscore {
786 worklist.push((owner_id.def_id, ComesFromAllowExpect::No));
790 }
791 }
792 _ => {}
793 }
794}
795
796fn create_and_seed_worklist(
797 tcx: TyCtxt<'_>,
798) -> (Vec<(LocalDefId, ComesFromAllowExpect)>, Vec<LocalDefId>) {
799 let effective_visibilities = &tcx.effective_visibilities(());
800 let mut unsolved_impl_item = Vec::new();
801 let mut worklist = effective_visibilities
802 .iter()
803 .filter_map(|(&id, effective_vis)| {
804 effective_vis
805 .is_public_at_level(Level::Reachable)
806 .then_some(id)
807 .map(|id| (id, ComesFromAllowExpect::No))
808 })
809 .chain(
811 tcx.entry_fn(())
812 .and_then(|(def_id, _)| def_id.as_local().map(|id| (id, ComesFromAllowExpect::No))),
813 )
814 .collect::<Vec<_>>();
815
816 let crate_items = tcx.hir_crate_items(());
817 for id in crate_items.owners() {
818 maybe_record_as_seed(tcx, id, &mut worklist, &mut unsolved_impl_item);
819 }
820
821 (worklist, unsolved_impl_item)
822}
823
824fn live_symbols_and_ignored_derived_traits(
825 tcx: TyCtxt<'_>,
826 (): (),
827) -> (LocalDefIdSet, LocalDefIdMap<FxIndexSet<DefId>>) {
828 let (worklist, mut unsolved_items) = create_and_seed_worklist(tcx);
829 let mut symbol_visitor = MarkSymbolVisitor {
830 worklist,
831 tcx,
832 maybe_typeck_results: None,
833 scanned: Default::default(),
834 live_symbols: Default::default(),
835 repr_unconditionally_treats_fields_as_live: false,
836 repr_has_repr_simd: false,
837 in_pat: false,
838 ignore_variant_stack: vec![],
839 ignored_derived_traits: Default::default(),
840 };
841 symbol_visitor.mark_live_symbols();
842
843 let mut items_to_check: Vec<_> = unsolved_items
846 .extract_if(.., |&mut local_def_id| {
847 symbol_visitor.check_impl_or_impl_item_live(local_def_id)
848 })
849 .collect();
850
851 while !items_to_check.is_empty() {
852 symbol_visitor
853 .worklist
854 .extend(items_to_check.drain(..).map(|id| (id, ComesFromAllowExpect::No)));
855 symbol_visitor.mark_live_symbols();
856
857 items_to_check.extend(unsolved_items.extract_if(.., |&mut local_def_id| {
858 symbol_visitor.check_impl_or_impl_item_live(local_def_id)
859 }));
860 }
861
862 (symbol_visitor.live_symbols, symbol_visitor.ignored_derived_traits)
863}
864
865struct DeadItem {
866 def_id: LocalDefId,
867 name: Symbol,
868 level: (lint::Level, Option<LintExpectationId>),
869}
870
871struct DeadVisitor<'tcx> {
872 tcx: TyCtxt<'tcx>,
873 live_symbols: &'tcx LocalDefIdSet,
874 ignored_derived_traits: &'tcx LocalDefIdMap<FxIndexSet<DefId>>,
875}
876
877enum ShouldWarnAboutField {
878 Yes,
879 No,
880}
881
882#[derive(Debug, Copy, Clone, PartialEq, Eq)]
883enum ReportOn {
884 TupleField,
886 NamedField,
888}
889
890impl<'tcx> DeadVisitor<'tcx> {
891 fn should_warn_about_field(&mut self, field: &ty::FieldDef) -> ShouldWarnAboutField {
892 if self.live_symbols.contains(&field.did.expect_local()) {
893 return ShouldWarnAboutField::No;
894 }
895 let field_type = self.tcx.type_of(field.did).instantiate_identity();
896 if field_type.is_phantom_data() {
897 return ShouldWarnAboutField::No;
898 }
899 let is_positional = field.name.as_str().starts_with(|c: char| c.is_ascii_digit());
900 if is_positional
901 && self
902 .tcx
903 .layout_of(
904 ty::TypingEnv::non_body_analysis(self.tcx, field.did)
905 .as_query_input(field_type),
906 )
907 .map_or(true, |layout| layout.is_zst())
908 {
909 return ShouldWarnAboutField::No;
910 }
911 ShouldWarnAboutField::Yes
912 }
913
914 fn def_lint_level(&self, id: LocalDefId) -> (lint::Level, Option<LintExpectationId>) {
915 let hir_id = self.tcx.local_def_id_to_hir_id(id);
916 let level = self.tcx.lint_level_at_node(DEAD_CODE, hir_id);
917 (level.level, level.lint_id)
918 }
919
920 fn lint_at_single_level(
927 &self,
928 dead_codes: &[&DeadItem],
929 participle: &str,
930 parent_item: Option<LocalDefId>,
931 report_on: ReportOn,
932 ) {
933 let Some(&first_item) = dead_codes.first() else { return };
934 let tcx = self.tcx;
935
936 let first_lint_level = first_item.level;
937 assert!(dead_codes.iter().skip(1).all(|item| item.level == first_lint_level));
938
939 let names: Vec<_> = dead_codes.iter().map(|item| item.name).collect();
940 let spans: Vec<_> = dead_codes
941 .iter()
942 .map(|item| {
943 let span = tcx.def_span(item.def_id);
944 let ident_span = tcx.def_ident_span(item.def_id);
945 ident_span.map(|s| s.with_ctxt(span.ctxt())).unwrap_or(span)
947 })
948 .collect();
949
950 let mut descr = tcx.def_descr(first_item.def_id.to_def_id());
951 if dead_codes.iter().any(|item| tcx.def_descr(item.def_id.to_def_id()) != descr) {
954 descr = "associated item"
955 }
956
957 let num = dead_codes.len();
958 let multiple = num > 6;
959 let name_list = names.into();
960
961 let parent_info = parent_item.map(|parent_item| {
962 let parent_descr = tcx.def_descr(parent_item.to_def_id());
963 let span = if let DefKind::Impl { .. } = tcx.def_kind(parent_item) {
964 tcx.def_span(parent_item)
965 } else {
966 tcx.def_ident_span(parent_item).unwrap()
967 };
968 ParentInfo { num, descr, parent_descr, span }
969 });
970
971 let mut encl_def_id = parent_item.unwrap_or(first_item.def_id);
972 if let DefKind::Variant = tcx.def_kind(encl_def_id) {
974 encl_def_id = tcx.local_parent(encl_def_id);
975 }
976
977 let ignored_derived_impls =
978 self.ignored_derived_traits.get(&encl_def_id).map(|ign_traits| {
979 let trait_list = ign_traits
980 .iter()
981 .map(|trait_id| self.tcx.item_name(*trait_id))
982 .collect::<Vec<_>>();
983 let trait_list_len = trait_list.len();
984 IgnoredDerivedImpls {
985 name: self.tcx.item_name(encl_def_id.to_def_id()),
986 trait_list: trait_list.into(),
987 trait_list_len,
988 }
989 });
990
991 let diag = match report_on {
992 ReportOn::TupleField => {
993 let tuple_fields = if let Some(parent_id) = parent_item
994 && let node = tcx.hir_node_by_def_id(parent_id)
995 && let hir::Node::Item(hir::Item {
996 kind: hir::ItemKind::Struct(_, _, hir::VariantData::Tuple(fields, _, _)),
997 ..
998 }) = node
999 {
1000 *fields
1001 } else {
1002 &[]
1003 };
1004
1005 let trailing_tuple_fields = if tuple_fields.len() >= dead_codes.len() {
1006 LocalDefIdSet::from_iter(
1007 tuple_fields
1008 .iter()
1009 .skip(tuple_fields.len() - dead_codes.len())
1010 .map(|f| f.def_id),
1011 )
1012 } else {
1013 LocalDefIdSet::default()
1014 };
1015
1016 let fields_suggestion =
1017 if dead_codes.iter().all(|dc| trailing_tuple_fields.contains(&dc.def_id)) {
1020 ChangeFields::Remove { num }
1021 } else {
1022 ChangeFields::ChangeToUnitTypeOrRemove { num, spans: spans.clone() }
1023 };
1024
1025 MultipleDeadCodes::UnusedTupleStructFields {
1026 multiple,
1027 num,
1028 descr,
1029 participle,
1030 name_list,
1031 change_fields_suggestion: fields_suggestion,
1032 parent_info,
1033 ignored_derived_impls,
1034 }
1035 }
1036 ReportOn::NamedField => {
1037 let enum_variants_with_same_name = dead_codes
1038 .iter()
1039 .filter_map(|dead_item| {
1040 if let DefKind::AssocFn | DefKind::AssocConst =
1041 tcx.def_kind(dead_item.def_id)
1042 && let impl_did = tcx.local_parent(dead_item.def_id)
1043 && let DefKind::Impl { of_trait: false } = tcx.def_kind(impl_did)
1044 && let ty::Adt(maybe_enum, _) =
1045 tcx.type_of(impl_did).instantiate_identity().kind()
1046 && maybe_enum.is_enum()
1047 && let Some(variant) =
1048 maybe_enum.variants().iter().find(|i| i.name == dead_item.name)
1049 {
1050 Some(crate::errors::EnumVariantSameName {
1051 dead_descr: tcx.def_descr(dead_item.def_id.to_def_id()),
1052 dead_name: dead_item.name,
1053 variant_span: tcx.def_span(variant.def_id),
1054 })
1055 } else {
1056 None
1057 }
1058 })
1059 .collect();
1060
1061 MultipleDeadCodes::DeadCodes {
1062 multiple,
1063 num,
1064 descr,
1065 participle,
1066 name_list,
1067 parent_info,
1068 ignored_derived_impls,
1069 enum_variants_with_same_name,
1070 }
1071 }
1072 };
1073
1074 let hir_id = tcx.local_def_id_to_hir_id(first_item.def_id);
1075 self.tcx.emit_node_span_lint(DEAD_CODE, hir_id, MultiSpan::from_spans(spans), diag);
1076 }
1077
1078 fn warn_multiple(
1079 &self,
1080 def_id: LocalDefId,
1081 participle: &str,
1082 dead_codes: Vec<DeadItem>,
1083 report_on: ReportOn,
1084 ) {
1085 let mut dead_codes = dead_codes
1086 .iter()
1087 .filter(|v| !v.name.as_str().starts_with('_'))
1088 .collect::<Vec<&DeadItem>>();
1089 if dead_codes.is_empty() {
1090 return;
1091 }
1092 dead_codes.sort_by_key(|v| v.level.0);
1094 for group in dead_codes.chunk_by(|a, b| a.level == b.level) {
1095 self.lint_at_single_level(&group, participle, Some(def_id), report_on);
1096 }
1097 }
1098
1099 fn warn_dead_code(&mut self, id: LocalDefId, participle: &str) {
1100 let item = DeadItem {
1101 def_id: id,
1102 name: self.tcx.item_name(id.to_def_id()),
1103 level: self.def_lint_level(id),
1104 };
1105 self.lint_at_single_level(&[&item], participle, None, ReportOn::NamedField);
1106 }
1107
1108 fn check_definition(&mut self, def_id: LocalDefId) {
1109 if self.is_live_code(def_id) {
1110 return;
1111 }
1112 match self.tcx.def_kind(def_id) {
1113 DefKind::AssocConst
1114 | DefKind::AssocTy
1115 | DefKind::AssocFn
1116 | DefKind::Fn
1117 | DefKind::Static { .. }
1118 | DefKind::Const
1119 | DefKind::TyAlias
1120 | DefKind::Enum
1121 | DefKind::Union
1122 | DefKind::ForeignTy
1123 | DefKind::Trait => self.warn_dead_code(def_id, "used"),
1124 DefKind::Struct => self.warn_dead_code(def_id, "constructed"),
1125 DefKind::Variant | DefKind::Field => bug!("should be handled specially"),
1126 _ => {}
1127 }
1128 }
1129
1130 fn is_live_code(&self, def_id: LocalDefId) -> bool {
1131 let Some(name) = self.tcx.opt_item_name(def_id.to_def_id()) else {
1134 return true;
1135 };
1136
1137 self.live_symbols.contains(&def_id) || name.as_str().starts_with('_')
1138 }
1139}
1140
1141fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalModDefId) {
1142 let (live_symbols, ignored_derived_traits) = tcx.live_symbols_and_ignored_derived_traits(());
1143 let mut visitor = DeadVisitor { tcx, live_symbols, ignored_derived_traits };
1144
1145 let module_items = tcx.hir_module_items(module);
1146
1147 for item in module_items.free_items() {
1148 let def_kind = tcx.def_kind(item.owner_id);
1149
1150 let mut dead_codes = Vec::new();
1151 if matches!(def_kind, DefKind::Impl { of_trait: false })
1157 || (def_kind == DefKind::Trait && live_symbols.contains(&item.owner_id.def_id))
1158 {
1159 for &def_id in tcx.associated_item_def_ids(item.owner_id.def_id) {
1160 if let Some(local_def_id) = def_id.as_local()
1161 && !visitor.is_live_code(local_def_id)
1162 {
1163 let name = tcx.item_name(def_id);
1164 let level = visitor.def_lint_level(local_def_id);
1165 dead_codes.push(DeadItem { def_id: local_def_id, name, level });
1166 }
1167 }
1168 }
1169 if !dead_codes.is_empty() {
1170 visitor.warn_multiple(item.owner_id.def_id, "used", dead_codes, ReportOn::NamedField);
1171 }
1172
1173 if !live_symbols.contains(&item.owner_id.def_id) {
1174 let parent = tcx.local_parent(item.owner_id.def_id);
1175 if parent != module.to_local_def_id() && !live_symbols.contains(&parent) {
1176 continue;
1178 }
1179 visitor.check_definition(item.owner_id.def_id);
1180 continue;
1181 }
1182
1183 if let DefKind::Struct | DefKind::Union | DefKind::Enum = def_kind {
1184 let adt = tcx.adt_def(item.owner_id);
1185 let mut dead_variants = Vec::new();
1186
1187 for variant in adt.variants() {
1188 let def_id = variant.def_id.expect_local();
1189 if !live_symbols.contains(&def_id) {
1190 let level = visitor.def_lint_level(def_id);
1192 dead_variants.push(DeadItem { def_id, name: variant.name, level });
1193 continue;
1194 }
1195
1196 let is_positional = variant.fields.raw.first().is_some_and(|field| {
1197 field.name.as_str().starts_with(|c: char| c.is_ascii_digit())
1198 });
1199 let report_on =
1200 if is_positional { ReportOn::TupleField } else { ReportOn::NamedField };
1201 let dead_fields = variant
1202 .fields
1203 .iter()
1204 .filter_map(|field| {
1205 let def_id = field.did.expect_local();
1206 if let ShouldWarnAboutField::Yes = visitor.should_warn_about_field(field) {
1207 let level = visitor.def_lint_level(def_id);
1208 Some(DeadItem { def_id, name: field.name, level })
1209 } else {
1210 None
1211 }
1212 })
1213 .collect();
1214 visitor.warn_multiple(def_id, "read", dead_fields, report_on);
1215 }
1216
1217 visitor.warn_multiple(
1218 item.owner_id.def_id,
1219 "constructed",
1220 dead_variants,
1221 ReportOn::NamedField,
1222 );
1223 }
1224 }
1225
1226 for foreign_item in module_items.foreign_items() {
1227 visitor.check_definition(foreign_item.owner_id.def_id);
1228 }
1229}
1230
1231pub(crate) fn provide(providers: &mut Providers) {
1232 *providers =
1233 Providers { live_symbols_and_ignored_derived_traits, check_mod_deathness, ..*providers };
1234}