//! # The rustc_public's IR Visitor
//!
//! ## Overview
//!
//! We currently only support an immutable visitor.
//! The structure of this visitor is similar to the ones internal to `rustc`,
//! and it follows the following conventions:
//!
//! For every mir item, the trait has a `visit_<item>` and a `super_<item>` method.
//! - `visit_<item>`, by default, calls `super_<item>`
//! - `super_<item>`, by default, destructures the `<item>` and calls `visit_<sub_item>` for
//!   all sub-items that compose the original item.
//!
//! In order to implement a visitor, override the `visit_*` methods for the types you are
//! interested in analyzing, and invoke (within that method call)
//! `self.super_*` to continue to the traverse.
//! Avoid calling `super` methods in other circumstances.
//!
//! For the most part, we do not destructure things external to the
//! MIR, e.g., types, spans, etc, but simply visit them and stop.
//! This avoids duplication with other visitors like `TypeFoldable`.
//!
//! ## Updating
//!
//! The code is written in a very deliberate style intended to minimize
//! the chance of things being overlooked.
//!
//! Use pattern matching to reference fields and ensure that all
//! matches are exhaustive.
//!
//! For this to work, ALL MATCHES MUST BE EXHAUSTIVE IN FIELDS AND VARIANTS.
//! That means you never write `..` to skip over fields, nor do you write `_`
//! to skip over variants in a `match`.
//!
//! The only place that `_` is acceptable is to match a field (or
//! variant argument) that does not require visiting.

use crate::mir::*;
use crate::ty::{GenericArgs, MirConst, Region, Ty, TyConst};
use crate::{Error, Opaque, Span};

