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

use rustc_errors::struct_span_err;
use rustc_hir as hir;
use rustc_hir::itemlikevisit::ItemLikeVisitor;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_middle::ty::{self, TyCtxt};
use rustc_trait_selection::traits;

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

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

impl ItemLikeVisitor<'v> for OrphanChecker<'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.hir_id);
        // "Trait" impl
        if let hir::ItemKind::Impl { generics, of_trait: Some(ref tr), self_ty, .. } = &item.kind {
            debug!(
                "coherence2::orphan check: trait impl {}",
                self.tcx.hir().node_to_string(item.hir_id)
            );
            let trait_ref = self.tcx.impl_trait_ref(def_id).unwrap();
            let trait_def_id = trait_ref.def_id;
            let sm = self.tcx.sess.source_map();
            let sp = sm.guess_head_span(item.span);
            match traits::orphan_check(self.tcx, def_id.to_def_id()) {
                Ok(()) => {}
                Err(traits::OrphanCheckErr::NonLocalInputType(tys)) => {
                    let mut err = struct_span_err!(
                        self.tcx.sess,
                        sp,
                        E0117,
                        "only traits defined in the current crate can be implemented for \
                         arbitrary types"
                    );
                    err.span_label(sp, "impl doesn't use only types from inside the current crate");
                    for (ty, is_target_ty) in &tys {
                        let mut ty = *ty;
                        self.tcx.infer_ctxt().enter(|infcx| {
                            // Remove the lifetimes unnecessary for this error.
                            ty = infcx.freshen(ty);
                        });
                        ty = match ty.kind {
                            // Remove the type arguments from the output, as they are not relevant.
                            // You can think of this as the reverse of `resolve_vars_if_possible`.
                            // That way if we had `Vec<MyType>`, we will properly attribute the
                            // problem to `Vec<T>` and avoid confusing the user if they were to see
                            // `MyType` in the error.
                            ty::Adt(def, _) => self.tcx.mk_adt(def, ty::List::empty()),
                            _ => ty,
                        };
                        let this = "this".to_string();
                        let (ty, postfix) = match &ty.kind {
                            ty::Slice(_) => (this, " because slices are always foreign"),
                            ty::Array(..) => (this, " because arrays are always foreign"),
                            ty::Tuple(..) => (this, " because tuples are always foreign"),
                            _ => (format!("`{}`", ty), ""),
                        };
                        let msg = format!("{} is not defined in the current crate{}", ty, postfix);
                        if *is_target_ty {
                            // Point at `D<A>` in `impl<A, B> for C<B> in D<A>`
                            err.span_label(self_ty.span, &msg);
                        } else {
                            // Point at `C<B>` in `impl<A, B> for C<B> in D<A>`
                            err.span_label(tr.path.span, &msg);
                        }
                    }
                    err.note("define and implement a trait or new type instead");
                    err.emit();
                    return;
                }
                Err(traits::OrphanCheckErr::UncoveredTy(param_ty, local_type)) => {
                    let mut sp = sp;
                    for param in generics.params {
                        if param.name.ident().to_string() == param_ty.to_string() {
                            sp = param.span;
                        }
                    }

                    match local_type {
                        Some(local_type) => {
                            struct_span_err!(
                                self.tcx.sess,
                                sp,
                                E0210,
                                "type parameter `{}` must be covered by another type \
                                when it appears before the first local type (`{}`)",
                                param_ty,
                                local_type
                            )
                            .span_label(
                                sp,
                                format!(
                                    "type parameter `{}` must be covered by another type \
                                when it appears before the first local type (`{}`)",
                                    param_ty, local_type
                                ),
                            )
                            .note(
                                "implementing a foreign trait is only possible if at \
                                    least one of the types for which is it implemented is local, \
                                    and no uncovered type parameters appear before that first \
                                    local type",
                            )
                            .note(
                                "in this case, 'before' refers to the following order: \
                                    `impl<..> ForeignTrait<T1, ..., Tn> for T0`, \
                                    where `T0` is the first and `Tn` is the last",
                            )
                            .emit();
                        }
                        None => {
                            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("implementing a foreign trait is only possible if at \
                                    least one of the types for which is it implemented is local"
                            ).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 orphan
            // 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.kind {
                    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.def_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.def_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<'_>) {}
}
