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

use rustc_data_structures::fx::FxHashSet;
use rustc_errors::{struct_span_err, DelayDm};
use rustc_errors::{Diagnostic, ErrorGuaranteed};
use rustc_hir as hir;
use rustc_middle::ty::util::CheckRegions;
use rustc_middle::ty::GenericArgs;
use rustc_middle::ty::{
    self, AliasKind, ImplPolarity, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt,
    TypeVisitor,
};
use rustc_session::lint;
use rustc_span::def_id::{DefId, LocalDefId};
use rustc_span::Span;
use rustc_trait_selection::traits;
use std::ops::ControlFlow;

#[instrument(skip(tcx), level = "debug")]
pub(crate) fn orphan_check_impl(
    tcx: TyCtxt<'_>,
    impl_def_id: LocalDefId,
) -> Result<(), ErrorGuaranteed> {
    let trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap().instantiate_identity();
    trait_ref.error_reported()?;

    let ret = do_orphan_check_impl(tcx, trait_ref, impl_def_id);
    if tcx.trait_is_auto(trait_ref.def_id) {
        lint_auto_trait_impl(tcx, trait_ref, impl_def_id);
    }

    ret
}

fn do_orphan_check_impl<'tcx>(
    tcx: TyCtxt<'tcx>,
    trait_ref: ty::TraitRef<'tcx>,
    def_id: LocalDefId,
) -> Result<(), ErrorGuaranteed> {
    let trait_def_id = trait_ref.def_id;

    match traits::orphan_check(tcx, def_id.to_def_id()) {
        Ok(()) => {}
        Err(err) => {
            let item = tcx.hir().expect_item(def_id);
            let hir::ItemKind::Impl(impl_) = item.kind else {
                bug!("{:?} is not an impl: {:?}", def_id, item);
            };
            let tr = impl_.of_trait.as_ref().unwrap();
            let sp = tcx.def_span(def_id);

            emit_orphan_check_error(
                tcx,
                sp,
                item.span,
                tr.path.span,
                trait_ref,
                impl_.self_ty.span,
                &impl_.generics,
                err,
            )?
        }
    }

    // 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 !Foo 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,
        tcx.trait_is_auto(trait_def_id)
    );

    if tcx.trait_is_auto(trait_def_id) {
        let self_ty = trait_ref.self_ty();

        // If the impl is in the same crate as the auto-trait, almost anything
        // goes.
        //
        //     impl MyAuto for Rc<Something> {}  // okay
        //     impl<T> !MyAuto for *const T {}   // okay
        //     impl<T> MyAuto for T {}           // okay
        //
        // But there is one important exception: implementing for a trait object
        // is not allowed.
        //
        //     impl MyAuto for dyn Trait {}      // NOT OKAY
        //     impl<T: ?Sized> MyAuto for T {}   // NOT OKAY
        //
        // With this restriction, it's guaranteed that an auto-trait is
        // implemented for a trait object if and only if the auto-trait is one
        // of the trait object's trait bounds (or a supertrait of a bound). In
        // other words `dyn Trait + AutoTrait` always implements AutoTrait,
        // while `dyn Trait` never implements AutoTrait.
        //
        // This is necessary in order for autotrait bounds on methods of trait
        // objects to be sound.
        //
        //     auto trait AutoTrait {}
        //
        //     trait ObjectSafeTrait {
        //         fn f(&self) where Self: AutoTrait;
        //     }
        //
        // We can allow f to be called on `dyn ObjectSafeTrait + AutoTrait`.
        //
        // If we didn't deny `impl AutoTrait for dyn Trait`, it would be unsound
        // for the ObjectSafeTrait shown above to be object safe because someone
        // could take some type implementing ObjectSafeTrait but not AutoTrait,
        // unsize it to `dyn ObjectSafeTrait`, and call .f() which has no
        // concrete implementation (issue #50781).
        enum LocalImpl {
            Allow,
            Disallow { problematic_kind: &'static str },
        }

        // If the auto-trait is from a dependency, it must only be getting
        // implemented for a nominal type, and specifically one local to the
        // current crate.
        //
        //     impl<T> Sync for MyStruct<T> {}   // okay
        //
        //     impl Sync for Rc<MyStruct> {}     // NOT OKAY
        enum NonlocalImpl {
            Allow,
            DisallowBecauseNonlocal,
            DisallowOther,
        }

        // Exhaustive match considering that this logic is essential for
        // soundness.
        let (local_impl, nonlocal_impl) = match self_ty.kind() {
            // struct Struct<T>;
            // impl AutoTrait for Struct<Foo> {}
            ty::Adt(self_def, _) => (
                LocalImpl::Allow,
                if self_def.did().is_local() {
                    NonlocalImpl::Allow
                } else {
                    NonlocalImpl::DisallowBecauseNonlocal
                },
            ),

            // extern { type OpaqueType; }
            // impl AutoTrait for OpaqueType {}
            ty::Foreign(did) => (
                LocalImpl::Allow,
                if did.is_local() {
                    NonlocalImpl::Allow
                } else {
                    NonlocalImpl::DisallowBecauseNonlocal
                },
            ),

            // impl AutoTrait for dyn Trait {}
            ty::Dynamic(..) => (
                LocalImpl::Disallow { problematic_kind: "trait object" },
                NonlocalImpl::DisallowOther,
            ),

            // impl<T> AutoTrait for T {}
            // impl<T: ?Sized> AutoTrait for T {}
            ty::Param(..) => (
                if self_ty.is_sized(tcx, tcx.param_env(def_id)) {
                    LocalImpl::Allow
                } else {
                    LocalImpl::Disallow { problematic_kind: "generic type" }
                },
                NonlocalImpl::DisallowOther,
            ),

            ty::Alias(kind, _) => {
                let problematic_kind = match kind {
                    // trait Id { type This: ?Sized; }
                    // impl<T: ?Sized> Id for T {
                    //     type This = T;
                    // }
                    // impl<T: ?Sized> AutoTrait for <T as Id>::This {}
                    AliasKind::Projection => "associated type",
                    // type Foo = (impl Sized, bool)
                    // impl AutoTrait for Foo {}
                    AliasKind::Weak => "type alias",
                    // type Opaque = impl Trait;
                    // impl AutoTrait for Opaque {}
                    AliasKind::Opaque => "opaque type",
                    // ```
                    // struct S<T>(T);
                    // impl<T: ?Sized> S<T> {
                    //     type This = T;
                    // }
                    // impl<T: ?Sized> AutoTrait for S<T>::This {}
                    // ```
                    // FIXME(inherent_associated_types): The example code above currently leads to a cycle
                    AliasKind::Inherent => "associated type",
                };
                (LocalImpl::Disallow { problematic_kind }, NonlocalImpl::DisallowOther)
            }

            ty::Bool
            | ty::Char
            | ty::Int(..)
            | ty::Uint(..)
            | ty::Float(..)
            | ty::Str
            | ty::Array(..)
            | ty::Slice(..)
            | ty::RawPtr(..)
            | ty::Ref(..)
            | ty::FnDef(..)
            | ty::FnPtr(..)
            | ty::Never
            | ty::Tuple(..) => (LocalImpl::Allow, NonlocalImpl::DisallowOther),

            ty::Closure(..)
            | ty::Generator(..)
            | ty::GeneratorWitness(..)
            | ty::GeneratorWitnessMIR(..)
            | ty::Bound(..)
            | ty::Placeholder(..)
            | ty::Infer(..) => {
                let sp = tcx.def_span(def_id);
                span_bug!(sp, "weird self type for autotrait impl")
            }

            ty::Error(..) => (LocalImpl::Allow, NonlocalImpl::Allow),
        };

        if trait_def_id.is_local() {
            match local_impl {
                LocalImpl::Allow => {}
                LocalImpl::Disallow { problematic_kind } => {
                    let msg = format!(
                        "traits with a default impl, like `{trait}`, \
                                cannot be implemented for {problematic_kind} `{self_ty}`",
                        trait = tcx.def_path_str(trait_def_id),
                    );
                    let label = format!(
                        "a trait object implements `{trait}` if and only if `{trait}` \
                                is one of the trait object's trait bounds",
                        trait = tcx.def_path_str(trait_def_id),
                    );
                    let sp = tcx.def_span(def_id);
                    let reported =
                        struct_span_err!(tcx.sess, sp, E0321, "{}", msg).note(label).emit();
                    return Err(reported);
                }
            }
        } else {
            if let Some((msg, label)) = match nonlocal_impl {
                NonlocalImpl::Allow => None,
                NonlocalImpl::DisallowBecauseNonlocal => Some((
                    format!(
                        "cross-crate traits with a default impl, like `{}`, \
                                can only be implemented for a struct/enum type \
                                defined in the current crate",
                        tcx.def_path_str(trait_def_id)
                    ),
                    "can't implement cross-crate trait for type in another crate",
                )),
                NonlocalImpl::DisallowOther => Some((
                    format!(
                        "cross-crate traits with a default impl, like `{}`, can \
                                only be implemented for a struct/enum type, not `{}`",
                        tcx.def_path_str(trait_def_id),
                        self_ty
                    ),
                    "can't implement cross-crate trait with a default impl for \
                            non-struct/enum type",
                )),
            } {
                let sp = tcx.def_span(def_id);
                let reported =
                    struct_span_err!(tcx.sess, sp, E0321, "{}", msg).span_label(sp, label).emit();
                return Err(reported);
            }
        }
    }

    Ok(())
}

