// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! This pass erases all early-bound regions from the types occuring in the MIR.
//! We want to do this once just before trans, so trans does not have to take
//! care erasing regions all over the place.

use rustc::middle::ty;
use rustc::mir::repr::*;
use transform::MirPass;
use mir_map::MirMap;

pub fn erase_regions<'tcx>(tcx: &ty::ctxt<'tcx>, mir_map: &mut MirMap<'tcx>) {
    let mut eraser = EraseRegions::new(tcx);

    for mir in mir_map.iter_mut().map(|(_, v)| v) {
        eraser.run_on_mir(mir);
    }
}

pub struct EraseRegions<'a, 'tcx: 'a> {
    tcx: &'a ty::ctxt<'tcx>,
}

impl<'a, 'tcx> MirPass<'tcx> for EraseRegions<'a, 'tcx> {

    fn run_on_mir(&mut self, mir: &mut Mir<'tcx>) {

        for basic_block in &mut mir.basic_blocks {
            self.erase_regions_basic_block(basic_block);
        }

        self.erase_regions_return_ty(&mut mir.return_ty);

        self.erase_regions_tys(mir.var_decls.iter_mut().map(|d| &mut d.ty));
        self.erase_regions_tys(mir.arg_decls.iter_mut().map(|d| &mut d.ty));
        self.erase_regions_tys(mir.temp_decls.iter_mut().map(|d| &mut d.ty));
    }
}

impl<'a, 'tcx> EraseRegions<'a, 'tcx> {

    pub fn new(tcx: &'a ty::ctxt<'tcx>) -> EraseRegions<'a, 'tcx> {
        EraseRegions {
            tcx: tcx
        }
    }

    fn erase_regions_basic_block(&mut self,
                                 basic_block: &mut BasicBlockData<'tcx>) {
        for statement in &mut basic_block.statements {
            self.erase_regions_statement(statement);
        }

        self.erase_regions_terminator(&mut basic_block.terminator);
    }

    fn erase_regions_statement(&mut self,
                               statement: &mut Statement<'tcx>) {
        match statement.kind {
            StatementKind::Assign(ref mut lvalue, ref mut rvalue) => {
                self.erase_regions_lvalue(lvalue);
                self.erase_regions_rvalue(rvalue);
            }
            StatementKind::Drop(_, ref mut lvalue) => {
                self.erase_regions_lvalue(lvalue);
            }
        }
    }

    fn erase_regions_terminator(&mut self,
                                terminator: &mut Terminator<'tcx>) {
        match *terminator {
            Terminator::Goto { .. } |
            Terminator::Diverge |
            Terminator::Return |
            Terminator::Panic { .. } => {
                /* nothing to do */
            }
            Terminator::If { ref mut cond, .. } => {
                self.erase_regions_operand(cond);
            }
            Terminator::Switch { ref mut discr, .. } => {
                self.erase_regions_lvalue(discr);
            }
            Terminator::SwitchInt {
                ref mut discr,
                ref mut switch_ty,
                ..
            } => {
                self.erase_regions_lvalue(discr);
                *switch_ty = self.tcx.erase_regions(switch_ty);
            },
            Terminator::Call {
                data: CallData {
                    ref mut destination,
                    ref mut func,
                    ref mut args
                },
                ..
            } => {
                self.erase_regions_lvalue(destination);
                self.erase_regions_operand(func);
                for arg in &mut *args {
                    self.erase_regions_operand(arg);
                }
            }
        }
    }

    fn erase_regions_operand(&mut self, operand: &mut Operand<'tcx>) {
        match *operand {
            Operand::Consume(ref mut lvalue) => {
                self.erase_regions_lvalue(lvalue);
            }
            Operand::Constant(ref mut constant) => {
                self.erase_regions_constant(constant);
            }
        }
    }

