use std::cell::LazyCell;
use std::ops::ControlFlow;

use rustc_abi::{ExternAbi, FieldIdx, ScalableElt};
use rustc_data_structures::unord::{UnordMap, UnordSet};
use rustc_errors::codes::*;
use rustc_errors::{Diag, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level, MultiSpan};
use rustc_hir as hir;
use rustc_hir::attrs::ReprAttr::ReprPacked;
use rustc_hir::def::{CtorKind, DefKind};
use rustc_hir::{LangItem, Node, find_attr, intravisit};
use rustc_infer::infer::{RegionVariableOrigin, TyCtxtInferExt};
use rustc_infer::traits::{Obligation, ObligationCauseCode, WellFormedLoc};
use rustc_lint_defs::builtin::{REPR_TRANSPARENT_NON_ZST_FIELDS, UNSUPPORTED_CALLING_CONVENTIONS};
use rustc_macros::Diagnostic;
use rustc_middle::hir::nested_filter;
use rustc_middle::middle::resolve_bound_vars::ResolvedArg;
use rustc_middle::middle::stability::EvalResult;
use rustc_middle::ty::error::TypeErrorToStringExt;
use rustc_middle::ty::layout::{LayoutError, MAX_SIMD_LANES};
use rustc_middle::ty::util::Discr;
use rustc_middle::ty::{
    AdtDef, BottomUpFolder, FnSig, GenericArgKind, RegionKind, TypeFoldable, TypeSuperVisitable,
    TypeVisitable, TypeVisitableExt, Unnormalized, fold_regions,
};
use rustc_session::lint::builtin::UNINHABITED_STATIC;
use rustc_span::sym;
use rustc_target::spec::{AbiMap, AbiMapping};
use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
use rustc_trait_selection::traits;
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
use tracing::{debug, instrument};
use ty::TypingMode;

use super::compare_impl_item::check_type_bounds;
use super::*;
use crate::check::wfcheck::{
    check_associated_item, check_trait_item, check_type_defn, check_variances_for_type_defn,
    check_where_clauses, enter_wf_checking_ctxt,
};

fn add_abi_diag_help<T: EmissionGuarantee>(abi: ExternAbi, diag: &mut Diag<'_, T>) {
    if let ExternAbi::Cdecl { unwind } = abi {
        let c_abi = ExternAbi::C { unwind };
        diag.help(format!("use `extern {c_abi}` instead",));
    } else if let ExternAbi::Stdcall { unwind } = abi {
        let c_abi = ExternAbi::C { unwind };
        let system_abi = ExternAbi::System { unwind };
        diag.help(format!(
            "if you need `extern {abi}` on win32 and `extern {c_abi}` everywhere else, \
                use `extern {system_abi}`"
        ));
    }
}

pub fn check_abi(tcx: TyCtxt<'_>, hir_id: hir::HirId, span: Span, abi: ExternAbi) {
    struct UnsupportedCallingConventions {
        abi: ExternAbi,
    }

    impl<'a> Diagnostic<'a, ()> for UnsupportedCallingConventions {
        fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> {
            let Self { abi } = self;
            let mut lint = Diag::new(
                dcx,
                level,
                format!("{abi} is not a supported ABI for the current target"),
            );
            add_abi_diag_help(abi, &mut lint);
            lint
        }
    }
    // FIXME: This should be checked earlier, e.g. in `rustc_ast_lowering`, as this
    // currently only guards function imports, function definitions, and function pointer types.
    // Functions in trait declarations can still use "deprecated" ABIs without any warning.

    match AbiMap::from_target(&tcx.sess.target).canonize_abi(abi, false) {
        AbiMapping::Direct(..) => (),
        // already erred in rustc_ast_lowering
        AbiMapping::Invalid => {
            tcx.dcx().span_delayed_bug(span, format!("{abi} should be rejected in ast_lowering"));
        }
        AbiMapping::Deprecated(..) => {
            tcx.emit_node_span_lint(
                UNSUPPORTED_CALLING_CONVENTIONS,
                hir_id,
                span,
                UnsupportedCallingConventions { abi },
            );
        }
    }
}

pub fn check_custom_abi(tcx: TyCtxt<'_>, def_id: LocalDefId, fn_sig: FnSig<'_>, fn_sig_span: Span) {
    if fn_sig.abi() == ExternAbi::Custom {
        // Function definitions that use `extern "custom"` must be naked functions.
        if !find_attr!(tcx, def_id, Naked(_)) {
            tcx.dcx().emit_err(crate::errors::AbiCustomClothedFunction {
                span: fn_sig_span,
                naked_span: tcx.def_span(def_id).shrink_to_lo(),
            });
        }
    }
}

fn check_struct(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
    let def = tcx.adt_def(def_id);
    let span = tcx.def_span(def_id);
    def.destructor(tcx); // force the destructor to be evaluated

    if let Some(scalable) = def.repr().scalable {
        check_scalable_vector(tcx, span, def_id, scalable);
    } else if def.repr().simd() {
        check_simd(tcx, span, def_id);
    }

    check_transparent(tcx, def);
    check_packed(tcx, span, def);
    check_type_defn(tcx, def_id, false)
}

fn check_union(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
    let def = tcx.adt_def(def_id);
    let span = tcx.def_span(def_id);
    def.destructor(tcx); // force the destructor to be evaluated
    check_transparent(tcx, def);
    check_union_fields(tcx, span, def_id);
    check_packed(tcx, span, def);
    check_type_defn(tcx, def_id, true)
}

fn allowed_union_or_unsafe_field<'tcx>(
    tcx: TyCtxt<'tcx>,
    ty: Ty<'tcx>,
    typing_env: ty::TypingEnv<'tcx>,
    span: Span,
) -> bool {
    // HACK (not that bad of a hack don't worry): Some codegen tests don't even define proper
    // impls for `Copy`. Let's short-circuit here for this validity check, since a lot of them
    // use unions. We should eventually fix all the tests to define that lang item or use
    // minicore stubs.
    if ty.is_trivially_pure_clone_copy() {
        return true;
    }
    // If `BikeshedGuaranteedNoDrop` is not defined in a `#[no_core]` test, fall back to `Copy`.
    // This is an underapproximation of `BikeshedGuaranteedNoDrop`,
    let def_id = tcx
        .lang_items()
        .get(LangItem::BikeshedGuaranteedNoDrop)
        .unwrap_or_else(|| tcx.require_lang_item(LangItem::Copy, span));
    let Ok(ty) = tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)) else {
        tcx.dcx().span_delayed_bug(span, "could not normalize field type");
        return true;
    };
    let (infcx, param_env) = tcx.infer_ctxt().build_with_typing_env(typing_env);
    infcx.predicate_must_hold_modulo_regions(&Obligation::new(
        tcx,
        ObligationCause::dummy_with_span(span),
        param_env,
        ty::TraitRef::new(tcx, def_id, [ty]),
    ))
}

/// Check that the fields of the `union` do not need dropping.
fn check_union_fields(tcx: TyCtxt<'_>, span: Span, item_def_id: LocalDefId) -> bool {
    let def = tcx.adt_def(item_def_id);
    assert!(def.is_union());

    let typing_env = ty::TypingEnv::non_body_analysis(tcx, item_def_id);
    let args = ty::GenericArgs::identity_for_item(tcx, item_def_id);

    for field in &def.non_enum_variant().fields {
        if !allowed_union_or_unsafe_field(
            tcx,
            field.ty(tcx, args).skip_norm_wip(),
            typing_env,
            span,
        ) {
            let (field_span, ty_span) = match tcx.hir_get_if_local(field.did) {
                // We are currently checking the type this field came from, so it must be local.
                Some(Node::Field(field)) => (field.span, field.ty.span),
                _ => unreachable!("mir field has to correspond to hir field"),
            };
            tcx.dcx().emit_err(errors::InvalidUnionField {
                field_span,
                sugg: errors::InvalidUnionFieldSuggestion {
                    lo: ty_span.shrink_to_lo(),
                    hi: ty_span.shrink_to_hi(),
                },
                note: (),
            });
            return false;
        }
    }

    true
}

/// Check that a `static` is inhabited.
fn check_static_inhabited(tcx: TyCtxt<'_>, def_id: LocalDefId) {
    #[derive(Diagnostic)]
    #[diag("static of uninhabited type")]
    #[note("uninhabited statics cannot be initialized, and any access would be an immediate error")]
    struct StaticOfUninhabitedType;

    // Make sure statics are inhabited.
    // Other parts of the compiler assume that there are no uninhabited places. In principle it
    // would be enough to check this for `extern` statics, as statics with an initializer will
    // have UB during initialization if they are uninhabited, but there also seems to be no good
    // reason to allow any statics to be uninhabited.
    let ty = tcx.type_of(def_id).instantiate_identity().skip_norm_wip();
    let span = tcx.def_span(def_id);
    let layout = match tcx.layout_of(ty::TypingEnv::fully_monomorphized().as_query_input(ty)) {
        Ok(l) => l,
        // Foreign statics that overflow their allowed size should emit an error
        Err(LayoutError::SizeOverflow(_))
            if matches!(tcx.def_kind(def_id), DefKind::Static{ .. }
                if tcx.def_kind(tcx.local_parent(def_id)) == DefKind::ForeignMod) =>
        {
            tcx.dcx().emit_err(errors::TooLargeStatic { span });
            return;
        }
        // SIMD types with invalid layout (e.g., zero-length) should emit an error
        Err(e @ LayoutError::InvalidSimd { .. }) => {
            let ty_span = tcx.ty_span(def_id);
            tcx.dcx().span_err(ty_span, e.to_string());
            return;
        }
        // Generic statics are rejected, but we still reach this case.
        Err(e) => {
            tcx.dcx().span_delayed_bug(span, format!("{e:?}"));
            return;
        }
    };
    if layout.is_uninhabited() {
        tcx.emit_node_span_lint(
            UNINHABITED_STATIC,
            tcx.local_def_id_to_hir_id(def_id),
            span,
            StaticOfUninhabitedType,
        );
    }
}

/// Checks that an opaque type does not contain cycles and does not use `Self` or `T::Foo`
/// projections that would result in "inheriting lifetimes".
fn check_opaque(tcx: TyCtxt<'_>, def_id: LocalDefId) {
    let hir::OpaqueTy { origin, .. } = *tcx.hir_expect_opaque_ty(def_id);

    // HACK(jynelson): trying to infer the type of `impl trait` breaks documenting
    // `async-std` (and `pub async fn` in general).
    // Since rustdoc doesn't care about the hidden type behind `impl Trait`, just don't look at it!
    // See https://github.com/rust-lang/rust/issues/75100
    if tcx.sess.opts.actually_rustdoc {
        return;
    }

    if tcx.type_of(def_id).instantiate_identity().skip_norm_wip().references_error() {
        return;
    }
    if check_opaque_for_cycles(tcx, def_id).is_err() {
        return;
    }

    let _ = check_opaque_meets_bounds(tcx, def_id, origin);
}

/// Checks that an opaque type does not contain cycles.
pub(super) fn check_opaque_for_cycles<'tcx>(
    tcx: TyCtxt<'tcx>,
    def_id: LocalDefId,
) -> Result<(), ErrorGuaranteed> {
    let args = GenericArgs::identity_for_item(tcx, def_id);

    // First, try to look at any opaque expansion cycles, considering coroutine fields
    // (even though these aren't necessarily true errors).
    if tcx.try_expand_impl_trait_type(def_id.to_def_id(), args).is_err() {
        let reported = opaque_type_cycle_error(tcx, def_id);
        return Err(reported);
    }

    Ok(())
}

