use std::any::{Any, TypeId};
use std::rc::Rc;
pub unsafe trait TidAble<'a>: Tid<'a> {
#[doc(hidden)]
type Static: ?Sized + Any;
}
pub trait TidExt<'a>: Tid<'a> {
fn is<T: Tid<'a>>(&self) -> bool {
self.self_id() == T::id()
}
fn downcast_ref<'b, T: Tid<'a>>(&'b self) -> Option<&'b T> {
if self.is::<T>() {
Some(unsafe { &*(self as *const _ as *const T) })
} else {
None
}
}
fn downcast_mut<'b, T: Tid<'a>>(&'b mut self) -> Option<&'b mut T> {
if self.is::<T>() {
Some(unsafe { &mut *(self as *mut _ as *mut T) })
} else {
None
}
}
fn downcast_rc<T: Tid<'a>>(self: Rc<Self>) -> Result<Rc<T>, Rc<Self>> {
if self.is::<T>() {
unsafe { Ok(Rc::from_raw(Rc::into_raw(self) as *const _)) }
} else {
Err(self)
}
}
fn downcast_box<T: Tid<'a>>(self: Box<Self>) -> Result<Box<T>, Box<Self>> {
if self.is::<T>() {
unsafe { Ok(Box::from_raw(Box::into_raw(self) as *mut _)) }
} else {
Err(self)
}
}
}
impl<'a, X: ?Sized + Tid<'a>> TidExt<'a> for X {}
pub unsafe trait Tid<'a>: 'a {
fn self_id(&self) -> TypeId;
fn id() -> TypeId
where
Self: Sized;
}
unsafe impl<'a, T: ?Sized + TidAble<'a>> Tid<'a> for T {
#[inline]
fn self_id(&self) -> TypeId {
TypeId::of::<T::Static>()
}
#[inline]
fn id() -> TypeId
where
Self: Sized,
{
TypeId::of::<T::Static>()
}
}
#[macro_export]
macro_rules! tid {
($struct: ident) => {
unsafe impl<'a> $crate::TidAble<'a> for $struct {
type Static = $struct;
}
};
($struct: ident < $lt: lifetime >) => {
unsafe impl<'a> $crate::TidAble<'a> for $struct<'a> {
type Static = $struct<'static>;
}
};
(impl <$lt:lifetime $(,$param:ident)*> $tr:ident<$lt2:lifetime> for $($struct: tt)+ ) => {
$crate::tid!{ inner impl <$lt $(,$param)* static> $tr<$lt2> for $($struct)+ }
};
(inner impl <$lt:lifetime $(,$param:ident)* static $( $static_param:ident)* > Tid<$lt2:lifetime> for $($struct: tt)+ ) => {
$crate::tid!{ inner impl <$lt $(,$param)* static $( $static_param)*> TidAble<$lt2> for $($struct)+ }
};
(inner impl <$lt:lifetime $(,$param:ident)* static $( $static_param:ident)* > TidAble<$lt2:lifetime> for $($struct: tt)+ ) => {
const _:() = {
use core::marker::PhantomData;
type __Alias<$lt $(,$param)* $(,$static_param)*> = $crate::before_where!{ $($struct)+ };
pub struct __TypeIdGenerator<$lt $(,$param:?Sized)* $(,$static_param:?Sized)*>
(PhantomData<& $lt ()> $(,PhantomData<$param>)* $(,PhantomData<$static_param>)*);
$crate::impl_block!{
after where { $($struct)+ }
{unsafe impl<$lt $(,$param:$crate::TidAble<$lt>)* $(,$static_param: 'static)* > $crate::TidAble<$lt2> for __Alias<$lt $(,$param)* $(,$static_param)*>}
{
type Static = __TypeIdGenerator<'static $(,$param::Static)* $(,$static_param)*>;
}
}
};
};
(inner impl <$lt:lifetime $(,$param:ident)* static $( $static_param:ident)* > $tr:ident<$lt2:lifetime> for $($struct: tt)+ ) => {
compile_error!{" wrong trait, should be TidAble or Tid "}
};
(temp $(,$param:ident)* static $(,$static_param:ident)* impl <$lt:lifetime , $token:ident : 'static $($tail: tt)+ ) => {
$crate::tid!{ temp $(,$param)* static $(,$static_param)* , $token impl <$lt $($tail)+}
};
(temp $(,$param:ident)* static $(,$static_param:ident)* impl <$lt:lifetime , $token:ident $($tail: tt)+ ) => {
$crate::tid!{ temp $(,$param)* ,$token static $(,$static_param)* impl <$lt $($tail)+ }
};
(temp $(,$param:ident)* static $(,$static_param:ident)* impl <$lt:lifetime> $($tail: tt)+ ) => {
$crate::tid!{ inner impl <$lt $(,$param)* static $( $static_param)* > $($tail)+ }
};
( impl $($tail: tt)+) => {
$crate::tid!{ temp static impl $($tail)+ }
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! before_where {
(inner { $($processed:tt)* } where $($tokens:tt)* ) => { $($processed)* };
(inner { $($processed:tt)* } $token:tt $($tokens:tt)* ) => {
$crate::before_where!(inner { $($processed)* $token } $($tokens)*)
};
(inner { $($processed:tt)* } ) => { $($processed)* };
($($tokens:tt)*) => {$crate::before_where!(inner {} $($tokens)*)};
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_block {
(
after where {}
{$($imp:tt)*}
{ $($block:tt)* }
) => {
$($imp)*
{
$($block)*
}
};
(
after where { where $($bounds:tt)* }
{$($imp:tt)*}
{ $($block:tt)* }
) => {
$($imp)*
where $($bounds)*
{
$($block)*
}
};
(
after where {$token:tt $($tokens:tt)*}
{$($imp:tt)*}
{ $($block:tt)* }
) => {
$crate::impl_block!{
after where { $($tokens)*}
{$($imp)*}
{ $($block)* }
}
};
}
pub use crate::tid as type_id;
#[cfg(test)]
mod tests {
use core::marker::PhantomData;
use super::*;
struct Plain;
tid!(Plain);
struct Borrowing<'a>(&'a str);
tid!(Borrowing<'a>);
struct Generic<'a, T>(PhantomData<(&'a str, T)>);
tid! { impl<'a, T> TidAble<'a> for Generic<'a, T> }
trait Node<'a>: Tid<'a> {}
tid! { impl<'a> TidAble<'a> for dyn Node<'a> + 'a }
impl<'a> Node<'a> for Borrowing<'a> {}
impl<'a> Node<'a> for Plain {}
#[test]
fn distinct_types_have_distinct_ids() {
assert_ne!(Plain::id(), Borrowing::id());
assert_ne!(
Generic::<'_, Plain>::id(),
Generic::<'_, Borrowing<'_>>::id()
);
}
#[test]
fn downcast_ref_through_trait_object() {
let text = String::from("input");
let node: &(dyn Node<'_> + '_) = &Borrowing(&text);
assert!(node.downcast_ref::<Borrowing<'_>>().is_some());
assert!(node.downcast_ref::<Plain>().is_none());
}
#[test]
fn downcast_rc_through_trait_object() {
let text = String::from("input");
let node: Rc<dyn Node<'_> + '_> = Rc::new(Borrowing(&text));
assert!(node.clone().downcast_rc::<Plain>().is_err());
let concrete = node.downcast_rc::<Borrowing<'_>>().ok().unwrap();
assert_eq!(concrete.0, "input");
}
#[test]
fn downcast_preserves_lifetime() {
fn extract<'a>(node: &(dyn Node<'a> + 'a)) -> Option<&'a str> {
node.downcast_ref::<Borrowing<'a>>().map(|it| it.0)
}
let text = String::from("input");
assert_eq!(extract(&Borrowing(&text)), Some("input"));
}
}