Diffstat (limited to 'src/protocol.rs')
| -rw-r--r-- | src/protocol.rs | 225 |
1 files changed, 200 insertions, 25 deletions
diff --git a/src/protocol.rs b/src/protocol.rs index 081e569..01336db 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -4,7 +4,7 @@ pub use any_hint::AnyHint; pub use any_visit::AnyVisit; pub use id::ProtocolId; -use crate::Error; +use crate::{error::VisitorError, HintGiven, UniError, Visitor, WalkerError, WalkerHints}; /// A protocol between a walker and visitor. /// @@ -16,7 +16,7 @@ use crate::Error; /// /// 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 { +pub trait Protocol<'value, 'ctx: 'value>: Any { /// Arbitrary hint metadata for the protocol. /// /// This allows a visitor to give extra information to a walker when hinting to @@ -35,9 +35,159 @@ pub trait Protocol<'value>: Any { /// 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>> + type Accessor<'walking, WalkerErr: 'value, VisitorErr: 'value> where 'value: 'walking; + + fn description() -> Option<&'static str> { + None + } +} + +pub struct ProtocolDescription { + id: fn() -> ProtocolId, + name: fn() -> &'static str, + description: fn() -> Option<&'static str>, +} + +impl ProtocolDescription { + pub const fn of<'value, 'ctx: 'value, P: Protocol<'value, 'ctx>>() -> Self { + Self { + id: || ProtocolId::of::<P>(), + name: || core::any::type_name::<P>(), + description: P::description, + } + } + + pub fn id(&self) -> ProtocolId { + (self.id)() + } + + pub fn name(&self) -> &'static str { + (self.name)() + } + + pub fn description(&self) -> Option<&'static str> { + (self.description)() + } +} + +impl core::fmt::Display for ProtocolDescription { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self.description() { + Some(description) => write!(f, "{} - {}", self.name(), description), + None => write!(f, "{}", self.name()), + } + } +} + +impl core::fmt::Debug for ProtocolDescription { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ProtocolDescription") + .field("id", &self.id()) + .field("name", &self.name()) + .field("description", &self.description()) + .finish() + } +} + +#[macro_export] +#[doc(hidden)] +macro_rules! protocol_list { + ($($protocol:ty),* $(,)?) => {{ + [ + $($crate::protocol::ProtocolDescription::of::<$protocol>()),* + ].into_iter() + }} +} + +#[doc(inline)] +pub use protocol_list; + +pub type HintOps<'walking, 'value, 'ctx, P, WalkerErr, VisitorErr> = + &'walking mut dyn Hint<'value, 'ctx, P, VisitorErr, Error = WalkerErr>; + +pub type VisitOps<'walking, 'value, 'ctx, P, WalkerErr, VisitorErr> = + &'walking mut dyn Visit<'value, 'ctx, P, WalkerErr, Error = VisitorErr>; + +/// Protocol specific hint for a walker. +pub trait Hint<'value, 'ctx: 'value, P: Protocol<'value, 'ctx>, VisitorErr> { + type Error; + + /// 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<'value, 'ctx, Self::Error, Error = VisitorErr>, + hint: P::Hint, + ) -> Result<HintGiven, UniError<Self::Error, VisitorErr>>; + + /// 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) -> Result<P::Known, Self::Error>; +} + +/// Protocol specific visit for a visitor. +pub trait Visit<'value, 'ctx: 'value, P: Protocol<'value, 'ctx>, WalkerErr> { + type Error; + + /// Visit a value from the walker. + fn visit<'walking>( + &'walking mut self, + accessor: P::Accessor<'walking, WalkerErr, Self::Error>, + ) -> Result<(), UniError<WalkerErr, Self::Error>>; +} + +/// Helper to lookup a protocol's hint on a walker. +/// +/// A `Ok(None)` is returned if the walker doesn't support the protocol. +/// A `Err(...)` is returned if the walker returns a hint instance not for the protocol. +pub fn lookup_hint< + 'value, + 'ctx: 'value, + P: Protocol<'value, 'ctx>, + VisitorErr: VisitorError<'value, 'ctx>, + H: ?Sized + WalkerHints<'value, 'ctx, VisitorErr>, +>( + hints: &mut H, +) -> Result<Option<HintOps<'_, 'value, 'ctx, P, H::Error, VisitorErr>>, VisitorErr> { + match hints.protocol(ProtocolId::of::<P>()) { + Some(hint) => match hint.downcast::<P>() { + Ok(hint) => Ok(Some(hint)), + Err(_) => Err(VisitorErr::wrong_hint::<P>()), + }, + None => Ok(None), + } +} + +/// Helper to lookup a protocol's visit on a visitor. +/// +/// A `Ok(None)` is returned if the visitor doesn't support the protocol. +/// A `Err(...)` is returned if the visitor returns a visit instance not for the protocol. +pub fn lookup_visit< + 'value, + 'ctx, + P: Protocol<'value, 'ctx>, + WalkerErr: WalkerError<'value, 'ctx>, + V: ?Sized + Visitor<'value, 'ctx, WalkerErr>, +>( + visitor: &mut V, +) -> Result<Option<VisitOps<'_, 'value, 'ctx, P, WalkerErr, V::Error>>, WalkerErr> { + match visitor.protocol(ProtocolId::of::<P>()) { + Some(visit) => match visit.downcast::<P>() { + Ok(visit) => Ok(Some(visit)), + Err(_) => Err(WalkerError::wrong_visit::<P>()), + }, + None => Ok(None), + } } mod id { @@ -54,7 +204,7 @@ mod id { /// Get the ID of a protocol. /// /// The ID is unique per protocol. - pub fn of<'value, P: Protocol<'value>>() -> Self { + pub fn of<'value, 'ctx: 'value, P: Protocol<'value, 'ctx>>() -> Self { Self(TypeId::of::<P>()) } } @@ -68,13 +218,14 @@ mod any_hint { use super::{Protocol, ProtocolId}; /// Form of `Hint` without `P`. - trait ErasedHint<'value, Err>: Any {} + trait ErasedHint<'value, 'ctx: 'value, WalkerErr, VisitorErr>: 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, ()>>(); + const DYN_PTR_SIZE: usize = + core::mem::size_of::<&mut dyn ErasedHint<'static, 'static, (), ()>>(); /// Type erased form of `&'walking mut dyn Hint<'value, P, Err>` where `P` is erased. - pub struct AnyHint<'walking, 'value, Err> { + pub struct AnyHint<'walking, 'value: 'walking, 'ctx: 'value, WalkerErr, VisitorErr> { /// ID of `P`. id: ProtocolId, @@ -82,14 +233,18 @@ mod any_hint { 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>>, + _marker: PhantomData<&'walking mut dyn ErasedHint<'value, 'ctx, VisitorErr, WalkerErr>>, } - impl<'walking, 'value, Err> AnyHint<'walking, 'value, Err> { + impl<'walking, 'value, 'ctx: 'value, WalkerErr, VisitorErr> + AnyHint<'walking, 'value, 'ctx, WalkerErr, VisitorErr> + { /// 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 { + pub fn new<P: Protocol<'value, 'ctx>>( + visit: &'walking mut dyn Hint<'value, 'ctx, P, VisitorErr, Error = WalkerErr>, + ) -> Self { Self { id: ProtocolId::of::<P>(), // SAFETY: A maybe uninit array of bytes can hold any pointer. @@ -102,9 +257,10 @@ mod any_hint { /// 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>>( + pub fn downcast<P: Protocol<'value, 'ctx>>( self, - ) -> Result<&'walking mut dyn Hint<'value, P, Err>, Self> { + ) -> Result<&'walking mut dyn Hint<'value, 'ctx, P, VisitorErr, Error = WalkerErr>, 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 @@ -128,13 +284,14 @@ mod any_visit { use super::{Protocol, ProtocolId}; /// Form of `Visit` without `P`. - trait ErasedVisit<'value, Err>: Any {} + trait ErasedVisit<'value, 'ctx: 'value, WalkerErr, VisitorErr>: 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, ()>>(); + const DYN_PTR_SIZE: usize = + core::mem::size_of::<&mut dyn ErasedVisit<'static, 'static, (), ()>>(); /// Type erased form of `&'walking mut dyn Visit<'value, P, Err>` where `P` is erased. - pub struct AnyVisit<'walking, 'value, Err> { + pub struct AnyVisit<'walking, 'value: 'walking, 'ctx: 'value, WalkerErr, VisitorErr> { /// ID of `P`. id: ProtocolId, @@ -142,14 +299,18 @@ mod any_visit { 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>>, + _marker: PhantomData<&'walking mut dyn ErasedVisit<'value, 'ctx, WalkerErr, VisitorErr>>, } - impl<'walking, 'value, Err> AnyVisit<'walking, 'value, Err> { + impl<'walking, 'value: 'walking, 'ctx: 'value, WalkerErr, VisitorErr> + AnyVisit<'walking, 'value, 'ctx, WalkerErr, VisitorErr> + { /// 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 { + pub fn new<P: Protocol<'value, 'ctx>>( + visit: &'walking mut dyn Visit<'value, 'ctx, P, WalkerErr, Error = VisitorErr>, + ) -> Self { Self { id: ProtocolId::of::<P>(), // SAFETY: A maybe uninit array of bytes can hold any pointer. @@ -162,9 +323,10 @@ mod any_visit { /// 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>>( + pub fn downcast<P: Protocol<'value, 'ctx>>( self, - ) -> Result<&'walking mut dyn Visit<'value, P, Err>, Self> { + ) -> Result<&'walking mut dyn Visit<'value, 'ctx, P, WalkerErr, Error = VisitorErr>, 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 @@ -189,20 +351,33 @@ mod generic_example { use super::{Protocol, ProtocolId}; - pub struct Generic<'walking, 'value, P, Err> { + pub struct Generic<'walking, 'value: 'walking, 'ctx: 'value, P, WalkerErr, VisitorErr> { id: ProtocolId, - fat_ptr: &'walking mut dyn Hint<'value, P, Err>, + fat_ptr: &'walking mut dyn Hint<'value, 'ctx, P, VisitorErr, Error = WalkerErr>, } - impl<'walking, 'value, P: Protocol<'value>, Err> Generic<'walking, 'value, P, Err> { - pub fn new(visit: &'walking mut dyn Hint<'value, P, Err>) -> Self { + impl< + 'walking, + 'value: 'walking, + 'ctx: 'value, + P: Protocol<'value, 'ctx>, + WalkerErr, + VisitorErr, + > Generic<'walking, 'value, 'ctx, P, WalkerErr, VisitorErr> + { + pub fn new( + visit: &'walking mut dyn Hint<'value, 'ctx, P, VisitorErr, Error = WalkerErr>, + ) -> Self { Self { id: ProtocolId::of::<P>(), fat_ptr: visit, } } - pub fn downcast(self) -> Result<&'walking mut dyn Hint<'value, P, Err>, Self> { + pub fn downcast( + self, + ) -> Result<&'walking mut dyn Hint<'value, 'ctx, P, VisitorErr, Error = WalkerErr>, Self> + { if self.id == ProtocolId::of::<P>() { // Notice how this is valid. Ok(self.fat_ptr) |