/// Check that the hidden type behind `impl Trait` actually implements `Trait`.
///
/// This is mostly checked at the places that specify the opaque type, but we
/// check those cases in the `param_env` of that function, which may have
/// bounds not on this opaque type:
///
/// ```ignore (illustrative)
/// type X<T> = impl Clone;
/// fn f<T: Clone>(t: T) -> X<T> {
///     t
/// }
/// ```
///
/// Without this check the above code is incorrectly accepted: we would ICE if
/// some tried, for example, to clone an `Option<X<&mut ()>>`.
#[instrument(level = "debug", skip(tcx))]
fn check_opaque_meets_bounds<'tcx>(
    tcx: TyCtxt<'tcx>,
    def_id: LocalDefId,
    origin: hir::OpaqueTyOrigin<LocalDefId>,
) -> Result<(), ErrorGuaranteed> {
    let (span, definition_def_id) =
        if let Some((span, def_id)) = best_definition_site_of_opaque(tcx, def_id, origin) {
            (span, Some(def_id))
        } else {
            (tcx.def_span(def_id), None)
        };

    let defining_use_anchor = match origin {
        hir::OpaqueTyOrigin::FnReturn { parent, .. }
        | hir::OpaqueTyOrigin::AsyncFn { parent, .. }
        | hir::OpaqueTyOrigin::TyAlias { parent, .. } => parent,
    };
    let param_env = tcx.param_env(defining_use_anchor);

    // FIXME(#132279): Once `PostBorrowckAnalysis` is supported in the old solver, this branch should be removed.
    let infcx = tcx.infer_ctxt().build(if tcx.next_trait_solver_globally() {
        TypingMode::post_borrowck_analysis(tcx, defining_use_anchor)
    } else {
        TypingMode::analysis_in_body(tcx, defining_use_anchor)
    });
    let ocx = ObligationCtxt::new_with_diagnostics(&infcx);

    let args = match origin {
        hir::OpaqueTyOrigin::FnReturn { parent, .. }
        | hir::OpaqueTyOrigin::AsyncFn { parent, .. }
        | hir::OpaqueTyOrigin::TyAlias { parent, .. } => GenericArgs::identity_for_item(
            tcx, parent,
        )
        .extend_to(tcx, def_id.to_def_id(), |param, _| {
            tcx.map_opaque_lifetime_to_parent_lifetime(param.def_id.expect_local()).into()
        }),
    };

    let opaque_ty = Ty::new_opaque(tcx, def_id.to_def_id(), args);

    // `ReErased` regions appear in the "parent_args" of closures/coroutines.
    // We're ignoring them here and replacing them with fresh region variables.
    // See tests in ui/type-alias-impl-trait/closure_{parent_args,wf_outlives}.rs.
    //
    // FIXME: Consider wrapping the hidden type in an existential `Binder` and instantiating it
    // here rather than using ReErased.
    let hidden_ty = tcx.type_of(def_id.to_def_id()).instantiate(tcx, args).skip_norm_wip();
    let hidden_ty = fold_regions(tcx, hidden_ty, |re, _dbi| match re.kind() {
        ty::ReErased => infcx.next_region_var(RegionVariableOrigin::Misc(span)),
        _ => re,
    });

    // HACK: We eagerly instantiate some bounds to report better errors for them...
    // This isn't necessary for correctness, since we register these bounds when
    // equating the opaque below, but we should clean this up in the new solver.
    for (predicate, pred_span) in tcx
        .explicit_item_bounds(def_id)
        .iter_instantiated_copied(tcx, args)
        .map(Unnormalized::skip_norm_wip)
    {
        let predicate = predicate.fold_with(&mut BottomUpFolder {
            tcx,
            ty_op: |ty| if ty == opaque_ty { hidden_ty } else { ty },
            lt_op: |lt| lt,
            ct_op: |ct| ct,
        });

        ocx.register_obligation(Obligation::new(
            tcx,
            ObligationCause::new(
                span,
                def_id,
                ObligationCauseCode::OpaqueTypeBound(pred_span, definition_def_id),
            ),
            param_env,
            predicate,
        ));
    }

    let misc_cause = ObligationCause::misc(span, def_id);
    // FIXME: We should just register the item bounds here, rather than equating.
    // FIXME(const_trait_impl): When we do that, please make sure to also register
    // the `[const]` bounds.
    match ocx.eq(&misc_cause, param_env, opaque_ty, hidden_ty) {
        Ok(()) => {}
        Err(ty_err) => {
            // Some types may be left "stranded" if they can't be reached
            // from a lowered rustc_middle bound but they're mentioned in the HIR.
            // This will happen, e.g., when a nested opaque is inside of a non-
            // existent associated type, like `impl Trait<Missing = impl Trait>`.
            // See <tests/ui/impl-trait/stranded-opaque.rs>.
            let ty_err = ty_err.to_string(tcx);
            let guar = tcx.dcx().span_delayed_bug(
                span,
                format!("could not unify `{hidden_ty}` with revealed type:\n{ty_err}"),
            );
            return Err(guar);
        }
    }

    // Additionally require the hidden type to be well-formed with only the generics of the opaque type.
    // Defining use functions may have more bounds than the opaque type, which is ok, as long as the
    // hidden type is well formed even without those bounds.
    let predicate =
        ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(hidden_ty.into())));
    ocx.register_obligation(Obligation::new(tcx, misc_cause.clone(), param_env, predicate));

    // Check that all obligations are satisfied by the implementation's
    // version.
    let errors = ocx.evaluate_obligations_error_on_ambiguity();
    if !errors.is_empty() {
        let guar = infcx.err_ctxt().report_fulfillment_errors(errors);
        return Err(guar);
    }

    let wf_tys = ocx.assumed_wf_types_and_report_errors(param_env, defining_use_anchor)?;
    ocx.resolve_regions_and_report_errors(defining_use_anchor, param_env, wf_tys)?;

    if infcx.next_trait_solver() {
        Ok(())
    } else if let hir::OpaqueTyOrigin::FnReturn { .. } | hir::OpaqueTyOrigin::AsyncFn { .. } =
        origin
    {
        // HACK: this should also fall through to the hidden type check below, but the original
        // implementation had a bug where equivalent lifetimes are not identical. This caused us
        // to reject existing stable code that is otherwise completely fine. The real fix is to
        // compare the hidden types via our type equivalence/relation infra instead of doing an
        // identity check.
        let _ = infcx.take_opaque_types();
        Ok(())
    } else {
        // Check that any hidden types found during wf checking match the hidden types that `type_of` sees.
        for (mut key, mut ty) in infcx.take_opaque_types() {
            ty.ty = infcx.resolve_vars_if_possible(ty.ty);
            key = infcx.resolve_vars_if_possible(key);
            sanity_check_found_hidden_type(tcx, key, ty)?;
        }
        Ok(())
    }
}

fn best_definition_site_of_opaque<'tcx>(
    tcx: TyCtxt<'tcx>,
    opaque_def_id: LocalDefId,
    origin: hir::OpaqueTyOrigin<LocalDefId>,
) -> Option<(Span, LocalDefId)> {
    struct TaitConstraintLocator<'tcx> {
        opaque_def_id: LocalDefId,
        tcx: TyCtxt<'tcx>,
    }
    impl<'tcx> TaitConstraintLocator<'tcx> {
        fn check(&self, item_def_id: LocalDefId) -> ControlFlow<(Span, LocalDefId)> {
            if !self.tcx.has_typeck_results(item_def_id) {
                return ControlFlow::Continue(());
            }

            let opaque_types_defined_by = self.tcx.opaque_types_defined_by(item_def_id);
            // Don't try to check items that cannot possibly constrain the type.
            if !opaque_types_defined_by.contains(&self.opaque_def_id) {
                return ControlFlow::Continue(());
            }

            if let Some(hidden_ty) = self
                .tcx
                .mir_borrowck(item_def_id)
                .ok()
                .and_then(|opaque_types| opaque_types.get(&self.opaque_def_id))
            {
                ControlFlow::Break((hidden_ty.span, item_def_id))
            } else {
                ControlFlow::Continue(())
            }
        }
    }
    impl<'tcx> intravisit::Visitor<'tcx> for TaitConstraintLocator<'tcx> {
        type NestedFilter = nested_filter::All;
        type Result = ControlFlow<(Span, LocalDefId)>;
        fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
            self.tcx
        }
        fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) -> Self::Result {
            intravisit::walk_expr(self, ex)
        }
        fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) -> Self::Result {
            self.check(it.owner_id.def_id)?;
            intravisit::walk_item(self, it)
        }
        fn visit_impl_item(&mut self, it: &'tcx hir::ImplItem<'tcx>) -> Self::Result {
            self.check(it.owner_id.def_id)?;
            intravisit::walk_impl_item(self, it)
        }
        fn visit_trait_item(&mut self, it: &'tcx hir::TraitItem<'tcx>) -> Self::Result {
            self.check(it.owner_id.def_id)?;
            intravisit::walk_trait_item(self, it)
        }
        fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) -> Self::Result {
            intravisit::walk_foreign_item(self, it)
        }
    }

    let mut locator = TaitConstraintLocator { tcx, opaque_def_id };
    match origin {
        hir::OpaqueTyOrigin::FnReturn { parent, .. }
        | hir::OpaqueTyOrigin::AsyncFn { parent, .. } => locator.check(parent).break_value(),
        hir::OpaqueTyOrigin::TyAlias { parent, in_assoc_ty: true } => {
            let impl_def_id = tcx.local_parent(parent);
            for assoc in tcx.associated_items(impl_def_id).in_definition_order() {
                match assoc.kind {
                    ty::AssocKind::Const { .. } | ty::AssocKind::Fn { .. } => {
                        if let ControlFlow::Break(span) = locator.check(assoc.def_id.expect_local())
                        {
                            return Some(span);
                        }
                    }
                    ty::AssocKind::Type { .. } => {}
                }
            }

            None
        }
        hir::OpaqueTyOrigin::TyAlias { in_assoc_ty: false, .. } => {
            tcx.hir_walk_toplevel_module(&mut locator).break_value()
        }
    }
}

fn sanity_check_found_hidden_type<'tcx>(
    tcx: TyCtxt<'tcx>,
    key: ty::OpaqueTypeKey<'tcx>,
    mut ty: ty::ProvisionalHiddenType<'tcx>,
) -> Result<(), ErrorGuaranteed> {
    if ty.ty.is_ty_var() {
        // Nothing was actually constrained.
        return Ok(());
    }
    if let &ty::Alias(ty::AliasTy { kind: ty::Opaque { def_id }, args, .. }) = ty.ty.kind() {
        if def_id == key.def_id.to_def_id() && args == key.args {
            // Nothing was actually constrained, this is an opaque usage that was
            // only discovered to be opaque after inference vars resolved.
            return Ok(());
        }
    }
    let erase_re_vars = |ty: Ty<'tcx>| {
        fold_regions(tcx, ty, |r, _| match r.kind() {
            RegionKind::ReVar(_) => tcx.lifetimes.re_erased,
            _ => r,
        })
    };
    // Closures frequently end up containing erased lifetimes in their final representation.
    // These correspond to lifetime variables that never got resolved, so we patch this up here.
    ty.ty = erase_re_vars(ty.ty);
    // Get the hidden type.
    let hidden_ty = tcx.type_of(key.def_id).instantiate(tcx, key.args).skip_norm_wip();
    let hidden_ty = erase_re_vars(hidden_ty);

    // If the hidden types differ, emit a type mismatch diagnostic.
    if hidden_ty == ty.ty {
        Ok(())
    } else {
        let span = tcx.def_span(key.def_id);
        let other = ty::ProvisionalHiddenType { ty: hidden_ty, span };
        Err(ty.build_mismatch_error(&other, tcx)?.emit())
    }
}

