1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
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)
            }
        }
    }
}