// Copyright 2012-2014 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.

//! ### Inferring borrow kinds for upvars
//!
//! Whenever there is a closure expression, we need to determine how each
//! upvar is used. We do this by initially assigning each upvar an
//! immutable "borrow kind" (see `ty::BorrowKind` for details) and then
//! "escalating" the kind as needed. The borrow kind proceeds according to
//! the following lattice:
//!
//!     ty::ImmBorrow -> ty::UniqueImmBorrow -> ty::MutBorrow
//!
//! So, for example, if we see an assignment `x = 5` to an upvar `x`, we
//! will promote its borrow kind to mutable borrow. If we see an `&mut x`
//! we'll do the same. Naturally, this applies not just to the upvar, but
//! to everything owned by `x`, so the result is the same for something
//! like `x.f = 5` and so on (presuming `x` is not a borrowed pointer to a
//! struct). These adjustments are performed in
//! `adjust_upvar_borrow_kind()` (you can trace backwards through the code
//! from there).
//!
//! The fact that we are inferring borrow kinds as we go results in a
//! semi-hacky interaction with mem-categorization. In particular,
//! mem-categorization will query the current borrow kind as it
//! categorizes, and we'll return the *current* value, but this may get
//! adjusted later. Therefore, in this module, we generally ignore the
//! borrow kind (and derived mutabilities) that are returned from
//! mem-categorization, since they may be inaccurate. (Another option
//! would be to use a unification scheme, where instead of returning a
//! concrete borrow kind like `ty::ImmBorrow`, we return a
//! `ty::InferBorrow(upvar_id)` or something like that, but this would
//! then mean that all later passes would have to check for these figments
//! and report an error, and it just seems like more mess in the end.)

use super::FnCtxt;

use middle::expr_use_visitor as euv;
use middle::mem_categorization as mc;
use middle::mem_categorization::Categorization;
use rustc::hir;
use rustc::hir::def_id::DefId;
use rustc::hir::def_id::LocalDefId;
use rustc::hir::intravisit::{self, NestedVisitorMap, Visitor};
use rustc::infer::UpvarRegion;
use rustc::ty::{self, Ty, TyCtxt, UpvarSubsts};
use syntax::ast;
use syntax_pos::Span;

impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
    pub fn closure_analyze(&self, body: &'gcx hir::Body) {
        InferBorrowKindVisitor { fcx: self }.visit_body(body);

        // it's our job to process these.
        assert!(self.deferred_call_resolutions.borrow().is_empty());
    }
}

struct InferBorrowKindVisitor<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> {
    fcx: &'a FnCtxt<'a, 'gcx, 'tcx>,
}

impl<'a, 'gcx, 'tcx> Visitor<'gcx> for InferBorrowKindVisitor<'a, 'gcx, 'tcx> {
    fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'gcx> {
        NestedVisitorMap::None
    }

    fn visit_expr(&mut self, expr: &'gcx hir::Expr) {
        if let hir::ExprKind::Closure(cc, _, body_id, _, _) = expr.node {
            let body = self.fcx.tcx.hir().body(body_id);
            self.visit_body(body);
            self.fcx
                .analyze_closure(expr.id, expr.hir_id, expr.span, body, cc);
        }

        intravisit::walk_expr(self, expr);
    }
}

impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
    fn analyze_closure(
        &self,
        closure_node_id: ast::NodeId,
        closure_hir_id: hir::HirId,
        span: Span,
        body: &hir::Body,
        capture_clause: hir::CaptureClause,
    ) {
        /*!
         * Analysis starting point.
         */

        debug!(
            "analyze_closure(id={:?}, body.id={:?})",
            closure_node_id,
            body.id()
        );

        // Extract the type of the closure.
        let (closure_def_id, substs) = match self.node_ty(closure_hir_id).sty {
            ty::Closure(def_id, substs) => (def_id, UpvarSubsts::Closure(substs)),
            ty::Generator(def_id, substs, _) => (def_id, UpvarSubsts::Generator(substs)),
            ty::Error => {
                // #51714: skip analysis when we have already encountered type errors
                return;
            }
            ref t => {
                span_bug!(
                    span,
                    "type of closure expr {:?} is not a closure {:?}",
                    closure_node_id,
                    t
                );
            }
        };

        let infer_kind = if let UpvarSubsts::Closure(closure_substs) = substs {
            if self.closure_kind(closure_def_id, closure_substs).is_none() {
                Some(closure_substs)
            } else {
                None
            }
        } else {
            None
        };

        self.tcx.with_freevars(closure_node_id, |freevars| {
            for freevar in freevars {
                let upvar_id = ty::UpvarId {
                    var_path: ty::UpvarPath {
                        hir_id : self.tcx.hir().node_to_hir_id(freevar.var_id()),
                    },
                    closure_expr_id: LocalDefId::from_def_id(closure_def_id),
                };
                debug!("seed upvar_id {:?}", upvar_id);

                let capture_kind = match capture_clause {
                    hir::CaptureByValue => ty::UpvarCapture::ByValue,
                    hir::CaptureByRef => {
                        let origin = UpvarRegion(upvar_id, span);
                        let freevar_region = self.next_region_var(origin);
                        let upvar_borrow = ty::UpvarBorrow {
                            kind: ty::ImmBorrow,
                            region: freevar_region,
                        };
                        ty::UpvarCapture::ByRef(upvar_borrow)
                    }
                };

                self.tables
                    .borrow_mut()
                    .upvar_capture_map
                    .insert(upvar_id, capture_kind);
            }
        });

        let body_owner_def_id = self.tcx.hir().body_owner_def_id(body.id());
        let region_scope_tree = &self.tcx.region_scope_tree(body_owner_def_id);
        let mut delegate = InferBorrowKind {
            fcx: self,
            closure_def_id: closure_def_id,
            current_closure_kind: ty::ClosureKind::LATTICE_BOTTOM,
            current_origin: None,
            adjust_upvar_captures: ty::UpvarCaptureMap::default(),
        };
        euv::ExprUseVisitor::with_infer(
            &mut delegate,
            &self.infcx,
            self.param_env,
            region_scope_tree,
            &self.tables.borrow(),
        ).consume_body(body);

        if let Some(closure_substs) = infer_kind {
            // Unify the (as yet unbound) type variable in the closure
            // substs with the kind we inferred.
            let inferred_kind = delegate.current_closure_kind;
            let closure_kind_ty = closure_substs.closure_kind_ty(closure_def_id, self.tcx);
            self.demand_eqtype(span, inferred_kind.to_ty(self.tcx), closure_kind_ty);

            // If we have an origin, store it.
            if let Some(origin) = delegate.current_origin {
                self.tables
                    .borrow_mut()
                    .closure_kind_origins_mut()
                    .insert(closure_hir_id, origin);
            }
        }

        self.tables
            .borrow_mut()
            .upvar_capture_map
            .extend(delegate.adjust_upvar_captures);

        // Now that we've analyzed the closure, we know how each
        // variable is borrowed, and we know what traits the closure
        // implements (Fn vs FnMut etc). We now have some updates to do
        // with that information.
        //
        // Note that no closure type C may have an upvar of type C
        // (though it may reference itself via a trait object). This
        // results from the desugaring of closures to a struct like
        // `Foo<..., UV0...UVn>`. If one of those upvars referenced
        // C, then the type would have infinite size (and the
        // inference algorithm will reject it).

        // Equate the type variables for the upvars with the actual types.
        let final_upvar_tys = self.final_upvar_tys(closure_node_id);
        debug!(
            "analyze_closure: id={:?} substs={:?} final_upvar_tys={:?}",
            closure_node_id, substs, final_upvar_tys
        );
        for (upvar_ty, final_upvar_ty) in substs
            .upvar_tys(closure_def_id, self.tcx)
            .zip(final_upvar_tys)
        {
            self.demand_suptype(span, upvar_ty, final_upvar_ty);
        }

        // If we are also inferred the closure kind here,
        // process any deferred resolutions.
        let deferred_call_resolutions = self.remove_deferred_call_resolutions(closure_def_id);
        for deferred_call_resolution in deferred_call_resolutions {
            deferred_call_resolution.resolve(self);
        }
    }

    // Returns a list of `ClosureUpvar`s for each upvar.
    fn final_upvar_tys(&self, closure_id: ast::NodeId) -> Vec<Ty<'tcx>> {
        // Presently an unboxed closure type cannot "escape" out of a
        // function, so we will only encounter ones that originated in the
        // local crate or were inlined into it along with some function.
        // This may change if abstract return types of some sort are
        // implemented.
        let tcx = self.tcx;
        let closure_def_index = tcx.hir().local_def_id(closure_id);

        tcx.with_freevars(closure_id, |freevars| {
            freevars
                .iter()
                .map(|freevar| {
                    let var_node_id = freevar.var_id();
                    let var_hir_id = tcx.hir().node_to_hir_id(var_node_id);
                    let freevar_ty = self.node_ty(var_hir_id);
                    let upvar_id = ty::UpvarId {
                        var_path: ty::UpvarPath {
                            hir_id: var_hir_id,
                        },
                        closure_expr_id: LocalDefId::from_def_id(closure_def_index),
                    };
                    let capture = self.tables.borrow().upvar_capture(upvar_id);

                    debug!(
                        "var_id={:?} freevar_ty={:?} capture={:?}",
                        var_node_id, freevar_ty, capture
                    );

                    match capture {
                        ty::UpvarCapture::ByValue => freevar_ty,
                        ty::UpvarCapture::ByRef(borrow) => tcx.mk_ref(
                            borrow.region,
                            ty::TypeAndMut {
                                ty: freevar_ty,
                                mutbl: borrow.kind.to_mutbl_lossy(),
                            },
                        ),
                    }
                }).collect()
        })
    }
}

