//! Things related to generic args in the next-trait-solver (`GenericArg`, `GenericArgs`, `Term`).
//!
//! Implementations of `GenericArg` and `Term` are pointer-tagged instead of an enum (rustc does
//! the same). This is done to save memory (which also helps speed) - one `GenericArg` is a machine
//! word instead of two, while matching on it is basically as cheap. The implementation for both
//! `GenericArg` and `Term` is shared in [`GenericArgImpl`]. This both simplifies the implementation,
//! as well as enables a noop conversion from `Term` to `GenericArg`.

use std::{hint::unreachable_unchecked, marker::PhantomData, ptr::NonNull};

use arrayvec::ArrayVec;
use hir_def::{GenericDefId, GenericParamId};
use intern::InternedRef;
use rustc_type_ir::{
    ClosureArgs, ConstVid, CoroutineArgs, CoroutineClosureArgs, FallibleTypeFolder,
    GenericTypeVisitable, Interner, TyVid, TypeFoldable, TypeFolder, TypeVisitable, TypeVisitor,
    Variance,
    inherent::{GenericArg as _, GenericsOf, IntoKind, SliceLike, Term as _, Ty as _},
    relate::{Relate, VarianceDiagInfo},
    walk::TypeWalker,
};

use crate::next_solver::{
    ConstInterned, RegionInterned, TyInterned, impl_foldable_for_interned_slice, interned_slice,
};

use super::{
    Const, DbInterner, EarlyParamRegion, ErrorGuaranteed, ParamConst, Region, SolverDefId, Ty,
    generics::Generics,
};

pub type GenericArgKind<'db> = rustc_type_ir::GenericArgKind<DbInterner<'db>>;
pub type TermKind<'db> = rustc_type_ir::TermKind<DbInterner<'db>>;

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
struct GenericArgImpl<'db> {
    /// # Invariant
    ///
    /// Contains an [`InternedRef`] of a [`Ty`], [`Const`] or [`Region`], bit-tagged as per the consts below.
    ptr: NonNull<()>,
    _marker: PhantomData<(Ty<'db>, Const<'db>, Region<'db>)>,
}

// SAFETY: We essentially own the `Ty`, `Const` or `Region`, and they are `Send + Sync`.
unsafe impl Send for GenericArgImpl<'_> {}
unsafe impl Sync for GenericArgImpl<'_> {}

impl<'db> GenericArgImpl<'db> {
    const KIND_MASK: usize = 0b11;
    const PTR_MASK: usize = !Self::KIND_MASK;
    const TY_TAG: usize = 0b00;
    const CONST_TAG: usize = 0b01;
    const REGION_TAG: usize = 0b10;

    #[inline]
    fn new_ty(ty: Ty<'db>) -> Self {
        Self {
            // SAFETY: We create it from an `InternedRef`, and it's never null.
            ptr: unsafe {
                NonNull::new_unchecked(
                    ty.interned
                        .as_raw()
                        .cast::<()>()
                        .cast_mut()
                        .map_addr(|addr| addr | Self::TY_TAG),
                )
            },
            _marker: PhantomData,
        }
    }

    #[inline]
    fn new_const(ty: Const<'db>) -> Self {
        Self {
            // SAFETY: We create it from an `InternedRef`, and it's never null.
            ptr: unsafe {
                NonNull::new_unchecked(
                    ty.interned
                        .as_raw()
                        .cast::<()>()
                        .cast_mut()
                        .map_addr(|addr| addr | Self::CONST_TAG),
                )
            },
            _marker: PhantomData,
        }
    }

    #[inline]
    fn new_region(ty: Region<'db>) -> Self {
        Self {
            // SAFETY: We create it from an `InternedRef`, and it's never null.
            ptr: unsafe {
                NonNull::new_unchecked(
                    ty.interned
                        .as_raw()
                        .cast::<()>()
                        .cast_mut()
                        .map_addr(|addr| addr | Self::REGION_TAG),
                )
            },
            _marker: PhantomData,
        }
    }

