rustc_mir_transform/
lint_and_remove_uninhabited.rs1use rustc_hir::def::DefKind;
2use rustc_middle::mir::*;
3use rustc_middle::ty::TyCtxt;
4use rustc_session::lint::builtin::UNREACHABLE_CODE;
5
6use crate::PassPolicy;
7use crate::diagnostics::UnreachableDueToUninhabited;
8
9pub(super) struct LintAndRemoveUninhabited;
12
13impl<'tcx> crate::MirPass<'tcx> for LintAndRemoveUninhabited {
14 #[tracing::instrument(level = "debug", skip_all)]
15 fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
16 let def_id = body.source.def_id().expect_local();
17 tracing::debug!(?def_id);
18 let parent_module = tcx.parent_module_from_def_id(def_id);
19 let typing_env = body.typing_env(tcx);
20
21 let return_ty_is_inhabited = matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn)
25 && body.local_decls[RETURN_PLACE].ty.is_inhabited_from(tcx, parent_module, typing_env);
26
27 let mut lints = vec![];
28 for bbdata in body.basic_blocks.as_mut() {
29 let term = bbdata.terminator_mut();
30 let TerminatorKind::Call { ref mut target, destination, .. } = term.kind else {
31 continue;
32 };
33 let Some(target_bb) = *target else { continue };
34
35 let ty = destination.ty(&body.local_decls, tcx).ty;
36 let ty_is_inhabited = ty.is_inhabited_from(tcx, parent_module, typing_env);
37 if !ty_is_inhabited {
38 if !ty.is_never() && return_ty_is_inhabited {
48 lints.push((target_bb, ty, term.source_info.span));
49 }
50
51 *target = None;
56 }
57 }
58
59 for (target_bb, orig_ty, orig_span) in lints {
60 if orig_span.in_external_macro(tcx.sess.source_map()) {
61 continue;
62 }
63
64 let Some((target_loc, descr)) = find_unreachable_code_from(target_bb, body) else {
65 continue;
66 };
67 let lint_root = body.source_scopes[target_loc.scope]
68 .local_data
69 .as_ref()
70 .unwrap_crate_local()
71 .lint_root;
72 tcx.emit_node_span_lint(
73 UNREACHABLE_CODE,
74 lint_root,
75 target_loc.span,
76 UnreachableDueToUninhabited {
77 expr: target_loc.span,
78 orig: orig_span,
79 descr,
80 ty: orig_ty,
81 },
82 );
83 }
84 }
85
86 fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy {
87 PassPolicy::Required
90 }
91}
92
93#[tracing::instrument(level = "debug", skip(body), ret)]
95fn find_unreachable_code_from<'tcx>(
96 bb: BasicBlock,
97 body: &Body<'tcx>,
98) -> Option<(SourceInfo, &'static str)> {
99 let bbdata = &body.basic_blocks[bb];
100 for stmt in &bbdata.statements {
101 match &stmt.kind {
102 StatementKind::Assign((_, Rvalue::Use(Operand::Constant(const_), _)))
104 if const_.ty().is_unit() =>
105 {
106 continue;
107 }
108 StatementKind::Assign((place, _)) if place.as_local() == Some(RETURN_PLACE) => {
112 continue;
113 }
114 StatementKind::StorageLive(_)
116 | StatementKind::StorageDead(_)
117 | StatementKind::BackwardIncompatibleDropHint { .. } => {
118 continue;
119 }
120 StatementKind::FakeRead(..) => return Some((stmt.source_info, "definition")),
121 _ => return Some((stmt.source_info, "expression")),
122 }
123 }
124
125 let term = bbdata.terminator();
126 match term.kind {
127 TerminatorKind::Goto { target } | TerminatorKind::Drop { target, .. } => {
130 if &body.basic_blocks.predecessors()[target][..] == &[bb] {
131 find_unreachable_code_from(target, body)
132 } else {
133 None
134 }
135 }
136 TerminatorKind::Return => None,
137 _ => Some((term.source_info, "expression")),
138 }
139}