struct InferBorrowKind<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> {
    fcx: &'a FnCtxt<'a, 'gcx, 'tcx>,

    // The def-id of the closure whose kind and upvar accesses are being inferred.
    closure_def_id: DefId,

    // The kind that we have inferred that the current closure
    // requires. Note that we *always* infer a minimal kind, even if
    // we don't always *use* that in the final result (i.e., sometimes
    // we've taken the closure kind from the expectations instead, and
    // for generators we don't even implement the closure traits
    // really).
    current_closure_kind: ty::ClosureKind,

    // If we modified `current_closure_kind`, this field contains a `Some()` with the
    // variable access that caused us to do so.
    current_origin: Option<(Span, ast::Name)>,

    // For each upvar that we access, we track the minimal kind of
    // access we need (ref, ref mut, move, etc).
    adjust_upvar_captures: ty::UpvarCaptureMap<'tcx>,
}

impl<'a, 'gcx, 'tcx> InferBorrowKind<'a, 'gcx, 'tcx> {
    fn adjust_upvar_borrow_kind_for_consume(
        &mut self,
        cmt: &mc::cmt_<'tcx>,
        mode: euv::ConsumeMode,
    ) {
        debug!(
            "adjust_upvar_borrow_kind_for_consume(cmt={:?}, mode={:?})",
            cmt, mode
        );

        // we only care about moves
        match mode {
            euv::Copy => {
                return;
            }
            euv::Move(_) => {}
        }

        let tcx = self.fcx.tcx;

        // watch out for a move of the deref of a borrowed pointer;
        // for that to be legal, the upvar would have to be borrowed
        // by value instead
        let guarantor = cmt.guarantor();
        debug!(
            "adjust_upvar_borrow_kind_for_consume: guarantor={:?}",
            guarantor
        );
        debug!(
            "adjust_upvar_borrow_kind_for_consume: guarantor.cat={:?}",
            guarantor.cat
        );
        if let Categorization::Deref(_, mc::BorrowedPtr(..)) = guarantor.cat {
            debug!(
                "adjust_upvar_borrow_kind_for_consume: found deref with note {:?}",
                cmt.note
            );
            match guarantor.note {
                mc::NoteUpvarRef(upvar_id) => {
                    debug!(
                        "adjust_upvar_borrow_kind_for_consume: \
                         setting upvar_id={:?} to by value",
                        upvar_id
                    );

                    // to move out of an upvar, this must be a FnOnce closure
                    self.adjust_closure_kind(
                        upvar_id.closure_expr_id,
                        ty::ClosureKind::FnOnce,
                        guarantor.span,
                        var_name(tcx, upvar_id.var_path.hir_id),
                    );

                    self.adjust_upvar_captures
                        .insert(upvar_id, ty::UpvarCapture::ByValue);
                }
                mc::NoteClosureEnv(upvar_id) => {
                    // we get just a closureenv ref if this is a
                    // `move` closure, or if the upvar has already
                    // been inferred to by-value. In any case, we
                    // must still adjust the kind of the closure
                    // to be a FnOnce closure to permit moves out
                    // of the environment.
                    self.adjust_closure_kind(
                        upvar_id.closure_expr_id,
                        ty::ClosureKind::FnOnce,
                        guarantor.span,
                        var_name(tcx, upvar_id.var_path.hir_id),
                    );
                }
                mc::NoteIndex | mc::NoteNone => {}
            }
        }
    }