    #[inline]
    fn kind(self) -> GenericArgKind<'db> {
        let ptr = self.ptr.as_ptr().map_addr(|addr| addr & Self::PTR_MASK);
        // SAFETY: We can only be created from a `Ty`, a `Const` or a `Region`, and the tag will match.
        unsafe {
            match self.ptr.addr().get() & Self::KIND_MASK {
                Self::TY_TAG => GenericArgKind::Type(Ty {
                    interned: InternedRef::from_raw(ptr.cast::<TyInterned>()),
                }),
                Self::CONST_TAG => GenericArgKind::Const(Const {
                    interned: InternedRef::from_raw(ptr.cast::<ConstInterned>()),
                }),
                Self::REGION_TAG => GenericArgKind::Lifetime(Region {
                    interned: InternedRef::from_raw(ptr.cast::<RegionInterned>()),
                }),
                _ => unreachable_unchecked(),
            }
        }
    }

    #[inline]
    fn term_kind(self) -> TermKind<'db> {
        let ptr = self.ptr.as_ptr().map_addr(|addr| addr & Self::PTR_MASK);
        // SAFETY: We can only be created from a `Ty`, a `Const` or a `Region`, and the tag will match.
        // It is the caller's responsibility (encapsulated within this module) to only call this with
        // `Term`, which cannot be constructed from a `Region`.
        unsafe {
            match self.ptr.addr().get() & Self::KIND_MASK {
                Self::TY_TAG => {
                    TermKind::Ty(Ty { interned: InternedRef::from_raw(ptr.cast::<TyInterned>()) })
                }
                Self::CONST_TAG => TermKind::Const(Const {
                    interned: InternedRef::from_raw(ptr.cast::<ConstInterned>()),
                }),
                _ => unreachable_unchecked(),
            }
        }
    }
}

#[derive(PartialEq, Eq, Hash)]
pub struct StoredGenericArg {
    ptr: GenericArgImpl<'static>,
}

impl Clone for StoredGenericArg {
    #[inline]
    fn clone(&self) -> Self {
        match self.ptr.kind() {
            GenericArgKind::Lifetime(it) => std::mem::forget(it.interned.to_owned()),
            GenericArgKind::Type(it) => std::mem::forget(it.interned.to_owned()),
            GenericArgKind::Const(it) => std::mem::forget(it.interned.to_owned()),
        }
        Self { ptr: self.ptr }
    }
}

impl Drop for StoredGenericArg {
    #[inline]
    fn drop(&mut self) {
        unsafe {
            match self.ptr.kind() {
                GenericArgKind::Lifetime(it) => it.interned.decrement_refcount(),
                GenericArgKind::Type(it) => it.interned.decrement_refcount(),
                GenericArgKind::Const(it) => it.interned.decrement_refcount(),
            }
        }
    }
}

impl StoredGenericArg {
    #[inline]
    fn new(value: GenericArg<'_>) -> Self {
        let result = Self { ptr: GenericArgImpl { ptr: value.ptr.ptr, _marker: PhantomData } };
        // Increase refcount.
        std::mem::forget(result.clone());
        result
    }

    #[inline]
    pub fn as_ref<'db>(&self) -> GenericArg<'db> {
        GenericArg { ptr: self.ptr }
    }
}

impl std::fmt::Debug for StoredGenericArg {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.as_ref().fmt(f)
    }
}

impl<'db> TypeVisitable<DbInterner<'db>> for StoredGenericArg {
    fn visit_with<V: TypeVisitor<DbInterner<'db>>>(&self, visitor: &mut V) -> V::Result {
        self.as_ref().visit_with(visitor)
    }
}

impl<'db> TypeFoldable<DbInterner<'db>> for StoredGenericArg {
    fn try_fold_with<F: FallibleTypeFolder<DbInterner<'db>>>(
        self,
        folder: &mut F,
    ) -> Result<Self, F::Error> {
        Ok(self.as_ref().try_fold_with(folder)?.store())
    }

    fn fold_with<F: TypeFolder<DbInterner<'db>>>(self, folder: &mut F) -> Self {
        self.as_ref().fold_with(folder).store()
    }
}

