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

use rustc::ty::{self, TyCtxt};
use rustc::session::config::BorrowckMode;
use rustc_errors::{DiagnosticBuilder, DiagnosticId};
use syntax_pos::{MultiSpan, Span};

use std::fmt;

#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Origin { Ast, Mir }

impl fmt::Display for Origin {
    fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
        // If the user passed `-Z borrowck=compare`, then include
        // origin info as part of the error report,
        // otherwise
        let display_origin = ty::tls::with_opt(|opt_tcx| {
            if let Some(tcx) = opt_tcx {
                tcx.sess.opts.borrowck_mode == BorrowckMode::Compare
            } else {
                false
            }
        });
        if display_origin {
            match *self {
                Origin::Mir => write!(w, " (Mir)"),
                Origin::Ast => write!(w, " (Ast)"),
            }
        } else {
            // Print no origin info
            Ok(())
        }
    }
}

impl Origin {
    /// Whether we should emit errors for the origin in the given mode
    pub fn should_emit_errors(self, mode: BorrowckMode) -> bool {
        match self {
            Origin::Ast => mode.use_ast(),
            Origin::Mir => mode.use_mir(),
        }
    }
}

pub trait BorrowckErrors {
    fn struct_span_err_with_code<'a, S: Into<MultiSpan>>(&'a self,
                                                         sp: S,
                                                         msg: &str,
                                                         code: DiagnosticId)
                                                         -> DiagnosticBuilder<'a>;

    fn struct_span_err<'a, S: Into<MultiSpan>>(&'a self,
                                               sp: S,
                                               msg: &str)
                                               -> DiagnosticBuilder<'a>;

    /// Cancels the given error if we shouldn't emit errors for a given
    /// origin in the current mode.
    ///
    /// Always make sure that the error gets passed through this function
    /// before you return it.
    fn cancel_if_wrong_origin<'a>(&'a self,
                                diag: DiagnosticBuilder<'a>,
                                o: Origin)
                                -> DiagnosticBuilder<'a>;

    fn cannot_move_when_borrowed(&self, span: Span, desc: &str, o: Origin)
                                 -> DiagnosticBuilder<'_>
    {
        let err = struct_span_err!(self, span, E0505,
                                   "cannot move out of `{}` because it is borrowed{OGN}",
                                   desc, OGN=o);
        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_use_when_mutably_borrowed(&self,
                                        span: Span,
                                        desc: &str,
                                        borrow_span: Span,
                                        borrow_desc: &str,
                                        o: Origin)
                                        -> DiagnosticBuilder<'_>
    {
        let mut err = struct_span_err!(self, span, E0503,
                         "cannot use `{}` because it was mutably borrowed{OGN}",
                         desc, OGN=o);

        err.span_label(borrow_span, format!("borrow of `{}` occurs here", borrow_desc));
        err.span_label(span, format!("use of borrowed `{}`", borrow_desc));

        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_act_on_uninitialized_variable(&self,
                                            span: Span,
                                            verb: &str,
                                            desc: &str,
                                            o: Origin)
                                            -> DiagnosticBuilder
    {
        let err = struct_span_err!(self, span, E0381,
                                   "{} of possibly uninitialized variable: `{}`{OGN}",
                                   verb, desc, OGN=o);
        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_mutably_borrow_multiply(&self,
                                      new_loan_span: Span,
                                      desc: &str,
                                      opt_via: &str,
                                      old_loan_span: Span,
                                      old_opt_via: &str,
                                      old_load_end_span: Option<Span>,
                                      o: Origin)
                                      -> DiagnosticBuilder
    {
        let mut err = struct_span_err!(self, new_loan_span, E0499,
                         "cannot borrow `{}`{} as mutable more than once at a time{OGN}",
                         desc, opt_via, OGN=o);
        if old_loan_span == new_loan_span {
            // Both borrows are happening in the same place
            // Meaning the borrow is occurring in a loop
            err.span_label(new_loan_span,
                           format!("mutable borrow starts here in previous \
                                    iteration of loop{}", opt_via));
            if let Some(old_load_end_span) = old_load_end_span {
                err.span_label(old_load_end_span, "mutable borrow ends here");
            }
        } else {
            err.span_label(old_loan_span,
                           format!("first mutable borrow occurs here{}", old_opt_via));
            err.span_label(new_loan_span,
                           format!("second mutable borrow occurs here{}", opt_via));
            if let Some(old_load_end_span) = old_load_end_span {
                err.span_label(old_load_end_span, "first borrow ends here");
            }
        }
        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_uniquely_borrow_by_two_closures(&self,
                                              new_loan_span: Span,
                                              desc: &str,
                                              old_loan_span: Span,
                                              old_load_end_span: Option<Span>,
                                              o: Origin)
                                              -> DiagnosticBuilder
    {
        let mut err = struct_span_err!(self, new_loan_span, E0524,
                         "two closures require unique access to `{}` at the same time{OGN}",
                         desc, OGN=o);
        err.span_label(
            old_loan_span,
            "first closure is constructed here");
        err.span_label(
            new_loan_span,
            "second closure is constructed here");
        if let Some(old_load_end_span) = old_load_end_span {
            err.span_label(
                old_load_end_span,
                "borrow from first closure ends here");
        }
        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_uniquely_borrow_by_one_closure(&self,
                                             new_loan_span: Span,
                                             desc_new: &str,
                                             opt_via: &str,
                                             old_loan_span: Span,
                                             noun_old: &str,
                                             old_opt_via: &str,
                                             previous_end_span: Option<Span>,
                                             o: Origin)
                                             -> DiagnosticBuilder
    {
        let mut err = struct_span_err!(self, new_loan_span, E0500,
                         "closure requires unique access to `{}` but {} is already borrowed{}{OGN}",
                         desc_new, noun_old, old_opt_via, OGN=o);
        err.span_label(new_loan_span,
                       format!("closure construction occurs here{}", opt_via));
        err.span_label(old_loan_span,
                       format!("borrow occurs here{}", old_opt_via));
        if let Some(previous_end_span) = previous_end_span {
            err.span_label(previous_end_span, "borrow ends here");
        }
        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_reborrow_already_uniquely_borrowed(&self,
                                                 new_loan_span: Span,
                                                 desc_new: &str,
                                                 opt_via: &str,
                                                 kind_new: &str,
                                                 old_loan_span: Span,
                                                 old_opt_via: &str,
                                                 previous_end_span: Option<Span>,
                                                 o: Origin)
                                                 -> DiagnosticBuilder
    {
        let mut err = struct_span_err!(self, new_loan_span, E0501,
                         "cannot borrow `{}`{} as {} because previous closure \
                          requires unique access{OGN}",
                         desc_new, opt_via, kind_new, OGN=o);
        err.span_label(new_loan_span,
                       format!("borrow occurs here{}", opt_via));
        err.span_label(old_loan_span,
                       format!("closure construction occurs here{}", old_opt_via));
        if let Some(previous_end_span) = previous_end_span {
            err.span_label(previous_end_span, "borrow from closure ends here");
        }
        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_reborrow_already_borrowed(&self,
                                        span: Span,
                                        desc_new: &str,
                                        msg_new: &str,
                                        kind_new: &str,
                                        old_span: Span,
                                        noun_old: &str,
                                        kind_old: &str,
                                        msg_old: &str,
                                        old_load_end_span: Option<Span>,
                                        o: Origin)
                                        -> DiagnosticBuilder
    {
        let mut err = struct_span_err!(self, span, E0502,
                         "cannot borrow `{}`{} as {} because {} is also borrowed as {}{}{OGN}",
                         desc_new, msg_new, kind_new, noun_old, kind_old, msg_old, OGN=o);
        err.span_label(span, format!("{} borrow occurs here{}", kind_new, msg_new));
        err.span_label(old_span, format!("{} borrow occurs here{}", kind_old, msg_old));
        if let Some(old_load_end_span) = old_load_end_span {
            err.span_label(old_load_end_span, format!("{} borrow ends here", kind_old));
        }
        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_assign_to_borrowed(&self, span: Span, borrow_span: Span, desc: &str, o: Origin)
                                 -> DiagnosticBuilder
    {
        let mut err = struct_span_err!(self, span, E0506,
                         "cannot assign to `{}` because it is borrowed{OGN}",
                         desc, OGN=o);

        err.span_label(borrow_span, format!("borrow of `{}` occurs here", desc));
        err.span_label(span, format!("assignment to borrowed `{}` occurs here", desc));

        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_move_into_closure(&self, span: Span, desc: &str, o: Origin)
                                -> DiagnosticBuilder
    {
        let err = struct_span_err!(self, span, E0504,
                                   "cannot move `{}` into closure because it is borrowed{OGN}",
                                   desc, OGN=o);

        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_reassign_immutable(&self, span: Span, desc: &str, is_arg: bool, o: Origin)
                                 -> DiagnosticBuilder
    {
        let msg = if is_arg {
            "to immutable argument"
        } else {
            "twice to immutable variable"
        };
        let err = struct_span_err!(self, span, E0384,
                                   "cannot assign {} `{}`{OGN}",
                                   msg, desc, OGN=o);

        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_assign(&self, span: Span, desc: &str, o: Origin) -> DiagnosticBuilder
    {
        let err = struct_span_err!(self, span, E0594,
                                  "cannot assign to {}{OGN}",
                                  desc, OGN=o);
        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_assign_static(&self, span: Span, desc: &str, o: Origin)
                            -> DiagnosticBuilder
    {
        self.cannot_assign(span, &format!("immutable static item `{}`", desc), o)
    }

    fn cannot_move_out_of(&self, move_from_span: Span, move_from_desc: &str, o: Origin)
                          -> DiagnosticBuilder
    {
        let mut err = struct_span_err!(self, move_from_span, E0507,
                                       "cannot move out of {}{OGN}",
                                       move_from_desc, OGN=o);
        err.span_label(
            move_from_span,
            format!("cannot move out of {}", move_from_desc));

        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_move_out_of_interior_noncopy(&self,
                                           move_from_span: Span,
                                           ty: ty::Ty,
                                           is_index: bool,
                                           o: Origin)
                                           -> DiagnosticBuilder
    {
        let type_name = match (&ty.sty, is_index) {
            (&ty::TyArray(_, _), true) => "array",
            (&ty::TySlice(_),    _) => "slice",
            _ => span_bug!(move_from_span, "this path should not cause illegal move"),
        };
        let mut err = struct_span_err!(self, move_from_span, E0508,
                                       "cannot move out of type `{}`, \
                                        a non-copy {}{OGN}",
                                       ty, type_name, OGN=o);
        err.span_label(move_from_span, "cannot move out of here");

        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_move_out_of_interior_of_drop(&self,
                                           move_from_span: Span,
                                           container_ty: ty::Ty,
                                           o: Origin)
                                           -> DiagnosticBuilder
    {
        let mut err = struct_span_err!(self, move_from_span, E0509,
                                       "cannot move out of type `{}`, \
                                        which implements the `Drop` trait{OGN}",
                                       container_ty, OGN=o);
        err.span_label(move_from_span, "cannot move out of here");

        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_act_on_moved_value(&self,
                                 use_span: Span,
                                 verb: &str,
                                 optional_adverb_for_moved: &str,
                                 moved_path: &str,
                                 o: Origin)
                                 -> DiagnosticBuilder
    {
        let err = struct_span_err!(self, use_span, E0382,
                                   "{} of {}moved value: `{}`{OGN}",
                                   verb, optional_adverb_for_moved, moved_path, OGN=o);

        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_partially_reinit_an_uninit_struct(&self,
                                                span: Span,
                                                uninit_path: &str,
                                                o: Origin)
                                                -> DiagnosticBuilder
    {
        let err = struct_span_err!(self,
                                   span,
                                   E0383,
                                   "partial reinitialization of uninitialized structure `{}`{OGN}",
                                   uninit_path, OGN=o);

        self.cancel_if_wrong_origin(err, o)
    }

    fn closure_cannot_assign_to_borrowed(&self,
                                         span: Span,
                                         descr: &str,
                                         o: Origin)
                                         -> DiagnosticBuilder
    {
        let err = struct_span_err!(self, span, E0595, "closure cannot assign to {}{OGN}",
                                   descr, OGN=o);

        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_borrow_path_as_mutable(&self,
                                     span: Span,
                                     path: &str,
                                     o: Origin)
                                     -> DiagnosticBuilder
    {
        let err = struct_span_err!(self, span, E0596, "cannot borrow {} as mutable{OGN}",
                                   path, OGN=o);

        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_borrow_across_generator_yield(&self,
                                            span: Span,
                                            yield_span: Span,
                                            o: Origin)
                                            -> DiagnosticBuilder
    {
        let mut err = struct_span_err!(self,
                                       span,
                                       E0626,
                                       "borrow may still be in use when generator yields{OGN}",
                                       OGN=o);
        err.span_label(yield_span, "possible yield occurs here");

        self.cancel_if_wrong_origin(err, o)
    }

    fn path_does_not_live_long_enough(&self,
                                      span: Span,
                                      path: &str,
                                      o: Origin)
                                      -> DiagnosticBuilder
    {
        let err = struct_span_err!(self, span, E0597, "{} does not live long enough{OGN}",
                                   path, OGN=o);

        self.cancel_if_wrong_origin(err, o)
    }

    fn lifetime_too_short_for_reborrow(&self,
                                       span: Span,
                                       path: &str,
                                       o: Origin)
                                       -> DiagnosticBuilder
    {
        let err = struct_span_err!(self, span, E0598,
                                   "lifetime of {} is too short to guarantee \
                                    its contents can be safely reborrowed{OGN}",
                                   path, OGN=o);

        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_act_on_capture_in_sharable_fn(&self,
                                            span: Span,
                                            bad_thing: &str,
                                            help: (Span, &str),
                                            o: Origin)
                                            -> DiagnosticBuilder
    {
        let (help_span, help_msg) = help;
        let mut err = struct_span_err!(self, span, E0387,
                                       "{} in a captured outer variable in an `Fn` closure{OGN}",
                                       bad_thing, OGN=o);
        err.span_help(help_span, help_msg);

        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_assign_into_immutable_reference(&self,
                                              span: Span,
                                              bad_thing: &str,
                                              o: Origin)
                                              -> DiagnosticBuilder
    {
        let mut err = struct_span_err!(self, span, E0389, "{} in a `&` reference{OGN}",
                                       bad_thing, OGN=o);
        err.span_label(span, "assignment into an immutable reference");

        self.cancel_if_wrong_origin(err, o)
    }

    fn cannot_capture_in_long_lived_closure(&self,
                                            closure_span: Span,
                                            borrowed_path: &str,
                                            capture_span: Span,
                                            o: Origin)
                                            -> DiagnosticBuilder
    {
        let mut err = struct_span_err!(self, closure_span, E0373,
                                       "closure may outlive the current function, \
                                        but it borrows {}, \
                                        which is owned by the current function{OGN}",
                                       borrowed_path, OGN=o);
        err.span_label(capture_span, format!("{} is borrowed here", borrowed_path))
            .span_label(closure_span, format!("may outlive borrowed value {}", borrowed_path));

        self.cancel_if_wrong_origin(err, o)
    }
}

impl<'b, 'gcx, 'tcx> BorrowckErrors for TyCtxt<'b, 'gcx, 'tcx> {
    fn struct_span_err_with_code<'a, S: Into<MultiSpan>>(&'a self,
                                                         sp: S,
                                                         msg: &str,
                                                         code: DiagnosticId)
                                                         -> DiagnosticBuilder<'a>
    {
        self.sess.struct_span_err_with_code(sp, msg, code)
    }

    fn struct_span_err<'a, S: Into<MultiSpan>>(&'a self,
                                               sp: S,
                                               msg: &str)
                                               -> DiagnosticBuilder<'a>
    {
        self.sess.struct_span_err(sp, msg)
    }

    fn cancel_if_wrong_origin<'a>(&'a self,
                                mut diag: DiagnosticBuilder<'a>,
                                o: Origin)
                                -> DiagnosticBuilder<'a>
    {
        if !o.should_emit_errors(self.sess.borrowck_mode()) {
            self.sess.diagnostic().cancel(&mut diag);
        }
        diag
    }
}