fn emit_orphan_check_error<'tcx>(
    tcx: TyCtxt<'tcx>,
    sp: Span,
    full_impl_span: Span,
    trait_span: Span,
    trait_ref: ty::TraitRef<'tcx>,
    self_ty_span: Span,
    generics: &hir::Generics<'tcx>,
    err: traits::OrphanCheckErr<'tcx>,
) -> Result<!, ErrorGuaranteed> {
    let self_ty = trait_ref.self_ty();
    Err(match err {
        traits::OrphanCheckErr::NonLocalInputType(tys) => {
            let msg = match self_ty.kind() {
                ty::Adt(..) => "can be implemented for types defined outside of the crate",
                _ if self_ty.is_primitive() => "can be implemented for primitive types",
                _ => "can be implemented for arbitrary types",
            };
            let mut err = struct_span_err!(
                tcx.sess,
                sp,
                E0117,
                "only traits defined in the current crate {msg}"
            );
            err.span_label(sp, "impl doesn't use only types from inside the current crate");
            for &(mut ty, is_target_ty) in &tys {
                ty = tcx.erase_regions(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, _) => Ty::new_adt(tcx, *def, ty::List::empty()),
                    _ => ty,
                };
                let msg = |ty: &str, postfix: &str| {
                    format!("{ty} is not defined in the current crate{postfix}")
                };

                let this = |name: &str| {
                    if !trait_ref.def_id.is_local() && !is_target_ty {
                        msg("this", " because this is a foreign trait")
                    } else {
                        msg("this", &format!(" because {name} are always foreign"))
                    }
                };
                let msg = match &ty.kind() {
                    ty::Slice(_) => this("slices"),
                    ty::Array(..) => this("arrays"),
                    ty::Tuple(..) => this("tuples"),
                    ty::Alias(ty::Opaque, ..) => {
                        "type alias impl trait is treated as if it were foreign, \
                        because its hidden type could be from a foreign crate"
                            .to_string()
                    }
                    ty::RawPtr(ptr_ty) => {
                        emit_newtype_suggestion_for_raw_ptr(
                            full_impl_span,
                            self_ty,
                            self_ty_span,
                            ptr_ty,
                            &mut err,
                        );

                        msg(&format!("`{ty}`"), " because raw pointers are always foreign")
                    }
                    _ => msg(&format!("`{ty}`"), ""),
                };

                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(trait_span, msg);
                }
            }
            err.note("define and implement a trait or new type instead");
            err.emit()
        }
        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!(
                    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 `{param_ty}` must be covered by another type \
                    when it appears before the first local type (`{local_type}`)"
                    ),
                )
                .note(
                    "implementing a foreign trait is only possible if at \
                        least one of the types for which it is 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!(
                    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 `{param_ty}` must be used as the type parameter for some \
                    local type",
                    ),
                )
                .note(
                    "implementing a foreign trait is only possible if at \
                        least one of the types for which it is implemented is local",
                )
                .note(
                    "only traits defined in the current crate can be \
                        implemented for a type parameter",
                )
                .emit(),
            }
        }
    })
}