#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct GenericArg<'db> {
    ptr: GenericArgImpl<'db>,
}

impl<'db> std::fmt::Debug for GenericArg<'db> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.kind() {
            GenericArgKind::Type(t) => std::fmt::Debug::fmt(&t, f),
            GenericArgKind::Lifetime(r) => std::fmt::Debug::fmt(&r, f),
            GenericArgKind::Const(c) => std::fmt::Debug::fmt(&c, f),
        }
    }
}

impl<'db> GenericArg<'db> {
    #[inline]
    pub fn store(self) -> StoredGenericArg {
        StoredGenericArg::new(self)
    }

    #[inline]
    pub fn kind(self) -> GenericArgKind<'db> {
        self.ptr.kind()
    }

    pub fn ty(self) -> Option<Ty<'db>> {
        match self.kind() {
            GenericArgKind::Type(ty) => Some(ty),
            _ => None,
        }
    }

    pub fn expect_ty(self) -> Ty<'db> {
        match self.kind() {
            GenericArgKind::Type(ty) => ty,
            _ => panic!("Expected ty, got {self:?}"),
        }
    }

    pub fn konst(self) -> Option<Const<'db>> {
        match self.kind() {
            GenericArgKind::Const(konst) => Some(konst),
            _ => None,
        }
    }

    pub fn region(self) -> Option<Region<'db>> {
        match self.kind() {
            GenericArgKind::Lifetime(r) => Some(r),
            _ => None,
        }
    }

    #[inline]
    pub(crate) fn expect_region(self) -> Region<'db> {
        match self.kind() {
            GenericArgKind::Lifetime(region) => region,
            _ => panic!("expected a region, got {self:?}"),
        }
    }

    pub fn error_from_id(interner: DbInterner<'db>, id: GenericParamId) -> GenericArg<'db> {
        match id {
            GenericParamId::TypeParamId(_) => Ty::new_error(interner, ErrorGuaranteed).into(),
            GenericParamId::ConstParamId(_) => Const::error(interner).into(),
            GenericParamId::LifetimeParamId(_) => Region::error(interner).into(),
        }
    }

    #[inline]
    pub fn walk(self) -> TypeWalker<DbInterner<'db>> {
        TypeWalker::new(self)
    }
}

impl<'db> From<Term<'db>> for GenericArg<'db> {
    #[inline]
    fn from(value: Term<'db>) -> Self {
        GenericArg { ptr: value.ptr }
    }
}

#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct Term<'db> {
    ptr: GenericArgImpl<'db>,
}

impl<'db> std::fmt::Debug for Term<'db> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.kind() {
            TermKind::Ty(t) => std::fmt::Debug::fmt(&t, f),
            TermKind::Const(c) => std::fmt::Debug::fmt(&c, f),
        }
    }
}

impl<'db> Term<'db> {
    #[inline]
    pub fn kind(self) -> TermKind<'db> {
        self.ptr.term_kind()
    }

    pub fn expect_type(&self) -> Ty<'db> {
        self.as_type().expect("expected a type, but found a const")
    }

    pub fn is_trivially_wf(&self, tcx: DbInterner<'db>) -> bool {
        match self.kind() {
            TermKind::Ty(ty) => ty.is_trivially_wf(tcx),
            TermKind::Const(ct) => ct.is_trivially_wf(),
        }
    }
}

impl<'db> From<Ty<'db>> for GenericArg<'db> {
    #[inline]
    fn from(value: Ty<'db>) -> Self {
        GenericArg { ptr: GenericArgImpl::new_ty(value) }
    }
}

impl<'db> From<Region<'db>> for GenericArg<'db> {
    #[inline]
    fn from(value: Region<'db>) -> Self {
        GenericArg { ptr: GenericArgImpl::new_region(value) }
    }
}

impl<'db> From<Const<'db>> for GenericArg<'db> {
    #[inline]
    fn from(value: Const<'db>) -> Self {
        GenericArg { ptr: GenericArgImpl::new_const(value) }
    }
}

impl<'db> IntoKind for GenericArg<'db> {
    type Kind = GenericArgKind<'db>;