/// Check that the opaque's precise captures list is valid (if present).
/// We check this for regular `impl Trait`s and also RPITITs, even though the latter
/// are technically GATs.
///
/// This function is responsible for:
/// 1. Checking that all type/const params are mention in the captures list.
/// 2. Checking that all lifetimes that are implicitly captured are mentioned.
/// 3. Asserting that all parameters mentioned in the captures list are invariant.
fn check_opaque_precise_captures<'tcx>(tcx: TyCtxt<'tcx>, opaque_def_id: LocalDefId) {
    let hir::OpaqueTy { bounds, .. } = *tcx.hir_node_by_def_id(opaque_def_id).expect_opaque_ty();
    let Some(precise_capturing_args) = bounds.iter().find_map(|bound| match *bound {
        hir::GenericBound::Use(bounds, ..) => Some(bounds),
        _ => None,
    }) else {
        // No precise capturing args; nothing to validate
        return;
    };

    let mut expected_captures = UnordSet::default();
    let mut shadowed_captures = UnordSet::default();
    let mut seen_params = UnordMap::default();
    let mut prev_non_lifetime_param = None;
    for arg in precise_capturing_args {
        let (hir_id, ident) = match *arg {
            hir::PreciseCapturingArg::Param(hir::PreciseCapturingNonLifetimeArg {
                hir_id,
                ident,
                ..
            }) => {
                if prev_non_lifetime_param.is_none() {
                    prev_non_lifetime_param = Some(ident);
                }
                (hir_id, ident)
            }
            hir::PreciseCapturingArg::Lifetime(&hir::Lifetime { hir_id, ident, .. }) => {
                if let Some(prev_non_lifetime_param) = prev_non_lifetime_param {
                    tcx.dcx().emit_err(errors::LifetimesMustBeFirst {
                        lifetime_span: ident.span,
                        name: ident.name,
                        other_span: prev_non_lifetime_param.span,
                    });
                }
                (hir_id, ident)
            }
        };

        let ident = ident.normalize_to_macros_2_0();
        if let Some(span) = seen_params.insert(ident, ident.span) {
            tcx.dcx().emit_err(errors::DuplicatePreciseCapture {
                name: ident.name,
                first_span: span,
                second_span: ident.span,
            });
        }

        match tcx.named_bound_var(hir_id) {
            Some(ResolvedArg::EarlyBound(def_id)) => {
                expected_captures.insert(def_id.to_def_id());

                // Make sure we allow capturing these lifetimes through `Self` and
                // `T::Assoc` projection syntax, too. These will occur when we only
                // see lifetimes are captured after hir-lowering -- this aligns with
                // the cases that were stabilized with the `impl_trait_projection`
                // feature -- see <https://github.com/rust-lang/rust/pull/115659>.
                if let DefKind::LifetimeParam = tcx.def_kind(def_id)
                    && let Some(def_id) = tcx
                        .map_opaque_lifetime_to_parent_lifetime(def_id)
                        .opt_param_def_id(tcx, tcx.parent(opaque_def_id.to_def_id()))
                {
                    shadowed_captures.insert(def_id);
                }
            }
            _ => {
                tcx.dcx()
                    .span_delayed_bug(tcx.hir_span(hir_id), "parameter should have been resolved");
            }
        }
    }

    let variances = tcx.variances_of(opaque_def_id);
    let mut def_id = Some(opaque_def_id.to_def_id());
    while let Some(generics) = def_id {
        let generics = tcx.generics_of(generics);
        def_id = generics.parent;

        for param in &generics.own_params {
            if expected_captures.contains(&param.def_id) {
                assert_eq!(
                    variances[param.index as usize],
                    ty::Invariant,
                    "precise captured param should be invariant"
                );
                continue;
            }
            // If a param is shadowed by a early-bound (duplicated) lifetime, then
            // it may or may not be captured as invariant, depending on if it shows
            // up through `Self` or `T::Assoc` syntax.
            if shadowed_captures.contains(&param.def_id) {
                continue;
            }

            match param.kind {
                ty::GenericParamDefKind::Lifetime => {
                    let use_span = tcx.def_span(param.def_id);
                    let opaque_span = tcx.def_span(opaque_def_id);
                    // Check if the lifetime param was captured but isn't named in the precise captures list.
                    if variances[param.index as usize] == ty::Invariant {
                        if let DefKind::OpaqueTy = tcx.def_kind(tcx.parent(param.def_id))
                            && let Some(def_id) = tcx
                                .map_opaque_lifetime_to_parent_lifetime(param.def_id.expect_local())
                                .opt_param_def_id(tcx, tcx.parent(opaque_def_id.to_def_id()))
                        {
                            tcx.dcx().emit_err(errors::LifetimeNotCaptured {
                                opaque_span,
                                use_span,
                                param_span: tcx.def_span(def_id),
                            });
                        } else {
                            if tcx.def_kind(tcx.parent(param.def_id)) == DefKind::Trait {
                                tcx.dcx().emit_err(errors::LifetimeImplicitlyCaptured {
                                    opaque_span,
                                    param_span: tcx.def_span(param.def_id),
                                });
                            } else {
                                // If the `use_span` is actually just the param itself, then we must
                                // have not duplicated the lifetime but captured the original.
                                // The "effective" `use_span` will be the span of the opaque itself,
                                // and the param span will be the def span of the param.
                                tcx.dcx().emit_err(errors::LifetimeNotCaptured {
                                    opaque_span,
                                    use_span: opaque_span,
                                    param_span: use_span,
                                });
                            }
                        }
                        continue;
                    }
                }
                ty::GenericParamDefKind::Type { .. } => {
                    if matches!(tcx.def_kind(param.def_id), DefKind::Trait | DefKind::TraitAlias) {
                        // FIXME(precise_capturing): Structured suggestion for this would be useful
                        tcx.dcx().emit_err(errors::SelfTyNotCaptured {
                            trait_span: tcx.def_span(param.def_id),
                            opaque_span: tcx.def_span(opaque_def_id),
                        });
                    } else {
                        // FIXME(precise_capturing): Structured suggestion for this would be useful
                        tcx.dcx().emit_err(errors::ParamNotCaptured {
                            param_span: tcx.def_span(param.def_id),
                            opaque_span: tcx.def_span(opaque_def_id),
                            kind: "type",
                        });
                    }
                }
                ty::GenericParamDefKind::Const { .. } => {
                    // FIXME(precise_capturing): Structured suggestion for this would be useful
                    tcx.dcx().emit_err(errors::ParamNotCaptured {
                        param_span: tcx.def_span(param.def_id),
                        opaque_span: tcx.def_span(opaque_def_id),
                        kind: "const",
                    });
                }
            }
        }
    }
}

fn is_enum_of_nonnullable_ptr<'tcx>(
    tcx: TyCtxt<'tcx>,
    adt_def: AdtDef<'tcx>,
    args: GenericArgsRef<'tcx>,
) -> bool {
    if adt_def.repr().inhibit_enum_layout_opt() {
        return false;
    }

    let [var_one, var_two] = &adt_def.variants().raw[..] else {
        return false;
    };
    let (([], [field]) | ([field], [])) = (&var_one.fields.raw[..], &var_two.fields.raw[..]) else {
        return false;
    };
    matches!(field.ty(tcx, args).skip_norm_wip().kind(), ty::FnPtr(..) | ty::Ref(..))
}

fn check_static_linkage(tcx: TyCtxt<'_>, def_id: LocalDefId) {
    if tcx.codegen_fn_attrs(def_id).import_linkage.is_some() {
        if match tcx.type_of(def_id).instantiate_identity().skip_norm_wip().kind() {
            ty::RawPtr(_, _) => false,
            ty::Adt(adt_def, args) => !is_enum_of_nonnullable_ptr(tcx, *adt_def, *args),
            _ => true,
        } {
            tcx.dcx().emit_err(errors::LinkageType { span: tcx.def_span(def_id) });
        }
    }
}

pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
    let mut res = Ok(());
    let generics = tcx.generics_of(def_id);

    for param in &generics.own_params {
        match param.kind {
            ty::GenericParamDefKind::Lifetime { .. } => {}
            ty::GenericParamDefKind::Type { has_default, .. } => {
                if has_default {
                    tcx.ensure_ok().type_of(param.def_id);
                }
            }
            ty::GenericParamDefKind::Const { has_default, .. } => {
                tcx.ensure_ok().type_of(param.def_id);
                if has_default {
                    // need to store default and type of default
                    let ct = tcx.const_param_default(param.def_id).skip_binder();
                    if let ty::ConstKind::Unevaluated(uv) = ct.kind()
                        && let Some(def_id) = uv.kind.opt_def_id()
                    {
                        tcx.ensure_ok().type_of(def_id);
                    }
                }
            }
        }
    }

    match tcx.def_kind(def_id) {
        DefKind::Static { .. } => {
            tcx.ensure_ok().generics_of(def_id);
            tcx.ensure_ok().type_of(def_id);
            tcx.ensure_ok().predicates_of(def_id);

            check_static_inhabited(tcx, def_id);
            check_static_linkage(tcx, def_id);
            let ty = tcx.type_of(def_id).instantiate_identity().skip_norm_wip();
            res = res.and(wfcheck::check_static_item(
                tcx, def_id, ty, /* should_check_for_sync */ true,
            ));

            // Only `Node::Item` and `Node::ForeignItem` still have HIR based
            // checks. Returning early here does not miss any checks and
            // avoids this query from having a direct dependency edge on the HIR
            return res;
        }
        DefKind::Enum => {
            tcx.ensure_ok().generics_of(def_id);
            tcx.ensure_ok().type_of(def_id);
            tcx.ensure_ok().predicates_of(def_id);
            crate::collect::check_enum_variant_types(tcx, def_id);
            check_enum(tcx, def_id);
            check_variances_for_type_defn(tcx, def_id);
            res = res.and(check_type_defn(tcx, def_id, true));
            // enums are fully handled by the type based check and have no hir wfcheck logic
            return res;
        }
        DefKind::Fn => {
            tcx.ensure_ok().generics_of(def_id);
            tcx.ensure_ok().type_of(def_id);
            tcx.ensure_ok().predicates_of(def_id);
            tcx.ensure_ok().fn_sig(def_id);
            tcx.ensure_ok().codegen_fn_attrs(def_id);
            if let Some(i) = tcx.intrinsic(def_id) {
                intrinsic::check_intrinsic_type(
                    tcx,
                    def_id,
                    tcx.def_ident_span(def_id).unwrap(),
                    i.name,
                )
            }
        }
        DefKind::Impl { of_trait } => {
            tcx.ensure_ok().generics_of(def_id);
            tcx.ensure_ok().type_of(def_id);
            tcx.ensure_ok().predicates_of(def_id);
            tcx.ensure_ok().associated_items(def_id);
            if of_trait {
                let impl_trait_header = tcx.impl_trait_header(def_id);
                res = res.and(tcx.ensure_result().coherent_trait(
                    impl_trait_header.trait_ref.instantiate_identity().skip_norm_wip().def_id,
                ));

                if res.is_ok() {
                    // Checking this only makes sense if the all trait impls satisfy basic
                    // requirements (see `coherent_trait` query), otherwise
                    // we run into infinite recursions a lot.
                    check_impl_items_against_trait(tcx, def_id, impl_trait_header);
                }
            }
        }
        DefKind::Trait => {
            tcx.ensure_ok().generics_of(def_id);
            tcx.ensure_ok().trait_def(def_id);
            tcx.ensure_ok().explicit_super_predicates_of(def_id);
            tcx.ensure_ok().predicates_of(def_id);
            tcx.ensure_ok().associated_items(def_id);
            let assoc_items = tcx.associated_items(def_id);

            for &assoc_item in assoc_items.in_definition_order() {
                match assoc_item.kind {
                    ty::AssocKind::Type { .. } if assoc_item.defaultness(tcx).has_value() => {
                        let trait_args = GenericArgs::identity_for_item(tcx, def_id);
                        let _: Result<_, rustc_errors::ErrorGuaranteed> = check_type_bounds(
                            tcx,
                            assoc_item,
                            assoc_item,
                            ty::TraitRef::new_from_args(tcx, def_id.to_def_id(), trait_args),
                        );
                    }
                    _ => {}
                }
            }
            res = res.and(wfcheck::check_trait(tcx, def_id));
            wfcheck::check_gat_where_clauses(tcx, def_id);
            // Trait aliases do not have hir checks anymore
            return res;
        }
        DefKind::TraitAlias => {
            tcx.ensure_ok().generics_of(def_id);
            tcx.ensure_ok().explicit_implied_predicates_of(def_id);
            tcx.ensure_ok().explicit_super_predicates_of(def_id);
            tcx.ensure_ok().predicates_of(def_id);
            res = res.and(wfcheck::check_trait(tcx, def_id));
            // Trait aliases do not have hir checks anymore
            return res;
        }
        def_kind @ (DefKind::Struct | DefKind::Union) => {
            tcx.ensure_ok().generics_of(def_id);
            tcx.ensure_ok().type_of(def_id);
            tcx.ensure_ok().predicates_of(def_id);

            let adt = tcx.adt_def(def_id).non_enum_variant();
            for f in adt.fields.iter() {
                tcx.ensure_ok().generics_of(f.did);
                tcx.ensure_ok().type_of(f.did);
                tcx.ensure_ok().predicates_of(f.did);
            }

            if let Some((_, ctor_def_id)) = adt.ctor {
                crate::collect::check_ctor(tcx, ctor_def_id.expect_local());
            }
            check_variances_for_type_defn(tcx, def_id);
            res = res.and(match def_kind {
                DefKind::Struct => check_struct(tcx, def_id),
                DefKind::Union => check_union(tcx, def_id),
                _ => unreachable!(),
            });
            // structs and enums are fully handled by the type based check and have no hir wfcheck logic
            return res;
        }
        DefKind::OpaqueTy => {
            check_opaque_precise_captures(tcx, def_id);

            let origin = tcx.local_opaque_ty_origin(def_id);
            if let hir::OpaqueTyOrigin::FnReturn { parent: fn_def_id, .. }
            | hir::OpaqueTyOrigin::AsyncFn { parent: fn_def_id, .. } = origin
                && let hir::Node::TraitItem(trait_item) = tcx.hir_node_by_def_id(fn_def_id)
                && let (_, hir::TraitFn::Required(..)) = trait_item.expect_fn()
            {
                // Skip opaques from RPIT in traits with no default body.
            } else {
                check_opaque(tcx, def_id);
            }

            tcx.ensure_ok().predicates_of(def_id);
            tcx.ensure_ok().explicit_item_bounds(def_id);
            tcx.ensure_ok().explicit_item_self_bounds(def_id);
            if tcx.is_conditionally_const(def_id) {
                tcx.ensure_ok().explicit_implied_const_bounds(def_id);
                tcx.ensure_ok().const_conditions(def_id);
            }

            // Only `Node::Item` and `Node::ForeignItem` still have HIR based
            // checks. Returning early here does not miss any checks and
            // avoids this query from having a direct dependency edge on the HIR
            return res;
        }
        DefKind::Const { .. } => {
            tcx.ensure_ok().generics_of(def_id);
            tcx.ensure_ok().type_of(def_id);
            tcx.ensure_ok().predicates_of(def_id);

            res = res.and(enter_wf_checking_ctxt(tcx, def_id, |wfcx| {
                let ty = tcx.type_of(def_id).instantiate_identity();
                let ty_span = tcx.ty_span(def_id);
                let ty = wfcx.deeply_normalize(ty_span, Some(WellFormedLoc::Ty(def_id)), ty);
                wfcx.register_wf_obligation(ty_span, Some(WellFormedLoc::Ty(def_id)), ty.into());
                wfcx.register_bound(
                    traits::ObligationCause::new(
                        ty_span,
                        def_id,
                        ObligationCauseCode::SizedConstOrStatic,
                    ),
                    tcx.param_env(def_id),
                    ty,
                    tcx.require_lang_item(LangItem::Sized, ty_span),
                );
                check_where_clauses(wfcx, def_id);

                if tcx.is_type_const(def_id) {
                    wfcheck::check_type_const(wfcx, def_id, ty, true)?;
                }
                Ok(())
            }));

            // Only `Node::Item` and `Node::ForeignItem` still have HIR based
            // checks. Returning early here does not miss any checks and
            // avoids this query from having a direct dependency edge on the HIR
            return res;
        }
        DefKind::TyAlias => {
            tcx.ensure_ok().generics_of(def_id);
            tcx.ensure_ok().type_of(def_id);
            tcx.ensure_ok().predicates_of(def_id);
            let ty = tcx.type_of(def_id).instantiate_identity();
            let span = tcx.def_span(def_id);
            if tcx.type_alias_is_lazy(def_id) {
                res = res.and(enter_wf_checking_ctxt(tcx, def_id, |wfcx| {
                    let item_ty = wfcx.deeply_normalize(span, Some(WellFormedLoc::Ty(def_id)), ty);
                    wfcx.register_wf_obligation(
                        span,
                        Some(WellFormedLoc::Ty(def_id)),
                        item_ty.into(),
                    );
                    check_where_clauses(wfcx, def_id);
                    Ok(())
                }));
            } else {
                check_type_alias_type_params_are_used(tcx, def_id);
                res = res.and(enter_wf_checking_ctxt(tcx, def_id, |wfcx| {
                    // HACK: We sometimes incidentally check that const arguments have the correct
                    // type as a side effect of the anon const desugaring. To make this "consistent"
                    // for users we explicitly check `ConstArgHasType` clauses so that const args
                    // that don't go through an anon const still have their types checked.
                    //
                    // We use the unnormalized type as this mirrors the behaviour that we previously
                    // would have had when all const arguments were anon consts.
                    //
                    // Changing this to normalized obligations is a breaking change:
                    // `type Bar = [(); panic!()];` would become an error
                    if let Some(unnormalized_obligations) = wfcx.unnormalized_obligations(span, ty.skip_norm_wip())
                    {
                        let filtered_obligations =
                            unnormalized_obligations.into_iter().filter(|o| {
                                matches!(o.predicate.kind().skip_binder(),
                                    ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(ct, _))
                                    if matches!(ct.kind(), ty::ConstKind::Param(..)))
                            });
                        wfcx.ocx.register_obligations(filtered_obligations)
                    }
                    Ok(())
                }));
            }

            // Only `Node::Item` and `Node::ForeignItem` still have HIR based
            // checks. Returning early here does not miss any checks and
            // avoids this query from having a direct dependency edge on the HIR
            return res;
        }
        DefKind::ForeignMod => {
            let it = tcx.hir_expect_item(def_id);
            let hir::ItemKind::ForeignMod { abi, items } = it.kind else {
                return Ok(());
            };

            check_abi(tcx, it.hir_id(), it.span, abi);

            for &item in items {
                let def_id = item.owner_id.def_id;

                let generics = tcx.generics_of(def_id);
                let own_counts = generics.own_counts();
                if generics.own_params.len() - own_counts.lifetimes != 0 {
                    let (kinds, kinds_pl, egs) = match (own_counts.types, own_counts.consts) {
                        (_, 0) => ("type", "types", Some("u32")),
                        // We don't specify an example value, because we can't generate
                        // a valid value for any type.
                        (0, _) => ("const", "consts", None),
                        _ => ("type or const", "types or consts", None),
                    };
                    let name = if find_attr!(tcx, def_id, RustcEiiForeignItem) {
                        "externally implementable items"
                    } else {
                        "foreign items"
                    };

                    let span = tcx.def_span(def_id);
                    struct_span_code_err!(
                        tcx.dcx(),
                        span,
                        E0044,
                        "{name} may not have {kinds} parameters",
                    )
                    .with_span_label(span, format!("can't have {kinds} parameters"))
                    .with_help(
                        // FIXME: once we start storing spans for type arguments, turn this
                        // into a suggestion.
                        format!(
                            "replace the {} parameters with concrete {}{}",
                            kinds,
                            kinds_pl,
                            egs.map(|egs| format!(" like `{egs}`")).unwrap_or_default(),
                        ),
                    )
                    .emit();
                }

                tcx.ensure_ok().generics_of(def_id);
                tcx.ensure_ok().type_of(def_id);
                tcx.ensure_ok().predicates_of(def_id);
                if tcx.is_conditionally_const(def_id) {
                    tcx.ensure_ok().explicit_implied_const_bounds(def_id);
                    tcx.ensure_ok().const_conditions(def_id);
                }
                match tcx.def_kind(def_id) {
                    DefKind::Fn => {
                        tcx.ensure_ok().codegen_fn_attrs(def_id);
                        tcx.ensure_ok().fn_sig(def_id);
                        let item = tcx.hir_foreign_item(item);
                        let hir::ForeignItemKind::Fn(sig, ..) = item.kind else { bug!() };
                        check_c_variadic_abi(tcx, sig.decl, abi, item.span);
                    }
                    DefKind::Static { .. } => {
                        tcx.ensure_ok().codegen_fn_attrs(def_id);
                    }
                    _ => (),
                }
            }
            // Doesn't have any hir based checks
            return res;
        }
        DefKind::Closure => {
            // This is guaranteed to be called by metadata encoding,
            // we still call it in wfcheck eagerly to ensure errors in codegen
            // attrs prevent lints from spamming the output.
            tcx.ensure_ok().codegen_fn_attrs(def_id);
            // We do not call `type_of` for closures here as that
            // depends on typecheck and would therefore hide
            // any further errors in case one typeck fails.

            // Only `Node::Item` and `Node::ForeignItem` still have HIR based
            // checks. Returning early here does not miss any checks and
            // avoids this query from having a direct dependency edge on the HIR
            return res;
        }
        DefKind::AssocFn => {
            tcx.ensure_ok().codegen_fn_attrs(def_id);
            tcx.ensure_ok().type_of(def_id);
            tcx.ensure_ok().fn_sig(def_id);
            tcx.ensure_ok().predicates_of(def_id);
            res = res.and(check_associated_item(tcx, def_id));
            let assoc_item = tcx.associated_item(def_id);
            match assoc_item.container {
                ty::AssocContainer::InherentImpl | ty::AssocContainer::TraitImpl(_) => {}
                ty::AssocContainer::Trait => {
                    res = res.and(check_trait_item(tcx, def_id));
                }
            }

            // Only `Node::Item` and `Node::ForeignItem` still have HIR based
            // checks. Returning early here does not miss any checks and
            // avoids this query from having a direct dependency edge on the HIR
            return res;
        }
        DefKind::AssocConst { .. } => {
            tcx.ensure_ok().type_of(def_id);
            tcx.ensure_ok().predicates_of(def_id);
            res = res.and(check_associated_item(tcx, def_id));
            let assoc_item = tcx.associated_item(def_id);
            match assoc_item.container {
                ty::AssocContainer::InherentImpl | ty::AssocContainer::TraitImpl(_) => {}
                ty::AssocContainer::Trait => {
                    res = res.and(check_trait_item(tcx, def_id));
                }
            }

            // Only `Node::Item` and `Node::ForeignItem` still have HIR based
            // checks. Returning early here does not miss any checks and
            // avoids this query from having a direct dependency edge on the HIR
            return res;
        }
        DefKind::AssocTy => {
            tcx.ensure_ok().predicates_of(def_id);
            res = res.and(check_associated_item(tcx, def_id));

            let assoc_item = tcx.associated_item(def_id);
            let has_type = match assoc_item.container {
                ty::AssocContainer::InherentImpl | ty::AssocContainer::TraitImpl(_) => true,
                ty::AssocContainer::Trait => {
                    tcx.ensure_ok().explicit_item_bounds(def_id);
                    tcx.ensure_ok().explicit_item_self_bounds(def_id);
                    if tcx.is_conditionally_const(def_id) {
                        tcx.ensure_ok().explicit_implied_const_bounds(def_id);
                        tcx.ensure_ok().const_conditions(def_id);
                    }
                    res = res.and(check_trait_item(tcx, def_id));
                    assoc_item.defaultness(tcx).has_value()
                }
            };
            if has_type {
                tcx.ensure_ok().type_of(def_id);
            }

            // Only `Node::Item` and `Node::ForeignItem` still have HIR based
            // checks. Returning early here does not miss any checks and
            // avoids this query from having a direct dependency edge on the HIR
            return res;
        }

        // These have no wf checks
        DefKind::AnonConst
        | DefKind::InlineConst
        | DefKind::ExternCrate
        | DefKind::Macro(..)
        | DefKind::Use
        | DefKind::GlobalAsm
        | DefKind::Mod => return res,
        _ => {}
    }
    let node = tcx.hir_node_by_def_id(def_id);
    res.and(match node {
        hir::Node::Crate(_) => bug!("check_well_formed cannot be applied to the crate root"),
        hir::Node::Item(item) => wfcheck::check_item(tcx, item),
        hir::Node::ForeignItem(item) => wfcheck::check_foreign_item(tcx, item),
        _ => unreachable!("{node:?}"),
    })
}

