Diffstat (limited to 'src/protocol.rs')
| -rw-r--r-- | src/protocol.rs | 341 |
1 files changed, 56 insertions, 285 deletions
diff --git a/src/protocol.rs b/src/protocol.rs index 617cc4d..2b48fc8 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -1,289 +1,93 @@ -use core::any::{type_name, Any}; +use core::any::{Any, TypeId}; -pub use any_hint::AnyHint; -pub use any_visit::AnyVisit; -pub use id::ProtocolId; +pub use any_implementation::AnyImpl; -use crate::{ControlFlow, Visitor, WalkerHints}; - -/// 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: 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<'ctx>; - - /// 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<'ctx>; - - /// 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, 'ctx: 'walking>; -} - -/// Protocol specific hint for a walker. -/// -/// A walker can implement any number of these for any protocols it supports. -pub trait Hint<'ctx, P: Protocol> { - /// Hint that protocol `P` should be used. - /// - /// After hinting a protocol, a walker should invoke only the same protocol on the visitor. - /// This is not forced though. - fn hint(&mut self, visitor: &mut dyn Visitor<'ctx>, hint: P::Hint<'ctx>) -> ControlFlow; - - /// Any information the walker has for the protocol. - /// - /// This information should be easy to get inside the walker, and should - /// only be used when making a decision of what protocol to hint as a visitor. - /// This can be helpful for doing things like preallocating space in the visitor. - /// - /// Most protocols will allow returning a value representing no knowledge is known by the - /// walker. - fn known(&mut self, hint: &P::Hint<'ctx>) -> P::Known<'ctx>; -} - -/// Protocol specific visit for a visitor. -/// -/// A visitor can implement any number of these for any protocols it supports. -pub trait Visit<'ctx, P: Protocol> { - /// Visit a value from the walker. - /// - /// The `accessor` will either be a concrete value or another walker. - /// This is dependant on the protocol `P`. The visitor can do whatever it wants - /// with the `accessor` during the function call. - fn visit(&mut self, accessor: P::Accessor<'_, 'ctx>) -> ControlFlow; -} - -#[derive(thiserror::Error, Debug)] -#[error("Expected Hint to be for `{expected}` but got one for `{got}`.")] -pub struct WalkerWrongProtocol { - pub got: &'static str, - pub expected: &'static str, -} - -#[derive(thiserror::Error, Debug)] -pub enum WalkerMissingProtocol { - #[error("Walker doesn't support the protocol `{0}`.")] - Missing(&'static str), - - #[error(transparent)] - Wrong(#[from] WalkerWrongProtocol), -} - -#[derive(thiserror::Error, Debug)] -#[error("Expected Visit to be for `{expected}` but got one for `{got}`.")] -pub struct VisitorWrongProtocol { - pub got: &'static str, - pub expected: &'static str, -} - -#[derive(thiserror::Error, Debug)] -pub enum VisitorMissingProtocol { - #[error("Visitor doesn't support the protocol `{0}`.")] - Missing(&'static str), - - #[error(transparent)] - Wrong(#[from] VisitorWrongProtocol), + type Object<'a, 'ctx: 'a>; } -/// Try to lookup a [`Hint`] on a walker. -pub fn try_lookup_hint<'ctx, P: Protocol, H: ?Sized + WalkerHints<'ctx>>( - hints: &mut H, -) -> Result<Option<&mut dyn Hint<'ctx, P>>, WalkerWrongProtocol> { - match hints.protocol(ProtocolId::of::<P>()) { - Some(protocol) => match protocol.downcast::<P>() { - Ok(hint) => Ok(Some(hint)), - Err(hint) => Err(WalkerWrongProtocol { - got: hint.protocol_type_name(), - expected: type_name::<P>(), - }), - }, - None => Ok(None), - } +pub trait Implementer<'ctx> { + fn interface(&mut self, id: TypeId) -> Option<AnyImpl<'_, 'ctx>>; } -pub fn lookup_hint<'ctx, P: Protocol, H: ?Sized + WalkerHints<'ctx>>( - hints: &mut H, -) -> Result<&mut dyn Hint<'ctx, P>, WalkerMissingProtocol> { - try_lookup_hint(hints)?.ok_or(WalkerMissingProtocol::Missing(type_name::<P>())) -} - -pub fn try_lookup_visit<'ctx, P: Protocol, V: ?Sized + Visitor<'ctx>>( - visitor: &mut V, -) -> Result<Option<&mut dyn Visit<'ctx, P>>, VisitorWrongProtocol> { - match visitor.protocol(ProtocolId::of::<P>()) { - Some(protocol) => match protocol.downcast::<P>() { - Ok(visit) => Ok(Some(visit)), - Err(visit) => Err(VisitorWrongProtocol { - got: visit.protocol_type_name(), - expected: type_name::<P>(), - }), - }, - None => Ok(None), - } +pub trait Implementation<'ctx, P: Protocol> { + fn as_object(&mut self) -> P::Object<'_, 'ctx>; } -pub fn lookup_visit<'ctx, P: Protocol, V: ?Sized + Visitor<'ctx>>( - visitor: &mut V, -) -> Result<&mut dyn Visit<'ctx, P>, VisitorMissingProtocol> { - try_lookup_visit(visitor)?.ok_or(VisitorMissingProtocol::Missing(type_name::<P>())) +pub trait ImplementerExt<'ctx>: Implementer<'ctx> { + fn interface_for<P: Protocol>(&mut self) -> Option<&mut dyn Implementation<'ctx, P>>; } -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<P: Protocol>() -> Self { - Self(TypeId::of::<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, } } } -mod any_hint { - use core::{ - any::{type_name, Any}, - marker::PhantomData, - mem::MaybeUninit, - }; - - use crate::Hint; - - use super::{Protocol, ProtocolId}; - - /// Form of `Hint` without `P`. - trait ErasedHint<'ctx>: 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, 'ctx> { - /// ID of `P`. - id: ProtocolId, - name: &'static str, - - /// 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<'ctx>>, - } - - impl<'walking, 'ctx> AnyHint<'walking, 'ctx> { - /// Erase the `P` in a hint. - /// - /// This allows returning a hint from a object safe method. - pub fn new<P: Protocol>(visit: &'walking mut dyn Hint<'ctx, P>) -> Self { - Self { - id: ProtocolId::of::<P>(), - name: type_name::<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, +#[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 + } } } - /// 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>(self) -> Result<&'walking mut dyn Hint<'ctx, P>, 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) + $(impl<$ctx> $crate::protocol::Implementation<$ctx, $protocol> for $name { + fn as_object(&mut self) -> <$protocol as $crate::protocol::Protocol>::Object<'_, $ctx> { + self } - } - - pub fn protocol_type_name(&self) -> &'static str { - self.name - } + })* } } +#[doc(inline)] +pub use implementer; -mod any_visit { - use core::{ - any::{type_name, Any}, - marker::PhantomData, - mem::MaybeUninit, - }; - - use crate::Visit; +mod any_implementation { + use core::{any::TypeId, marker::PhantomData, mem::MaybeUninit}; - use super::{Protocol, ProtocolId}; + use super::{Implementation, Protocol}; - /// Form of `Visit` without `P`. - trait ErasedVisit<'ctx>: Any {} + trait ErasedImplementation<'ctx> {} - /// Get the size of pointers to trait objects for this target. - const DYN_PTR_SIZE: usize = core::mem::size_of::<&mut dyn ErasedVisit<'static>>(); + const DYN_PTR_SIZE: usize = core::mem::size_of::<&mut dyn ErasedImplementation<'static>>(); - /// Type erased form of `&'walking mut dyn Visit<'value, P, Err>` where `P` is erased. - pub struct AnyVisit<'walking, 'ctx> { - /// ID of `P`. - id: ProtocolId, - name: &'static str, - - /// This field stores a `&'walking mut dyn Visit<'value, P, Err>`. + pub struct AnyImpl<'a, 'ctx> { + id: TypeId, fat_ptr: MaybeUninit<[u8; DYN_PTR_SIZE]>, - - /// Mimick what we actually store with a trait without `P`. - _marker: PhantomData<&'walking mut dyn ErasedVisit<'ctx>>, + _marker: PhantomData<&'a mut dyn ErasedImplementation<'ctx>>, } - impl<'walking, 'ctx> AnyVisit<'walking, 'ctx> { - /// Erase the `P` in a Visit. - /// - /// This allows returning a Visit from a object safe method. - pub fn new<P: Protocol>(visit: &'walking mut dyn Visit<'ctx, P>) -> Self { + impl<'a, 'ctx> AnyImpl<'a, 'ctx> { + pub fn new<P: Protocol>(implementation: &'a mut dyn Implementation<'ctx, P>) -> Self { Self { - id: ProtocolId::of::<P>(), - name: type_name::<P>(), + 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(visit) }, + fat_ptr: unsafe { core::mem::transmute(implementation) }, _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>(self) -> Result<&'walking mut dyn Visit<'ctx, P>, Self> { - if self.id == ProtocolId::of::<P>() { + 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. @@ -296,41 +100,8 @@ mod any_visit { } } - pub fn protocol_type_name(&self) -> &'static str { - self.name - } - } -} - -/// 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, 'ctx, P> { - id: ProtocolId, - fat_ptr: &'walking mut dyn Hint<'ctx, P>, - } - - impl<'walking, 'ctx, P: Protocol> Generic<'walking, 'ctx, P> { - pub fn new(visit: &'walking mut dyn Hint<'ctx, P>) -> Self { - Self { - id: ProtocolId::of::<P>(), - fat_ptr: visit, - } - } - - pub fn downcast(self) -> Result<&'walking mut dyn Hint<'ctx, P>, Self> { - if self.id == ProtocolId::of::<P>() { - // Notice how this is valid. - Ok(self.fat_ptr) - } else { - Err(self) - } + pub fn id(&self) -> TypeId { + self.id } } } |