    #[inline]
    fn kind(self) -> Self::Kind {
        self.ptr.kind()
    }
}

impl<'db, V> GenericTypeVisitable<V> for GenericArg<'db>
where
    GenericArgKind<'db>: GenericTypeVisitable<V>,
{
    fn generic_visit_with(&self, visitor: &mut V) {
        self.kind().generic_visit_with(visitor);
    }
}

impl<'db, V> GenericTypeVisitable<V> for Term<'db>
where
    TermKind<'db>: GenericTypeVisitable<V>,
{
    fn generic_visit_with(&self, visitor: &mut V) {
        self.kind().generic_visit_with(visitor);
    }
}

impl<'db> TypeVisitable<DbInterner<'db>> for GenericArg<'db> {
    fn visit_with<V: TypeVisitor<DbInterner<'db>>>(&self, visitor: &mut V) -> V::Result {
        match self.kind() {
            GenericArgKind::Lifetime(it) => it.visit_with(visitor),
            GenericArgKind::Type(it) => it.visit_with(visitor),
            GenericArgKind::Const(it) => it.visit_with(visitor),
        }
    }
}

impl<'db> TypeVisitable<DbInterner<'db>> for Term<'db> {
    fn visit_with<V: TypeVisitor<DbInterner<'db>>>(&self, visitor: &mut V) -> V::Result {
        match self.kind() {
            TermKind::Ty(it) => it.visit_with(visitor),
            TermKind::Const(it) => it.visit_with(visitor),
        }
    }
}

impl<'db> TypeFoldable<DbInterner<'db>> for GenericArg<'db> {
    fn try_fold_with<F: FallibleTypeFolder<DbInterner<'db>>>(
        self,
        folder: &mut F,
    ) -> Result<Self, F::Error> {
        Ok(match self.kind() {
            GenericArgKind::Lifetime(it) => it.try_fold_with(folder)?.into(),
            GenericArgKind::Type(it) => it.try_fold_with(folder)?.into(),
            GenericArgKind::Const(it) => it.try_fold_with(folder)?.into(),
        })
    }

    fn fold_with<F: TypeFolder<DbInterner<'db>>>(self, folder: &mut F) -> Self {
        match self.kind() {
            GenericArgKind::Lifetime(it) => it.fold_with(folder).into(),
            GenericArgKind::Type(it) => it.fold_with(folder).into(),
            GenericArgKind::Const(it) => it.fold_with(folder).into(),
        }
    }
}

impl<'db> TypeFoldable<DbInterner<'db>> for Term<'db> {
    fn try_fold_with<F: FallibleTypeFolder<DbInterner<'db>>>(
        self,
        folder: &mut F,
    ) -> Result<Self, F::Error> {
        Ok(match self.kind() {
            TermKind::Ty(it) => it.try_fold_with(folder)?.into(),
            TermKind::Const(it) => it.try_fold_with(folder)?.into(),
        })
    }

    fn fold_with<F: TypeFolder<DbInterner<'db>>>(self, folder: &mut F) -> Self {
        match self.kind() {
            TermKind::Ty(it) => it.fold_with(folder).into(),
            TermKind::Const(it) => it.fold_with(folder).into(),
        }
    }
}

impl<'db> Relate<DbInterner<'db>> for GenericArg<'db> {
    fn relate<R: rustc_type_ir::relate::TypeRelation<DbInterner<'db>>>(
        relation: &mut R,
        a: Self,
        b: Self,
    ) -> rustc_type_ir::relate::RelateResult<DbInterner<'db>, Self> {
        match (a.kind(), b.kind()) {
            (GenericArgKind::Lifetime(a_lt), GenericArgKind::Lifetime(b_lt)) => {
                Ok(relation.relate(a_lt, b_lt)?.into())
            }
            (GenericArgKind::Type(a_ty), GenericArgKind::Type(b_ty)) => {
                Ok(relation.relate(a_ty, b_ty)?.into())
            }
            (GenericArgKind::Const(a_ct), GenericArgKind::Const(b_ct)) => {
                Ok(relation.relate(a_ct, b_ct)?.into())
            }
            (GenericArgKind::Lifetime(unpacked), x) => {
                unreachable!("impossible case reached: can't relate: {:?} with {:?}", unpacked, x)
            }
            (GenericArgKind::Type(unpacked), x) => {
                unreachable!("impossible case reached: can't relate: {:?} with {:?}", unpacked, x)
            }
            (GenericArgKind::Const(unpacked), x) => {
                unreachable!("impossible case reached: can't relate: {:?} with {:?}", unpacked, x)
            }
        }
    }
}

