1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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
        }
    }
}