diff --git a/miniconf/src/impls.rs b/miniconf/src/impls.rs index 53058e03..9fb0aff5 100644 --- a/miniconf/src/impls.rs +++ b/miniconf/src/impls.rs @@ -15,10 +15,7 @@ macro_rules! impl_tuple { #[allow(unreachable_code, unused_mut, unused)] impl<$($t: TreeKey),+> TreeKey for ($($t,)+) { fn traverse_all() -> Result { - let k = KeyLookup::numbered($n); - let mut walk = W::internal(); - $(walk = walk.merge(&$t::traverse_all()?, $i, &k)?;)+ - Ok(walk) + W::internal(&[$(&$t::traverse_all()?, )+], &KeyLookup::numbered($n)) } fn traverse_by_key(mut keys: K, mut func: F) -> Result> @@ -114,7 +111,7 @@ impl Assert { impl TreeKey for [T; N] { fn traverse_all() -> Result { let () = Assert::::GREATER; // internal nodes must have at least one leaf - W::internal().merge(&T::traverse_all()?, 0, &KeyLookup::homogeneous(N)) + W::internal(&[&T::traverse_all()?], &KeyLookup::homogeneous(N)) } fn traverse_by_key(mut keys: K, mut func: F) -> Result> @@ -247,9 +244,7 @@ const RESULT_LOOKUP: KeyLookup = KeyLookup::Named(&["Ok", "Err"]); impl TreeKey for Result { #[inline] fn traverse_all() -> Result { - W::internal() - .merge(&T::traverse_all()?, 0, &RESULT_LOOKUP)? - .merge(&E::traverse_all()?, 1, &RESULT_LOOKUP) + W::internal(&[&T::traverse_all()?, &E::traverse_all()?], &RESULT_LOOKUP) } #[inline] @@ -331,10 +326,8 @@ const BOUND_LOOKUP: KeyLookup = KeyLookup::Named(&["Included", "Excluded"]); impl TreeKey for Bound { #[inline] fn traverse_all() -> Result { - let t = T::traverse_all()?; - W::internal() - .merge(&t, 0, &BOUND_LOOKUP)? - .merge(&t, 1, &BOUND_LOOKUP) + let t: W = T::traverse_all()?; + W::internal(&[&t; 2], &BOUND_LOOKUP) } #[inline] @@ -415,10 +408,8 @@ const RANGE_LOOKUP: KeyLookup = KeyLookup::Named(&["start", "end"]); impl TreeKey for Range { #[inline] fn traverse_all() -> Result { - let t = T::traverse_all()?; - W::internal() - .merge(&t, 0, &RANGE_LOOKUP)? - .merge(&t, 1, &RANGE_LOOKUP) + let t: W = T::traverse_all()?; + W::internal(&[&t; 2], &RANGE_LOOKUP) } #[inline] @@ -497,10 +488,8 @@ impl TreeAny for Range { impl TreeKey for RangeInclusive { #[inline] fn traverse_all() -> Result { - let t = T::traverse_all()?; - W::internal() - .merge(&t, 0, &RANGE_LOOKUP)? - .merge(&t, 1, &RANGE_LOOKUP) + let t: W = T::traverse_all()?; + W::internal(&[&t; 2], &RANGE_LOOKUP) } #[inline] @@ -538,7 +527,7 @@ const RANGE_FROM_LOOKUP: KeyLookup = KeyLookup::Named(&["start"]); impl TreeKey for RangeFrom { #[inline] fn traverse_all() -> Result { - W::internal().merge(&T::traverse_all()?, 0, &RANGE_FROM_LOOKUP) + W::internal(&[&T::traverse_all()?], &RANGE_FROM_LOOKUP) } #[inline] @@ -615,7 +604,7 @@ const RANGE_TO_LOOKUP: KeyLookup = KeyLookup::Named(&["end"]); impl TreeKey for RangeTo { #[inline] fn traverse_all() -> Result { - W::internal().merge(&T::traverse_all()?, 0, &RANGE_TO_LOOKUP) + W::internal(&[&T::traverse_all()?], &RANGE_TO_LOOKUP) } #[inline] diff --git a/miniconf/src/iter.rs b/miniconf/src/iter.rs index 190f9f30..0f7bbb78 100644 --- a/miniconf/src/iter.rs +++ b/miniconf/src/iter.rs @@ -133,7 +133,7 @@ impl NodeIter { ); ExactSize { iter: self, - count: meta.count, + count: meta.count.get(), } } diff --git a/miniconf/src/lib.rs b/miniconf/src/lib.rs index dfc8298d..53e3dd5a 100644 --- a/miniconf/src/lib.rs +++ b/miniconf/src/lib.rs @@ -27,9 +27,7 @@ mod walk; pub use walk::*; #[cfg(feature = "trees")] -mod trees; -#[cfg(feature = "trees")] -pub use trees::*; +pub mod trees; #[cfg(feature = "derive")] pub use miniconf_derive::*; diff --git a/miniconf/src/tree.rs b/miniconf/src/tree.rs index ef769947..edda1c8c 100644 --- a/miniconf/src/tree.rs +++ b/miniconf/src/tree.rs @@ -162,7 +162,7 @@ pub trait TreeKey { /// bar: [Leaf; 2], /// }; /// let m: Metadata = S::traverse_all().unwrap(); - /// assert_eq!((m.max_depth, m.max_length, m.count), (2, 4, 3)); + /// assert_eq!((m.max_depth, m.max_length, m.count.get()), (2, 4, 3)); /// ``` fn traverse_all() -> Result; diff --git a/miniconf/src/trees.rs b/miniconf/src/trees.rs index 8ff3dde3..08f92662 100644 --- a/miniconf/src/trees.rs +++ b/miniconf/src/trees.rs @@ -1,9 +1,11 @@ +//! Tools to convert from TreeKey nodes to `trees::Tree` + use crate::{IntoKeys, KeyLookup, Node, Transcode, Traversal, TreeKey, Walk}; use trees::Tree; /// Build a [`trees::Tree`] of keys for a `TreeKey`. -pub fn tree( +pub fn nodes( state: &mut [usize], depth: usize, ) -> Result, Traversal> { @@ -11,7 +13,7 @@ pub fn tree( if !root.data().1.is_leaf() && depth < state.len() { debug_assert_eq!(state[depth], 0); loop { - match tree::(state, depth + 1) { + match nodes::(state, depth + 1) { Ok(child) => { debug_assert_eq!(child.data().1.depth(), depth + 1); root.push_back(child); @@ -36,29 +38,19 @@ struct TreeWalk(Tree>); impl Walk for TreeWalk { type Error = core::convert::Infallible; - fn internal() -> Self { - Self(Tree::new(None)) - } fn leaf() -> Self { Self(Tree::new(None)) } - fn merge(mut self, walk: &Self, index: usize, lookup: &KeyLookup) -> Result { - if let Some(l) = self.0.root().data() { - debug_assert_eq!(l, lookup); - } - self.0 - .root_mut() - .data_mut() - .get_or_insert_with(|| lookup.clone()); - if matches!(lookup, KeyLookup::Homogeneous(_)) { - debug_assert_eq!(index, 0); + fn internal(children: &[&Self], lookup: &KeyLookup) -> Result { + let mut root = Tree::new(Some(lookup.clone())); + for child in children.iter() { + root.push_back(child.0.clone()); } - self.0.push_back(walk.0.clone()); - Ok(self) + Ok(Self(root)) } } /// Build a Tree of KeyLookup -pub fn tree_all() -> Tree> { +pub fn all() -> Tree> { M::traverse_all::().unwrap().0 } diff --git a/miniconf/src/walk.rs b/miniconf/src/walk.rs index a984debb..c09d9681 100644 --- a/miniconf/src/walk.rs +++ b/miniconf/src/walk.rs @@ -1,10 +1,12 @@ +use core::num::NonZero; + use crate::{KeyLookup, Packed}; /// Metadata about a `TreeKey` namespace. /// /// Metadata includes paths that may be [`crate::Traversal::Absent`] at runtime. #[non_exhaustive] -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct Metadata { /// The maximum length of a path in bytes. /// @@ -20,7 +22,7 @@ pub struct Metadata { pub max_depth: usize, /// The exact total number of keys. - pub count: usize, + pub count: NonZero, /// The maximum number of bits (see [`crate::Packed`]) pub max_bits: u32, @@ -42,56 +44,62 @@ pub trait Walk: Sized { /// Error type for `merge()` type Error; - /// Return the walk starting point for an an empty internal node - fn internal() -> Self; - /// Return the walk starting point for a single leaf node fn leaf() -> Self; /// Merge node metadata into self. /// /// # Args - /// * `walk`: The walk of the node to merge. - /// * `index`: Either the child index or zero for homogeneous. + /// * `children`: The walk of the children to merge. /// * `lookup`: The namespace the node(s) are in. - fn merge(self, walk: &Self, index: usize, lookup: &KeyLookup) -> Result; + fn internal(children: &[&Self], lookup: &KeyLookup) -> Result; } impl Walk for Metadata { type Error = core::convert::Infallible; - #[inline] - fn internal() -> Self { - Default::default() - } - #[inline] fn leaf() -> Self { Self { - count: 1, - ..Default::default() + count: NonZero::::MIN, + max_length: 0, + max_depth: 0, + max_bits: 0, } } #[inline] - fn merge(mut self, meta: &Self, index: usize, lookup: &KeyLookup) -> Result { - let (len, count) = match lookup { - KeyLookup::Named(names) => (names[index].len(), 1), - KeyLookup::Numbered(_len) => { - (index.checked_ilog10().unwrap_or_default() as usize + 1, 1) - } - KeyLookup::Homogeneous(len) => { - debug_assert_eq!(index, 0); - (len.ilog10() as usize + 1, len.get()) - } - }; - self.max_depth = self.max_depth.max(1 + meta.max_depth); - self.max_length = self.max_length.max(len + meta.max_length); - debug_assert_ne!(meta.count, 0); - self.count += count * meta.count; - self.max_bits = self - .max_bits - .max(Packed::bits_for(lookup.len().get() - 1) + meta.max_bits); - Ok(self) + fn internal(children: &[&Self], lookup: &KeyLookup) -> Result { + let mut max_depth = 0; + let mut max_length = 0; + let mut count = 0; + let mut max_bits = 0; + // TODO: swap loop and match + for (index, child) in children.iter().enumerate() { + let (len, n) = match lookup { + KeyLookup::Named(names) => { + debug_assert_eq!(children.len(), names.len()); + (names[index].len(), 1) + } + KeyLookup::Numbered(len) => { + debug_assert_eq!(children.len(), len.get()); + (index.checked_ilog10().unwrap_or_default() as usize + 1, 1) + } + KeyLookup::Homogeneous(len) => { + debug_assert_eq!(children.len(), 1); + (len.ilog10() as usize + 1, len.get()) + } + }; + max_depth = max_depth.max(1 + child.max_depth); + max_length = max_length.max(len + child.max_length); + count += n * child.count.get(); + max_bits = max_bits.max(Packed::bits_for(lookup.len().get() - 1) + child.max_bits); + } + Ok(Self { + max_bits, + max_depth, + max_length, + count: NonZero::new(count).unwrap(), + }) } } diff --git a/miniconf/tests/arrays.rs b/miniconf/tests/arrays.rs index 66f45c0b..b0a67378 100644 --- a/miniconf/tests/arrays.rs +++ b/miniconf/tests/arrays.rs @@ -132,5 +132,5 @@ fn metadata() { let m: Metadata = Settings::traverse_all().unwrap(); assert_eq!(m.max_depth, 4); assert_eq!(m.max_length("/"), "/aam/0/0/c".len()); - assert_eq!(m.count, 11); + assert_eq!(m.count.get(), 11); } diff --git a/miniconf/tests/basic.rs b/miniconf/tests/basic.rs index c31a0a06..8c19896b 100644 --- a/miniconf/tests/basic.rs +++ b/miniconf/tests/basic.rs @@ -18,7 +18,7 @@ fn meta() { let meta = Settings::traverse_all::().unwrap(); assert_eq!(meta.max_depth, 2); assert_eq!(meta.max_length("/"), "/c/inner".len()); - assert_eq!(meta.count, 3); + assert_eq!(meta.count.get(), 3); } #[test] diff --git a/miniconf/tests/generics.rs b/miniconf/tests/generics.rs index 6ab11709..39f64c70 100644 --- a/miniconf/tests/generics.rs +++ b/miniconf/tests/generics.rs @@ -15,7 +15,7 @@ fn generic_type() { let metadata = Settings::::traverse_all::().unwrap(); assert_eq!(metadata.max_depth, 1); assert_eq!(metadata.max_length, "data".len()); - assert_eq!(metadata.count, 1); + assert_eq!(metadata.count.get(), 1); } #[test] @@ -34,7 +34,7 @@ fn generic_array() { let metadata = Settings::::traverse_all::().unwrap(); assert_eq!(metadata.max_depth, 2); assert_eq!(metadata.max_length("/"), "/data/0".len()); - assert_eq!(metadata.count, 2); + assert_eq!(metadata.count.get(), 2); } #[test] @@ -58,7 +58,7 @@ fn generic_struct() { let metadata = Settings::::traverse_all::().unwrap(); assert_eq!(metadata.max_depth, 1); assert_eq!(metadata.max_length("/"), "/inner".len()); - assert_eq!(metadata.count, 1); + assert_eq!(metadata.count.get(), 1); } #[test] diff --git a/miniconf/tests/iter.rs b/miniconf/tests/iter.rs index 29edbe56..1a97bce3 100644 --- a/miniconf/tests/iter.rs +++ b/miniconf/tests/iter.rs @@ -114,15 +114,17 @@ fn root() { ); } +#[cfg(feature = "trees")] #[test] fn tree() { let mut state = [0; 4]; - let t = miniconf::tree::>(&mut state, 0).unwrap(); + let t = miniconf::trees::nodes::>(&mut state, 0).unwrap(); println!("{t:?}"); } +#[cfg(feature = "trees")] #[test] fn tree_all() { - let t = miniconf::tree_all::(); + let t = miniconf::trees::all::(); println!("{t:?}"); } diff --git a/miniconf/tests/packed.rs b/miniconf/tests/packed.rs index 640923f0..f2c85532 100644 --- a/miniconf/tests/packed.rs +++ b/miniconf/tests/packed.rs @@ -152,7 +152,7 @@ fn size() { let meta: Metadata = A31::traverse_all().unwrap(); assert_eq!(meta.max_bits, 31); assert_eq!(meta.max_depth, 31); - assert_eq!(meta.count, 1usize.pow(31)); + assert_eq!(meta.count.get(), 1usize.pow(31)); assert_eq!(meta.max_length, 31); // Another way to get to 32 bit is to take 15 length-3 (2 bit) levels and one length-1 (1 bit) level to fill it, needing (3**15 ~ 14 M) storage. @@ -167,6 +167,6 @@ fn size() { let meta: Metadata = A16::traverse_all().unwrap(); assert_eq!(meta.max_bits, 31); assert_eq!(meta.max_depth, 16); - assert_eq!(meta.count, 3usize.pow(15)); + assert_eq!(meta.count.get(), 3usize.pow(15)); assert_eq!(meta.max_length, 16); } diff --git a/miniconf/tests/skipped.rs b/miniconf/tests/skipped.rs index 8fa36657..c3872a8b 100644 --- a/miniconf/tests/skipped.rs +++ b/miniconf/tests/skipped.rs @@ -16,7 +16,7 @@ fn meta() { let meta = Settings::traverse_all::().unwrap(); assert_eq!(meta.max_depth, 1); assert_eq!(meta.max_length("/"), "/value".len()); - assert_eq!(meta.count, 1); + assert_eq!(meta.count.get(), 1); } #[test] diff --git a/miniconf/tests/structs.rs b/miniconf/tests/structs.rs index 40eae70c..971d458c 100644 --- a/miniconf/tests/structs.rs +++ b/miniconf/tests/structs.rs @@ -42,7 +42,7 @@ fn structs() { let metadata = Settings::traverse_all::().unwrap(); assert_eq!(metadata.max_depth, 2); assert_eq!(metadata.max_length("/"), "/d/a".len()); - assert_eq!(metadata.count, 4); + assert_eq!(metadata.count.get(), 4); assert_eq!(paths::(), ["/a", "/b", "/c", "/d/a"]); } diff --git a/miniconf_derive/src/field.rs b/miniconf_derive/src/field.rs index c53a5600..270d1ab6 100644 --- a/miniconf_derive/src/field.rs +++ b/miniconf_derive/src/field.rs @@ -106,7 +106,7 @@ impl TreeField { pub fn traverse_all(&self) -> TokenStream { let typ = self.typ(); - quote_spanned!(self.span()=> <#typ as ::miniconf::TreeKey>::traverse_all()?) + quote_spanned!(self.span()=> <#typ as ::miniconf::TreeKey>::traverse_all()) } fn getter(&self, i: Option) -> TokenStream { diff --git a/miniconf_derive/src/tree.rs b/miniconf_derive/src/tree.rs index ad5d2801..38856047 100644 --- a/miniconf_derive/src/tree.rs +++ b/miniconf_derive/src/tree.rs @@ -186,15 +186,6 @@ impl Tree { let where_clause = self.bound_generics(TreeTrait::Key, orig_where_clause); let fields = self.fields(); let fields_len = fields.len(); - let traverse_all_arms = fields.iter().enumerate().map(|(i, f)| { - let w = f.traverse_all(); - if self.flatten.is_present() { - quote!(walk = #w;) - } else { - quote!(walk = walk.merge(&#w, #i, &Self::__MINICONF_LOOKUP)?;) - } - }); - let traverse_arms = fields.iter().enumerate().map(|(i, f)| f.traverse_by_key(i)); let names: Option> = match &self.data { Data::Struct(fields) if fields.style.is_struct() => Some( fields @@ -218,18 +209,22 @@ impl Tree { _ => None, }; let names = match names { - None => quote! { ::miniconf::KeyLookup::Numbered( - match ::core::num::NonZero::new(#fields_len) { - Some(n) => n, - None => unreachable!(), + None => quote! { + ::miniconf::KeyLookup::Numbered( + match ::core::num::NonZero::new(#fields_len) { + Some(n) => n, + None => unreachable!(), + }, + ) }, - )}, Some(names) => quote!(::miniconf::KeyLookup::Named(&[#(#names ,)*])), }; + let traverse_arms = fields.iter().enumerate().map(|(i, f)| f.traverse_by_key(i)); let index = self.index(); - let (traverse, increment) = if self.flatten.is_present() { - (None, None) + let (traverse, increment, traverse_all) = if self.flatten.is_present() { + (None, None, fields[0].traverse_all()) } else { + let w = fields.iter().map(|f| f.traverse_all()); ( Some(quote! { let name = Self::__MINICONF_LOOKUP.lookup(index)?; @@ -237,6 +232,7 @@ impl Tree { .map_err(|err| ::miniconf::Error::Inner(1, err))?; }), Some(quote!(::miniconf::Error::increment_result)), + quote!(W::internal(&[#(&#w? ,)*], &Self::__MINICONF_LOOKUP)), ) }; @@ -250,10 +246,7 @@ impl Tree { #[automatically_derived] impl #impl_generics ::miniconf::TreeKey for #ident #ty_generics #where_clause { fn traverse_all() -> ::core::result::Result { - #[allow(unused_mut)] - let mut walk = W::internal(); - #(#traverse_all_arms)* - ::core::result::Result::Ok(walk) + #traverse_all } fn traverse_by_key(mut keys: K, mut func: F) -> ::core::result::Result>