use core::any::Any;
pub use any_hint::AnyHint;
pub use any_visit::AnyVisit;
pub use id::ProtocolId;
use crate::{error::VisitorError, HintGiven, UniError, Visitor, WalkerError, 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<'value, 'ctx: '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, 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 {
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, 'ctx: 'value, P: Protocol<'value, 'ctx>>() -> 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, '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, 'static, (), ()>>();
/// Type erased form of `&'walking mut dyn Hint<'value, P, Err>` where `P` is erased.
pub struct AnyHint<'walking, 'value: 'walking, 'ctx: 'value, WalkerErr, VisitorErr> {
/// 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, 'ctx, VisitorErr, WalkerErr>>,
}
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, '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.
// 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, 'ctx>>(
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
// 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, '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, 'static, (), ()>>();
/// Type erased form of `&'walking mut dyn Visit<'value, P, Err>` where `P` is erased.
pub struct AnyVisit<'walking, 'value: 'walking, 'ctx: 'value, WalkerErr, VisitorErr> {
/// 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, 'ctx, WalkerErr, VisitorErr>>,
}
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, '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.
// 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, 'ctx>>(
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
// 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: 'walking, 'ctx: 'value, P, WalkerErr, VisitorErr> {
id: ProtocolId,
fat_ptr: &'walking mut dyn Hint<'value, 'ctx, P, VisitorErr, Error = WalkerErr>,
}
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, 'ctx, P, VisitorErr, Error = WalkerErr>, Self>
{
if self.id == ProtocolId::of::<P>() {
// Notice how this is valid.
Ok(self.fat_ptr)
} else {
Err(self)
}
}
}
}