interned_slice!(
    GenericArgsStorage,
    GenericArgs,
    StoredGenericArgs,
    generic_args,
    GenericArg<'db>,
    GenericArg<'static>,
);
impl_foldable_for_interned_slice!(GenericArgs);

impl<'db> rustc_type_ir::inherent::GenericArg<DbInterner<'db>> for GenericArg<'db> {}

impl<'db> TypeVisitable<DbInterner<'db>> for StoredGenericArgs {
    fn visit_with<V: TypeVisitor<DbInterner<'db>>>(&self, visitor: &mut V) -> V::Result {
        self.as_ref().visit_with(visitor)
    }
}

impl<'db> TypeFoldable<DbInterner<'db>> for StoredGenericArgs {
    fn try_fold_with<F: FallibleTypeFolder<DbInterner<'db>>>(
        self,
        folder: &mut F,
    ) -> Result<Self, F::Error> {
        Ok(self.as_ref().try_fold_with(folder)?.store())
    }

    fn fold_with<F: TypeFolder<DbInterner<'db>>>(self, folder: &mut F) -> Self {
        self.as_ref().fold_with(folder).store()
    }
}

trait GenericArgsBuilder<'db>: AsRef<[GenericArg<'db>]> {
    fn push(&mut self, arg: GenericArg<'db>);
}

impl<'db, const N: usize> GenericArgsBuilder<'db> for ArrayVec<GenericArg<'db>, N> {
    fn push(&mut self, arg: GenericArg<'db>) {
        self.push(arg);
    }
}

impl<'db> GenericArgsBuilder<'db> for Vec<GenericArg<'db>> {
    fn push(&mut self, arg: GenericArg<'db>) {
        self.push(arg);
    }
}