macro_rules! make_mir_visitor {
    ($visitor_trait_name:ident, $($mutability:ident)?) => {
        pub trait $visitor_trait_name {
            fn visit_body(&mut self, body: &$($mutability)? Body) {
                self.super_body(body)
            }

            fn visit_basic_block(&mut self, bb: &$($mutability)? BasicBlock) {
                self.super_basic_block(bb)
            }

            fn visit_ret_decl(&mut self, local: Local, decl: &$($mutability)? LocalDecl) {
                self.super_ret_decl(local, decl)
            }

            fn visit_arg_decl(&mut self, local: Local, decl: &$($mutability)? LocalDecl) {
                self.super_arg_decl(local, decl)
            }

            fn visit_local_decl(&mut self, local: Local, decl: &$($mutability)? LocalDecl) {
                self.super_local_decl(local, decl)
            }

            fn visit_statement(&mut self, stmt: &$($mutability)? Statement, location: Location) {
                self.super_statement(stmt, location)
            }

            fn visit_terminator(&mut self, term: &$($mutability)? Terminator, location: Location) {
                self.super_terminator(term, location)
            }

            fn visit_span(&mut self, span: &$($mutability)? Span) {
                self.super_span(span)
            }

            fn visit_place(&mut self, place: &$($mutability)? Place, ptx: PlaceContext, location: Location) {
                self.super_place(place, ptx, location)
            }

            visit_place_fns!($($mutability)?);

            fn visit_local(&mut self, local: &$($mutability)? Local, ptx: PlaceContext, location: Location) {
                let _ = (local, ptx, location);
            }

            fn visit_rvalue(&mut self, rvalue: &$($mutability)? Rvalue, location: Location) {
                self.super_rvalue(rvalue, location)
            }

            fn visit_operand(&mut self, operand: &$($mutability)? Operand, location: Location) {
                self.super_operand(operand, location)
            }

            fn visit_user_type_projection(&mut self, projection: &$($mutability)? UserTypeProjection) {
                self.super_user_type_projection(projection)
            }

            fn visit_ty(&mut self, ty: &$($mutability)? Ty, location: Location) {
                let _ = location;
                self.super_ty(ty)
            }

            fn visit_const_operand(&mut self, constant: &$($mutability)? ConstOperand, location: Location) {
                self.super_const_operand(constant, location)
            }

            fn visit_mir_const(&mut self, constant: &$($mutability)? MirConst, location: Location) {
                self.super_mir_const(constant, location)
            }

            fn visit_ty_const(&mut self, constant: &$($mutability)? TyConst, location: Location) {
                let _ = location;
                self.super_ty_const(constant)
            }

            fn visit_region(&mut self, region: &$($mutability)? Region, location: Location) {
                let _ = location;
                self.super_region(region)
            }

            fn visit_args(&mut self, args: &$($mutability)? GenericArgs, location: Location) {
                let _ = location;
                self.super_args(args)
            }

            fn visit_assert_msg(&mut self, msg: &$($mutability)? AssertMessage, location: Location) {
                self.super_assert_msg(msg, location)
            }

            fn visit_var_debug_info(&mut self, var_debug_info: &$($mutability)? VarDebugInfo) {
                self.super_var_debug_info(var_debug_info);
            }

            fn super_body(&mut self, body: &$($mutability)? Body) {
                super_body!(self, body, $($mutability)?);
            }

            fn super_basic_block(&mut self, bb: &$($mutability)? BasicBlock) {
                let BasicBlock { statements, terminator } = bb;
                for stmt in statements {
                    self.visit_statement(stmt, Location(stmt.span));
                }
                self.visit_terminator(terminator, Location(terminator.span));
            }

            fn super_local_decl(&mut self, local: Local, decl: &$($mutability)? LocalDecl) {
                let _ = local;
                let LocalDecl { ty, span, .. } = decl;
                self.visit_ty(ty, Location(*span));
            }

            fn super_ret_decl(&mut self, local: Local, decl: &$($mutability)? LocalDecl) {
                self.super_local_decl(local, decl)
            }

            fn super_arg_decl(&mut self, local: Local, decl: &$($mutability)? LocalDecl) {
                self.super_local_decl(local, decl)
            }

            fn super_statement(&mut self, stmt: &$($mutability)? Statement, location: Location) {
                let Statement { kind, span } = stmt;
                self.visit_span(span);
                match kind {
                    StatementKind::Assign(place, rvalue) => {
                        self.visit_place(place, PlaceContext::MUTATING, location);
                        self.visit_rvalue(rvalue, location);
                    }
                    StatementKind::FakeRead(_, place) | StatementKind::PlaceMention(place) => {
                        self.visit_place(place, PlaceContext::NON_MUTATING, location);
                    }
                    StatementKind::SetDiscriminant { place, .. } => {
                        self.visit_place(place, PlaceContext::MUTATING, location);
                    }
                    StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
                        self.visit_local(local, PlaceContext::NON_USE, location);
                    }
                    StatementKind::AscribeUserType { place, projections, variance: _ } => {
                        self.visit_place(place, PlaceContext::NON_USE, location);
                        self.visit_user_type_projection(projections);
                    }
                    StatementKind::Coverage(coverage) => visit_opaque(coverage),
                    StatementKind::Intrinsic(intrinsic) => match intrinsic {
                        NonDivergingIntrinsic::Assume(operand) => {
                            self.visit_operand(operand, location);
                        }
                        NonDivergingIntrinsic::CopyNonOverlapping(CopyNonOverlapping {
                            src,
                            dst,
                            count,
                        }) => {
                            self.visit_operand(src, location);
                            self.visit_operand(dst, location);
                            self.visit_operand(count, location);
                        }
                    },
                    StatementKind::ConstEvalCounter | StatementKind::Nop => {}
                }
            }

            fn super_terminator(&mut self, term: &$($mutability)? Terminator, location: Location) {
                let Terminator { kind, span } = term;
                self.visit_span(span);
                match kind {
                    TerminatorKind::Goto { .. }
                    | TerminatorKind::Resume
                    | TerminatorKind::Abort
                    | TerminatorKind::Unreachable => {}
                    TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => {
                        self.visit_operand(cond, location);
                        self.visit_assert_msg(msg, location);
                    }
                    TerminatorKind::Drop { place, target: _, unwind: _ } => {
                        self.visit_place(place, PlaceContext::MUTATING, location);
                    }
                    TerminatorKind::Call { func, args, destination, target: _, unwind: _ } => {
                        self.visit_operand(func, location);
                        for arg in args {
                            self.visit_operand(arg, location);
                        }
                        self.visit_place(destination, PlaceContext::MUTATING, location);
                    }
                    TerminatorKind::InlineAsm { operands, .. } => {
                        for op in operands {
                            let InlineAsmOperand { in_value, out_place, raw_rpr: _ } = op;
                            if let Some(input) = in_value {
                                self.visit_operand(input, location);
                            }
                            if let Some(output) = out_place {
                                self.visit_place(output, PlaceContext::MUTATING, location);
                            }
                        }
                    }
                    TerminatorKind::Return => {
                        let $($mutability)? local = RETURN_LOCAL;
                        self.visit_local(&$($mutability)? local, PlaceContext::NON_MUTATING, location);
                    }
                    TerminatorKind::SwitchInt { discr, targets: _ } => {
                        self.visit_operand(discr, location);
                    }
                }
            }

            fn super_span(&mut self, span: &$($mutability)? Span) {
                let _ = span;
            }

            fn super_rvalue(&mut self, rvalue: &$($mutability)? Rvalue, location: Location) {
                match rvalue {
                    Rvalue::AddressOf(mutability, place) => {
                        let pcx = PlaceContext { is_mut: *mutability == RawPtrKind::Mut };
                        self.visit_place(place, pcx, location);
                    }
                    Rvalue::Aggregate(_, operands) => {
                        for op in operands {
                            self.visit_operand(op, location);
                        }
                    }
                    Rvalue::BinaryOp(_, lhs, rhs) | Rvalue::CheckedBinaryOp(_, lhs, rhs) => {
                        self.visit_operand(lhs, location);
                        self.visit_operand(rhs, location);
                    }
                    Rvalue::Cast(_, op, ty) => {
                        self.visit_operand(op, location);
                        self.visit_ty(ty, location);
                    }
                    Rvalue::CopyForDeref(place) | Rvalue::Discriminant(place) | Rvalue::Len(place) => {
                        self.visit_place(place, PlaceContext::NON_MUTATING, location);
                    }
                    Rvalue::Ref(region, kind, place) => {
                        self.visit_region(region, location);
                        let pcx = PlaceContext { is_mut: matches!(kind, BorrowKind::Mut { .. }) };
                        self.visit_place(place, pcx, location);
                    }
                    Rvalue::Reborrow(target, mutability, place) => {
                        self.visit_ty(target, location);
                        let pcx = PlaceContext { is_mut: matches!(mutability, Mutability::Mut) };
                        self.visit_place(place, pcx, location);
                    }
                    Rvalue::Repeat(op, constant) => {
                        self.visit_operand(op, location);
                        self.visit_ty_const(constant, location);
                    }
                    Rvalue::ThreadLocalRef(_) => {}
                    Rvalue::UnaryOp(_, op) | Rvalue::Use(op, _) => {
                        self.visit_operand(op, location);
                    }
                }
            }

            fn super_operand(&mut self, operand: &$($mutability)? Operand, location: Location) {
                match operand {
                    Operand::Copy(place) | Operand::Move(place) => {
                        self.visit_place(place, PlaceContext::NON_MUTATING, location)
                    }
                    Operand::Constant(constant) => {
                        self.visit_const_operand(constant, location);
                    }
                    Operand::RuntimeChecks(_) => {}
                }
            }

            fn super_user_type_projection(&mut self, projection: &$($mutability)? UserTypeProjection) {
                // This is a no-op on mir::Visitor.
                let _ = projection;
            }

            fn super_ty(&mut self, ty: &$($mutability)? Ty) {
                let _ = ty;
            }

            fn super_const_operand(&mut self, constant: &$($mutability)? ConstOperand, location: Location) {
                let ConstOperand { span, user_ty: _, const_ } = constant;
                self.visit_span(span);
                self.visit_mir_const(const_, location);
            }

            fn super_mir_const(&mut self, constant: &$($mutability)? MirConst, location: Location) {
                let MirConst { kind: _, ty, id: _ } = constant;
                self.visit_ty(ty, location);
            }

            fn super_ty_const(&mut self, constant: &$($mutability)? TyConst) {
                let _ = constant;
            }

            fn super_region(&mut self, region: &$($mutability)? Region) {
                let _ = region;
            }

            fn super_args(&mut self, args: &$($mutability)? GenericArgs) {
                let _ = args;
            }

            fn super_var_debug_info(&mut self, var_debug_info: &$($mutability)? VarDebugInfo) {
                let VarDebugInfo { source_info, composite, value, name: _, argument_index: _ } =
                    var_debug_info;
                self.visit_span(&$($mutability)? source_info.span);
                let location = Location(source_info.span);
                if let Some(composite) = composite {
                    self.visit_ty(&$($mutability)? composite.ty, location);
                }
                match value {
                    VarDebugInfoContents::Place(place) => {
                        self.visit_place(place, PlaceContext::NON_USE, location);
                    }
                    VarDebugInfoContents::Const(constant) => {
                        self.visit_mir_const(&$($mutability)? constant.const_, location);
                    }
                }
            }

            fn super_assert_msg(&mut self, msg: &$($mutability)? AssertMessage, location: Location) {
                match msg {
                    AssertMessage::BoundsCheck { len, index } => {
                        self.visit_operand(len, location);
                        self.visit_operand(index, location);
                    }
                    AssertMessage::Overflow(_, left, right) => {
                        self.visit_operand(left, location);
                        self.visit_operand(right, location);
                    }
                    AssertMessage::OverflowNeg(op)
                    | AssertMessage::DivisionByZero(op)
                    | AssertMessage::RemainderByZero(op)
                    | AssertMessage::InvalidEnumConstruction(op) => {
                        self.visit_operand(op, location);
                    }
                    AssertMessage::ResumedAfterReturn(_)
                    | AssertMessage::ResumedAfterPanic(_)
                    | AssertMessage::NullPointerDereference
                    | AssertMessage::ResumedAfterDrop(_) => {
                        //nothing to visit
                    }
                    AssertMessage::MisalignedPointerDereference { required, found } => {
                        self.visit_operand(required, location);
                        self.visit_operand(found, location);
                    }
                }
            }
        }
    };
}

