diff --git a/xso/src/dynxso.rs b/xso/src/dynxso.rs index 04390043bb11374690dfec397103ce4b8705d9f8..debba090c7e8f7ffa006927d2ec46632e69fbd45 100644 --- a/xso/src/dynxso.rs +++ b/xso/src/dynxso.rs @@ -32,10 +32,10 @@ //! ## Example //! #![cfg_attr( - not(feature = "macros"), + not(all(feature = "macros", feature = "std")), doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n" )] -#![cfg_attr(feature = "macros", doc = "\n```\n")] +#![cfg_attr(all(feature = "macros", feature = "std"), doc = "\n```\n")] //! # use core::any::Any; //! # use xso::{dynxso::{Xso, BuilderRegistry}, FromXml, from_bytes, derive_dyn_traits}; //! trait MyPayload: Any {} @@ -69,19 +69,22 @@ use alloc::{ }, vec::{self, Vec}, }; +#[cfg(feature = "std")] +use core::marker::PhantomData; use core::{ any::{Any, TypeId}, fmt, - marker::PhantomData, ops::{Deref, DerefMut}, slice, }; +#[cfg(feature = "std")] use std::sync::Mutex; +#[cfg(feature = "std")] +use crate::fromxml::XmlNameMatcher; use crate::{ asxml::AsXmlDyn, error::{Error, FromEventsError}, - fromxml::XmlNameMatcher, AsXml, Context, FromEventsBuilder, FromXml, Item, }; @@ -92,9 +95,25 @@ use crate::{ /// that is a useful thing to have, see the [`dynxso`][`crate::dynxso`] /// module. /// +/// ## Syntax +/// +/// This macro can be called in two forms: +/// +/// - `derive_dyn_traits!(Trait)` uses the default [`BuilderRegistry`] +/// as [`DynXso::Registry`] type and is only available if `xso` is built +/// with the `"std"` feature. +/// - `derive_dyn_traits!(Trait use Type = expr)` where `Type` is used as +/// [`DynXso::Registry`], initialized with `expr`. This form is available +/// for any set of crate features. +/// /// ## Example /// -/// ``` +/// When `std` is enabled, the simple syntax can be used. +#[cfg_attr( + not(feature = "std"), + doc = "Because the std feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n" +)] +#[cfg_attr(feature = "std", doc = "\n```\n")] /// # use core::any::Any; /// use xso::derive_dyn_traits; /// trait Foo: Any {} @@ -104,19 +123,38 @@ use crate::{ /// Note that the trait this macro is called on **must** have a bound on /// `Any`, otherwise the generated code will not compile: /// -/// ```compile_fail +#[cfg_attr( + not(feature = "std"), + doc = "Because the std feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n" +)] +#[cfg_attr(feature = "std", doc = "\n```compile_fail\n")] /// use xso::derive_dyn_traits; /// trait Foo {} /// derive_dyn_traits!(Foo); /// // ↑ will generate a bunch of errors about incompatible types /// ``` +/// +/// If the `std` feature is not enabled or if you want to use another +/// `Registry` for whichever reason, the explicit form can be used: +/// +/// ``` +/// # use core::any::Any; +/// use xso::derive_dyn_traits; +/// trait Foo: Any {} +/// struct Registry { /* .. */ } +/// derive_dyn_traits!(Foo use Registry = Registry { /* .. */ }); +/// ``` +/// +/// In that case, you should review the trait requirements of the +/// [`DynXso::Registry`] associated type. #[macro_export] macro_rules! derive_dyn_traits { - ($trait:ident) => { + ($trait:ident use $registry:ty = $reginit:expr) => { impl $crate::dynxso::DynXso for dyn $trait { - fn registry() -> &'static $crate::dynxso::BuilderRegistry { - static DATA: $crate::dynxso::BuilderRegistry = - $crate::dynxso::BuilderRegistry::new(); + type Registry = $registry; + + fn registry() -> &'static Self::Registry { + static DATA: $registry = $reginit; &DATA } @@ -173,8 +211,30 @@ macro_rules! derive_dyn_traits { } } }; + ($trait:ident) => { + $crate::_internal_derive_dyn_traits_std_only!($trait); + }; +} + +#[macro_export] +#[doc(hidden)] +#[cfg(feature = "std")] +macro_rules! _internal_derive_dyn_traits_std_only { + ($trait:ident) => { + $crate::derive_dyn_traits!($trait use $crate::dynxso::BuilderRegistry = $crate::dynxso::BuilderRegistry::new()); + }; +} + +#[macro_export] +#[doc(hidden)] +#[cfg(not(feature = "std"))] +macro_rules! _internal_derive_dyn_traits_std_only { + ($trait:ident) => { + compile_error!(concat!("derive_dyn_traits!(", stringify!($trait), ") can only be used if the xso crate has been built with the \"std\" feature enabled. Without \"std\", the explicit form of derive_dyn_traits!(", stringify!($trait), " use .. = ..) must be used (see docs).")); + }; } +#[cfg(feature = "std")] type BuilderRegistryBuilder = Box< dyn Fn( rxml::QName, @@ -186,12 +246,14 @@ type BuilderRegistryBuilder = Box< + 'static, >; +#[cfg(feature = "std")] struct BuilderRegistryEntry { matcher: XmlNameMatcher<'static>, ty: TypeId, builder: BuilderRegistryBuilder, } +#[cfg(feature = "std")] impl fmt::Debug for BuilderRegistryEntry { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("BuilderRegistryEntry") @@ -203,16 +265,7 @@ impl fmt::Debug for BuilderRegistryEntry { /// # Registry for type-erased [`FromXml`] builders which construct `T` /// -/// This registry holds type-erased [`FromXml::from_events`] implementations. -/// It can be used to dynamically dispatch to a set of `FromXml` -/// implementations which is not known at compile time. -/// -/// Under the hood, this is used by the `FromXml` implementation on -/// [`Xso`][`Xso`], via the [`DynXso`] trait. -/// -/// Note that this registry does not allow to add arbitrary builders. All -/// builders must originate in a [`FromXml`] implementation and their output -/// must be convertible to `T`. +/// For general information on builder registries, see [`registry`]. /// /// ## Performance trade-offs /// @@ -238,7 +291,7 @@ impl fmt::Debug for BuilderRegistryEntry { doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n" )] #[cfg_attr(feature = "macros", doc = "\n```\n")] -/// # use xso::{dynxso::{BuilderRegistry, MayContain}, FromXml, from_bytes, exports::rxml::{Namespace, AttrMap, QName, Event, parser::EventMetrics, xml_ncname}, Context, FromEventsBuilder, error::FromEventsError}; +/// # use xso::{dynxso::{BuilderRegistry, MayContain, registry::{DynXsoRegistryAdd, DynXsoRegistryLookup}}, FromXml, from_bytes, exports::rxml::{Namespace, AttrMap, QName, Event, parser::EventMetrics, xml_ncname}, Context, FromEventsBuilder, error::FromEventsError}; /// #[derive(FromXml, Debug, PartialEq)] /// #[xml(namespace = "urn:example", name = "foo")] /// struct Foo; @@ -299,7 +352,7 @@ impl fmt::Debug for BuilderRegistryEntry { doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n" )] #[cfg_attr(feature = "macros", doc = "\n```\n")] -/// # use xso::{dynxso::{BuilderRegistry, MayContain}, FromXml, from_bytes, exports::rxml::{Namespace, AttrMap, QName, Event, parser::EventMetrics}, Context, FromEventsBuilder, error::FromEventsError}; +/// # use xso::{dynxso::{BuilderRegistry, MayContain, registry::{DynXsoRegistryAdd, DynXsoRegistryLookup}}, FromXml, from_bytes, exports::rxml::{Namespace, AttrMap, QName, Event, parser::EventMetrics}, Context, FromEventsBuilder, error::FromEventsError}; /// # #[derive(FromXml, Debug, PartialEq)] /// # #[xml(namespace = "urn:example", name = "foo")] /// # struct Foo; @@ -350,10 +403,12 @@ impl fmt::Debug for BuilderRegistryEntry { /// ); /// ``` #[derive(Debug)] +#[cfg(feature = "std")] pub struct BuilderRegistry { inner: Mutex>>, } +#[cfg(feature = "std")] impl BuilderRegistry { /// Create an empty registry. pub const fn new() -> Self { @@ -409,11 +464,41 @@ impl BuilderRegistry { self.inner.lock().unwrap().reserve(n); } - /// Add a new builder to the registry. - /// - /// This allows to add any `FromXml` implementation whose output can be - /// converted to `T`. - pub fn add(&self) + fn try_build( + inner: &mut Vec>, + mut name: rxml::QName, + mut attrs: rxml::AttrMap, + ctx: &Context<'_>, + matcher_builder: impl for<'x> FnOnce(&'x rxml::QName) -> XmlNameMatcher<'x>, + ) -> Result>>, FromEventsError> { + let matcher = matcher_builder(&name); + let start_scan_at = inner.partition_point(|entry| entry.matcher < matcher); + + for entry in &inner[start_scan_at..] { + if !entry.matcher.matches(&name) { + return Err(FromEventsError::Mismatch { name, attrs }); + } + + match (entry.builder)(name, attrs, ctx) { + Ok(v) => return Ok(v), + Err(FromEventsError::Invalid(e)) => return Err(FromEventsError::Invalid(e)), + Err(FromEventsError::Mismatch { + name: new_name, + attrs: new_attrs, + }) => { + name = new_name; + attrs = new_attrs; + } + } + } + + Err(FromEventsError::Mismatch { name, attrs }) + } +} + +#[cfg(feature = "std")] +impl DynXsoRegistryAdd for BuilderRegistry { + fn add(&self) where T: MayContain, { @@ -452,50 +537,11 @@ impl BuilderRegistry { }), ) } +} - fn try_build( - inner: &mut Vec>, - mut name: rxml::QName, - mut attrs: rxml::AttrMap, - ctx: &Context<'_>, - matcher_builder: impl for<'x> FnOnce(&'x rxml::QName) -> XmlNameMatcher<'x>, - ) -> Result>>, FromEventsError> { - let matcher = matcher_builder(&name); - let start_scan_at = inner.partition_point(|entry| entry.matcher < matcher); - - for entry in &inner[start_scan_at..] { - if !entry.matcher.matches(&name) { - return Err(FromEventsError::Mismatch { name, attrs }); - } - - match (entry.builder)(name, attrs, ctx) { - Ok(v) => return Ok(v), - Err(FromEventsError::Invalid(e)) => return Err(FromEventsError::Invalid(e)), - Err(FromEventsError::Mismatch { - name: new_name, - attrs: new_attrs, - }) => { - name = new_name; - attrs = new_attrs; - } - } - } - - Err(FromEventsError::Mismatch { name, attrs }) - } - - /// Make a builder for the given element header. - /// - /// This tries all applicable `FromXml` implementations which have - /// previously been added via [`add`][`Self::add`] in unspecified order. - /// The first implementation to either fail or succeed at constructing a - /// builder determines the result. Implementations which return a - /// [`FromEventsError::Mismatch`][`crate::error::FromEventsError::Mismatch`] - /// are ignored. - /// - /// If all applicable implementations return `Mismatch`, this function - /// returns `Mismatch`, too. - pub fn make_builder( +#[cfg(feature = "std")] +impl DynXsoRegistryLookup for BuilderRegistry { + fn make_builder( &self, name: rxml::QName, attrs: rxml::AttrMap, @@ -522,6 +568,62 @@ impl BuilderRegistry { } } +/// # Helper traits for dynamic XSO builder registries. +/// +/// Builder registries hold type-erased [`FromXml::from_events`] +/// implementations. Registries can be used to dynamically dispatch to a set +/// of `FromXml` implementations which is not known at compile time. +/// +/// Under the hood, they are used by the `FromXml` implementation on +/// [`Xso`][`Xso`], via the [`DynXso::Registry`] type. +/// +/// Note that registries generally do not allow to add arbitrary builders. All +/// builders must originate in a [`FromXml`] implementation and their output +/// must be convertible to the specific type the registry is defined for. +/// +/// The default implementation is [`BuilderRegistry`], which is only available +/// if `xso` is built with the `"std"` feature due to the inherent need for a +/// `Mutex`. +pub mod registry { + use super::*; + + /// Trait for a builder registry supports constructing elements. + pub trait DynXsoRegistryLookup { + /// Make a builder for the given element header. + /// + /// This tries all applicable `FromXml` implementations which have + /// previously been added via [`add`][`DynXsoRegistryAdd::add`] in + /// unspecified order. The first implementation to either fail or + /// succeed at constructing a builder determines the result. + /// Implementations which return a + /// [`FromEventsError::Mismatch`][`crate::error::FromEventsError::Mismatch`] + /// are ignored. + /// + /// If all applicable implementations return `Mismatch`, this function + /// returns `Mismatch`, too. + fn make_builder( + &self, + name: rxml::QName, + attrs: rxml::AttrMap, + ctx: &Context<'_>, + ) -> Result>>, FromEventsError>; + } + + /// Trait for a builder registry supports registering new builders at + /// runtime. + pub trait DynXsoRegistryAdd { + /// Add a new builder to the registry. + /// + /// This allows to add any `FromXml` implementation whose output can be + /// converted to `T`. + fn add(&self) + where + T: MayContain; + } +} + +use registry::*; + /// # Dynamic XSO type /// /// This trait provides the infrastructure for dynamic XSO types. In @@ -544,11 +646,24 @@ impl BuilderRegistry { /// *Hint*: It should not be necessary for user code to directly interact /// with this trait. pub trait DynXso: 'static { + /// Builder registry type for this dynamic type. + /// + /// The `Registry` type *should* implement the following traits: + /// + /// - [`DynXsoRegistryAdd`] is required to make + /// [`Xso::::register_type()`][`Xso::register_type`] available. + /// - [`DynXsoRegistryLookup`] is required to make [`FromXml`] available + /// on [`Xso`][`Xso`] (and, by extension, on + /// [`XsoVec`][`XsoVec`]). + /// + /// However, any type with static lifetime can be used, even without the + /// trait implementations above, if the limitations are acceptable. + type Registry: 'static; + /// Return the builder registry for this dynamic type. /// - /// The builder registry is used by [`Xso::register_type`] and the - /// `FromXml` implementation. - fn registry() -> &'static BuilderRegistry; + /// See [`Registry`][`Self::Registry`] for details. + fn registry() -> &'static Self::Registry; /// Try to downcast a boxed dynamic XSO to a specific type. /// @@ -661,7 +776,8 @@ impl Xso { /// # use core::any::Any; /// # use xso::{dynxso::Xso, derive_dyn_traits}; /// trait Trait: Any {} - /// derive_dyn_traits!(Trait); + #[cfg_attr(feature = "std", doc = "derive_dyn_traits!(Trait);")] + #[cfg_attr(not(feature = "std"), doc = "derive_dyn_traits!(Trait use () = ());")] /// /// struct Foo; /// impl Trait for Foo {} @@ -683,7 +799,8 @@ impl Xso { /// # use core::any::Any; /// # use xso::{dynxso::Xso, derive_dyn_traits}; /// trait Trait: Any {} - /// derive_dyn_traits!(Trait); + #[cfg_attr(feature = "std", doc = "derive_dyn_traits!(Trait);")] + #[cfg_attr(not(feature = "std"), doc = "derive_dyn_traits!(Trait use () = ());")] /// /// struct Foo; /// impl Trait for Foo {} @@ -705,7 +822,8 @@ impl Xso { /// # use core::any::Any; /// # use xso::{dynxso::Xso, derive_dyn_traits}; /// trait Trait: Any {} - /// derive_dyn_traits!(Trait); + #[cfg_attr(feature = "std", doc = "derive_dyn_traits!(Trait);")] + #[cfg_attr(not(feature = "std"), doc = "derive_dyn_traits!(Trait use () = ());")] /// /// struct Foo; /// impl Trait for Foo {} @@ -750,7 +868,8 @@ impl Xso { /// # use core::any::Any; /// # use xso::{dynxso::Xso, derive_dyn_traits}; /// trait Trait: Any {} - /// derive_dyn_traits!(Trait); + #[cfg_attr(feature = "std", doc = "derive_dyn_traits!(Trait);")] + #[cfg_attr(not(feature = "std"), doc = "derive_dyn_traits!(Trait use () = ());")] /// /// struct Foo; /// impl Trait for Foo {} @@ -777,7 +896,8 @@ impl Xso { /// # use core::any::Any; /// # use xso::{dynxso::Xso, derive_dyn_traits}; /// trait Trait: Any {} - /// derive_dyn_traits!(Trait); + #[cfg_attr(feature = "std", doc = "derive_dyn_traits!(Trait);")] + #[cfg_attr(not(feature = "std"), doc = "derive_dyn_traits!(Trait use () = ());")] /// /// struct Foo; /// impl Trait for Foo {} @@ -801,7 +921,9 @@ impl Xso { fn inner_type_id(&self) -> TypeId { DynXso::type_id(&*self.inner) } +} +impl + 'static, T: DynXso + ?Sized + 'static> Xso { /// Register a new type to be constructible. /// /// Only types registered through this function can be parsed from XML via @@ -809,10 +931,10 @@ impl Xso { /// [`dynxso`][`crate::dynxso`] for details. /// #[cfg_attr( - not(feature = "macros"), - doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n" + not(all(feature = "macros", feature = "std")), + doc = "Because the macros and std features were not enabled at doc build time, the example cannot be tested.\n\n```ignore\n" )] - #[cfg_attr(feature = "macros", doc = "\n```\n")] + #[cfg_attr(all(feature = "macros", feature = "std"), doc = "\n```\n")] /// # use core::any::Any; /// # use xso::{dynxso::Xso, derive_dyn_traits, from_bytes, FromXml}; /// trait Trait: Any {} @@ -882,7 +1004,9 @@ impl>> FromEventsBuilder for UnboxBuilde } } -impl FromXml for Xso { +impl + 'static, T: DynXso + ?Sized + 'static> FromXml + for Xso +{ type Builder = DynBuilder>>>; fn from_events( diff --git a/xso/src/lib.rs b/xso/src/lib.rs index b456106312df9021d816d4206f22ad252e60e2db..8ae16e66444283e93ab079bec133f25e160dd809 100644 --- a/xso/src/lib.rs +++ b/xso/src/lib.rs @@ -31,7 +31,6 @@ extern crate std; use std::io; pub mod asxml; -#[cfg(feature = "std")] pub mod dynxso; pub mod error; pub mod fromxml; diff --git a/xso/src/xso_vec_test_prelude.rs b/xso/src/xso_vec_test_prelude.rs index 18405423babce6b193feb9062a2adc400c172644..b5fdc9d7dce5c1a2a5c98c6aea0b5c70edf45081 100644 --- a/xso/src/xso_vec_test_prelude.rs +++ b/xso/src/xso_vec_test_prelude.rs @@ -1,7 +1,7 @@ # use core::any::Any; # use core::fmt::Debug; -# use xso::{derive_dyn_traits, dynxso::{XsoVec, TakeOneError}}; +# use xso::{dynxso::{XsoVec, TakeOneError, DynXso, MayContain}, derive_dyn_traits}; # # trait Trait: Any + Debug {} # -# derive_dyn_traits!(Trait); +# derive_dyn_traits!(Trait use () = ());