    fn erase_regions_lvalue(&mut self, lvalue: &mut Lvalue<'tcx>) {
        match *lvalue {
            Lvalue::Var(_)        |
            Lvalue::Temp(_)       |
            Lvalue::Arg(_)        |
            Lvalue::Static(_)     |
            Lvalue::ReturnPointer => {}
            Lvalue::Projection(ref mut lvalue_projection) => {
                self.erase_regions_lvalue(&mut lvalue_projection.base);
                match lvalue_projection.elem {
                    ProjectionElem::Deref              |
                    ProjectionElem::Field(_)           |
                    ProjectionElem::Downcast(..)       |
                    ProjectionElem::ConstantIndex {..} => { /* nothing to do */ }
                    ProjectionElem::Index(ref mut index) => {
                        self.erase_regions_operand(index);
                    }
                }
            }
        }
    }

    fn erase_regions_rvalue(&mut self, rvalue: &mut Rvalue<'tcx>) {
        match *rvalue {
            Rvalue::Use(ref mut operand) => {
                self.erase_regions_operand(operand)
            }
            Rvalue::Repeat(ref mut operand, ref mut constant) => {
                self.erase_regions_operand(operand);
                self.erase_regions_constant(constant);
            }
            Rvalue::Ref(ref mut region, _, ref mut lvalue) => {
                *region = ty::ReStatic;
                self.erase_regions_lvalue(lvalue);
            }
            Rvalue::Len(ref mut lvalue) => self.erase_regions_lvalue(lvalue),
            Rvalue::Cast(_, ref mut operand, ref mut ty) => {
                self.erase_regions_operand(operand);
                *ty = self.tcx.erase_regions(ty);
            }
            Rvalue::BinaryOp(_, ref mut operand1, ref mut operand2) => {
                self.erase_regions_operand(operand1);
                self.erase_regions_operand(operand2);
            }
            Rvalue::UnaryOp(_, ref mut operand) => {
                self.erase_regions_operand(operand);
            }
            Rvalue::Box(ref mut ty) => *ty = self.tcx.erase_regions(ty),
            Rvalue::Aggregate(ref mut aggregate_kind, ref mut operands) => {
                match *aggregate_kind {
                    AggregateKind::Vec   |
                    AggregateKind::Tuple => {},
                    AggregateKind::Adt(_, _, ref mut substs) => {
                        let erased = self.tcx.erase_regions(*substs);
                        *substs = self.tcx.mk_substs(erased);
                    }
                    AggregateKind::Closure(def_id, ref mut closure_substs) => {
                        let cloned = Box::new(closure_substs.clone());
                        let ty = self.tcx.mk_closure_from_closure_substs(def_id,
                                                                         cloned);
                        let erased = self.tcx.erase_regions(&ty);
                        *closure_substs = match erased.sty {
                            ty::TyClosure(_, ref closure_substs) => &*closure_substs,
                            _ => unreachable!()
                        };
                    }
                }
                for operand in &mut *operands {
                    self.erase_regions_operand(operand);
                }
            }
            Rvalue::Slice { ref mut input, .. } => {
                self.erase_regions_lvalue(input);
            }
            Rvalue::InlineAsm(_) => {},
        }
    }

    fn erase_regions_constant(&mut self, constant: &mut Constant<'tcx>) {
        constant.ty = self.tcx.erase_regions(&constant.ty);
        match constant.literal {
            Literal::Item { ref mut substs, .. } => {
                *substs = self.tcx.mk_substs(self.tcx.erase_regions(substs));
            }
            Literal::Value { .. } => { /* nothing to do */ }
        }
    }

    fn erase_regions_return_ty(&mut self, fn_output: &mut ty::FnOutput<'tcx>) {
        match *fn_output {
            ty::FnConverging(ref mut ty) => {
                *ty = self.tcx.erase_regions(ty);
            },
            ty::FnDiverging => {}
        }
    }

    fn erase_regions_tys<'b, T>(&mut self, tys: T)
        where T: Iterator<Item = &'b mut ty::Ty<'tcx>>,
              'tcx: 'b
    {
        for ty in tys {
            *ty = self.tcx.erase_regions(ty);
        }
    }
}
