// ----------------------------------------------------------------------
// Checking loans
//
// Phase 2 of check: we walk down the tree and check that:
// 1. assignments are always made to mutable locations;
// 2. loans made in overlapping scopes do not conflict
// 3. assignments do not affect things loaned out as immutable
// 4. moves do not affect things loaned out in any way

use self::UseError::*;

use borrowck::*;
use borrowck::InteriorKind::{InteriorElement, InteriorField};
use rustc::middle::expr_use_visitor as euv;
use rustc::middle::expr_use_visitor::MutateMode;
use rustc::middle::mem_categorization as mc;
use rustc::middle::mem_categorization::Categorization;
use rustc::middle::region;
use rustc::ty::{self, TyCtxt, RegionKind};
use syntax::ast;
use syntax_pos::Span;
use rustc::hir;
use rustc::hir::Node;
use rustc_mir::util::borrowck_errors::{BorrowckErrors, Origin};

use std::rc::Rc;

// FIXME (#16118): These functions are intended to allow the borrow checker to
// be less precise in its handling of Box while still allowing moves out of a
// Box. They should be removed when Unique is removed from LoanPath.

fn owned_ptr_base_path<'a, 'tcx>(loan_path: &'a LoanPath<'tcx>) -> &'a LoanPath<'tcx> {
    //! Returns the base of the leftmost dereference of an Unique in
    //! `loan_path`. If there is no dereference of an Unique in `loan_path`,
    //! then it just returns `loan_path` itself.

    return match helper(loan_path) {
        Some(new_loan_path) => new_loan_path,
        None => loan_path.clone()
    };

    fn helper<'a, 'tcx>(loan_path: &'a LoanPath<'tcx>) -> Option<&'a LoanPath<'tcx>> {
        match loan_path.kind {
            LpVar(_) | LpUpvar(_) => None,
            LpExtend(ref lp_base, _, LpDeref(mc::Unique)) => {
                match helper(&lp_base) {
                    v @ Some(_) => v,
                    None => Some(&lp_base)
                }
            }
            LpDowncast(ref lp_base, _) |
            LpExtend(ref lp_base, ..) => helper(&lp_base)
        }
    }
}

fn owned_ptr_base_path_rc<'tcx>(loan_path: &Rc<LoanPath<'tcx>>) -> Rc<LoanPath<'tcx>> {
    //! The equivalent of `owned_ptr_base_path` for an &Rc<LoanPath> rather than
    //! a &LoanPath.

    return match helper(loan_path) {
        Some(new_loan_path) => new_loan_path,
        None => loan_path.clone()
    };

    fn helper<'tcx>(loan_path: &Rc<LoanPath<'tcx>>) -> Option<Rc<LoanPath<'tcx>>> {
        match loan_path.kind {
            LpVar(_) | LpUpvar(_) => None,
            LpExtend(ref lp_base, _, LpDeref(mc::Unique)) => {
                match helper(lp_base) {
                    v @ Some(_) => v,
                    None => Some(lp_base.clone())
                }
            }
            LpDowncast(ref lp_base, _) |
            LpExtend(ref lp_base, ..) => helper(lp_base)
        }
    }
}

struct CheckLoanCtxt<'a, 'tcx: 'a> {
    bccx: &'a BorrowckCtxt<'a, 'tcx>,
    dfcx_loans: &'a LoanDataFlow<'a, 'tcx>,
    move_data: &'a move_data::FlowedMoveData<'a, 'tcx>,
    all_loans: &'a [Loan<'tcx>],
    movable_generator: bool,
}

