Skip to main content

xso/dynxso/
linktime.rs

1use alloc::boxed::Box;
2use core::{cmp::Ordering, marker::PhantomData};
3
4use crate::{
5    dynxso::MayContain,
6    error::{Error, FromEventsError},
7    fromxml::XmlNameMatcher,
8    Context, FromEventsBuilder, FromXml,
9};
10
11/// Dynamic XSO traits using a link-time registry
12///
13/// This macro is used for (a) declaring [`DynXso`][`crate::dynxso::DynXso`]
14/// traits using a registry filled at link-time and (b) for registering types
15/// with a trait.
16///
17/// ## Declaring a link-time `DynXso` trait
18///
19/// To declare a trait as a link-time `DynXso` trait, wrap its declaration in
20/// a `linktime!` invocation.
21///
22/// ### Example
23///
24/// ```rust
25/// use xso::{dynxso::linktime, asxml::AsXmlDyn};
26///
27/// linktime! {
28///     pub trait MyPayload: AsXmlDyn {}
29/// }
30/// ```
31///
32/// ### Usage notes
33///
34/// - A bound on [`Any`][`core::any::Any`] is automatically added, because it
35///   is a requirement for implementing [`DynXso`][`crate::dynxso::DynXso`]
36///   on `dyn Trait`.
37/// - If you want to specify more than one trait bound, you have to separate
38///   them with `,` instead of `+`, due to rustc limitations with declarative
39///   macros.
40/// - A trait bound on [`AsXmlDyn`][`crate::asxml::AsXmlDyn`] is required (but
41///   has to be added manually, as seen in the above example) for
42///   [`Xso<dyn Trait>`][`crate::dynxso::Xso`] to implement
43///   [`AsXml`][`crate::AsXml`].
44/// - This macro automatically calls [`crate::derive_dyn_traits!`], but does
45///   not use the default `BuilderRegistry`, removing the `std` dependency at
46///   the cost of being unable to register more types at runtime.
47///
48/// ## Implement a `DynXso` trait
49///
50/// To implement a trait declared with `linktime!`, wrap the `impl` block in
51/// a `linktime!` invocation.
52///
53/// Doing so allows a [`Xso<dyn Trait>`][`crate::dynxso::Xso`]'s [`FromXml`]
54/// implementation to parse the registered type.
55///
56/// ### Example
57///
58#[cfg_attr(
59    not(feature = "macros"),
60    doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
61)]
62#[cfg_attr(feature = "macros", doc = "\n```\n")]
63/// use xso::{dynxso::{linktime, Xso}, FromXml, AsXml, asxml::AsXmlDyn, from_bytes};
64///
65/// linktime! {
66///     trait Foo: AsXmlDyn {}
67/// }
68///
69/// #[derive(FromXml, AsXml, Debug, Clone, Copy)]
70/// #[xml(namespace = "urn:example", name = "a")]
71/// struct A {}
72///
73/// #[derive(FromXml, AsXml, Debug, Clone, Copy)]
74/// #[xml(namespace = "urn:example", name = "b")]
75/// struct B {}
76///
77/// linktime! {
78///     impl Foo for A {}
79///     impl Foo for B {}
80/// }
81///
82/// let foo: Xso<dyn Foo> = from_bytes(b"<a xmlns='urn:example'/>").unwrap();
83/// let foo: Xso<dyn Foo> = from_bytes(b"<b xmlns='urn:example'/>").unwrap();
84/// ```
85///
86/// ### Usage notes
87///
88/// - `linktime!` can be placed anywhere; unlike the `Xso::register_type`
89///   calls needed with the
90///   [`BuilderRegistry`][`crate::dynxso::BuilderRegistry`], registrations are
91///   handled at compile-time (or more precisely: at link-time, with a minimal
92///   startup overhead of postprocessing the list generated by the linker).
93#[doc(hidden)]
94#[macro_export]
95macro_rules! __linktime {
96    // First variant: declaring a trait.
97    (
98        $(
99            $(#[$meta:meta])*
100            $vis:vis trait $trait:ident $(: $bound:path $(, $bound2:path)*)? {
101                $($item:item)*
102            }
103        )*
104    ) => {
105        $(
106            // Just "copy" the trait decl over, but add the mandatory bound on
107            // Any.
108            $(#[$meta])*
109            $vis trait $trait: core::any::Any $(+ $bound $(+ $bound2)*)? {
110                $($item)*
111            }
112
113            $crate::exports::paste::paste! {
114                #[doc = concat!("Link-time type registry for ", stringify!($trait), ".\n\nGenerated by the `linktime!` macro in the `xso` crate.")]
115                #[doc(hidden)]
116                $vis struct [<$trait LinktimeRegistry>]();
117
118                $crate::exports::scattered_collect::declarative::gather! {
119                    #[gather]
120                    #[doc = concat!("Link-time type registry data for ", stringify!($trait), ".\n\nGenerated by the `linktime!` macro in the `xso` crate.")]
121                    #[doc(hidden)]
122                    $vis static [<$trait:upper _LINKTIME_REGISTRY_DATA>]: $crate::exports::scattered_collect::ScatteredSortedSlice<
123                        $crate::dynxso::linktime::BuilderRegistryEntry<dyn $trait>
124                    >;
125                }
126
127                impl $crate::dynxso::registry::DynXsoRegistryLookup<dyn $trait> for [<$trait LinktimeRegistry>] {
128                    fn make_builder(
129                        &self,
130                        name: $crate::exports::rxml::QName,
131                        attrs: $crate::exports::rxml::AttrMap,
132                        ctx: &$crate::Context<'_>,
133                    ) -> Result<
134                        Box<dyn $crate::FromEventsBuilder<Output = Box<dyn $trait>>>,
135                        $crate::error::FromEventsError,
136                    > {
137                        let registry = &[<$trait:upper _LINKTIME_REGISTRY_DATA>][..];
138                        $crate::dynxso::linktime::make_builder(registry, name, attrs, ctx)
139                    }
140                }
141
142                // This is a fun trick!
143                //
144                // You'd think that that wouldn't work, because statics and traits
145                // cannot share the same name, but in fact, they can! And the
146                // particularly good thing about this is that macros can, too,
147                // because what is *actually* relevant is the macro, not the
148                // static: the scatter! macro needs access to a hidden macro
149                // generated by the gather! trait and named exactly like the
150                // registry static.
151                $vis use [<$trait:upper _LINKTIME_REGISTRY_DATA>] as $trait;
152
153                $crate::derive_dyn_traits!(
154                    $trait
155                    use [<$trait LinktimeRegistry>] = [<$trait LinktimeRegistry>]()
156                );
157            }
158        )*
159    };
160
161    // Second variant: Implementing a trait.
162    (
163        $(
164            $(#[$meta:meta])*
165            impl $trait_seg1:ident $(:: $trait_segn:ident)* for $ty:ty {
166                $($item:item)*
167            }
168        )*
169    ) => {
170        $(
171            // Just "copy" the `impl` block over.
172            impl $trait_seg1 $(:: $trait_segn)* for $ty {
173                $($item)*
174            }
175
176            $crate::exports::scattered_collect::declarative::scatter! {
177                // Register the type using linker magic.
178                //
179                // The `scatter!` macro invocation needs us to pass the
180                // `static` we declared above with the `gather!` macro (well,
181                // in fact it needs the macro which overloads the `static`).
182                #[scatter($trait_seg1 $(:: $trait_segn)*)]
183                const _: $crate::dynxso::linktime::BuilderRegistryEntry<dyn $trait_seg1 $(:: $trait_segn)*> = $crate::dynxso::linktime::BuilderRegistryEntry {
184                    matcher: <$ty as $crate::FromXml>::XML_NAME_MATCHER,
185                    builder: $crate::dynxso::linktime::build_boxed::<$ty, dyn $trait_seg1 $(:: $trait_segn)*>,
186                };
187            }
188        )*
189    }
190}
191
192/// A function to attempt to construct a specific XSO.
193///
194/// This can be used as a type-erased [`FromXml::from_events`] if `T` is a
195/// `dyn Trait`.
196///
197/// **NOT** part of the public API -- only exposed for use by macros.
198#[doc(hidden)]
199pub type Builder<T> = fn(
200    rxml::QName,
201    rxml::AttrMap,
202    &Context<'_>,
203) -> Result<Box<dyn FromEventsBuilder<Output = Box<T>>>, FromEventsError>;
204
205/// Entry in a linktime registry of builder functions for `T`.
206///
207/// **NOT** part of the public API -- only exposed for use by macros.
208#[doc(hidden)]
209pub struct BuilderRegistryEntry<T: ?Sized> {
210    pub matcher: XmlNameMatcher<'static>,
211    pub builder: Builder<T>,
212}
213
214impl<T: ?Sized> PartialEq for BuilderRegistryEntry<T> {
215    fn eq(&self, other: &Self) -> bool {
216        (self.matcher, self.builder) == (other.matcher, other.builder)
217    }
218}
219
220impl<T: ?Sized> Eq for BuilderRegistryEntry<T> {}
221
222impl<T: ?Sized> Ord for BuilderRegistryEntry<T> {
223    fn cmp(&self, other: &Self) -> Ordering {
224        (&self.matcher, self.builder).cmp(&(&other.matcher, other.builder))
225    }
226}
227
228impl<T: ?Sized> PartialOrd for BuilderRegistryEntry<T> {
229    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
230        Some(self.cmp(other))
231    }
232}
233
234/// Look up builders matching the result of `matcher_builder` in `registry`
235/// and try initialising them in the order they are in the registry.
236///
237/// If a builder returns [`FromEventsError::Invalid`], that error is propagated
238/// immediately. If a builder returns [`FromEventsError::Mismatch`], the next
239/// matching builder is tried. If there are no more matching builders, the
240/// function returns the `Mismatch` error.
241///
242/// `matcher_builder` is a closure instead of an argument, because we want to
243/// borrow from the `name`, which must be passed by value to `from_events`.
244fn try_build<T: ?Sized>(
245    registry: &[BuilderRegistryEntry<T>],
246    mut name: rxml::QName,
247    mut attrs: rxml::AttrMap,
248    ctx: &Context<'_>,
249    matcher_builder: impl for<'x> FnOnce(&'x rxml::QName) -> XmlNameMatcher<'x>,
250) -> Result<Box<dyn FromEventsBuilder<Output = Box<T>>>, FromEventsError> {
251    let matcher = matcher_builder(&name);
252    let start_scan_at = registry.partition_point(|entry| entry.matcher < matcher);
253    let end_scan_at = registry.partition_point(|entry| entry.matcher >= matcher);
254
255    for entry in &registry[start_scan_at..end_scan_at] {
256        if !entry.matcher.matches(&name) {
257            return Err(FromEventsError::Mismatch { name, attrs });
258        }
259
260        match (entry.builder)(name, attrs, ctx) {
261            Ok(v) => return Ok(v),
262            Err(FromEventsError::Invalid(e)) => return Err(FromEventsError::Invalid(e)),
263            Err(FromEventsError::Mismatch {
264                name: new_name,
265                attrs: new_attrs,
266            }) => {
267                name = new_name;
268                attrs = new_attrs;
269            }
270        }
271    }
272
273    Err(FromEventsError::Mismatch { name, attrs })
274}
275
276/// Find and start a builder matching the given `name` and `attrs` from the
277/// `registry`.
278///
279/// **NOT** part of the public API -- only exposed for use by macros.
280#[doc(hidden)]
281pub fn make_builder<T: ?Sized>(
282    registry: &[BuilderRegistryEntry<T>],
283    name: rxml::QName,
284    attrs: rxml::AttrMap,
285    ctx: &Context<'_>,
286) -> Result<Box<dyn FromEventsBuilder<Output = Box<T>>>, FromEventsError> {
287    let (name, attrs) = match try_build(registry, name, attrs, ctx, |qname| {
288        XmlNameMatcher::Specific(qname.0.as_str(), qname.1.as_str())
289    }) {
290        Ok(v) => return Ok(v),
291        Err(FromEventsError::Invalid(e)) => return Err(FromEventsError::Invalid(e)),
292        Err(FromEventsError::Mismatch { name, attrs }) => (name, attrs),
293    };
294
295    let (name, attrs) = match try_build(registry, name, attrs, ctx, |qname| {
296        XmlNameMatcher::InNamespace(qname.0.as_str())
297    }) {
298        Ok(v) => return Ok(v),
299        Err(FromEventsError::Invalid(e)) => return Err(FromEventsError::Invalid(e)),
300        Err(FromEventsError::Mismatch { name, attrs }) => (name, attrs),
301    };
302
303    try_build(registry, name, attrs, ctx, |_| XmlNameMatcher::Any)
304}
305
306/// Build a `U` using its [`FromXml`] implementation and return it as `Box<T>`.
307///
308/// **NOT** part of the public API -- only exposed for use by macros.
309#[doc(hidden)]
310pub fn build_boxed<U: FromXml + 'static, T: ?Sized + 'static>(
311    name: rxml::QName,
312    attrs: rxml::AttrMap,
313    ctx: &Context<'_>,
314) -> Result<Box<dyn crate::FromEventsBuilder<Output = Box<T>>>, FromEventsError>
315where
316    T: super::MayContain<U>,
317{
318    struct Wrapper<B, X: ?Sized> {
319        inner: B,
320        output: PhantomData<X>,
321    }
322
323    impl<X: ?Sized, O, B: FromEventsBuilder<Output = O>> FromEventsBuilder for Wrapper<B, X>
324    where
325        X: MayContain<O>,
326    {
327        type Output = Box<X>;
328
329        fn feed(
330            &mut self,
331            ev: rxml::Event,
332            ctx: &Context<'_>,
333        ) -> Result<Option<Self::Output>, Error> {
334            self.inner
335                .feed(ev, ctx)
336                .map(|x| x.map(|x| <X as MayContain<O>>::upcast_into(x)))
337        }
338    }
339
340    <U as crate::FromXml>::from_events(name, attrs, ctx).map(|builder| {
341        Box::new(Wrapper {
342            inner: builder,
343            output: PhantomData,
344        }) as Box<dyn FromEventsBuilder<Output = Box<T>>>
345    })
346}