impl<'db> GenericArgs<'db> {
    #[inline(always)]
    fn fill_builder<F>(
        args: &mut impl GenericArgsBuilder<'db>,
        defs: &Generics<'db>,
        mut mk_kind: F,
    ) where
        F: FnMut(u32, GenericParamId, &[GenericArg<'db>]) -> GenericArg<'db>,
    {
        defs.iter_id().enumerate().for_each(|(idx, param_id)| {
            let new_arg = mk_kind(idx as u32, param_id, args.as_ref());
            args.push(new_arg);
        });
    }

    #[cold]
    fn fill_vec_builder<F>(defs: &Generics<'db>, count: usize, mk_kind: F) -> GenericArgs<'db>
    where
        F: FnMut(u32, GenericParamId, &[GenericArg<'db>]) -> GenericArg<'db>,
    {
        let mut args = Vec::with_capacity(count);
        Self::fill_builder(&mut args, defs, mk_kind);
        GenericArgs::new_from_slice(&args)
    }

    /// Creates an `GenericArgs` for generic parameter definitions,
    /// by calling closures to obtain each kind.
    /// The closures get to observe the `GenericArgs` as they're
    /// being built, which can be used to correctly
    /// replace defaults of generic parameters.
    pub fn for_item<F>(
        interner: DbInterner<'db>,
        def_id: SolverDefId,
        mk_kind: F,
    ) -> GenericArgs<'db>
    where
        F: FnMut(u32, GenericParamId, &[GenericArg<'db>]) -> GenericArg<'db>,
    {
        let defs = interner.generics_of(def_id);
        let count = defs.count();

        if count == 0 {
            GenericArgs::default()
        } else if count <= 10 {
            let mut args = ArrayVec::<_, 10>::new();
            Self::fill_builder(&mut args, &defs, mk_kind);
            GenericArgs::new_from_slice(&args)
        } else {
            Self::fill_vec_builder(&defs, count, mk_kind)
        }
    }

    /// Creates an all-error `GenericArgs`.
    pub fn error_for_item(interner: DbInterner<'db>, def_id: SolverDefId) -> GenericArgs<'db> {
        GenericArgs::for_item(interner, def_id, |_, id, _| GenericArg::error_from_id(interner, id))
    }

    /// Like `for_item`, but prefers the default of a parameter if it has any.
    pub fn for_item_with_defaults<F>(
        interner: DbInterner<'db>,
        def_id: GenericDefId,
        mut fallback: F,
    ) -> GenericArgs<'db>
    where
        F: FnMut(u32, GenericParamId, &[GenericArg<'db>]) -> GenericArg<'db>,
    {
        let defaults = interner.db.generic_defaults(def_id);
        Self::for_item(interner, def_id.into(), |idx, id, prev| match defaults.get(idx as usize) {
            Some(default) => default.instantiate(interner, prev).skip_norm_wip(),
            None => fallback(idx, id, prev),
        })
    }

    /// Like `for_item()`, but calls first uses the args from `first`.
    pub fn fill_rest<F>(
        interner: DbInterner<'db>,
        def_id: SolverDefId,
        first: impl IntoIterator<Item = GenericArg<'db>>,
        mut fallback: F,
    ) -> GenericArgs<'db>
    where
        F: FnMut(u32, GenericParamId, &[GenericArg<'db>]) -> GenericArg<'db>,
    {
        let mut iter = first.into_iter();
        Self::for_item(interner, def_id, |idx, id, prev| {
            iter.next().unwrap_or_else(|| fallback(idx, id, prev))
        })
    }

    /// Appends default param values to `first` if needed. Params without default will call `fallback()`.
    pub fn fill_with_defaults<F>(
        interner: DbInterner<'db>,
        def_id: GenericDefId,
        first: impl IntoIterator<Item = GenericArg<'db>>,
        mut fallback: F,
    ) -> GenericArgs<'db>
    where
        F: FnMut(u32, GenericParamId, &[GenericArg<'db>]) -> GenericArg<'db>,
    {
        let defaults = interner.db.generic_defaults(def_id);
        Self::fill_rest(interner, def_id.into(), first, |idx, id, prev| {
            defaults
                .get(idx as usize)
                .map(|default| default.instantiate(interner, prev).skip_norm_wip())
                .unwrap_or_else(|| fallback(idx, id, prev))
        })
    }

    pub fn types(self) -> impl Iterator<Item = Ty<'db>> {
        self.iter().filter_map(|it| it.as_type())
    }

    pub fn consts(self) -> impl Iterator<Item = Const<'db>> {
        self.iter().filter_map(|it| it.as_const())
    }

    pub fn regions(self) -> impl Iterator<Item = Region<'db>> {
        self.iter().filter_map(|it| it.as_region())
    }
}

impl<'db> rustc_type_ir::relate::Relate<DbInterner<'db>> for GenericArgs<'db> {
    fn relate<R: rustc_type_ir::relate::TypeRelation<DbInterner<'db>>>(
        relation: &mut R,
        a: Self,
        b: Self,
    ) -> rustc_type_ir::relate::RelateResult<DbInterner<'db>, Self> {
        GenericArgs::new_from_iter(
            relation.cx(),
            std::iter::zip(a.iter(), b.iter()).map(|(a, b)| {
                relation.relate_with_variance(
                    Variance::Invariant,
                    VarianceDiagInfo::default(),
                    a,
                    b,
                )
            }),
        )
    }
}

impl<'db> rustc_type_ir::inherent::GenericArgs<DbInterner<'db>> for GenericArgs<'db> {
    fn as_closure(self) -> ClosureArgs<DbInterner<'db>> {
        ClosureArgs { args: self }
    }
    fn as_coroutine(self) -> CoroutineArgs<DbInterner<'db>> {
        CoroutineArgs { args: self }
    }
    fn as_coroutine_closure(self) -> CoroutineClosureArgs<DbInterner<'db>> {
        CoroutineClosureArgs { args: self }
    }
    fn rebase_onto(
        self,
        interner: DbInterner<'db>,
        source_def_id: <DbInterner<'db> as rustc_type_ir::Interner>::DefId,
        target: <DbInterner<'db> as rustc_type_ir::Interner>::GenericArgs,
    ) -> <DbInterner<'db> as rustc_type_ir::Interner>::GenericArgs {
        let defs = interner.generics_of(source_def_id);
        interner.mk_args_from_iter(target.iter().chain(self.iter().skip(defs.count())))
    }

    fn identity_for_item(
        interner: DbInterner<'db>,
        def_id: <DbInterner<'db> as rustc_type_ir::Interner>::DefId,
    ) -> <DbInterner<'db> as rustc_type_ir::Interner>::GenericArgs {
        Self::for_item(interner, def_id, |index, kind, _| mk_param(interner, index, kind))
    }

    fn extend_with_error(
        interner: DbInterner<'db>,
        def_id: <DbInterner<'db> as rustc_type_ir::Interner>::DefId,
        original_args: &[<DbInterner<'db> as rustc_type_ir::Interner>::GenericArg],
    ) -> <DbInterner<'db> as rustc_type_ir::Interner>::GenericArgs {
        Self::for_item(interner, def_id, |index, kind, _| {
            if let Some(arg) = original_args.get(index as usize) {
                *arg
            } else {
                error_for_param_kind(kind, interner)
            }
        })
    }
    fn type_at(self, i: usize) -> <DbInterner<'db> as rustc_type_ir::Interner>::Ty {
        self.get(i)
            .and_then(|g| g.as_type())
            .unwrap_or_else(|| Ty::new_error(DbInterner::conjure(), ErrorGuaranteed))
    }

    fn region_at(self, i: usize) -> <DbInterner<'db> as rustc_type_ir::Interner>::Region {
        self.get(i)
            .and_then(|g| g.as_region())
            .unwrap_or_else(|| Region::error(DbInterner::conjure()))
    }