impl<'a, 'tcx> euv::Delegate<'tcx> for CheckLoanCtxt<'a, 'tcx> {
    fn consume(&mut self,
               consume_id: ast::NodeId,
               consume_span: Span,
               cmt: &mc::cmt_<'tcx>,
               mode: euv::ConsumeMode) {
        debug!("consume(consume_id={}, cmt={:?}, mode={:?})",
               consume_id, cmt, mode);

        let hir_id = self.tcx().hir().node_to_hir_id(consume_id);
        self.consume_common(hir_id.local_id, consume_span, cmt, mode);
    }

    fn matched_pat(&mut self,
                   _matched_pat: &hir::Pat,
                   _cmt: &mc::cmt_,
                   _mode: euv::MatchMode) { }

    fn consume_pat(&mut self,
                   consume_pat: &hir::Pat,
                   cmt: &mc::cmt_<'tcx>,
                   mode: euv::ConsumeMode) {
        debug!("consume_pat(consume_pat={:?}, cmt={:?}, mode={:?})",
               consume_pat,
               cmt,
               mode);

        self.consume_common(consume_pat.hir_id.local_id, consume_pat.span, cmt, mode);
    }

    fn borrow(&mut self,
              borrow_id: ast::NodeId,
              borrow_span: Span,
              cmt: &mc::cmt_<'tcx>,
              loan_region: ty::Region<'tcx>,
              bk: ty::BorrowKind,
              loan_cause: euv::LoanCause)
    {
        debug!("borrow(borrow_id={}, cmt={:?}, loan_region={:?}, \
               bk={:?}, loan_cause={:?})",
               borrow_id, cmt, loan_region,
               bk, loan_cause);

        let hir_id = self.tcx().hir().node_to_hir_id(borrow_id);
        if let Some(lp) = opt_loan_path(cmt) {
            let moved_value_use_kind = match loan_cause {
                euv::ClosureCapture(_) => MovedInCapture,
                _ => MovedInUse,
            };
            self.check_if_path_is_moved(hir_id.local_id, borrow_span, moved_value_use_kind, &lp);
        }

        self.check_for_conflicting_loans(hir_id.local_id);

        self.check_for_loans_across_yields(cmt, loan_region, borrow_span);
    }

    fn mutate(&mut self,
              assignment_id: ast::NodeId,
              assignment_span: Span,
              assignee_cmt: &mc::cmt_<'tcx>,
              mode: euv::MutateMode)
    {
        debug!("mutate(assignment_id={}, assignee_cmt={:?})",
               assignment_id, assignee_cmt);

        if let Some(lp) = opt_loan_path(assignee_cmt) {
            match mode {
                MutateMode::Init | MutateMode::JustWrite => {
                    // In a case like `path = 1`, then path does not
                    // have to be *FULLY* initialized, but we still
                    // must be careful lest it contains derefs of
                    // pointers.
                    self.check_if_assigned_path_is_moved(assignee_cmt.hir_id.local_id,
                                                         assignment_span,
                                                         MovedInUse,
                                                         &lp);
                }
                MutateMode::WriteAndRead => {
                    // In a case like `path += 1`, then path must be
                    // fully initialized, since we will read it before
                    // we write it.
                    self.check_if_path_is_moved(assignee_cmt.hir_id.local_id,
                                                assignment_span,
                                                MovedInUse,
                                                &lp);
                }
            }
        }
        self.check_assignment(self.tcx().hir().node_to_hir_id(assignment_id).local_id,
                              assignment_span, assignee_cmt);
    }

    fn decl_without_init(&mut self, _id: ast::NodeId, _span: Span) { }
}

pub fn check_loans<'a, 'b, 'c, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
                                     dfcx_loans: &LoanDataFlow<'b, 'tcx>,
                                     move_data: &move_data::FlowedMoveData<'c, 'tcx>,
                                     all_loans: &[Loan<'tcx>],
                                     body: &hir::Body) {
    debug!("check_loans(body id={})", body.value.id);

    let def_id = bccx.tcx.hir().body_owner_def_id(body.id());

    let node_id = bccx.tcx.hir().as_local_node_id(def_id).unwrap();
    let movable_generator = !match bccx.tcx.hir().get(node_id) {
        Node::Expr(&hir::Expr {
            node: hir::ExprKind::Closure(.., Some(hir::GeneratorMovability::Static)),
            ..
        }) => true,
        _ => false,
    };

    let param_env = bccx.tcx.param_env(def_id);
    let mut clcx = CheckLoanCtxt {
        bccx,
        dfcx_loans,
        move_data,
        all_loans,
        movable_generator,
    };
    let rvalue_promotable_map = bccx.tcx.rvalue_promotable_map(def_id);
    euv::ExprUseVisitor::new(&mut clcx,
                             bccx.tcx,
                             param_env,
                             &bccx.region_scope_tree,
                             bccx.tables,
                             Some(rvalue_promotable_map))
        .consume_body(body);
}

