use core::cmp::Ordering;
use core::fmt::{Debug, Formatter, Result as FmtResult};
use core::hash::{Hash, Hasher};
use core::marker::PhantomData;
use core::mem::{transmute_copy, ManuallyDrop};
use crate::{static_type_eq, type_eq, type_eq_ignore_lifetimes, LifetimeFree, TypeFn};
pub struct Specialization<T1, T2>(PhantomData<T1>, PhantomData<T2>)
where
T1: ?Sized,
T2: ?Sized;
impl<T1, T2> Specialization<T1, T2>
where
T1: ?Sized,
T2: ?Sized,
{
#[inline]
#[must_use]
pub fn try_new() -> Option<Self>
where
T2: LifetimeFree,
{
type_eq::<T1, T2>().then_some(unsafe { Self::new_unchecked() })
}
#[inline]
#[must_use]
pub fn try_new_static() -> Option<Self>
where
T1: 'static,
T2: 'static,
{
static_type_eq::<T1, T2>().then_some(unsafe { Self::new_unchecked() })
}
#[inline]
#[must_use]
pub unsafe fn try_new_ignore_lifetimes() -> Option<Self> {
type_eq_ignore_lifetimes::<T1, T2>().then_some(unsafe { Self::new_unchecked() })
}
#[inline]
#[must_use]
pub const unsafe fn new_unchecked() -> Self {
Self(PhantomData, PhantomData)
}
#[inline]
#[must_use]
pub const fn rev(&self) -> Specialization<T2, T1> {
Specialization(PhantomData, PhantomData)
}
#[inline]
#[must_use]
pub const fn map<M>(
&self,
) -> Specialization<<M as TypeFn<T1>>::Output, <M as TypeFn<T2>>::Output>
where
M: TypeFn<T1> + TypeFn<T2>,
{
Specialization(PhantomData, PhantomData)
}
#[inline]
pub fn specialize(&self, value: T1) -> T2
where
T1: Sized,
T2: Sized,
{
unsafe { transmute_copy::<T1, T2>(&ManuallyDrop::new(value)) }
}
#[inline]
pub const fn specialize_ref<'a>(&self, value: &'a T1) -> &'a T2 {
unsafe { transmute_copy::<&'a T1, &'a T2>(&value) }
}
#[inline]
pub fn specialize_mut<'a>(&self, value: &'a mut T1) -> &'a mut T2 {
unsafe { transmute_copy::<&'a mut T1, &'a mut T2>(&value) }
}
}
impl<T1, T2> Copy for Specialization<T1, T2>
where
T1: ?Sized,
T2: ?Sized,
{
}
impl<T1, T2> Clone for Specialization<T1, T2>
where
T1: ?Sized,
T2: ?Sized,
{
#[inline]
fn clone(&self) -> Self {
*self
}
}
impl<T1, T2> Eq for Specialization<T1, T2>
where
T1: ?Sized,
T2: ?Sized,
{
}
impl<T1, T2> PartialEq for Specialization<T1, T2>
where
T1: ?Sized,
T2: ?Sized,
{
#[inline]
fn eq(&self, _: &Self) -> bool {
true
}
}
impl<T1, T2> Ord for Specialization<T1, T2>
where
T1: ?Sized,
T2: ?Sized,
{
#[inline]
fn cmp(&self, _: &Self) -> Ordering {
Ordering::Equal
}
}
impl<T1, T2> PartialOrd for Specialization<T1, T2>
where
T1: ?Sized,
T2: ?Sized,
{
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T1, T2> Hash for Specialization<T1, T2>
where
T1: ?Sized,
T2: ?Sized,
{
#[inline]
fn hash<H: Hasher>(&self, _: &mut H) {}
}
impl<T1, T2> Debug for Specialization<T1, T2>
where
T1: ?Sized,
T2: ?Sized,
{
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
Self(f0, f1) => f
.debug_tuple("Specialization")
.field(&f0)
.field(&f1)
.finish(),
}
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "std")]
use alloc::format;
#[cfg(feature = "std")]
use core::hash::{Hash, Hasher};
#[cfg(feature = "std")]
use std::hash::DefaultHasher;
use crate::{LifetimeFree, Specialization, TypeFn};
#[expect(clippy::arithmetic_side_effects, reason = "okay in tests")]
fn specialized_inc_if_i32_dec_if_u32<T>(value: T) -> T
where
T: LifetimeFree,
{
if let Some(spec) = Specialization::<T, i32>::try_new() {
spec.rev().specialize(spec.specialize(value) + 1)
} else if let Some(spec) = Specialization::<T, u32>::try_new() {
spec.rev().specialize(spec.specialize(value) - 1)
} else {
value
}
}
#[test]
fn test_checked_specialization() {
assert_eq!(specialized_inc_if_i32_dec_if_u32(123_i32), 124);
assert_eq!(specialized_inc_if_i32_dec_if_u32(123_u32), 122);
assert_eq!(specialized_inc_if_i32_dec_if_u32(123_i16), 123);
assert_eq!(specialized_inc_if_i32_dec_if_u32(123_u16), 123);
}
#[test]
fn test_checked_specialization_map() {
#[derive(Eq, PartialEq, Debug)]
struct Wrapper<T>(T);
fn as_wrapped_u32_ref<T>(value: &Wrapper<T>) -> Option<&Wrapper<u32>>
where
T: LifetimeFree,
{
struct MapIntoWrapper;
impl<T> TypeFn<T> for MapIntoWrapper {
type Output = Wrapper<T>;
}
Some(
Specialization::<T, u32>::try_new()?
.map::<MapIntoWrapper>()
.specialize_ref(value),
)
}
assert_eq!(
as_wrapped_u32_ref(&Wrapper(123_u32)),
Some(&Wrapper(123_u32))
);
assert_eq!(as_wrapped_u32_ref(&Wrapper(123_i32)), None);
assert_eq!(as_wrapped_u32_ref(&Wrapper((1, 2, 3, 4))), None);
}
#[cfg(feature = "std")]
#[test]
fn test_checked_specialization_basic_trait_impls() {
#[expect(clippy::unwrap_used, reason = "Okay in tests")]
let spec1 = Specialization::<u32, u32>::try_new().unwrap();
let spec2 = spec1;
#[expect(clippy::clone_on_copy, reason = "Okay in tests")]
let _ = spec1.clone();
assert_eq!(spec1, spec2);
assert!(spec1 <= spec2);
let mut hasher = DefaultHasher::new();
let hash1 = hasher.finish();
spec1.hash(&mut hasher);
let hash2 = hasher.finish();
assert_eq!(hash1, hash2);
assert_eq!(
format!("{spec1:?}"),
"Specialization(PhantomData<u32>, PhantomData<u32>)"
);
}
}