frontend 0.4.0

rustc's frontend with no LLVM and no std: parsing through MIR, as a library
//! This pass transforms derefs of Box into a deref of the pointer inside Box.
//!
//! Box is not actually a pointer so it is incorrect to dereference it directly.

// `#![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_abi::FieldIdx;
use crate::rustc_middle::mir::visit::MutVisitor;
use crate::rustc_middle::mir::*;
use crate::span_bug;
use crate::rustc_middle::ty::{self, PatternKind, Ty, TyCtxt};

use crate::rustc_mir_transform::PassPolicy;
use crate::rustc_mir_transform::patch::MirPatch;

/// Constructs the types used when accessing a Box's pointer
fn build_ptr_tys<'tcx>(
    tcx: TyCtxt<'tcx>,
    pointee: Ty<'tcx>,
    unique_def: ty::AdtDef<'tcx>,
    nonnull_def: ty::AdtDef<'tcx>,
) -> (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>) {
    let args = tcx.mk_args(&[pointee.into()]);
    let unique_ty = Ty::new_adt(tcx, unique_def, args);
    let nonnull_ty = Ty::new_adt(tcx, nonnull_def, args);
    let ptr_ty = Ty::new_imm_ptr(tcx, pointee);

    (unique_ty, nonnull_ty, ptr_ty)
}

struct ElaborateBoxDerefVisitor<'a, 'tcx> {
    tcx: TyCtxt<'tcx>,
    local_decls: &'a mut LocalDecls<'tcx>,
    patch: MirPatch<'tcx>,
}

impl<'a, 'tcx> MutVisitor<'tcx> for ElaborateBoxDerefVisitor<'a, 'tcx> {
    fn tcx(&self) -> TyCtxt<'tcx> {
        self.tcx
    }

    fn visit_place(
        &mut self,
        place: &mut Place<'tcx>,
        context: visit::PlaceContext,
        location: Location,
    ) {
        let tcx = self.tcx;

        let base_ty = self.local_decls[place.local].ty;

        // Derefer ensures that derefs are always the first projection
        if let Some(PlaceElem::Deref) = place.projection.first()
            && let Some(boxed_ty) = base_ty.boxed_ty()
        {
            let source_info = self.local_decls[place.local].source_info;

            let ptr_ty = Ty::new_imm_ptr(tcx, boxed_ty);

            let ptr_local = self.patch.new_temp(ptr_ty, source_info.span);

            // Project to the first field (a `Unique`), then transmute that. We could project one
            // further but in the end we'd hit a pattern type so we'd always have to transmute.
            let field_place =
                Place::from(place.local).project_to_field(FieldIdx::ZERO, &*self.local_decls, tcx);
            self.patch.add_assign(
                location,
                Place::from(ptr_local),
                Rvalue::Cast(CastKind::BoxDerefTransmute, Operand::Copy(field_place), ptr_ty),
            );

            place.local = ptr_local;
        }

        self.super_place(place, context, location);
    }
}

pub(super) struct ElaborateBoxDerefs;

impl<'tcx> crate::rustc_mir_transform::MirPass<'tcx> for ElaborateBoxDerefs {
    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
        // If box is not present, this pass doesn't need to do anything.
        let Some(def_id) = tcx.lang_items().owned_box() else { return };

        let unique_did = tcx.adt_def(def_id).non_enum_variant().fields[FieldIdx::ZERO].did;

        let Some(unique_def) =
            tcx.type_of(unique_did).instantiate_identity().skip_norm_wip().ty_adt_def()
        else {
            span_bug!(tcx.def_span(unique_did), "expected Box to contain Unique")
        };

        let nonnull_did = unique_def.non_enum_variant().fields[FieldIdx::ZERO].did;

        let Some(nonnull_def) =
            tcx.type_of(nonnull_did).instantiate_identity().skip_norm_wip().ty_adt_def()
        else {
            span_bug!(tcx.def_span(nonnull_did), "expected Unique to contain Nonnull")
        };

        let patch = MirPatch::new(body);

        let local_decls = &mut body.local_decls;

        let mut visitor = ElaborateBoxDerefVisitor { tcx, local_decls, patch };

        for (block, data) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() {
            visitor.visit_basic_block_data(block, data);
        }

        visitor.patch.apply(body);

        for debug_info in body.var_debug_info.iter_mut() {
            if let VarDebugInfoContents::Place(place) = &mut debug_info.value {
                let mut new_projections: Option<Vec<_>> = None;

                for (base, elem) in place.iter_projections() {
                    let base_ty = base.ty(&body.local_decls, tcx).ty;

                    if let PlaceElem::Deref = elem
                        && let Some(boxed_ty) = base_ty.boxed_ty()
                    {
                        // Clone the projections before us, since now we need to mutate them.
                        let new_projections =
                            new_projections.get_or_insert_with(|| base.projection.to_vec());

                        let (unique_ty, nonnull_ty, ptr_ty) =
                            build_ptr_tys(tcx, boxed_ty, unique_def, nonnull_def);

                        new_projections.extend_from_slice(&[
                            PlaceElem::Field(FieldIdx::ZERO, unique_ty),
                            PlaceElem::Field(FieldIdx::ZERO, nonnull_ty),
                        ]);
                        // While we can't project into a pattern type in a basic block,
                        // this is debug info where it's fine.
                        let pat_ty = Ty::new_pat(tcx, ptr_ty, tcx.mk_pat(PatternKind::NotNull));
                        new_projections.push(PlaceElem::Field(FieldIdx::ZERO, pat_ty));
                        new_projections.push(PlaceElem::Field(FieldIdx::ZERO, ptr_ty));
                        new_projections.push(PlaceElem::Deref);
                    } else if let Some(new_projections) = new_projections.as_mut() {
                        // Keep building up our projections list once we've started it.
                        new_projections.push(elem);
                    }
                }

                // Store the mutated projections if we actually changed something.
                if let Some(new_projections) = new_projections {
                    place.projection = tcx.mk_place_elems(&new_projections);
                }
            }
        }
    }

    fn policy(&self, _sess: &crate::rustc_session::Session) -> PassPolicy {
        // Implements Box dereference semantics so backends and Miri do not have to handle them.
        PassPolicy::Required
    }
}