Skip to main content

rustc_mir_transform/
lint_and_remove_uninhabited.rs

1use rustc_hir::def::DefKind;
2use rustc_middle::mir::*;
3use rustc_middle::ty::TyCtxt;
4use rustc_session::lint::builtin::UNREACHABLE_CODE;
5
6use crate::errors::UnreachableDueToUninhabited;
7
8/// Lint unreachable code due to uninhabited values from function calls,
9/// and remove return edges from those calls.
10pub(super) struct LintAndRemoveUninhabited;
11
12impl<'tcx> crate::MirPass<'tcx> for LintAndRemoveUninhabited {
13    #[tracing::instrument(level = "debug", skip_all)]
14    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
15        let def_id = body.source.def_id().expect_local();
16        tracing::debug!(?def_id);
17        let parent_module = tcx.parent_module_from_def_id(def_id).to_def_id();
18        let typing_env = body.typing_env(tcx);
19
20        // check if the function's return type is inhabited
21        // this was added here because of this regression
22        // https://github.com/rust-lang/rust/issues/149571
23        let return_ty_is_inhabited = matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn)
24            && body.local_decls[RETURN_PLACE].ty.is_inhabited_from(tcx, parent_module, typing_env);
25
26        let mut lints = vec![];
27        for bbdata in body.basic_blocks.as_mut() {
28            let term = bbdata.terminator_mut();
29            let TerminatorKind::Call { ref mut target, destination, .. } = term.kind else {
30                continue;
31            };
32            let Some(target_bb) = *target else { continue };
33
34            let ty = destination.ty(&body.local_decls, tcx).ty;
35            let ty_is_inhabited = ty.is_inhabited_from(tcx, parent_module, typing_env);
36            if !ty_is_inhabited {
37                // Unreachable code warnings are already emitted during type checking.
38                // However, during type checking, full type information is being
39                // calculated but not yet available, so the check for diverging
40                // expressions due to uninhabited result types is pretty crude and
41                // only checks whether ty.is_never(). Here, we have full type
42                // information available and can issue warnings for less obviously
43                // uninhabited types (e.g. empty enums). The check above is used so
44                // that we do not emit the same warning twice if the uninhabited type
45                // is indeed `!`.
46                if !ty.is_never() && return_ty_is_inhabited {
47                    lints.push((target_bb, ty, term.source_info.span));
48                }
49
50                // The presence or absence of a return edge affects control-flow sensitive
51                // MIR checks and ultimately whether code is accepted or not. We can only
52                // omit the return edge if a return type is visibly uninhabited to a module
53                // that makes the call.
54                *target = None;
55            }
56        }
57
58        for (target_bb, orig_ty, orig_span) in lints {
59            if orig_span.in_external_macro(tcx.sess.source_map()) {
60                continue;
61            }
62
63            let Some((target_loc, descr)) = find_unreachable_code_from(target_bb, body) else {
64                continue;
65            };
66            let lint_root = body.source_scopes[target_loc.scope]
67                .local_data
68                .as_ref()
69                .unwrap_crate_local()
70                .lint_root;
71            tcx.emit_node_span_lint(
72                UNREACHABLE_CODE,
73                lint_root,
74                target_loc.span,
75                UnreachableDueToUninhabited {
76                    expr: target_loc.span,
77                    orig: orig_span,
78                    descr,
79                    ty: orig_ty,
80                },
81            );
82        }
83    }
84
85    fn is_required(&self) -> bool {
86        true
87    }
88}
89
90/// Starting at a target unreachable block, find some user code to lint as unreachable
91#[tracing::instrument(level = "debug", skip(body), ret)]
92fn find_unreachable_code_from<'tcx>(
93    bb: BasicBlock,
94    body: &Body<'tcx>,
95) -> Option<(SourceInfo, &'static str)> {
96    let bb = &body.basic_blocks[bb];
97    for stmt in &bb.statements {
98        match &stmt.kind {
99            // Ignore the implicit `()` return place assignment for unit functions/blocks
100            StatementKind::Assign((_, Rvalue::Use(Operand::Constant(const_), _)))
101                if const_.ty().is_unit() =>
102            {
103                continue;
104            }
105            // Ignore return value plumbing. After a call returning a non-`!`
106            // uninhabited type, a tail expression can be unreachable while
107            // still being needed to satisfy the surrounding return type.
108            StatementKind::Assign((place, _)) if place.as_local() == Some(RETURN_PLACE) => {
109                continue;
110            }
111            StatementKind::StorageLive(_) | StatementKind::StorageDead(_) => {
112                continue;
113            }
114            StatementKind::FakeRead(..) => return Some((stmt.source_info, "definition")),
115            _ => return Some((stmt.source_info, "expression")),
116        }
117    }
118
119    let term = bb.terminator();
120    match term.kind {
121        // No user code in this bb, and our goto target may be reachable via other paths
122        TerminatorKind::Goto { .. } | TerminatorKind::Return => None,
123        _ => Some((term.source_info, "expression")),
124    }
125}