    /// Indicates that `cmt` is being directly mutated (e.g., assigned
    /// to). If cmt contains any by-ref upvars, this implies that
    /// those upvars must be borrowed using an `&mut` borrow.
    fn adjust_upvar_borrow_kind_for_mut(&mut self, cmt: &mc::cmt_<'tcx>) {
        debug!("adjust_upvar_borrow_kind_for_mut(cmt={:?})", cmt);

        match cmt.cat.clone() {
            Categorization::Deref(base, mc::Unique)
            | Categorization::Interior(base, _)
            | Categorization::Downcast(base, _) => {
                // Interior or owned data is mutable if base is
                // mutable, so iterate to the base.
                self.adjust_upvar_borrow_kind_for_mut(&base);
            }

            Categorization::Deref(base, mc::BorrowedPtr(..)) => {
                if !self.try_adjust_upvar_deref(cmt, ty::MutBorrow) {
                    // assignment to deref of an `&mut`
                    // borrowed pointer implies that the
                    // pointer itself must be unique, but not
                    // necessarily *mutable*
                    self.adjust_upvar_borrow_kind_for_unique(&base);
                }
            }

            Categorization::Deref(_, mc::UnsafePtr(..))
            | Categorization::StaticItem
            | Categorization::ThreadLocal(..)
            | Categorization::Rvalue(..)
            | Categorization::Local(_)
            | Categorization::Upvar(..) => {
                return;
            }
        }
    }

    fn adjust_upvar_borrow_kind_for_unique(&mut self, cmt: &mc::cmt_<'tcx>) {
        debug!("adjust_upvar_borrow_kind_for_unique(cmt={:?})", cmt);

        match cmt.cat.clone() {
            Categorization::Deref(base, mc::Unique)
            | Categorization::Interior(base, _)
            | Categorization::Downcast(base, _) => {
                // Interior or owned data is unique if base is
                // unique.
                self.adjust_upvar_borrow_kind_for_unique(&base);
            }

            Categorization::Deref(base, mc::BorrowedPtr(..)) => {
                if !self.try_adjust_upvar_deref(cmt, ty::UniqueImmBorrow) {
                    // for a borrowed pointer to be unique, its
                    // base must be unique
                    self.adjust_upvar_borrow_kind_for_unique(&base);
                }
            }

            Categorization::Deref(_, mc::UnsafePtr(..))
            | Categorization::StaticItem
            | Categorization::ThreadLocal(..)
            | Categorization::Rvalue(..)
            | Categorization::Local(_)
            | Categorization::Upvar(..) => {}
        }
    }

    fn try_adjust_upvar_deref(
        &mut self,
        cmt: &mc::cmt_<'tcx>,
        borrow_kind: ty::BorrowKind,
    ) -> bool {
        assert!(match borrow_kind {
            ty::MutBorrow => true,
            ty::UniqueImmBorrow => true,

            // imm borrows never require adjusting any kinds, so we don't wind up here
            ty::ImmBorrow => false,
        });

        let tcx = self.fcx.tcx;

        match cmt.note {
            mc::NoteUpvarRef(upvar_id) => {
                // if this is an implicit deref of an
                // upvar, then we need to modify the
                // borrow_kind of the upvar to make sure it
                // is inferred to mutable if necessary
                self.adjust_upvar_borrow_kind(upvar_id, borrow_kind);

                // also need to be in an FnMut closure since this is not an ImmBorrow
                self.adjust_closure_kind(
                    upvar_id.closure_expr_id,
                    ty::ClosureKind::FnMut,
                    cmt.span,
                    var_name(tcx, upvar_id.var_path.hir_id),
                );

                true
            }
            mc::NoteClosureEnv(upvar_id) => {
                // this kind of deref occurs in a `move` closure, or
                // for a by-value upvar; in either case, to mutate an
                // upvar, we need to be an FnMut closure
                self.adjust_closure_kind(
                    upvar_id.closure_expr_id,
                    ty::ClosureKind::FnMut,
                    cmt.span,
                    var_name(tcx, upvar_id.var_path.hir_id),
                );

                true
            }
            mc::NoteIndex | mc::NoteNone => false,
        }
    }