pub(super) fn check_specialization_validity<'tcx>(
    tcx: TyCtxt<'tcx>,
    trait_def: &ty::TraitDef,
    trait_item: ty::AssocItem,
    impl_id: DefId,
    impl_item: DefId,
) {
    let Ok(ancestors) = trait_def.ancestors(tcx, impl_id) else { return };
    let mut ancestor_impls = ancestors.skip(1).filter_map(|parent| {
        if parent.is_from_trait() {
            None
        } else {
            Some((parent, parent.item(tcx, trait_item.def_id)))
        }
    });

    let opt_result = ancestor_impls.find_map(|(parent_impl, parent_item)| {
        match parent_item {
            // Parent impl exists, and contains the parent item we're trying to specialize, but
            // doesn't mark it `default`.
            Some(parent_item) if traits::impl_item_is_final(tcx, &parent_item) => {
                Some(Err(parent_impl.def_id()))
            }

            // Parent impl contains item and makes it specializable.
            Some(_) => Some(Ok(())),

            // Parent impl doesn't mention the item. This means it's inherited from the
            // grandparent. In that case, if parent is a `default impl`, inherited items use the
            // "defaultness" from the grandparent, else they are final.
            None => {
                if tcx.defaultness(parent_impl.def_id()).is_default() {
                    None
                } else {
                    Some(Err(parent_impl.def_id()))
                }
            }
        }
    });

    // If `opt_result` is `None`, we have only encountered `default impl`s that don't contain the
    // item. This is allowed, the item isn't actually getting specialized here.
    let result = opt_result.unwrap_or(Ok(()));

    if let Err(parent_impl) = result {
        if !tcx.is_impl_trait_in_trait(impl_item) {
            let span = tcx.def_span(impl_item);
            let ident = tcx.item_ident(impl_item);

            let err = match tcx.span_of_impl(parent_impl) {
                Ok(sp) => errors::ImplNotMarkedDefault::Ok { span, ident, ok_label: sp },
                Err(cname) => errors::ImplNotMarkedDefault::Err { span, ident, cname },
            };

            tcx.dcx().emit_err(err);
        } else {
            tcx.dcx().delayed_bug(format!("parent item: {parent_impl:?} not marked as default"));
        }
    }
}

fn check_overriding_final_trait_item<'tcx>(
    tcx: TyCtxt<'tcx>,
    trait_item: ty::AssocItem,
    impl_item: ty::AssocItem,
) {
    if trait_item.defaultness(tcx).is_final() {
        tcx.dcx().emit_err(errors::OverridingFinalTraitFunction {
            impl_span: tcx.def_span(impl_item.def_id),
            trait_span: tcx.def_span(trait_item.def_id),
            ident: tcx.item_ident(impl_item.def_id),
        });
    }
}

fn check_impl_items_against_trait<'tcx>(
    tcx: TyCtxt<'tcx>,
    impl_id: LocalDefId,
    impl_trait_header: ty::ImplTraitHeader<'tcx>,
) {
    let trait_ref = impl_trait_header.trait_ref.instantiate_identity().skip_norm_wip();
    // If the trait reference itself is erroneous (so the compilation is going
    // to fail), skip checking the items here -- the `impl_item` table in `tcx`
    // isn't populated for such impls.
    if trait_ref.references_error() {
        return;
    }

    let impl_item_refs = tcx.associated_item_def_ids(impl_id);

    // Negative impls are not expected to have any items
    match impl_trait_header.polarity {
        ty::ImplPolarity::Reservation | ty::ImplPolarity::Positive => {}
        ty::ImplPolarity::Negative => {
            if let [first_item_ref, ..] = *impl_item_refs {
                let first_item_span = tcx.def_span(first_item_ref);
                struct_span_code_err!(
                    tcx.dcx(),
                    first_item_span,
                    E0749,
                    "negative impls cannot have any items"
                )
                .emit();
            }
            return;
        }
    }

    let trait_def = tcx.trait_def(trait_ref.def_id);

    let self_is_guaranteed_unsize_self = tcx.impl_self_is_guaranteed_unsized(impl_id);

    for &impl_item in impl_item_refs {
        let ty_impl_item = tcx.associated_item(impl_item);
        let ty_trait_item = match ty_impl_item.expect_trait_impl() {
            Ok(trait_item_id) => tcx.associated_item(trait_item_id),
            Err(ErrorGuaranteed { .. }) => continue,
        };

        let res = tcx.ensure_result().compare_impl_item(impl_item.expect_local());
        if res.is_ok() {
            match ty_impl_item.kind {
                ty::AssocKind::Fn { .. } => {
                    compare_impl_item::refine::check_refining_return_position_impl_trait_in_trait(
                        tcx,
                        ty_impl_item,
                        ty_trait_item,
                        tcx.impl_trait_ref(ty_impl_item.container_id(tcx))
                            .instantiate_identity()
                            .skip_norm_wip(),
                    );
                }
                ty::AssocKind::Const { .. } => {}
                ty::AssocKind::Type { .. } => {}
            }
        }

        if self_is_guaranteed_unsize_self && tcx.generics_require_sized_self(ty_trait_item.def_id) {
            tcx.emit_node_span_lint(
                rustc_lint_defs::builtin::DEAD_CODE,
                tcx.local_def_id_to_hir_id(ty_impl_item.def_id.expect_local()),
                tcx.def_span(ty_impl_item.def_id),
                errors::UselessImplItem,
            )
        }

        check_specialization_validity(
            tcx,
            trait_def,
            ty_trait_item,
            impl_id.to_def_id(),
            impl_item,
        );

        check_overriding_final_trait_item(tcx, ty_trait_item, ty_impl_item);
    }

    if let Ok(ancestors) = trait_def.ancestors(tcx, impl_id.to_def_id()) {
        // Check for missing items from trait
        let mut missing_items = Vec::new();

        let mut must_implement_one_of: Option<&[Ident]> =
            trait_def.must_implement_one_of.as_deref();

        for &trait_item_id in tcx.associated_item_def_ids(trait_ref.def_id) {
            let leaf_def = ancestors.leaf_def(tcx, trait_item_id);

            let is_implemented = leaf_def
                .as_ref()
                .is_some_and(|node_item| node_item.item.defaultness(tcx).has_value());

            if !is_implemented
                && tcx.defaultness(impl_id).is_final()
                // unsized types don't need to implement methods that have `Self: Sized` bounds.
                && !(self_is_guaranteed_unsize_self && tcx.generics_require_sized_self(trait_item_id))
            {
                missing_items.push(tcx.associated_item(trait_item_id));
            }

            // true if this item is specifically implemented in this impl
            let is_implemented_here =
                leaf_def.as_ref().is_some_and(|node_item| !node_item.defining_node.is_from_trait());

            if !is_implemented_here {
                let full_impl_span = tcx.hir_span_with_body(tcx.local_def_id_to_hir_id(impl_id));
                match tcx.eval_default_body_stability(trait_item_id, full_impl_span) {
                    // When the feature `pin_ergonomics` is disabled, we report `Drop::drop` is missing,
                    // instead of `Drop::drop` is unstable that might be confusing.
                    EvalResult::Deny { .. }
                        if !tcx.features().pin_ergonomics()
                            && tcx.is_lang_item(trait_ref.def_id, hir::LangItem::Drop)
                            && tcx.item_name(trait_item_id) == sym::drop =>
                    {
                        missing_items.push(tcx.associated_item(trait_item_id));
                    }
                    EvalResult::Deny { feature, reason, issue, .. } => default_body_is_unstable(
                        tcx,
                        full_impl_span,
                        trait_item_id,
                        feature,
                        reason,
                        issue,
                    ),

                    // Unmarked default bodies are considered stable (at least for now).
                    EvalResult::Allow | EvalResult::Unmarked => {}
                }
            }

            if let Some(required_items) = &must_implement_one_of {
                if is_implemented_here {
                    let trait_item = tcx.associated_item(trait_item_id);
                    if required_items.contains(&trait_item.ident(tcx)) {
                        must_implement_one_of = None;
                    }
                }
            }

            if let Some(leaf_def) = &leaf_def
                && !leaf_def.is_final()
                && let def_id = leaf_def.item.def_id
                && tcx.impl_method_has_trait_impl_trait_tys(def_id)
            {
                let def_kind = tcx.def_kind(def_id);
                let descr = tcx.def_kind_descr(def_kind, def_id);
                let (msg, feature) = if tcx.asyncness(def_id).is_async() {
                    (
                        format!("async {descr} in trait cannot be specialized"),
                        "async functions in traits",
                    )
                } else {
                    (
                        format!(
                            "{descr} with return-position `impl Trait` in trait cannot be specialized"
                        ),
                        "return position `impl Trait` in traits",
                    )
                };
                tcx.dcx()
                    .struct_span_err(tcx.def_span(def_id), msg)
                    .with_note(format!(
                        "specialization behaves in inconsistent and surprising ways with \
                        {feature}, and for now is disallowed"
                    ))
                    .emit();
            }
        }

        if !missing_items.is_empty() {
            let full_impl_span = tcx.hir_span_with_body(tcx.local_def_id_to_hir_id(impl_id));
            missing_items_err(tcx, impl_id, &missing_items, full_impl_span);
        }

        if let Some(missing_items) = must_implement_one_of {
            let attr_span = find_attr!(tcx, trait_ref.def_id, RustcMustImplementOneOf {attr_span, ..} => *attr_span);

            missing_items_must_implement_one_of_err(
                tcx,
                tcx.def_span(impl_id),
                missing_items,
                attr_span,
            );
        }
    }
}