    fn const_at(self, i: usize) -> <DbInterner<'db> as rustc_type_ir::Interner>::Const {
        self.get(i)
            .and_then(|g| g.as_const())
            .unwrap_or_else(|| Const::error(DbInterner::conjure()))
    }

    fn split_closure_args(self) -> rustc_type_ir::ClosureArgsParts<DbInterner<'db>> {
        // FIXME: should use `ClosureSubst` when possible
        match self.as_slice() {
            [parent_args @ .., closure_kind_ty, sig_ty, tupled_upvars_ty] => {
                rustc_type_ir::ClosureArgsParts {
                    parent_args,
                    closure_sig_as_fn_ptr_ty: sig_ty.expect_ty(),
                    closure_kind_ty: closure_kind_ty.expect_ty(),
                    tupled_upvars_ty: tupled_upvars_ty.expect_ty(),
                }
            }
            _ => {
                unreachable!("unexpected closure sig");
            }
        }
    }

    fn split_coroutine_closure_args(
        self,
    ) -> rustc_type_ir::CoroutineClosureArgsParts<DbInterner<'db>> {
        match self.as_slice() {
            [
                parent_args @ ..,
                closure_kind_ty,
                signature_parts_ty,
                tupled_upvars_ty,
                coroutine_captures_by_ref_ty,
            ] => rustc_type_ir::CoroutineClosureArgsParts {
                parent_args,
                closure_kind_ty: closure_kind_ty.expect_ty(),
                signature_parts_ty: signature_parts_ty.expect_ty(),
                tupled_upvars_ty: tupled_upvars_ty.expect_ty(),
                coroutine_captures_by_ref_ty: coroutine_captures_by_ref_ty.expect_ty(),
            },
            _ => panic!("GenericArgs were likely not for a CoroutineClosure."),
        }
    }

    fn split_coroutine_args(self) -> rustc_type_ir::CoroutineArgsParts<DbInterner<'db>> {
        match self.as_slice() {
            [parent_args @ .., kind_ty, resume_ty, yield_ty, return_ty, tupled_upvars_ty] => {
                rustc_type_ir::CoroutineArgsParts {
                    parent_args,
                    kind_ty: kind_ty.expect_ty(),
                    resume_ty: resume_ty.expect_ty(),
                    yield_ty: yield_ty.expect_ty(),
                    return_ty: return_ty.expect_ty(),
                    tupled_upvars_ty: tupled_upvars_ty.expect_ty(),
                }
            }
            _ => panic!("GenericArgs were likely not for a Coroutine."),
        }
    }
}

