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::PassPolicy;
7use crate::diagnostics::UnreachableDueToUninhabited;
8
9/// Lint unreachable code due to uninhabited values from function calls,
10/// and remove return edges from those calls.
11pub(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        // check if the function's return type is inhabited
22        // this was added here because of this regression
23        // https://github.com/rust-lang/rust/issues/149571
24        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                // Unreachable code warnings are already emitted during type checking.
39                // However, during type checking, full type information is being
40                // calculated but not yet available, so the check for diverging
41                // expressions due to uninhabited result types is pretty crude and
42                // only checks whether ty.is_never(). Here, we have full type
43                // information available and can issue warnings for less obviously
44                // uninhabited types (e.g. empty enums). The check above is used so
45                // that we do not emit the same warning twice if the uninhabited type
46                // is indeed `!`.
47                if !ty.is_never() && return_ty_is_inhabited {
48                    lints.push((target_bb, ty, term.source_info.span));
49                }
50
51                // The presence or absence of a return edge affects control-flow sensitive
52                // MIR checks and ultimately whether code is accepted or not. We can only
53                // omit the return edge if a return type is visibly uninhabited to a module
54                // that makes the call.
55                *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        // Removing visibly uninhabited return edges determines the control flow seen by MIR checks.
88        // Cannot remove UB: removing the return edge would *introduce* UB if the call actually returned.
89        PassPolicy::Required
90    }
91}
92
93/// Starting at a target unreachable block, find some user code to lint as unreachable
94#[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            // Ignore the implicit `()` return place assignment for unit functions/blocks
103            StatementKind::Assign((_, Rvalue::Use(Operand::Constant(const_), _)))
104                if const_.ty().is_unit() =>
105            {
106                continue;
107            }
108            // Ignore return value plumbing. After a call returning a non-`!`
109            // uninhabited type, a tail expression can be unreachable while
110            // still being needed to satisfy the surrounding return type.
111            StatementKind::Assign((place, _)) if place.as_local() == Some(RETURN_PLACE) => {
112                continue;
113            }
114            // Ignore statements inserted by MIR building that do not correspond to user code.
115            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        // The user does not care for `goto` and compiler-generated drops. If the target block is
128        // only reachable through those terminators, continue searching there.
129        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}