macro_rules! super_body {
    ($self:ident, $body:ident, mut) => {
        for bb in $body.blocks.iter_mut() {
            $self.visit_basic_block(bb);
        }

        $self.visit_ret_decl(RETURN_LOCAL, $body.ret_local_mut());

        for (idx, arg) in $body.arg_locals_mut().iter_mut().enumerate() {
            $self.visit_arg_decl(idx + 1, arg)
        }

        let local_start = $body.arg_count + 1;
        for (idx, arg) in $body.inner_locals_mut().iter_mut().enumerate() {
            $self.visit_local_decl(idx + local_start, arg)
        }

        for info in $body.var_debug_info.iter_mut() {
            $self.visit_var_debug_info(info);
        }

        $self.visit_span(&mut $body.span)
    };

    ($self:ident, $body:ident, ) => {
        let Body { blocks, locals: _, arg_count, var_debug_info, spread_arg: _, span } = $body;

        for bb in blocks {
            $self.visit_basic_block(bb);
        }

        $self.visit_ret_decl(RETURN_LOCAL, $body.ret_local());

        for (idx, arg) in $body.arg_locals().iter().enumerate() {
            $self.visit_arg_decl(idx + 1, arg)
        }

        let local_start = arg_count + 1;
        for (idx, arg) in $body.inner_locals().iter().enumerate() {
            $self.visit_local_decl(idx + local_start, arg)
        }

        for info in var_debug_info.iter() {
            $self.visit_var_debug_info(info);
        }

        $self.visit_span(span)
    };
}

