//! Orphan checker: every impl either implements a trait defined in this
//! crate or pertains to a type defined in this crate.

use rustc::traits;
use rustc::ty::{self, TyCtxt};
use rustc::hir::itemlikevisit::ItemLikeVisitor;
use rustc::hir;

pub fn check<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) {
    let mut orphan = OrphanChecker { tcx };
    tcx.hir().krate().visit_all_item_likes(&mut orphan);
}

struct OrphanChecker<'cx, 'tcx: 'cx> {
    tcx: TyCtxt<'cx, 'tcx, 'tcx>,
}

impl<'cx, 'tcx, 'v> ItemLikeVisitor<'v> for OrphanChecker<'cx, 'tcx> {
    /// Checks exactly one impl for orphan rules and other such
    /// restrictions.  In this fn, it can happen that multiple errors
    /// apply to a specific impl, so just return after reporting one
    /// to prevent inundating the user with a bunch of similar error
    /// reports.
    fn visit_item(&mut self, item: &hir::Item) {
        let def_id = self.tcx.hir().local_def_id(item.id);
        // "Trait" impl
        if let hir::ItemKind::Impl(.., Some(_), _, _) = item.node {
            debug!("coherence2::orphan check: trait impl {}",
                   self.tcx.hir().node_to_string(item.id));
            let trait_ref = self.tcx.impl_trait_ref(def_id).unwrap();
            let trait_def_id = trait_ref.def_id;
            let cm = self.tcx.sess.source_map();
            let sp = cm.def_span(item.span);
            match traits::orphan_check(self.tcx, def_id) {
                Ok(()) => {}
                Err(traits::OrphanCheckErr::NoLocalInputType) => {
                    struct_span_err!(self.tcx.sess,
                                     sp,
                                     E0117,
                                     "only traits defined in the current crate can be \
                                      implemented for arbitrary types")
                        .span_label(sp, "impl doesn't use types inside crate")
                        .note("the impl does not reference any types defined in this crate")
                        .note("define and implement a trait or new type instead")
                        .emit();
                    return;
                }
                Err(traits::OrphanCheckErr::UncoveredTy(param_ty)) => {
                    struct_span_err!(self.tcx.sess,
                                     sp,
                                     E0210,
                                     "type parameter `{}` must be used as the type parameter \
                                      for some local type (e.g., `MyStruct<{}>`)",
                                     param_ty,
                                     param_ty)
                        .span_label(sp,
                                    format!("type parameter `{}` must be used as the type \
                                             parameter for some local type", param_ty))
                        .note("only traits defined in the current crate can be implemented \
                               for a type parameter")
                        .emit();
                    return;
                }
            }

            // In addition to the above rules, we restrict impls of auto traits
            // so that they can only be implemented on nominal types, such as structs,
            // enums or foreign types. To see why this restriction exists, consider the
            // following example (#22978). Imagine that crate A defines an auto trait
            // `Foo` and a fn that operates on pairs of types:
            //
            // ```
            // // Crate A
            // auto trait Foo { }
            // fn two_foos<A:Foo,B:Foo>(..) {
            //     one_foo::<(A,B)>(..)
            // }
            // fn one_foo<T:Foo>(..) { .. }
            // ```
            //
            // This type-checks fine; in particular the fn
            // `two_foos` is able to conclude that `(A,B):Foo`
            // because `A:Foo` and `B:Foo`.
            //
            // Now imagine that crate B comes along and does the following:
            //
            // ```
            // struct A { }
            // struct B { }
            // impl Foo for A { }
            // impl Foo for B { }
            // impl !Send for (A, B) { }
            // ```
            //
            // This final impl is legal according to the orpan
            // rules, but it invalidates the reasoning from
            // `two_foos` above.
            debug!("trait_ref={:?} trait_def_id={:?} trait_is_auto={}",
                   trait_ref,
                   trait_def_id,
                   self.tcx.trait_is_auto(trait_def_id));
            if self.tcx.trait_is_auto(trait_def_id) &&
               !trait_def_id.is_local() {
                let self_ty = trait_ref.self_ty();
                let opt_self_def_id = match self_ty.sty {
                    ty::Adt(self_def, _) => Some(self_def.did),
                    ty::Foreign(did) => Some(did),
                    _ => None,
                };

                let msg = match opt_self_def_id {
                    // We only want to permit nominal types, but not *all* nominal types.
                    // They must be local to the current crate, so that people
                    // can't do `unsafe impl Send for Rc<SomethingLocal>` or
                    // `impl !Send for Box<SomethingLocalAndSend>`.
                    Some(self_def_id) => {
                        if self_def_id.is_local() {
                            None
                        } else {
                            Some((
                                format!("cross-crate traits with a default impl, like `{}`, \
                                         can only be implemented for a struct/enum type \
                                         defined in the current crate",
                                        self.tcx.item_path_str(trait_def_id)),
                                "can't implement cross-crate trait for type in another crate"
                            ))
                        }
                    }
                    _ => {
                        Some((format!("cross-crate traits with a default impl, like `{}`, can \
                                       only be implemented for a struct/enum type, not `{}`",
                                      self.tcx.item_path_str(trait_def_id),
                                      self_ty),
                              "can't implement cross-crate trait with a default impl for \
                               non-struct/enum type"))
                    }
                };

                if let Some((msg, label)) = msg {
                    struct_span_err!(self.tcx.sess, sp, E0321, "{}", msg)
                        .span_label(sp, label)
                        .emit();
                    return;
                }
            }
        }
    }

    fn visit_trait_item(&mut self, _trait_item: &hir::TraitItem) {
    }

    fn visit_impl_item(&mut self, _impl_item: &hir::ImplItem) {
    }
}