fn check_simd(tcx: TyCtxt<'_>, sp: Span, def_id: LocalDefId) {
    let t = tcx.type_of(def_id).instantiate_identity().skip_norm_wip();
    if let ty::Adt(def, args) = t.kind()
        && def.is_struct()
    {
        let fields = &def.non_enum_variant().fields;
        if fields.is_empty() {
            struct_span_code_err!(tcx.dcx(), sp, E0075, "SIMD vector cannot be empty").emit();
            return;
        }

        let array_field = &fields[FieldIdx::ZERO];
        let array_ty = array_field.ty(tcx, args).skip_norm_wip();
        let ty::Array(element_ty, len_const) = array_ty.kind() else {
            struct_span_code_err!(
                tcx.dcx(),
                sp,
                E0076,
                "SIMD vector's only field must be an array"
            )
            .with_span_label(tcx.def_span(array_field.did), "not an array")
            .emit();
            return;
        };

        if let Some(second_field) = fields.get(FieldIdx::ONE) {
            struct_span_code_err!(tcx.dcx(), sp, E0075, "SIMD vector cannot have multiple fields")
                .with_span_label(tcx.def_span(second_field.did), "excess field")
                .emit();
            return;
        }

        // FIXME(repr_simd): This check is nice, but perhaps unnecessary due to the fact
        // we do not expect users to implement their own `repr(simd)` types. If they could,
        // this check is easily side-steppable by hiding the const behind normalization.
        // The consequence is that the error is, in general, only observable post-mono.
        if let Some(len) = len_const.try_to_target_usize(tcx) {
            if len == 0 {
                struct_span_code_err!(tcx.dcx(), sp, E0075, "SIMD vector cannot be empty").emit();
                return;
            } else if len > MAX_SIMD_LANES {
                struct_span_code_err!(
                    tcx.dcx(),
                    sp,
                    E0075,
                    "SIMD vector cannot have more than {MAX_SIMD_LANES} elements",
                )
                .emit();
                return;
            }
        }

        // Check that we use types valid for use in the lanes of a SIMD "vector register"
        // These are scalar types which directly match a "machine" type
        // Yes: Integers, floats, "thin" pointers
        // No: char, "wide" pointers, compound types
        match element_ty.kind() {
            ty::Param(_) => (), // pass struct<T>([T; 4]) through, let monomorphization catch errors
            ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::RawPtr(_, _) => (), // struct([u8; 4]) is ok
            _ => {
                struct_span_code_err!(
                    tcx.dcx(),
                    sp,
                    E0077,
                    "SIMD vector element type should be a \
                        primitive scalar (integer/float/pointer) type"
                )
                .emit();
                return;
            }
        }
    }
}

#[tracing::instrument(skip(tcx), level = "debug")]
fn check_scalable_vector(tcx: TyCtxt<'_>, span: Span, def_id: LocalDefId, scalable: ScalableElt) {
    let ty = tcx.type_of(def_id).instantiate_identity().skip_norm_wip();
    let ty::Adt(def, args) = ty.kind() else { return };
    if !def.is_struct() {
        tcx.dcx().delayed_bug("`rustc_scalable_vector` applied to non-struct");
        return;
    }

    let fields = &def.non_enum_variant().fields;
    match scalable {
        ScalableElt::ElementCount(..) if fields.is_empty() => {
            let mut err =
                tcx.dcx().struct_span_err(span, "scalable vectors must have a single field");
            err.help("scalable vector types' only field must be a primitive scalar type");
            err.emit();
            return;
        }
        ScalableElt::ElementCount(..) if fields.len() >= 2 => {
            tcx.dcx().struct_span_err(span, "scalable vectors cannot have multiple fields").emit();
            return;
        }
        ScalableElt::Container if fields.is_empty() => {
            let mut err = tcx
                .dcx()
                .struct_span_err(span, "scalable vector tuples must have at least one field");
            err.help("tuples of scalable vectors can only contain multiple of the same scalable vector type");
            err.emit();
            return;
        }
        ScalableElt::Container if fields.len() > 8 => {
            let mut err = tcx
                .dcx()
                .struct_span_err(span, "scalable vector tuples can have at most eight fields");
            err.help("tuples of scalable vectors can only contain multiple of the same scalable vector type");
            err.emit();
            return;
        }
        _ => {}
    }

    match scalable {
        ScalableElt::ElementCount(..) => {
            let element_ty = &fields[FieldIdx::ZERO].ty(tcx, args).skip_norm_wip();

            // Check that `element_ty` only uses types valid in the lanes of a scalable vector
            // register: scalar types which directly match a "machine" type - integers, floats and
            // bools
            match element_ty.kind() {
                ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Bool => (),
                _ => {
                    let mut err = tcx.dcx().struct_span_err(
                        span,
                        "element type of a scalable vector must be a primitive scalar",
                    );
                    err.help("only `u*`, `i*`, `f*` and `bool` types are accepted");
                    err.emit();
                }
            }
        }
        ScalableElt::Container => {
            let mut prev_field_ty = None;
            for field in fields.iter() {
                let element_ty = field.ty(tcx, args).skip_norm_wip();
                if let ty::Adt(def, _) = element_ty.kind()
                    && def.repr().scalable()
                {
                    match def
                        .repr()
                        .scalable
                        .expect("`repr().scalable.is_some()` != `repr().scalable()`")
                    {
                        ScalableElt::ElementCount(_) => { /* expected field */ }
                        ScalableElt::Container => {
                            tcx.dcx().span_err(
                                tcx.def_span(field.did),
                                "scalable vector structs cannot contain other scalable vector structs",
                            );
                            break;
                        }
                    }
                } else {
                    tcx.dcx().span_err(
                        tcx.def_span(field.did),
                        "scalable vector structs can only have scalable vector fields",
                    );
                    break;
                }

                if let Some(prev_ty) = prev_field_ty.replace(element_ty)
                    && prev_ty != element_ty
                {
                    tcx.dcx().span_err(
                        tcx.def_span(field.did),
                        "all fields in a scalable vector struct must be the same type",
                    );
                    break;
                }
            }
        }
    }
}

pub(super) fn check_packed(tcx: TyCtxt<'_>, sp: Span, def: ty::AdtDef<'_>) {
    let repr = def.repr();
    if repr.packed() {
        // `#[pin_v2]` on a packed type is unsound: drop glue for a packed type moves an
        // over-aligned field to an aligned location before running its destructor, which would
        // move a structurally pinned field out from under a `Pin<&mut _>` that was handed out.
        if def.is_pin_project() {
            tcx.dcx().emit_err(errors::PinV2OnPacked {
                span: sp,
                pin_v2_span: find_attr!(tcx, def.did(), PinV2(span) => *span),
                adt_name: tcx.item_name(def.did()),
            });
        }
        if let Some(reprs) = find_attr!(tcx, def.did(), Repr { reprs, .. } => reprs) {
            for (r, _) in reprs {
                if let ReprPacked(pack) = r
                    && let Some(repr_pack) = repr.pack
                    && pack != &repr_pack
                {
                    struct_span_code_err!(
                        tcx.dcx(),
                        sp,
                        E0634,
                        "type has conflicting packed representation hints"
                    )
                    .emit();
                }
            }
        }
        if repr.align.is_some() {
            struct_span_code_err!(
                tcx.dcx(),
                sp,
                E0587,
                "type has conflicting packed and align representation hints"
            )
            .emit();
        } else if let Some(def_spans) = check_packed_inner(tcx, def.did(), &mut vec![]) {
            let mut err = struct_span_code_err!(
                tcx.dcx(),
                sp,
                E0588,
                "packed type cannot transitively contain a `#[repr(align)]` type"
            );

            err.span_note(
                tcx.def_span(def_spans[0].0),
                format!("`{}` has a `#[repr(align)]` attribute", tcx.item_name(def_spans[0].0)),
            );

            if def_spans.len() > 2 {
                let mut first = true;
                for (adt_def, span) in def_spans.iter().skip(1).rev() {
                    let ident = tcx.item_name(*adt_def);
                    err.span_note(
                        *span,
                        if first {
                            format!(
                                "`{}` contains a field of type `{}`",
                                tcx.type_of(def.did()).instantiate_identity().skip_norm_wip(),
                                ident
                            )
                        } else {
                            format!("...which contains a field of type `{ident}`")
                        },
                    );
                    first = false;
                }
            }

            err.emit();
        }
    }
}

pub(super) fn check_packed_inner(
    tcx: TyCtxt<'_>,
    def_id: DefId,
    stack: &mut Vec<DefId>,
) -> Option<Vec<(DefId, Span)>> {
    if let ty::Adt(def, args) = tcx.type_of(def_id).instantiate_identity().skip_norm_wip().kind() {
        if def.is_struct() || def.is_union() {
            if def.repr().align.is_some() {
                return Some(vec![(def.did(), DUMMY_SP)]);
            }

            stack.push(def_id);
            for field in &def.non_enum_variant().fields {
                if let ty::Adt(def, _) = field.ty(tcx, args).skip_norm_wip().kind()
                    && !stack.contains(&def.did())
                    && let Some(mut defs) = check_packed_inner(tcx, def.did(), stack)
                {
                    defs.push((def.did(), field.ident(tcx).span));
                    return Some(defs);
                }
            }
            stack.pop();
        }
    }

    None
}

pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>) {
    struct ZeroSizedFieldReprTransparentIncompatibility<'tcx> {
        unsuited: UnsuitedInfo<'tcx>,
    }

    impl<'a, 'tcx> Diagnostic<'a, ()> for ZeroSizedFieldReprTransparentIncompatibility<'tcx> {
        fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> {
            let Self { unsuited } = self;
            let (title, note) = match unsuited.reason {
                UnsuitedReason::NonExhaustive => (
                    "external non-exhaustive types",
                    "is marked with `#[non_exhaustive]`, so it could become non-zero-sized in the future.",
                ),
                UnsuitedReason::PrivateField => (
                    "external types with private fields",
                    "contains private fields, so it could become non-zero-sized in the future.",
                ),
                UnsuitedReason::ReprC => (
                    "`repr(C)` types",
                    "is a `#[repr(C)]` type, so it is not guaranteed to be zero-sized on all targets.",
                ),
            };
            Diag::new(
                dcx,
                level,
                format!("zero-sized fields in `repr(transparent)` cannot contain {title}"),
            )
            .with_note(format!(
                "this field contains `{field_ty}`, which {note}",
                field_ty = unsuited.ty,
            ))
        }
    }

    if !adt.repr().transparent() {
        return;
    }

    if adt.is_union() && !tcx.features().transparent_unions() {
        feature_err(
            &tcx.sess,
            sym::transparent_unions,
            tcx.def_span(adt.did()),
            "transparent unions are unstable",
        )
        .emit();
    }

    if adt.variants().len() != 1 {
        bad_variant_count(tcx, adt, tcx.def_span(adt.did()), adt.did());
        // Don't bother checking the fields.
        return;
    }

    let typing_env = ty::TypingEnv::non_body_analysis(tcx, adt.did());
    // For each field, figure out if it has "trivial" layout (i.e., is a 1-ZST).
    struct FieldInfo<'tcx> {
        span: Span,
        trivial: bool,
        ty: Ty<'tcx>,
    }

    let field_infos = adt.all_fields().map(|field| {
        let ty = field.ty(tcx, GenericArgs::identity_for_item(tcx, field.did)).skip_norm_wip();
        let layout = tcx.layout_of(typing_env.as_query_input(ty));
        // We are currently checking the type this field came from, so it must be local
        let span = tcx.hir_span_if_local(field.did).unwrap();
        let trivial = layout.is_ok_and(|layout| layout.is_1zst());
        FieldInfo { span, trivial, ty }
    });

    let non_trivial_fields = field_infos
        .clone()
        .filter_map(|field| if !field.trivial { Some(field.span) } else { None });
    let non_trivial_count = non_trivial_fields.clone().count();
    if non_trivial_count >= 2 {
        bad_non_zero_sized_fields(
            tcx,
            adt,
            non_trivial_count,
            non_trivial_fields,
            tcx.def_span(adt.did()),
        );
        return;
    }

    // Even some 1-ZST fields are not allowed though, if they have `non_exhaustive` or private
    // fields or `repr(C)`. We call those fields "unsuited".
    struct UnsuitedInfo<'tcx> {
        /// The source of the problem, a type that is found somewhere within the field type.
        ty: Ty<'tcx>,
        reason: UnsuitedReason,
    }
    enum UnsuitedReason {
        NonExhaustive,
        PrivateField,
        ReprC,
    }

    fn check_unsuited<'tcx>(
        tcx: TyCtxt<'tcx>,
        typing_env: ty::TypingEnv<'tcx>,
        ty: Ty<'tcx>,
    ) -> ControlFlow<UnsuitedInfo<'tcx>> {
        // We can encounter projections during traversal, so ensure the type is normalized.
        let ty =
            tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)).unwrap_or(ty);
        match ty.kind() {
            ty::Tuple(list) => list.iter().try_for_each(|t| check_unsuited(tcx, typing_env, t)),
            ty::Array(ty, _) => check_unsuited(tcx, typing_env, *ty),
            ty::Adt(def, args) => {
                if !def.did().is_local() && !find_attr!(tcx, def.did(), RustcPubTransparent(_)) {
                    let non_exhaustive = def.is_variant_list_non_exhaustive()
                        || def.variants().iter().any(ty::VariantDef::is_field_list_non_exhaustive);
                    let has_priv = def.all_fields().any(|f| !f.vis.is_public());
                    if non_exhaustive || has_priv {
                        return ControlFlow::Break(UnsuitedInfo {
                            ty,
                            reason: if non_exhaustive {
                                UnsuitedReason::NonExhaustive
                            } else {
                                UnsuitedReason::PrivateField
                            },
                        });
                    }
                }
                if def.repr().c() {
                    return ControlFlow::Break(UnsuitedInfo { ty, reason: UnsuitedReason::ReprC });
                }
                def.all_fields()
                    .map(|field| field.ty(tcx, args).skip_norm_wip())
                    .try_for_each(|t| check_unsuited(tcx, typing_env, t))
            }
            _ => ControlFlow::Continue(()),
        }
    }

    let mut prev_unsuited_1zst = false;
    for field in field_infos {
        if field.trivial
            && let Some(unsuited) = check_unsuited(tcx, typing_env, field.ty).break_value()
        {
            // If there are any non-trivial fields, then there can be no non-exhaustive 1-zsts.
            // Otherwise, it's only an issue if there's >1 non-exhaustive 1-zst.
            if non_trivial_count > 0 || prev_unsuited_1zst {
                tcx.emit_node_span_lint(
                    REPR_TRANSPARENT_NON_ZST_FIELDS,
                    tcx.local_def_id_to_hir_id(adt.did().expect_local()),
                    field.span,
                    ZeroSizedFieldReprTransparentIncompatibility { unsuited },
                );
            } else {
                prev_unsuited_1zst = true;
            }
        }
    }
}

#[allow(trivial_numeric_casts)]
fn check_enum(tcx: TyCtxt<'_>, def_id: LocalDefId) {
    let def = tcx.adt_def(def_id);
    def.destructor(tcx); // force the destructor to be evaluated

    if def.variants().is_empty() {
        find_attr!(tcx, def_id, Repr { reprs, first_span } => {
            struct_span_code_err!(
                tcx.dcx(),
                reprs.first().map(|repr| repr.1).unwrap_or(*first_span),
                E0084,
                "unsupported representation for zero-variant enum"
            )
            .with_span_label(tcx.def_span(def_id), "zero-variant enum")
            .emit();
        });
    }

    for v in def.variants() {
        if let ty::VariantDiscr::Explicit(discr_def_id) = v.discr {
            tcx.ensure_ok().typeck(discr_def_id.expect_local());
        }
    }

    if def.repr().int.is_none() {
        let is_unit = |var: &ty::VariantDef| matches!(var.ctor_kind(), Some(CtorKind::Const));
        let get_disr = |var: &ty::VariantDef| match var.discr {
            ty::VariantDiscr::Explicit(disr) => Some(disr),
            ty::VariantDiscr::Relative(_) => None,
        };

        let non_unit = def.variants().iter().find(|var| !is_unit(var));
        let disr_unit =
            def.variants().iter().filter(|var| is_unit(var)).find_map(|var| get_disr(var));
        let disr_non_unit =
            def.variants().iter().filter(|var| !is_unit(var)).find_map(|var| get_disr(var));

        if disr_non_unit.is_some() || (disr_unit.is_some() && non_unit.is_some()) {
            let mut err = struct_span_code_err!(
                tcx.dcx(),
                tcx.def_span(def_id),
                E0732,
                "`#[repr(inttype)]` must be specified for enums with explicit discriminants and non-unit variants"
            );
            if let Some(disr_non_unit) = disr_non_unit {
                err.span_label(
                    tcx.def_span(disr_non_unit),
                    "explicit discriminant on non-unit variant specified here",
                );
            } else {
                err.span_label(
                    tcx.def_span(disr_unit.unwrap()),
                    "explicit discriminant specified here",
                );
                err.span_label(
                    tcx.def_span(non_unit.unwrap().def_id),
                    "non-unit discriminant declared here",
                );
            }
            err.emit();
        }
    }

    detect_discriminant_duplicate(tcx, def);
    check_transparent(tcx, def);
}

/// Part of enum check. Given the discriminants of an enum, errors if two or more discriminants are equal
fn detect_discriminant_duplicate<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>) {
    // Helper closure to reduce duplicate code. This gets called everytime we detect a duplicate.
    // Here `idx` refers to the order of which the discriminant appears, and its index in `vs`
    let report = |dis: Discr<'tcx>, idx, err: &mut Diag<'_>| {
        let var = adt.variant(idx); // HIR for the duplicate discriminant
        let (span, display_discr) = match var.discr {
            ty::VariantDiscr::Explicit(discr_def_id) => {
                // In the case the discriminant is both a duplicate and overflowed, let the user know
                if let hir::Node::AnonConst(expr) =
                    tcx.hir_node_by_def_id(discr_def_id.expect_local())
                    && let hir::ExprKind::Lit(lit) = &tcx.hir_body(expr.body).value.kind
                    && let rustc_ast::LitKind::Int(lit_value, _int_kind) = &lit.node
                    && *lit_value != dis.val
                {
                    (tcx.def_span(discr_def_id), format!("`{dis}` (overflowed from `{lit_value}`)"))
                } else {
                    // Otherwise, format the value as-is
                    (tcx.def_span(discr_def_id), format!("`{dis}`"))
                }
            }
            // This should not happen.
            ty::VariantDiscr::Relative(0) => (tcx.def_span(var.def_id), format!("`{dis}`")),
            ty::VariantDiscr::Relative(distance_to_explicit) => {
                // At this point we know this discriminant is a duplicate, and was not explicitly
                // assigned by the user. Here we iterate backwards to fetch the HIR for the last
                // explicitly assigned discriminant, and letting the user know that this was the
                // increment startpoint, and how many steps from there leading to the duplicate
                if let Some(explicit_idx) =
                    idx.as_u32().checked_sub(distance_to_explicit).map(VariantIdx::from_u32)
                {
                    let explicit_variant = adt.variant(explicit_idx);
                    let ve_ident = var.name;
                    let ex_ident = explicit_variant.name;
                    let sp = if distance_to_explicit > 1 { "variants" } else { "variant" };

                    err.span_label(
                        tcx.def_span(explicit_variant.def_id),
                        format!(
                            "discriminant for `{ve_ident}` incremented from this startpoint \
                            (`{ex_ident}` + {distance_to_explicit} {sp} later \
                             => `{ve_ident}` = {dis})"
                        ),
                    );
                }

                (tcx.def_span(var.def_id), format!("`{dis}`"))
            }
        };

        err.span_label(span, format!("{display_discr} assigned here"));
    };

    let mut discrs = adt.discriminants(tcx).collect::<Vec<_>>();

    // Here we loop through the discriminants, comparing each discriminant to another.
    // When a duplicate is detected, we instantiate an error and point to both
    // initial and duplicate value. The duplicate discriminant is then discarded by swapping
    // it with the last element and decrementing the `vec.len` (which is why we have to evaluate
    // `discrs.len()` anew every iteration, and why this could be tricky to do in a functional
    // style as we are mutating `discrs` on the fly).
    let mut i = 0;
    while i < discrs.len() {
        let var_i_idx = discrs[i].0;
        let mut error: Option<Diag<'_, _>> = None;

        let mut o = i + 1;
        while o < discrs.len() {
            let var_o_idx = discrs[o].0;

            if discrs[i].1.val == discrs[o].1.val {
                let err = error.get_or_insert_with(|| {
                    let mut ret = struct_span_code_err!(
                        tcx.dcx(),
                        tcx.def_span(adt.did()),
                        E0081,
                        "discriminant value `{}` assigned more than once",
                        discrs[i].1,
                    );

                    report(discrs[i].1, var_i_idx, &mut ret);

                    ret
                });

                report(discrs[o].1, var_o_idx, err);

                // Safe to unwrap here, as we wouldn't reach this point if `discrs` was empty
                discrs[o] = *discrs.last().unwrap();
                discrs.pop();
            } else {
                o += 1;
            }
        }

        if let Some(e) = error {
            e.emit();
        }

        i += 1;
    }
}