#[derive(PartialEq)]
enum UseError<'tcx> {
    UseOk,
    UseWhileBorrowed(/*loan*/Rc<LoanPath<'tcx>>, /*loan*/Span)
}

fn compatible_borrow_kinds(borrow_kind1: ty::BorrowKind,
                           borrow_kind2: ty::BorrowKind)
                           -> bool {
    borrow_kind1 == ty::ImmBorrow && borrow_kind2 == ty::ImmBorrow
}

impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
    pub fn tcx(&self) -> TyCtxt<'a, 'tcx, 'tcx> { self.bccx.tcx }

    pub fn each_issued_loan<F>(&self, node: hir::ItemLocalId, mut op: F) -> bool where
        F: FnMut(&Loan<'tcx>) -> bool,
    {
        //! Iterates over each loan that has been issued
        //! on entrance to `node`, regardless of whether it is
        //! actually *in scope* at that point.  Sometimes loans
        //! are issued for future scopes and thus they may have been
        //! *issued* but not yet be in effect.

        self.dfcx_loans.each_bit_on_entry(node, |loan_index| {
            let loan = &self.all_loans[loan_index];
            op(loan)
        })
    }

    pub fn each_in_scope_loan<F>(&self, scope: region::Scope, mut op: F) -> bool where
        F: FnMut(&Loan<'tcx>) -> bool,
    {
        //! Like `each_issued_loan()`, but only considers loans that are
        //! currently in scope.

        self.each_issued_loan(scope.item_local_id(), |loan| {
            if self.bccx.region_scope_tree.is_subscope_of(scope, loan.kill_scope) {
                op(loan)
            } else {
                true
            }
        })
    }

    fn each_in_scope_loan_affecting_path<F>(&self,
                                            scope: region::Scope,
                                            loan_path: &LoanPath<'tcx>,
                                            mut op: F)
                                            -> bool where
        F: FnMut(&Loan<'tcx>) -> bool,
    {
        //! Iterates through all of the in-scope loans affecting `loan_path`,
        //! calling `op`, and ceasing iteration if `false` is returned.

        // First, we check for a loan restricting the path P being used. This
        // accounts for borrows of P but also borrows of subpaths, like P.a.b.
        // Consider the following example:
        //
        //     let x = &mut a.b.c; // Restricts a, a.b, and a.b.c
        //     let y = a;          // Conflicts with restriction

        let loan_path = owned_ptr_base_path(loan_path);
        let cont = self.each_in_scope_loan(scope, |loan| {
            let mut ret = true;
            for restr_path in &loan.restricted_paths {
                if **restr_path == *loan_path {
                    if !op(loan) {
                        ret = false;
                        break;
                    }
                }
            }
            ret
        });

        if !cont {
            return false;
        }

        // Next, we must check for *loans* (not restrictions) on the path P or
        // any base path. This rejects examples like the following:
        //
        //     let x = &mut a.b;
        //     let y = a.b.c;
        //
        // Limiting this search to *loans* and not *restrictions* means that
        // examples like the following continue to work:
        //
        //     let x = &mut a.b;
        //     let y = a.c;

        let mut loan_path = loan_path;
        loop {
            match loan_path.kind {
                LpVar(_) | LpUpvar(_) => {
                    break;
                }
                LpDowncast(ref lp_base, _) |
                LpExtend(ref lp_base, ..) => {
                    loan_path = &lp_base;
                }
            }

            let cont = self.each_in_scope_loan(scope, |loan| {
                if *loan.loan_path == *loan_path {
                    op(loan)
                } else {
                    true
                }
            });

            if !cont {
                return false;
            }
        }

        return true;
    }

    pub fn loans_generated_by(&self, node: hir::ItemLocalId) -> Vec<usize> {
        //! Returns a vector of the loans that are generated as
        //! we enter `node`.

        let mut result = Vec::new();
        self.dfcx_loans.each_gen_bit(node, |loan_index| {
            result.push(loan_index);
            true
        });
        return result;
    }

    pub fn check_for_loans_across_yields(&self,
                                         cmt: &mc::cmt_<'tcx>,
                                         loan_region: ty::Region<'tcx>,
                                         borrow_span: Span) {
        pub fn borrow_of_local_data<'tcx>(cmt: &mc::cmt_<'tcx>) -> bool {
            match cmt.cat {
                // Borrows of static items is allowed
                Categorization::StaticItem => false,
                // Reborrow of already borrowed data is ignored
                // Any errors will be caught on the initial borrow
                Categorization::Deref(..) => false,

                // By-ref upvars has Derefs so they will get ignored.
                // Generators counts as FnOnce so this leaves only
                // by-move upvars, which is local data for generators
                Categorization::Upvar(..) => true,

                Categorization::ThreadLocal(region) |
                Categorization::Rvalue(region) => {
                    // Rvalues promoted to 'static are no longer local
                    if let RegionKind::ReStatic = *region {
                        false
                    } else {
                        true
                    }
                }

                // Borrow of local data must be checked
                Categorization::Local(..) => true,

                // For interior references and downcasts, find out if the base is local
                Categorization::Downcast(ref cmt_base, _) |
                Categorization::Interior(ref cmt_base, _) => borrow_of_local_data(&cmt_base),
            }
        }

        if !self.movable_generator {
            return;
        }

        if !borrow_of_local_data(cmt) {
            return;
        }

        let scope = match *loan_region {
            // A concrete region in which we will look for a yield expression
            RegionKind::ReScope(scope) => scope,

            // There cannot be yields inside an empty region
            RegionKind::ReEmpty => return,

            // Local data cannot have these lifetimes
            RegionKind::ReEarlyBound(..) |
            RegionKind::ReLateBound(..) |
            RegionKind::ReFree(..) |
            RegionKind::ReStatic => {
                self.bccx
                    .tcx
                    .sess.delay_span_bug(borrow_span,
                                         &format!("unexpected region for local data {:?}",
                                                  loan_region));
                return
            }

            // These cannot exist in borrowck
            RegionKind::ReVar(..) |
            RegionKind::RePlaceholder(..) |
            RegionKind::ReClosureBound(..) |
            RegionKind::ReErased => span_bug!(borrow_span,
                                              "unexpected region in borrowck {:?}",
                                              loan_region),
        };

        let body_id = self.bccx.body.value.hir_id.local_id;

        if self.bccx.region_scope_tree.containing_body(scope) != Some(body_id) {
            // We are borrowing local data longer than its storage.
            // This should result in other borrowck errors.
            self.bccx.tcx.sess.delay_span_bug(borrow_span,
                                              "borrowing local data longer than its storage");
            return;
        }

        if let Some(yield_span) = self.bccx
                                      .region_scope_tree
                                      .yield_in_scope_for_expr(scope,
                                                               cmt.hir_id,
                                                               self.bccx.body)
        {
            self.bccx.cannot_borrow_across_generator_yield(borrow_span,
                                                           yield_span,
                                                           Origin::Ast).emit();
            self.bccx.signal_error();
        }
    }

    pub fn check_for_conflicting_loans(&self, node: hir::ItemLocalId) {
        //! Checks to see whether any of the loans that are issued
        //! on entrance to `node` conflict with loans that have already been
        //! issued when we enter `node` (for example, we do not
        //! permit two `&mut` borrows of the same variable).
        //!
        //! (Note that some loans can be *issued* without necessarily
        //! taking effect yet.)

        debug!("check_for_conflicting_loans(node={:?})", node);

        let new_loan_indices = self.loans_generated_by(node);
        debug!("new_loan_indices = {:?}", new_loan_indices);

        for &new_loan_index in &new_loan_indices {
            self.each_issued_loan(node, |issued_loan| {
                let new_loan = &self.all_loans[new_loan_index];
                // Only report an error for the first issued loan that conflicts
                // to avoid O(n^2) errors.
                self.report_error_if_loans_conflict(issued_loan, new_loan)
            });
        }

        for (i, &x) in new_loan_indices.iter().enumerate() {
            let old_loan = &self.all_loans[x];
            for &y in &new_loan_indices[(i+1) ..] {
                let new_loan = &self.all_loans[y];
                self.report_error_if_loans_conflict(old_loan, new_loan);
            }
        }
    }

    pub fn report_error_if_loans_conflict(&self,
                                          old_loan: &Loan<'tcx>,
                                          new_loan: &Loan<'tcx>)
                                          -> bool {
        //! Checks whether `old_loan` and `new_loan` can safely be issued
        //! simultaneously.

        debug!("report_error_if_loans_conflict(old_loan={:?}, new_loan={:?})",
               old_loan,
               new_loan);

        // Should only be called for loans that are in scope at the same time.
        assert!(self.bccx.region_scope_tree.scopes_intersect(old_loan.kill_scope,
                                                       new_loan.kill_scope));

        let err_old_new = self.report_error_if_loan_conflicts_with_restriction(
            old_loan, new_loan, old_loan, new_loan).err();
        let err_new_old = self.report_error_if_loan_conflicts_with_restriction(
            new_loan, old_loan, old_loan, new_loan).err();

        match (err_old_new, err_new_old) {
            (Some(mut err), None) | (None, Some(mut err)) => {
                err.emit();
                self.bccx.signal_error();
            }
            (Some(mut err_old), Some(mut err_new)) => {
                err_old.emit();
                self.bccx.signal_error();
                err_new.cancel();
            }
            (None, None) => return true,
        }

        false
    }

    pub fn report_error_if_loan_conflicts_with_restriction(&self,
                                                           loan1: &Loan<'tcx>,
                                                           loan2: &Loan<'tcx>,
                                                           old_loan: &Loan<'tcx>,
                                                           new_loan: &Loan<'tcx>)
                                                           -> Result<(), DiagnosticBuilder<'a>> {
        //! Checks whether the restrictions introduced by `loan1` would
        //! prohibit `loan2`. Returns false if an error is reported.

        debug!("report_error_if_loan_conflicts_with_restriction(\
                loan1={:?}, loan2={:?})",
               loan1,
               loan2);

        if compatible_borrow_kinds(loan1.kind, loan2.kind) {
            return Ok(());
        }

        let loan2_base_path = owned_ptr_base_path_rc(&loan2.loan_path);
        for restr_path in &loan1.restricted_paths {
            if *restr_path != loan2_base_path { continue; }

            // If new_loan is something like `x.a`, and old_loan is something like `x.b`, we would
            // normally generate a rather confusing message (in this case, for multiple mutable
            // borrows):
            //
            //     error: cannot borrow `x.b` as mutable more than once at a time
            //     note: previous borrow of `x.a` occurs here; the mutable borrow prevents
            //     subsequent moves, borrows, or modification of `x.a` until the borrow ends
            //
            // What we want to do instead is get the 'common ancestor' of the two borrow paths and
            // use that for most of the message instead, giving is something like this:
            //
            //     error: cannot borrow `x` as mutable more than once at a time
            //     note: previous borrow of `x` occurs here (through borrowing `x.a`); the mutable
            //     borrow prevents subsequent moves, borrows, or modification of `x` until the
            //     borrow ends

            let common = new_loan.loan_path.common(&old_loan.loan_path);
            let (nl, ol, new_loan_msg, old_loan_msg) = {
                if new_loan.loan_path.has_fork(&old_loan.loan_path) && common.is_some() {
                    let nl = self.bccx.loan_path_to_string(&common.unwrap());
                    let ol = nl.clone();
                    let new_loan_msg = format!(" (via `{}`)",
                                               self.bccx.loan_path_to_string(
                                                   &new_loan.loan_path));
                    let old_loan_msg = format!(" (via `{}`)",
                                               self.bccx.loan_path_to_string(
                                                   &old_loan.loan_path));
                    (nl, ol, new_loan_msg, old_loan_msg)
                } else {
                    (self.bccx.loan_path_to_string(&new_loan.loan_path),
                     self.bccx.loan_path_to_string(&old_loan.loan_path),
                     String::new(),
                     String::new())
                }
            };

            let ol_pronoun = if new_loan.loan_path == old_loan.loan_path {
                "it".to_string()
            } else {
                format!("`{}`", ol)
            };

            // We want to assemble all the relevant locations for the error.
            //
            // 1. Where did the new loan occur.
            //    - if due to closure creation, where was the variable used in closure?
            // 2. Where did old loan occur.
            // 3. Where does old loan expire.

            let previous_end_span =
                Some(self.tcx().sess.source_map().end_point(
                        old_loan.kill_scope.span(self.tcx(), &self.bccx.region_scope_tree)));

            let mut err = match (new_loan.kind, old_loan.kind) {
                (ty::MutBorrow, ty::MutBorrow) =>
                    self.bccx.cannot_mutably_borrow_multiply(
                        new_loan.span, &nl, &new_loan_msg, old_loan.span, &old_loan_msg,
                        previous_end_span, Origin::Ast),
                (ty::UniqueImmBorrow, ty::UniqueImmBorrow) =>
                    self.bccx.cannot_uniquely_borrow_by_two_closures(
                        new_loan.span, &nl, old_loan.span, previous_end_span, Origin::Ast),
                (ty::UniqueImmBorrow, _) =>
                    self.bccx.cannot_uniquely_borrow_by_one_closure(
                        new_loan.span, "closure", &nl, &new_loan_msg,
                        old_loan.span, &ol_pronoun, &old_loan_msg, previous_end_span, Origin::Ast),
                (_, ty::UniqueImmBorrow) => {
                    let new_loan_str = &new_loan.kind.to_user_str();
                    self.bccx.cannot_reborrow_already_uniquely_borrowed(
                        new_loan.span, "closure", &nl, &new_loan_msg, new_loan_str,
                        old_loan.span, &old_loan_msg, previous_end_span, "", Origin::Ast)
                }
                (..) =>
                    self.bccx.cannot_reborrow_already_borrowed(
                        new_loan.span,
                        &nl, &new_loan_msg, &new_loan.kind.to_user_str(),
                        old_loan.span, &ol_pronoun, &old_loan.kind.to_user_str(), &old_loan_msg,
                        previous_end_span, Origin::Ast)
            };

            match new_loan.cause {
                euv::ClosureCapture(span) => {
                    err.span_label(
                        span,
                        format!("borrow occurs due to use of `{}` in closure", nl));
                }
                _ => { }
            }

            match old_loan.cause {
                euv::ClosureCapture(span) => {
                    err.span_label(
                        span,
                        format!("previous borrow occurs due to use of `{}` in closure",
                                 ol));
                }
                _ => { }
            }

            return Err(err);
        }

        Ok(())
    }

    fn consume_common(&self,
                      id: hir::ItemLocalId,
                      span: Span,
                      cmt: &mc::cmt_<'tcx>,
                      mode: euv::ConsumeMode) {
        if let Some(lp) = opt_loan_path(cmt) {
            let moved_value_use_kind = match mode {
                euv::Copy => {
                    self.check_for_copy_of_frozen_path(id, span, &lp);
                    MovedInUse
                }
                euv::Move(_) => {
                    match self.move_data.kind_of_move_of_path(id, &lp) {
                        None => {
                            // Sometimes moves don't have a move kind;
                            // this either means that the original move
                            // was from something illegal to move,
                            // or was moved from referent of an unsafe
                            // pointer or something like that.
                            MovedInUse
                        }
                        Some(move_kind) => {
                            self.check_for_move_of_borrowed_path(id, span,
                                                                 &lp, move_kind);
                            if move_kind == move_data::Captured {
                                MovedInCapture
                            } else {
                                MovedInUse
                            }
                        }
                    }
                }
            };

            self.check_if_path_is_moved(id, span, moved_value_use_kind, &lp);
        }
    }

    fn check_for_copy_of_frozen_path(&self,
                                     id: hir::ItemLocalId,
                                     span: Span,
                                     copy_path: &LoanPath<'tcx>) {
        match self.analyze_restrictions_on_use(id, copy_path, ty::ImmBorrow) {
            UseOk => { }
            UseWhileBorrowed(loan_path, loan_span) => {
                let desc = self.bccx.loan_path_to_string(copy_path);
                self.bccx.cannot_use_when_mutably_borrowed(
                        span, &desc,
                        loan_span, &self.bccx.loan_path_to_string(&loan_path),
                        Origin::Ast)
                    .emit();
                self.bccx.signal_error();
            }
        }
    }

    fn check_for_move_of_borrowed_path(&self,
                                       id: hir::ItemLocalId,
                                       span: Span,
                                       move_path: &LoanPath<'tcx>,
                                       move_kind: move_data::MoveKind) {
        // We want to detect if there are any loans at all, so we search for
        // any loans incompatible with MutBorrrow, since all other kinds of
        // loans are incompatible with that.
        match self.analyze_restrictions_on_use(id, move_path, ty::MutBorrow) {
            UseOk => { }
            UseWhileBorrowed(loan_path, loan_span) => {
                let mut err = match move_kind {
                    move_data::Captured => {
                        let mut err = self.bccx.cannot_move_into_closure(
                            span, &self.bccx.loan_path_to_string(move_path), Origin::Ast);
                        err.span_label(
                            loan_span,
                            format!("borrow of `{}` occurs here",
                                    &self.bccx.loan_path_to_string(&loan_path))
                            );
                        err.span_label(
                            span,
                            "move into closure occurs here"
                            );
                        err
                    }
                    move_data::Declared |
                    move_data::MoveExpr |
                    move_data::MovePat => {
                        let desc = self.bccx.loan_path_to_string(move_path);
                        let mut err = self.bccx.cannot_move_when_borrowed(span, &desc, Origin::Ast);
                        err.span_label(
                            loan_span,
                            format!("borrow of `{}` occurs here",
                                    &self.bccx.loan_path_to_string(&loan_path))
                            );
                        err.span_label(
                            span,
                            format!("move out of `{}` occurs here",
                                &self.bccx.loan_path_to_string(move_path))
                            );
                        err
                    }
                };

                err.emit();
                self.bccx.signal_error();
            }
        }
    }

    pub fn analyze_restrictions_on_use(&self,
                                       expr_id: hir::ItemLocalId,
                                       use_path: &LoanPath<'tcx>,
                                       borrow_kind: ty::BorrowKind)
                                       -> UseError<'tcx> {
        debug!("analyze_restrictions_on_use(expr_id={:?}, use_path={:?})",
               expr_id, use_path);

        let mut ret = UseOk;

        let scope = region::Scope {
            id: expr_id,
            data: region::ScopeData::Node
        };
        self.each_in_scope_loan_affecting_path(
            scope, use_path, |loan| {
            if !compatible_borrow_kinds(loan.kind, borrow_kind) {
                ret = UseWhileBorrowed(loan.loan_path.clone(), loan.span);
                false
            } else {
                true
            }
        });

        return ret;
    }

    /// Reports an error if `expr` (which should be a path)
    /// is using a moved/uninitialized value
    fn check_if_path_is_moved(&self,
                              id: hir::ItemLocalId,
                              span: Span,
                              use_kind: MovedValueUseKind,
                              lp: &Rc<LoanPath<'tcx>>) {
        debug!("check_if_path_is_moved(id={:?}, use_kind={:?}, lp={:?})",
               id, use_kind, lp);

        // FIXME: if you find yourself tempted to cut and paste
        // the body below and then specializing the error reporting,
        // consider refactoring this instead!

        let base_lp = owned_ptr_base_path_rc(lp);
        self.move_data.each_move_of(id, &base_lp, |the_move, moved_lp| {
            self.bccx.report_use_of_moved_value(
                span,
                use_kind,
                &lp,
                the_move,
                moved_lp);
            false
        });
    }

    /// Reports an error if assigning to `lp` will use a
    /// moved/uninitialized value. Mainly this is concerned with
    /// detecting derefs of uninitialized pointers.
    ///
    /// For example:
    ///
    /// ```
    /// let a: i32;
    /// a = 10; // ok, even though a is uninitialized
    /// ```
    ///
    /// ```
    /// struct Point { x: u32, y: u32 }
    /// let mut p: Point;
    /// p.x = 22; // ok, even though `p` is uninitialized
    /// ```
    ///
    /// ```compile_fail,E0381
    /// # struct Point { x: u32, y: u32 }
    /// let mut p: Box<Point>;
    /// (*p).x = 22; // not ok, p is uninitialized, can't deref
    /// ```
    fn check_if_assigned_path_is_moved(&self,
                                       id: hir::ItemLocalId,
                                       span: Span,
                                       use_kind: MovedValueUseKind,
                                       lp: &Rc<LoanPath<'tcx>>)
    {
        match lp.kind {
            LpVar(_) | LpUpvar(_) => {
                // assigning to `x` does not require that `x` is initialized
            }
            LpDowncast(ref lp_base, _) => {
                // assigning to `(P->Variant).f` is ok if assigning to `P` is ok
                self.check_if_assigned_path_is_moved(id, span,
                                                     use_kind, lp_base);
            }
            LpExtend(ref lp_base, _, LpInterior(_, InteriorField(_))) => {
                match lp_base.to_type().sty {
                    ty::Adt(def, _) if def.has_dtor(self.tcx()) => {
                        // In the case where the owner implements drop, then
                        // the path must be initialized to prevent a case of
                        // partial reinitialization
                        //
                        // FIXME: could refactor via hypothetical
                        // generalized check_if_path_is_moved
                        let loan_path = owned_ptr_base_path_rc(lp_base);
                        self.move_data.each_move_of(id, &loan_path, |_, _| {
                            self.bccx
                                .report_partial_reinitialization_of_uninitialized_structure(
                                    span,
                                    &loan_path);
                            false
                        });
                        return;
                    },
                    _ => {},
                }

                // assigning to `P.f` is ok if assigning to `P` is ok
                self.check_if_assigned_path_is_moved(id, span,
                                                     use_kind, lp_base);
            }
            LpExtend(ref lp_base, _, LpInterior(_, InteriorElement)) |
            LpExtend(ref lp_base, _, LpDeref(_)) => {
                // assigning to `P[i]` requires `P` is initialized
                // assigning to `(*P)` requires `P` is initialized
                self.check_if_path_is_moved(id, span, use_kind, lp_base);
            }
        }
    }

    fn check_assignment(&self,
                        assignment_id: hir::ItemLocalId,
                        assignment_span: Span,
                        assignee_cmt: &mc::cmt_<'tcx>) {
        debug!("check_assignment(assignee_cmt={:?})", assignee_cmt);

        // Check that we don't invalidate any outstanding loans
        if let Some(loan_path) = opt_loan_path(assignee_cmt) {
            let scope = region::Scope {
                id: assignment_id,
                data: region::ScopeData::Node
            };
            self.each_in_scope_loan_affecting_path(scope, &loan_path, |loan| {
                self.report_illegal_mutation(assignment_span, &loan_path, loan);
                false
            });
        }

        // Check for reassignments to (immutable) local variables. This
        // needs to be done here instead of in check_loans because we
        // depend on move data.
        if let Categorization::Local(local_id) = assignee_cmt.cat {
            let lp = opt_loan_path(assignee_cmt).unwrap();
            self.move_data.each_assignment_of(assignment_id, &lp, |assign| {
                if assignee_cmt.mutbl.is_mutable() {
                    let hir_id = self.bccx.tcx.hir().node_to_hir_id(local_id);
                    self.bccx.used_mut_nodes.borrow_mut().insert(hir_id);
                } else {
                    self.bccx.report_reassigned_immutable_variable(
                        assignment_span,
                        &lp,
                        assign);
                }
                false
            });
            return
        }
    }

    pub fn report_illegal_mutation(&self,
                                   span: Span,
                                   loan_path: &LoanPath<'tcx>,
                                   loan: &Loan) {
        self.bccx.cannot_assign_to_borrowed(
            span, loan.span, &self.bccx.loan_path_to_string(loan_path), Origin::Ast)
            .emit();
        self.bccx.signal_error();
    }
}