fn emit_newtype_suggestion_for_raw_ptr(
    full_impl_span: Span,
    self_ty: Ty<'_>,
    self_ty_span: Span,
    ptr_ty: &ty::TypeAndMut<'_>,
    diag: &mut Diagnostic,
) {
    if !self_ty.has_param() {
        let mut_key = ptr_ty.mutbl.prefix_str();
        let msg_sugg = "consider introducing a new wrapper type".to_owned();
        let sugg = vec![
            (
                full_impl_span.shrink_to_lo(),
                format!("struct WrapperType(*{}{});\n\n", mut_key, ptr_ty.ty),
            ),
            (self_ty_span, "WrapperType".to_owned()),
        ];
        diag.multipart_suggestion(msg_sugg, sugg, rustc_errors::Applicability::MaybeIncorrect);
    }
}

/// Lint impls of auto traits if they are likely to have
/// unsound or surprising effects on auto impls.
fn lint_auto_trait_impl<'tcx>(
    tcx: TyCtxt<'tcx>,
    trait_ref: ty::TraitRef<'tcx>,
    impl_def_id: LocalDefId,
) {
    assert_eq!(trait_ref.args.len(), 1);
    let self_ty = trait_ref.self_ty();
    let (self_type_did, args) = match self_ty.kind() {
        ty::Adt(def, args) => (def.did(), args),
        _ => {
            // FIXME: should also lint for stuff like `&i32` but
            // considering that auto traits are unstable, that
            // isn't too important for now as this only affects
            // crates using `nightly`, and std.
            return;
        }
    };

    // Impls which completely cover a given root type are fine as they
    // disable auto impls entirely. So only lint if the args
    // are not a permutation of the identity args.
    let Err(arg) = tcx.uses_unique_generic_params(args, CheckRegions::No) else {
        // ok
        return;
    };

    // Ideally:
    //
    // - compute the requirements for the auto impl candidate
    // - check whether these are implied by the non covering impls
    // - if not, emit the lint
    //
    // What we do here is a bit simpler:
    //
    // - badly check if an auto impl candidate definitely does not apply
    //   for the given simplified type
    // - if so, do not lint
    if fast_reject_auto_impl(tcx, trait_ref.def_id, self_ty) {
        // ok
        return;
    }

    tcx.struct_span_lint_hir(
        lint::builtin::SUSPICIOUS_AUTO_TRAIT_IMPLS,
        tcx.hir().local_def_id_to_hir_id(impl_def_id),
        tcx.def_span(impl_def_id),
        DelayDm(|| {
            format!(
                "cross-crate traits with a default impl, like `{}`, \
                         should not be specialized",
                tcx.def_path_str(trait_ref.def_id),
            )
        }),
        |lint| {
            let item_span = tcx.def_span(self_type_did);
            let self_descr = tcx.def_descr(self_type_did);
            match arg {
                ty::util::NotUniqueParam::DuplicateParam(arg) => {
                    lint.note(format!("`{arg}` is mentioned multiple times"));
                }
                ty::util::NotUniqueParam::NotParam(arg) => {
                    lint.note(format!("`{arg}` is not a generic parameter"));
                }
            }
            lint.span_note(
                item_span,
                format!(
                    "try using the same sequence of generic parameters as the {self_descr} definition",
                ),
            )
        },
    );
}

