Diffstat (limited to 'src/protocol.rs')
| -rw-r--r-- | src/protocol.rs | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/src/protocol.rs b/src/protocol.rs new file mode 100644 index 0000000..081e569 --- /dev/null +++ b/src/protocol.rs @@ -0,0 +1,214 @@ +use core::any::Any; + +pub use any_hint::AnyHint; +pub use any_visit::AnyVisit; +pub use id::ProtocolId; + +use crate::Error; + +/// A protocol between a walker and visitor. +/// +/// On the walker side this takes the form of hints a visitor can give. +/// On the visitor side this takes the form of visits a walker can inject values into. +/// +/// When a visitor hints a walker should use a particular protocol, its expected +/// that the walker visits using that protocol. +/// +/// A protocol never needs to be a value, so it's recommended to use an uninhabited type +/// like an empty enum to represent them. +pub trait Protocol<'value>: Any { + /// Arbitrary hint metadata for the protocol. + /// + /// This allows a visitor to give extra information to a walker when hinting to + /// use the protocol. + type Hint; + + /// Data known about the protocol before hinting. + /// + /// This allows a walker to give extra information to a visitor to make a + /// better decision when selecting a hint. + type Known; + + /// The visit data the walker provides to the visitor. + /// + /// This may be actual data or another walker for a part of the bigger value. + /// The '`walking` lifetime is only alive while the walker is walking. + /// As such, a visitor cannot borrow from a `'walking` lifetime containing type + /// for it's output. + type Accessor<'walking, Err: Error<'value>> + where + 'value: 'walking; +} + +mod id { + use super::Protocol; + use core::any::TypeId; + + /// ID of a protocol. + /// + /// This can be used to query if a walker or visitor supports a protocol. + #[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Debug, Copy, Clone)] + pub struct ProtocolId(TypeId); + + impl ProtocolId { + /// Get the ID of a protocol. + /// + /// The ID is unique per protocol. + pub fn of<'value, P: Protocol<'value>>() -> Self { + Self(TypeId::of::<P>()) + } + } +} + +mod any_hint { + use core::{any::Any, marker::PhantomData, mem::MaybeUninit}; + + use crate::Hint; + + use super::{Protocol, ProtocolId}; + + /// Form of `Hint` without `P`. + trait ErasedHint<'value, Err>: Any {} + + /// Get the size of pointers to trait objects for this target. + const DYN_PTR_SIZE: usize = core::mem::size_of::<&mut dyn ErasedHint<'static, ()>>(); + + /// Type erased form of `&'walking mut dyn Hint<'value, P, Err>` where `P` is erased. + pub struct AnyHint<'walking, 'value, Err> { + /// ID of `P`. + id: ProtocolId, + + /// This field stores a `&'walking mut dyn Hint<'value, P, Err>`. + fat_ptr: MaybeUninit<[u8; DYN_PTR_SIZE]>, + + /// Mimick what we actually store with a trait without `P`. + _marker: PhantomData<&'walking mut dyn ErasedHint<'value, Err>>, + } + + impl<'walking, 'value, Err> AnyHint<'walking, 'value, Err> { + /// Erase the `P` in a hint. + /// + /// This allows returning a hint from a object safe method. + pub fn new<P: Protocol<'value>>(visit: &'walking mut dyn Hint<'value, P, Err>) -> Self { + Self { + id: ProtocolId::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(visit) }, + _marker: PhantomData, + } + } + + /// Try to downcast the hint for the given protocol. + /// + /// If the hint is of the wrong type then `None` is returned. + pub fn downcast<P: Protocol<'value>>( + self, + ) -> Result<&'walking mut dyn Hint<'value, P, Err>, Self> { + if self.id == ProtocolId::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) + } + } + } +} + +mod any_visit { + use core::{any::Any, marker::PhantomData, mem::MaybeUninit}; + + use crate::Visit; + + use super::{Protocol, ProtocolId}; + + /// Form of `Visit` without `P`. + trait ErasedVisit<'value, Err>: Any {} + + /// Get the size of pointers to trait objects for this target. + const DYN_PTR_SIZE: usize = core::mem::size_of::<&mut dyn ErasedVisit<'static, ()>>(); + + /// Type erased form of `&'walking mut dyn Visit<'value, P, Err>` where `P` is erased. + pub struct AnyVisit<'walking, 'value, Err> { + /// ID of `P`. + id: ProtocolId, + + /// This field stores a `&'walking mut dyn Visit<'value, P, Err>`. + fat_ptr: MaybeUninit<[u8; DYN_PTR_SIZE]>, + + /// Mimick what we actually store with a trait without `P`. + _marker: PhantomData<&'walking mut dyn ErasedVisit<'value, Err>>, + } + + impl<'walking, 'value, Err> AnyVisit<'walking, 'value, Err> { + /// Erase the `P` in a visit. + /// + /// This allows returning a visit from a object safe method. + pub fn new<P: Protocol<'value>>(visit: &'walking mut dyn Visit<'value, P, Err>) -> Self { + Self { + id: ProtocolId::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(visit) }, + _marker: PhantomData, + } + } + + /// Try to downcast the visit for the given protocol. + /// + /// If the visit is of the wrong type then `None` is returned. + pub fn downcast<P: Protocol<'value>>( + self, + ) -> Result<&'walking mut dyn Visit<'value, P, Err>, Self> { + if self.id == ProtocolId::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) + } + } + } +} + +/// The following shows a safe form of the generic types in this module. +/// This shows how the lifetimes are correct. +#[cfg(test)] +#[allow(unused)] +mod generic_example { + use crate::Hint; + + use super::{Protocol, ProtocolId}; + + pub struct Generic<'walking, 'value, P, Err> { + id: ProtocolId, + fat_ptr: &'walking mut dyn Hint<'value, P, Err>, + } + + impl<'walking, 'value, P: Protocol<'value>, Err> Generic<'walking, 'value, P, Err> { + pub fn new(visit: &'walking mut dyn Hint<'value, P, Err>) -> Self { + Self { + id: ProtocolId::of::<P>(), + fat_ptr: visit, + } + } + + pub fn downcast(self) -> Result<&'walking mut dyn Hint<'value, P, Err>, Self> { + if self.id == ProtocolId::of::<P>() { + // Notice how this is valid. + Ok(self.fat_ptr) + } else { + Err(self) + } + } + } +} |