    /// We infer the borrow_kind with which to borrow upvars in a stack closure.
    /// The borrow_kind basically follows a lattice of `imm < unique-imm < mut`,
    /// moving from left to right as needed (but never right to left).
    /// Here the argument `mutbl` is the borrow_kind that is required by
    /// some particular use.
    fn adjust_upvar_borrow_kind(&mut self, upvar_id: ty::UpvarId, kind: ty::BorrowKind) {
        let upvar_capture = self
            .adjust_upvar_captures
            .get(&upvar_id)
            .cloned()
            .unwrap_or_else(|| self.fcx.tables.borrow().upvar_capture(upvar_id));
        debug!(
            "adjust_upvar_borrow_kind(upvar_id={:?}, upvar_capture={:?}, kind={:?})",
            upvar_id, upvar_capture, kind
        );

        match upvar_capture {
            ty::UpvarCapture::ByValue => {
                // Upvar is already by-value, the strongest criteria.
            }
            ty::UpvarCapture::ByRef(mut upvar_borrow) => {
                match (upvar_borrow.kind, kind) {
                    // Take RHS:
                    (ty::ImmBorrow, ty::UniqueImmBorrow)
                    | (ty::ImmBorrow, ty::MutBorrow)
                    | (ty::UniqueImmBorrow, ty::MutBorrow) => {
                        upvar_borrow.kind = kind;
                        self.adjust_upvar_captures
                            .insert(upvar_id, ty::UpvarCapture::ByRef(upvar_borrow));
                    }
                    // Take LHS:
                    (ty::ImmBorrow, ty::ImmBorrow)
                    | (ty::UniqueImmBorrow, ty::ImmBorrow)
                    | (ty::UniqueImmBorrow, ty::UniqueImmBorrow)
                    | (ty::MutBorrow, _) => {}
                }
            }
        }
    }

    fn adjust_closure_kind(
        &mut self,
        closure_id: LocalDefId,
        new_kind: ty::ClosureKind,
        upvar_span: Span,
        var_name: ast::Name,
    ) {
        debug!(
            "adjust_closure_kind(closure_id={:?}, new_kind={:?}, upvar_span={:?}, var_name={})",
            closure_id, new_kind, upvar_span, var_name
        );

        // Is this the closure whose kind is currently being inferred?
        if closure_id.to_def_id() != self.closure_def_id {
            debug!("adjust_closure_kind: not current closure");
            return;
        }

        // closures start out as `Fn`.
        let existing_kind = self.current_closure_kind;

        debug!(
            "adjust_closure_kind: closure_id={:?}, existing_kind={:?}, new_kind={:?}",
            closure_id, existing_kind, new_kind
        );

        match (existing_kind, new_kind) {
            (ty::ClosureKind::Fn, ty::ClosureKind::Fn)
            | (ty::ClosureKind::FnMut, ty::ClosureKind::Fn)
            | (ty::ClosureKind::FnMut, ty::ClosureKind::FnMut)
            | (ty::ClosureKind::FnOnce, _) => {
                // no change needed
            }

            (ty::ClosureKind::Fn, ty::ClosureKind::FnMut)
            | (ty::ClosureKind::Fn, ty::ClosureKind::FnOnce)
            | (ty::ClosureKind::FnMut, ty::ClosureKind::FnOnce) => {
                // new kind is stronger than the old kind
                self.current_closure_kind = new_kind;
                self.current_origin = Some((upvar_span, var_name));
            }
        }
    }
}

impl<'a, 'gcx, 'tcx> euv::Delegate<'tcx> for InferBorrowKind<'a, 'gcx, 'tcx> {
    fn consume(
        &mut self,
        _consume_id: ast::NodeId,
        _consume_span: Span,
        cmt: &mc::cmt_<'tcx>,
        mode: euv::ConsumeMode,
    ) {
        debug!("consume(cmt={:?},mode={:?})", cmt, mode);
        self.adjust_upvar_borrow_kind_for_consume(cmt, mode);
    }

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

    fn consume_pat(
        &mut self,
        _consume_pat: &hir::Pat,
        cmt: &mc::cmt_<'tcx>,
        mode: euv::ConsumeMode,
    ) {
        debug!("consume_pat(cmt={:?},mode={:?})", cmt, mode);
        self.adjust_upvar_borrow_kind_for_consume(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={:?}, bk={:?})",
            borrow_id, cmt, bk
        );

        match bk {
            ty::ImmBorrow => {}
            ty::UniqueImmBorrow => {
                self.adjust_upvar_borrow_kind_for_unique(cmt);
            }
            ty::MutBorrow => {
                self.adjust_upvar_borrow_kind_for_mut(cmt);
            }
        }
    }

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

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

        self.adjust_upvar_borrow_kind_for_mut(assignee_cmt);
    }
}

fn var_name(tcx: TyCtxt, var_hir_id: hir::HirId) -> ast::Name {
    let var_node_id = tcx.hir().hir_to_node_id(var_hir_id);
    tcx.hir().name(var_node_id)
}