fn fast_reject_auto_impl<'tcx>(tcx: TyCtxt<'tcx>, trait_def_id: DefId, self_ty: Ty<'tcx>) -> bool {
    struct DisableAutoTraitVisitor<'tcx> {
        tcx: TyCtxt<'tcx>,
        trait_def_id: DefId,
        self_ty_root: Ty<'tcx>,
        seen: FxHashSet<DefId>,
    }

    impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for DisableAutoTraitVisitor<'tcx> {
        type BreakTy = ();
        fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
            let tcx = self.tcx;
            if ty != self.self_ty_root {
                for impl_def_id in tcx.non_blanket_impls_for_ty(self.trait_def_id, ty) {
                    match tcx.impl_polarity(impl_def_id) {
                        ImplPolarity::Negative => return ControlFlow::Break(()),
                        ImplPolarity::Reservation => {}
                        // FIXME(@lcnr): That's probably not good enough, idk
                        //
                        // We might just want to take the rustdoc code and somehow avoid
                        // explicit impls for `Self`.
                        ImplPolarity::Positive => return ControlFlow::Continue(()),
                    }
                }
            }

            match ty.kind() {
                ty::Adt(def, args) if def.is_phantom_data() => args.visit_with(self),
                ty::Adt(def, args) => {
                    // @lcnr: This is the only place where cycles can happen. We avoid this
                    // by only visiting each `DefId` once.
                    //
                    // This will be is incorrect in subtle cases, but I don't care :)
                    if self.seen.insert(def.did()) {
                        for ty in def.all_fields().map(|field| field.ty(tcx, args)) {
                            ty.visit_with(self)?;
                        }
                    }

                    ControlFlow::Continue(())
                }
                _ => ty.super_visit_with(self),
            }
        }
    }

    let self_ty_root = match self_ty.kind() {
        ty::Adt(def, _) => Ty::new_adt(tcx, *def, GenericArgs::identity_for_item(tcx, def.did())),
        _ => unimplemented!("unexpected self ty {:?}", self_ty),
    };

    self_ty_root
        .visit_with(&mut DisableAutoTraitVisitor {
            tcx,
            self_ty_root,
            trait_def_id,
            seen: FxHashSet::default(),
        })
        .is_break()
}