pub fn mk_param<'db>(interner: DbInterner<'db>, index: u32, id: GenericParamId) -> GenericArg<'db> {
    match id {
        GenericParamId::LifetimeParamId(id) => {
            Region::new_early_param(interner, EarlyParamRegion { index, id }).into()
        }
        GenericParamId::TypeParamId(id) => Ty::new_param(interner, id, index).into(),
        GenericParamId::ConstParamId(id) => {
            Const::new_param(interner, ParamConst { index, id }).into()
        }
    }
}

pub fn error_for_param_kind<'db>(id: GenericParamId, interner: DbInterner<'db>) -> GenericArg<'db> {
    match id {
        GenericParamId::LifetimeParamId(_) => Region::error(interner).into(),
        GenericParamId::TypeParamId(_) => Ty::new_error(interner, ErrorGuaranteed).into(),
        GenericParamId::ConstParamId(_) => Const::error(interner).into(),
    }
}

impl<'db> IntoKind for Term<'db> {
    type Kind = TermKind<'db>;

    #[inline]
    fn kind(self) -> Self::Kind {
        self.ptr.term_kind()
    }
}

impl<'db> From<Ty<'db>> for Term<'db> {
    #[inline]
    fn from(value: Ty<'db>) -> Self {
        Term { ptr: GenericArgImpl::new_ty(value) }
    }
}

impl<'db> From<Const<'db>> for Term<'db> {
    #[inline]
    fn from(value: Const<'db>) -> Self {
        Term { ptr: GenericArgImpl::new_const(value) }
    }
}

impl<'db> Relate<DbInterner<'db>> for Term<'db> {
    fn relate<R: rustc_type_ir::relate::TypeRelation<DbInterner<'db>>>(
        relation: &mut R,
        a: Self,
        b: Self,
    ) -> rustc_type_ir::relate::RelateResult<DbInterner<'db>, Self> {
        match (a.kind(), b.kind()) {
            (TermKind::Ty(a_ty), TermKind::Ty(b_ty)) => Ok(relation.relate(a_ty, b_ty)?.into()),
            (TermKind::Const(a_ct), TermKind::Const(b_ct)) => {
                Ok(relation.relate(a_ct, b_ct)?.into())
            }
            (TermKind::Ty(unpacked), x) => {
                unreachable!("impossible case reached: can't relate: {:?} with {:?}", unpacked, x)
            }
            (TermKind::Const(unpacked), x) => {
                unreachable!("impossible case reached: can't relate: {:?} with {:?}", unpacked, x)
            }
        }
    }
}

impl<'db> rustc_type_ir::inherent::Term<DbInterner<'db>> for Term<'db> {}

#[derive(Clone, Eq, PartialEq, Debug)]
pub enum TermVid {
    Ty(TyVid),
    Const(ConstVid),
}

impl From<TyVid> for TermVid {
    fn from(value: TyVid) -> Self {
        TermVid::Ty(value)
    }
}

impl From<ConstVid> for TermVid {
    fn from(value: ConstVid) -> Self {
        TermVid::Const(value)
    }
}

impl<'db> DbInterner<'db> {
    pub(super) fn mk_args(self, args: &[GenericArg<'db>]) -> GenericArgs<'db> {
        GenericArgs::new_from_slice(args)
    }

    pub(super) fn mk_args_from_iter<I, T>(self, iter: I) -> T::Output
    where
        I: Iterator<Item = T>,
        T: rustc_type_ir::CollectAndApply<GenericArg<'db>, GenericArgs<'db>>,
    {
        T::collect_and_apply(iter, |xs| self.mk_args(xs))
    }
}