fn check_type_alias_type_params_are_used<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) {
    let generics = tcx.generics_of(def_id);
    if generics.own_counts().types == 0 {
        return;
    }

    let ty = tcx.type_of(def_id).instantiate_identity().skip_norm_wip();
    if ty.references_error() {
        // If there is already another error, do not emit an error for not using a type parameter.
        return;
    }

    // Lazily calculated because it is only needed in case of an error.
    let bounded_params = LazyCell::new(|| {
        tcx.explicit_predicates_of(def_id)
            .predicates
            .iter()
            .filter_map(|(predicate, span)| {
                let bounded_ty = match predicate.kind().skip_binder() {
                    ty::ClauseKind::Trait(pred) => pred.trait_ref.self_ty(),
                    ty::ClauseKind::TypeOutlives(pred) => pred.0,
                    _ => return None,
                };
                if let ty::Param(param) = bounded_ty.kind() {
                    Some((param.index, span))
                } else {
                    None
                }
            })
            // FIXME: This assumes that elaborated `Sized` bounds come first (which does hold at the
            // time of writing). This is a bit fragile since we later use the span to detect elaborated
            // `Sized` bounds. If they came last for example, this would break `Trait + /*elab*/Sized`
            // since it would overwrite the span of the user-written bound. This could be fixed by
            // folding the spans with `Span::to` which requires a bit of effort I think.
            .collect::<FxIndexMap<_, _>>()
    });

    let mut params_used = DenseBitSet::new_empty(generics.own_params.len());
    for leaf in ty.walk() {
        if let GenericArgKind::Type(leaf_ty) = leaf.kind()
            && let ty::Param(param) = leaf_ty.kind()
        {
            debug!("found use of ty param {:?}", param);
            params_used.insert(param.index);
        }
    }

    for param in &generics.own_params {
        if !params_used.contains(param.index)
            && let ty::GenericParamDefKind::Type { .. } = param.kind
        {
            let span = tcx.def_span(param.def_id);
            let param_name = Ident::new(param.name, span);

            // The corresponding predicates are post-`Sized`-elaboration. Therefore we
            // * check for emptiness to detect lone user-written `?Sized` bounds
            // * compare the param span to the pred span to detect lone user-written `Sized` bounds
            let has_explicit_bounds = bounded_params.is_empty()
                || (*bounded_params).get(&param.index).is_some_and(|&&pred_sp| pred_sp != span);
            let const_param_help = !has_explicit_bounds;

            let mut diag = tcx.dcx().create_err(errors::UnusedGenericParameter {
                span,
                param_name,
                param_def_kind: tcx.def_descr(param.def_id),
                help: errors::UnusedGenericParameterHelp::TyAlias { param_name },
                usage_spans: vec![],
                const_param_help,
            });
            diag.code(E0091);
            diag.emit();
        }
    }
}

/// Emit an error for recursive opaque types.
///
/// If this is a return `impl Trait`, find the item's return expressions and point at them. For
/// direct recursion this is enough, but for indirect recursion also point at the last intermediary
/// `impl Trait`.
///
/// If all the return expressions evaluate to `!`, then we explain that the error will go away
/// after changing it. This can happen when a user uses `panic!()` or similar as a placeholder.
fn opaque_type_cycle_error(tcx: TyCtxt<'_>, opaque_def_id: LocalDefId) -> ErrorGuaranteed {
    let span = tcx.def_span(opaque_def_id);
    let mut err = struct_span_code_err!(tcx.dcx(), span, E0720, "cannot resolve opaque type");

    let mut label = false;
    if let Some((def_id, visitor)) = get_owner_return_paths(tcx, opaque_def_id) {
        let typeck_results = tcx.typeck(def_id);
        if visitor
            .returns
            .iter()
            .filter_map(|expr| typeck_results.node_type_opt(expr.hir_id))
            .all(|ty| matches!(ty.kind(), ty::Never))
        {
            let spans = visitor
                .returns
                .iter()
                .filter(|expr| typeck_results.node_type_opt(expr.hir_id).is_some())
                .map(|expr| expr.span)
                .collect::<Vec<Span>>();
            let span_len = spans.len();
            if span_len == 1 {
                err.span_label(spans[0], "this returned value is of `!` type");
            } else {
                let mut multispan: MultiSpan = spans.clone().into();
                for span in spans {
                    multispan.push_span_label(span, "this returned value is of `!` type");
                }
                err.span_note(multispan, "these returned values have a concrete \"never\" type");
            }
            err.help("this error will resolve once the item's body returns a concrete type");
        } else {
            let mut seen = FxHashSet::default();
            seen.insert(span);
            err.span_label(span, "recursive opaque type");
            label = true;
            for (sp, ty) in visitor
                .returns
                .iter()
                .filter_map(|e| typeck_results.node_type_opt(e.hir_id).map(|t| (e.span, t)))
                .filter(|(_, ty)| !matches!(ty.kind(), ty::Never))
            {
                #[derive(Default)]
                struct OpaqueTypeCollector {
                    opaques: Vec<DefId>,
                    closures: Vec<DefId>,
                }
                impl<'tcx> ty::TypeVisitor<TyCtxt<'tcx>> for OpaqueTypeCollector {
                    fn visit_ty(&mut self, t: Ty<'tcx>) {
                        match *t.kind() {
                            ty::Alias(ty::AliasTy { kind: ty::Opaque { def_id: def }, .. }) => {
                                self.opaques.push(def);
                            }
                            ty::Closure(def_id, ..) | ty::Coroutine(def_id, ..) => {
                                self.closures.push(def_id);
                                t.super_visit_with(self);
                            }
                            _ => t.super_visit_with(self),
                        }
                    }
                }

                let mut visitor = OpaqueTypeCollector::default();
                ty.visit_with(&mut visitor);
                for def_id in visitor.opaques {
                    let ty_span = tcx.def_span(def_id);
                    if !seen.contains(&ty_span) {
                        let descr = if ty.is_opaque() { "opaque " } else { "" };
                        err.span_label(ty_span, format!("returning this {descr}type `{ty}`"));
                        seen.insert(ty_span);
                    }
                    err.span_label(sp, format!("returning here with type `{ty}`"));
                }

                for closure_def_id in visitor.closures {
                    let Some(closure_local_did) = closure_def_id.as_local() else {
                        continue;
                    };
                    let typeck_results = tcx.typeck(closure_local_did);

                    let mut label_match = |ty: Ty<'_>, span| {
                        for arg in ty.walk() {
                            if let ty::GenericArgKind::Type(ty) = arg.kind()
                                && let ty::Alias(ty::AliasTy {
                                    kind: ty::Opaque { def_id: captured_def_id },
                                    ..
                                }) = *ty.kind()
                                && captured_def_id == opaque_def_id.to_def_id()
                            {
                                err.span_label(
                                    span,
                                    format!(
                                        "{} captures itself here",
                                        tcx.def_descr(closure_def_id)
                                    ),
                                );
                            }
                        }
                    };

                    // Label any closure upvars that capture the opaque
                    for capture in typeck_results.closure_min_captures_flattened(closure_local_did)
                    {
                        label_match(capture.place.ty(), capture.get_path_span(tcx));
                    }
                    // Label any coroutine locals that capture the opaque
                    if tcx.is_coroutine(closure_def_id)
                        && let Some(coroutine_layout) = tcx.mir_coroutine_witnesses(closure_def_id)
                    {
                        for interior_ty in &coroutine_layout.field_tys {
                            label_match(interior_ty.ty, interior_ty.source_info.span);
                        }
                    }
                }
            }
        }
    }
    if !label {
        err.span_label(span, "cannot resolve opaque type");
    }
    err.emit()
}

pub(super) fn check_coroutine_obligations(
    tcx: TyCtxt<'_>,
    def_id: LocalDefId,
) -> Result<(), ErrorGuaranteed> {
    debug_assert!(!tcx.is_typeck_child(def_id.to_def_id()));

    let typeck_results = tcx.typeck(def_id);
    let param_env = tcx.param_env(def_id);

    debug!(?typeck_results.coroutine_stalled_predicates);

    let mode = if tcx.next_trait_solver_globally() {
        // This query is conceptually between HIR typeck and
        // MIR borrowck. We use the opaque types defined by HIR
        // and ignore region constraints.
        TypingMode::borrowck(tcx, def_id)
    } else {
        TypingMode::analysis_in_body(tcx, def_id)
    };

    // Typeck writeback gives us predicates with their regions erased.
    // We only need to check the goals while ignoring lifetimes to give good
    // error message and to avoid breaking the assumption of `mir_borrowck`
    // that all obligations already hold modulo regions.
    let infcx = tcx.infer_ctxt().ignoring_regions().build(mode);

    let ocx = ObligationCtxt::new_with_diagnostics(&infcx);
    for (predicate, cause) in &typeck_results.coroutine_stalled_predicates {
        ocx.register_obligation(Obligation::new(tcx, cause.clone(), param_env, *predicate));
    }

    let errors = ocx.evaluate_obligations_error_on_ambiguity();
    debug!(?errors);
    if !errors.is_empty() {
        return Err(infcx.err_ctxt().report_fulfillment_errors(errors));
    }

    if !tcx.next_trait_solver_globally() {
        // Check that any hidden types found when checking these stalled coroutine obligations
        // are valid.
        for (key, ty) in infcx.take_opaque_types() {
            let hidden_type = infcx.resolve_vars_if_possible(ty);
            let key = infcx.resolve_vars_if_possible(key);
            sanity_check_found_hidden_type(tcx, key, hidden_type)?;
        }
    } else {
        // We're not checking region constraints here, so we can simply drop the
        // added opaque type uses in `TypingMode::Borrowck`.
        let _ = infcx.take_opaque_types();
    }

    Ok(())
}

pub(super) fn check_potentially_region_dependent_goals<'tcx>(
    tcx: TyCtxt<'tcx>,
    def_id: LocalDefId,
) -> Result<(), ErrorGuaranteed> {
    if !tcx.next_trait_solver_globally() {
        return Ok(());
    }
    let typeck_results = tcx.typeck(def_id);
    let param_env = tcx.param_env(def_id);

    // We use `TypingMode::Borrowck` as we want to use the opaque types computed by HIR typeck.
    let typing_mode = TypingMode::borrowck(tcx, def_id);
    let infcx = tcx.infer_ctxt().ignoring_regions().build(typing_mode);
    let ocx = ObligationCtxt::new_with_diagnostics(&infcx);
    for (predicate, cause) in &typeck_results.potentially_region_dependent_goals {
        let predicate = fold_regions(tcx, *predicate, |_, _| {
            infcx.next_region_var(RegionVariableOrigin::Misc(cause.span))
        });
        ocx.register_obligation(Obligation::new(tcx, cause.clone(), param_env, predicate));
    }

    let errors = ocx.evaluate_obligations_error_on_ambiguity();
    debug!(?errors);
    if errors.is_empty() { Ok(()) } else { Err(infcx.err_ctxt().report_fulfillment_errors(errors)) }
}