macro_rules! visit_place_fns {
    (mut) => {
        fn super_place(&mut self, place: &mut Place, ptx: PlaceContext, location: Location) {
            self.visit_local(&mut place.local, ptx, location);

            for elem in place.projection.iter_mut() {
                self.visit_projection_elem(elem, ptx, location);
            }
        }

        // We don't have to replicate the `process_projection()` like we did in
        // `rustc_middle::mir::visit.rs` here because the `projection` field in `Place`
        // of Stable-MIR is not an immutable borrow, unlike in `Place` of MIR.
        fn visit_projection_elem(
            &mut self,
            elem: &mut ProjectionElem,
            ptx: PlaceContext,
            location: Location,
        ) {
            self.super_projection_elem(elem, ptx, location)
        }

        fn super_projection_elem(
            &mut self,
            elem: &mut ProjectionElem,
            ptx: PlaceContext,
            location: Location,
        ) {
            match elem {
                ProjectionElem::Deref => {}
                ProjectionElem::Field(_idx, ty) => self.visit_ty(ty, location),
                ProjectionElem::Index(local) => self.visit_local(local, ptx, location),
                ProjectionElem::ConstantIndex { offset: _, min_length: _, from_end: _ } => {}
                ProjectionElem::Subslice { from: _, to: _, from_end: _ } => {}
                ProjectionElem::Downcast(_idx) => {}
                ProjectionElem::OpaqueCast(ty) => self.visit_ty(ty, location),
            }
        }
    };

    () => {
        fn super_place(&mut self, place: &Place, ptx: PlaceContext, location: Location) {
            self.visit_local(&place.local, ptx, location);

            for (idx, elem) in place.projection.iter().enumerate() {
                let place_ref =
                    PlaceRef { local: place.local, projection: &place.projection[..idx] };
                self.visit_projection_elem(place_ref, elem, ptx, location);
            }
        }

        fn visit_projection_elem<'a>(
            &mut self,
            place_ref: PlaceRef<'a>,
            elem: &ProjectionElem,
            ptx: PlaceContext,
            location: Location,
        ) {
            let _ = place_ref;
            self.super_projection_elem(elem, ptx, location);
        }

        fn super_projection_elem(
            &mut self,
            elem: &ProjectionElem,
            ptx: PlaceContext,
            location: Location,
        ) {
            match elem {
                ProjectionElem::Deref => {}
                ProjectionElem::Field(_idx, ty) => self.visit_ty(ty, location),
                ProjectionElem::Index(local) => self.visit_local(local, ptx, location),
                ProjectionElem::ConstantIndex { offset: _, min_length: _, from_end: _ } => {}
                ProjectionElem::Subslice { from: _, to: _, from_end: _ } => {}
                ProjectionElem::Downcast(_idx) => {}
                ProjectionElem::OpaqueCast(ty) => self.visit_ty(ty, location),
            }
        }
    };
}

