frontend 0.4.0

rustc's frontend with no LLVM and no std: parsing through MIR, as a library
// `#![no_std]`: these arrive with the standard prelude and name no path, so a `std::`
// search cannot see them - and a `#[derive]` can use them without the name appearing
// in this file at all, which is why they are not trimmed by inspection.
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;

use crate::rustc_hir::def::DefKind;
use crate::rustc_lint_defs::builtin::UNREACHABLE_CODE;
use crate::rustc_middle::mir::*;
use crate::rustc_middle::ty::TyCtxt;

use crate::rustc_mir_transform::PassPolicy;
use crate::rustc_mir_transform::diagnostics::UnreachableDueToUninhabited;

/// Lint unreachable code due to uninhabited values from function calls,
/// and remove return edges from those calls.
pub(super) struct LintAndRemoveUninhabited;

impl<'tcx> crate::rustc_mir_transform::MirPass<'tcx> for LintAndRemoveUninhabited {
    #[tracing::instrument(level = "debug", skip_all)]
    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
        let def_id = body.source.def_id().expect_local();
        tracing::debug!(?def_id);
        let parent_module = tcx.parent_module_from_def_id(def_id);
        let typing_env = body.typing_env(tcx);

        // check if the function's return type is inhabited
        // this was added here because of this regression
        // https://github.com/rust-lang/rust/issues/149571
        let return_ty_is_inhabited = matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn)
            && body.local_decls[RETURN_PLACE].ty.is_inhabited_from(tcx, parent_module, typing_env);

        let mut lints = vec![];
        for bbdata in body.basic_blocks.as_mut() {
            let term = bbdata.terminator_mut();
            let TerminatorKind::Call { ref mut target, destination, .. } = term.kind else {
                continue;
            };
            let Some(target_bb) = *target else { continue };

            let ty = destination.ty(&body.local_decls, tcx).ty;
            let ty_is_inhabited = ty.is_inhabited_from(tcx, parent_module, typing_env);
            if !ty_is_inhabited {
                // Unreachable code warnings are already emitted during type checking.
                // However, during type checking, full type information is being
                // calculated but not yet available, so the check for diverging
                // expressions due to uninhabited result types is pretty crude and
                // only checks whether ty.is_never(). Here, we have full type
                // information available and can issue warnings for less obviously
                // uninhabited types (e.g. empty enums). The check above is used so
                // that we do not emit the same warning twice if the uninhabited type
                // is indeed `!`.
                if !ty.is_never() && return_ty_is_inhabited {
                    lints.push((target_bb, ty, term.source_info.span));
                }

                // The presence or absence of a return edge affects control-flow sensitive
                // MIR checks and ultimately whether code is accepted or not. We can only
                // omit the return edge if a return type is visibly uninhabited to a module
                // that makes the call.
                *target = None;
            }
        }

        for (target_bb, orig_ty, orig_span) in lints {
            if orig_span.in_external_macro(tcx.sess.source_map()) {
                continue;
            }

            let Some((target_loc, descr)) = find_unreachable_code_from(target_bb, body) else {
                continue;
            };
            let lint_root = body.source_scopes[target_loc.scope]
                .local_data
                .as_ref()
                .unwrap_crate_local()
                .lint_root;
            tcx.emit_node_span_lint(
                UNREACHABLE_CODE,
                lint_root,
                target_loc.span,
                UnreachableDueToUninhabited {
                    expr: target_loc.span,
                    orig: orig_span,
                    descr,
                    ty: orig_ty,
                },
            );
        }
    }

    fn policy(&self, _sess: &crate::rustc_session::Session) -> PassPolicy {
        // Removing visibly uninhabited return edges determines the control flow seen by MIR checks.
        // Cannot remove UB: removing the return edge would *introduce* UB if the call actually returned.
        PassPolicy::Required
    }
}

/// Starting at a target unreachable block, find some user code to lint as unreachable
#[tracing::instrument(level = "debug", skip(body), ret)]
fn find_unreachable_code_from<'tcx>(
    bb: BasicBlock,
    body: &Body<'tcx>,
) -> Option<(SourceInfo, &'static str)> {
    let bbdata = &body.basic_blocks[bb];
    for stmt in &bbdata.statements {
        match &stmt.kind {
            // Ignore the implicit `()` return place assignment for unit functions/blocks
            StatementKind::Assign(assign)
                if let (_, Rvalue::Use(Operand::Constant(const_), _)) = &**assign
                    && const_.ty().is_unit() =>
            {
                continue;
            }
            // Ignore return value plumbing. After a call returning a non-`!`
            // uninhabited type, a tail expression can be unreachable while
            // still being needed to satisfy the surrounding return type.
            StatementKind::Assign(assign) if assign.0.as_local() == Some(RETURN_PLACE) => {
                continue;
            }
            // Ignore statements inserted by MIR building that do not correspond to user code.
            StatementKind::StorageLive(_)
            | StatementKind::StorageDead(_)
            | StatementKind::BackwardIncompatibleDropHint { .. } => {
                continue;
            }
            StatementKind::FakeRead(..) => return Some((stmt.source_info, "definition")),
            _ => return Some((stmt.source_info, "expression")),
        }
    }

    let term = bbdata.terminator();
    match term.kind {
        // The user does not care for `goto` and compiler-generated drops. If the target block is
        // only reachable through those terminators, continue searching there.
        TerminatorKind::Goto { target } | TerminatorKind::Drop { target, .. } => {
            if &body.basic_blocks.predecessors()[target][..] == &[bb] {
                find_unreachable_code_from(target, body)
            } else {
                None
            }
        }
        TerminatorKind::Return => None,
        _ => Some((term.source_info, "expression")),
    }
}