// ----------------------------------------------------------------------
// Loan(Ex, M, S) = Ls holds if ToAddr(Ex) will remain valid for the entirety
// of the scope S, presuming that the returned set of loans `Ls` are honored.

export public_methods;
use result::{Result, Ok, Err};

impl borrowck_ctxt {
    fn loan(cmt: cmt,
            scope_region: ty::region,
            mutbl: ast::mutability) -> bckres<@DVec<loan>> {
        let lc = loan_ctxt_(@{bccx: self,
                              scope_region: scope_region,
                              loans: @DVec()});
        match lc.loan(cmt, mutbl) {
          Ok(()) => {Ok(lc.loans)}
          Err(e) => {Err(e)}
        }
    }
}

type loan_ctxt_ = {
    bccx: borrowck_ctxt,

    // the region scope for which we must preserve the memory
    scope_region: ty::region,

    // accumulated list of loans that will be required
    loans: @DVec<loan>
};

enum loan_ctxt {
    loan_ctxt_(@loan_ctxt_)
}

impl loan_ctxt {
    fn tcx() -> ty::ctxt { self.bccx.tcx }

    fn issue_loan(cmt: cmt,
                  scope_ub: ty::region,
                  req_mutbl: ast::mutability) -> bckres<()> {
        if self.bccx.is_subregion_of(self.scope_region, scope_ub) {
            match req_mutbl {
                m_mutbl => {
                    // We do not allow non-mutable data to be loaned
                    // out as mutable under any circumstances.
                    if cmt.mutbl != m_mutbl {
                        return Err({cmt:cmt,
                                    code:err_mutbl(req_mutbl)});
                    }
                }
                m_const | m_imm => {
                    // However, mutable data can be loaned out as
                    // immutable (and any data as const).  The
                    // `check_loans` pass will then guarantee that no
                    // writes occur for the duration of the loan.
                }
            }

            (*self.loans).push({
                // Note: cmt.lp must be Some(_) because otherwise this
                // loan process does not apply at all.
                lp: cmt.lp.get(),
                cmt: cmt,
                mutbl: req_mutbl});
            return Ok(());
        } else {
            // The loan being requested lives longer than the data
            // being loaned out!
            return Err({cmt:cmt,
                        code:err_out_of_scope(scope_ub,
                                              self.scope_region)});
        }
    }

    fn loan(cmt: cmt, req_mutbl: ast::mutability) -> bckres<()> {
        debug!("loan(%s, %s)",
               self.bccx.cmt_to_repr(cmt),
               self.bccx.mut_to_str(req_mutbl));
        let _i = indenter();

        // see stable() above; should only be called when `cmt` is lendable
        if cmt.lp.is_none() {
            self.bccx.tcx.sess.span_bug(
                cmt.span,
                ~"loan() called with non-lendable value");
        }

        match cmt.cat {
          cat_binding(_) | cat_rvalue | cat_special(_) => {
            // should never be loanable
            self.bccx.tcx.sess.span_bug(
                cmt.span,
                ~"rvalue with a non-none lp");
          }
          cat_local(local_id) | cat_arg(local_id) => {
            let local_scope_id = self.tcx().region_map.get(local_id);
            self.issue_loan(cmt, ty::re_scope(local_scope_id), req_mutbl)
          }
          cat_stack_upvar(cmt) => {
            self.loan(cmt, req_mutbl) // NDM correct?
          }
          cat_discr(base, _) => {
            self.loan(base, req_mutbl)
          }
          cat_comp(cmt_base, comp_field(*)) |
          cat_comp(cmt_base, comp_index(*)) |
          cat_comp(cmt_base, comp_tuple) => {
            // For most components, the type of the embedded data is
            // stable.  Therefore, the base structure need only be
            // const---unless the component must be immutable.  In
            // that case, it must also be embedded in an immutable
            // location, or else the whole structure could be
            // overwritten and the component along with it.
            self.loan_stable_comp(cmt, cmt_base, req_mutbl)
          }
          cat_comp(cmt_base, comp_variant(enum_did)) => {
            // For enums, the memory is unstable if there are multiple
            // variants, because if the enum value is overwritten then
            // the memory changes type.
            if ty::enum_is_univariant(self.bccx.tcx, enum_did) {
                self.loan_stable_comp(cmt, cmt_base, req_mutbl)
            } else {
                self.loan_unstable_deref(cmt, cmt_base, req_mutbl)
            }
          }
          cat_deref(cmt_base, _, uniq_ptr) => {
            // For unique pointers, the memory being pointed out is
            // unstable because if the unique pointer is overwritten
            // then the memory is freed.
            self.loan_unstable_deref(cmt, cmt_base, req_mutbl)
          }
          cat_deref(_, _, unsafe_ptr) |
          cat_deref(_, _, gc_ptr) |
          cat_deref(_, _, region_ptr(_)) => {
            // Aliased data is simply not lendable.
            self.bccx.tcx.sess.span_bug(
                cmt.span,
                ~"aliased ptr with a non-none lp");
          }
        }
    }

    // A "stable component" is one where assigning the base of the
    // component cannot cause the component itself to change types.
    // Example: record fields.
    fn loan_stable_comp(cmt: cmt,
                        cmt_base: cmt,
                        req_mutbl: ast::mutability) -> bckres<()> {
        let base_mutbl = match req_mutbl {
          m_imm => m_imm,
          m_const | m_mutbl => m_const
        };

        do self.loan(cmt_base, base_mutbl).chain |_ok| {
            // can use static for the scope because the base
            // determines the lifetime, ultimately
            self.issue_loan(cmt, ty::re_static, req_mutbl)
        }
    }

    // An "unstable deref" means a deref of a ptr/comp where, if the
    // base of the deref is assigned to, pointers into the result of the
    // deref would be invalidated. Examples: interior of variants, uniques.
    fn loan_unstable_deref(cmt: cmt,
                           cmt_base: cmt,
                           req_mutbl: ast::mutability) -> bckres<()> {
        // Variant components: the base must be immutable, because
        // if it is overwritten, the types of the embedded data
        // could change.
        do self.loan(cmt_base, m_imm).chain |_ok| {
            // can use static, as in loan_stable_comp()
            self.issue_loan(cmt, ty::re_static, req_mutbl)
        }
    }
}