make_mir_visitor!(MirVisitor,);
make_mir_visitor!(MutMirVisitor, mut);

/// This function is a no-op that gets used to ensure this visitor is kept up-to-date.
///
/// The idea is that whenever we replace an Opaque type by a real type, the compiler will fail
/// when trying to invoke `visit_opaque`.
///
/// If you are here because your compilation is broken, replace the failing call to `visit_opaque()`
/// by a `visit_<CONSTRUCT>` for your construct.
fn visit_opaque(_: &Opaque) {}

/// The location of a statement / terminator in the code and the CFG.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Location(Span);

impl Location {
    pub fn span(&self) -> Span {
        self.0
    }
}

/// Location of the statement at the given index for a given basic block. Assumes that `stmt_idx`
/// and `bb_idx` are valid for a given body.
pub fn statement_location(body: &Body, bb_idx: &BasicBlockIdx, stmt_idx: usize) -> Location {
    let bb = &body.blocks[*bb_idx];
    let stmt = &bb.statements[stmt_idx];
    Location(stmt.span)
}

/// Location of the terminator for a given basic block. Assumes that `bb_idx` is valid for a given
/// body.
pub fn terminator_location(body: &Body, bb_idx: &BasicBlockIdx) -> Location {
    let bb = &body.blocks[*bb_idx];
    let terminator = &bb.terminator;
    Location(terminator.span)
}

/// Reference to a place used to represent a partial projection.
pub struct PlaceRef<'a> {
    pub local: Local,
    pub projection: &'a [ProjectionElem],
}

impl PlaceRef<'_> {
    /// Get the type of this place.
    pub fn ty(&self, locals: &[LocalDecl]) -> Result<Ty, Error> {
        self.projection.iter().try_fold(locals[self.local].ty, |place_ty, elem| elem.ty(place_ty))
    }
}

/// Information about a place's usage.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct PlaceContext {
    /// Whether the access is mutable or not. Keep this private so we can increment the type in a
    /// backward compatible manner.
    is_mut: bool,
}

impl PlaceContext {
    const MUTATING: Self = PlaceContext { is_mut: true };
    const NON_MUTATING: Self = PlaceContext { is_mut: false };
    const NON_USE: Self = PlaceContext { is_mut: false };

    pub fn is_mutating(&self) -> bool {
        self.is_mut
    }
}
