use core::any::{Any, TypeId};
pub use any_implementation::AnyImpl;
pub trait Protocol: Any {
type Object<'a, 'ctx: 'a>;
}
pub trait Implementer<'ctx> {
fn interface(&mut self, id: TypeId) -> Option<AnyImpl<'_, 'ctx>>;
}
pub trait Implementation<'ctx, P: Protocol> {
fn as_object(&mut self) -> P::Object<'_, 'ctx>;
}
pub trait ImplementerExt<'ctx>: Implementer<'ctx> {
fn interface_for<P: Protocol>(&mut self) -> Option<&mut dyn Implementation<'ctx, P>>;
}
impl<'ctx, T: Implementer<'ctx> + ?Sized> ImplementerExt<'ctx> for T {
fn interface_for<P: Protocol>(&mut self) -> Option<&mut dyn Implementation<'ctx, P>> {
match self.interface(TypeId::of::<P>()) {
Some(interface) => match interface.downcast::<P>() {
Ok(implementation) => Some(implementation),
Err(interface) => panic!(
"unexpected type ID for protocol implementation: `{:?}`, expected: `{:?}`",
interface.id(),
TypeId::of::<P>()
),
},
None => None,
}
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! implementer {
{
impl[$ctx:lifetime $($generic:tt)*] $name:ty = [$($protocol:ty),* $(,)?];
} => {
impl<$ctx $($generic)*> $crate::protocol::Implementer<$ctx> for $name {
#[inline]
fn interface(&mut self, id: ::core::any::TypeId) -> ::core::option::Option<$crate::protocol::AnyImpl<'_, $ctx>> {
match id {
$(id if id == ::core::any::TypeId::of::<$protocol>() => Some($crate::protocol::AnyImpl::new::<$protocol>(self)),)*
_ => None
}
}
}
$crate::implementer! {
$ctx $name [$($protocol),*] [$($generic)*]
}
};
{
$ctx:lifetime $name:ty [$protocol:ty] [$($generic:tt)*]
} => {
impl<$ctx $($generic)*> $crate::protocol::Implementation<$ctx, $protocol> for $name {
fn as_object(&mut self) -> <$protocol as $crate::protocol::Protocol>::Object<'_, $ctx> {
self
}
}
};
{
$ctx:lifetime $name:ty [$($protocol:ty),*] $generic:tt
} => {
$($crate::implementer! {
$ctx $name [$protocol] $generic
})*
};
}
#[doc(inline)]
pub use implementer;
mod any_implementation {
use core::{any::TypeId, marker::PhantomData, mem::MaybeUninit};
use super::{Implementation, Protocol};
trait ErasedImplementation<'ctx> {}
const DYN_PTR_SIZE: usize = core::mem::size_of::<&mut dyn ErasedImplementation<'static>>();
pub struct AnyImpl<'a, 'ctx> {
id: TypeId,
fat_ptr: MaybeUninit<[u8; DYN_PTR_SIZE]>,
_marker: PhantomData<&'a mut dyn ErasedImplementation<'ctx>>,
}
impl<'a, 'ctx> AnyImpl<'a, 'ctx> {
pub fn new<P: Protocol>(implementation: &'a mut dyn Implementation<'ctx, P>) -> Self {
Self {
id: TypeId::of::<P>(),
// SAFETY: A maybe uninit array of bytes can hold any pointer.
// Additionally, transmute makes sure the size is correct.
fat_ptr: unsafe { core::mem::transmute(implementation) },
_marker: PhantomData,
}
}
pub fn downcast<P: Protocol>(self) -> Result<&'a mut dyn Implementation<'ctx, P>, Self> {
if self.id == TypeId::of::<P>() {
// SAFETY: Only `new` can make a value of this type, and it stores the ID of `P`.
// If the IDs are equal then we can act like any and downcast back to the real
// type.
//
// An important note is this method takes ownership. Which allows it to return
// the borrow with the `'walking` lifetime instead of a sub-borrow.
Ok(unsafe { core::mem::transmute(self.fat_ptr) })
} else {
Err(self)
}
}
pub fn id(&self) -> TypeId {
self.id
}
}
}