Diffstat (limited to 'src/protocol.rs')
-rw-r--r--src/protocol.rs214
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)
+ }
+ }
+ }
+}