diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b182cd9..988c7dde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/quartiq/miniconf/compare/v0.13.0...HEAD) - DATE +### Added +* Derive support for enums with newtype/unit/skipped variants + ### Removed * The `KeyLookup::NAMES` associated constant is now an implementation detail - ## [0.13.0](https://github.com/quartiq/miniconf/compare/v0.12.0...v0.13.0) - 2024-07-10 ### Changed diff --git a/miniconf/Cargo.toml b/miniconf/Cargo.toml index a2db00e8..ca6effb0 100644 --- a/miniconf/Cargo.toml +++ b/miniconf/Cargo.toml @@ -9,7 +9,6 @@ description = "Serialize/deserialize/access reflection for trees" repository = "https://github.com/quartiq/miniconf" keywords = ["config", "serde", "no_std", "reflection", "graph"] categories = ["embedded", "config", "data-structures", "parsing"] -resolver = "2" [dependencies] serde = { version = "1.0.120", default-features = false } @@ -23,7 +22,6 @@ default = ["derive"] json-core = ["dep:serde-json-core"] postcard = ["dep:postcard"] derive = ["dep:miniconf_derive", "serde/derive"] -std = [] [package.metadata.docs.rs] all-features = true @@ -37,6 +35,7 @@ embedded-io-adapters = { version = "0.6.1", features = ["tokio-1"] } heapless = "0.8.0" yafnv = "3.0.0" tokio = { version = "1.38.0", features = ["io-std", "rt", "macros"] } +strum = { version = "0.26.3", features = ["derive"] } [[test]] name = "arrays" @@ -74,6 +73,10 @@ required-features = ["derive"] name = "structs" required-features = ["json-core", "derive"] +[[test]] +name = "enum" +required-features = ["json-core", "derive"] + [[test]] name = "validate" required-features = ["json-core", "derive"] diff --git a/miniconf/README.md b/miniconf/README.md index 83e5794f..1f4e1c32 100644 --- a/miniconf/README.md +++ b/miniconf/README.md @@ -107,7 +107,7 @@ let len = settings.get_json("/struct_", &mut buf).unwrap(); assert_eq!(&buf[..len], br#"{"a":3,"b":3}"#); // Iterating over all paths -for path in Settings::nodes::>() { +for path in Settings::nodes::, '/'>>() { let (path, node) = path.unwrap(); assert!(node.is_leaf()); // Serialize each @@ -168,7 +168,7 @@ Fields/items that form internal nodes (non-leaf) need to implement the respectiv Leaf fields/items need to support the respective [`serde`] trait (and the desired `serde::Serializer`/`serde::Deserializer` backend). -Structs, arrays, and Options can then be cascaded to construct more complex trees. +Structs, enums, arrays, and Options can then be cascaded to construct more complex trees. When using the derive macro, the behavior and tree recursion depth can be configured for each struct field using the `#[tree(depth(Y))]` attribute. @@ -187,10 +187,11 @@ It implements [`Keys`]. ## Limitations -Access to inner fields of some types is not yet supported, e.g. enums -other than [`Option`]. These are still however usable in their atomic `serde` form as leaf nodes. +The derive macros don't support enums with record (named fields) variants or tuple (non-newtype) variants. +These are still however usable in their atomic `serde` form as leaf nodes. -Many `std` smart pointers are not supported or handled in any special way: `Box`, `Rc`, `Arc`. +The derive macros don't handle `std`/`alloc` smart pointers ( `Box`, `Rc`, `Arc`) in any special way. +They however still be handled with accessors (`get`, `get_mut`, `validate`). ## Features diff --git a/miniconf/src/lib.rs b/miniconf/src/lib.rs index 9d5d2a37..26a4f6d3 100644 --- a/miniconf/src/lib.rs +++ b/miniconf/src/lib.rs @@ -1,6 +1,5 @@ -#![cfg_attr(not(any(feature = "std", test, doctest)), no_std)] -#![cfg_attr(feature = "json-core", doc = include_str!("../README.md"))] -#![cfg_attr(not(feature = "json-core"), doc = "miniconf")] +#![no_std] +#![doc = include_str!("../README.md")] #![deny(rust_2018_compatibility)] #![deny(rust_2018_idioms)] #![warn(missing_docs)] diff --git a/miniconf/src/node.rs b/miniconf/src/node.rs index 52683d2b..f5e3a12c 100644 --- a/miniconf/src/node.rs +++ b/miniconf/src/node.rs @@ -225,10 +225,12 @@ impl Transcode for Path { { M::traverse_by_key(keys.into_keys(), |index, name, _len| { self.0 - .write_char(self.separator()) - .and_then(|()| match name { - Some(name) => self.0.write_str(name), - None => self.0.write_str(itoa::Buffer::new().format(index)), + .write_char(S) + .and_then(|()| { + let mut buf = itoa::Buffer::new(); + let name = name.unwrap_or_else(|| buf.format(index)); + debug_assert!(!name.contains(S)); + self.0.write_str(name) }) .or(Err(())) }) @@ -311,10 +313,10 @@ mod test { #[test] fn strsplit() { + use heapless::Vec; for p in ["/d/1", "/a/bccc//d/e/", "", "/", "a/b", "a"] { - let a: Vec<_> = PathIter::<'_, '/'>::new(p).collect(); - println!("{p} {:?}", a); - let b: Vec<_> = p.split('/').skip(1).collect(); + let a: Vec<_, 10> = PathIter::<'_, '/'>::new(p).collect(); + let b: Vec<_, 10> = p.split('/').skip(1).collect(); assert_eq!(a, b); } } diff --git a/miniconf/tests/enum.rs b/miniconf/tests/enum.rs new file mode 100644 index 00000000..fa3014d4 --- /dev/null +++ b/miniconf/tests/enum.rs @@ -0,0 +1,69 @@ +use miniconf::{JsonCoreSlash, Path, Tree, TreeDeserialize, TreeKey, TreeSerialize}; +use strum::{AsRefStr, EnumString}; + +#[derive(Tree, Default, PartialEq, Debug)] +struct Inner { + a: i32, +} + +#[derive(Tree, Default, EnumString, AsRefStr, PartialEq, Debug)] +enum Enum { + #[default] + None, + #[strum(serialize = "foo")] + #[tree(rename = "foo")] + A(i32), + B(#[tree(depth = 1)] Inner), +} + +#[derive(TreeKey, TreeSerialize, TreeDeserialize, Default)] +struct Settings { + #[tree(typ = "&str", get = Self::get_tag, validate = Self::set_tag)] + tag: (), + #[tree(depth = 2)] + en: Enum, +} + +impl Settings { + fn get_tag(&self) -> Result<&str, &'static str> { + Ok(self.en.as_ref()) + } + + fn set_tag(&mut self, tag: &str) -> Result<(), &'static str> { + self.en = Enum::try_from(tag).or(Err("invalid tag"))?; + Ok(()) + } +} + +#[test] +fn enum_switch() { + let mut s = Settings::default(); + assert_eq!(s.en, Enum::None); + s.set_json("/tag", b"\"foo\"").unwrap(); + assert_eq!( + s.set_json("/tag", b"\"bar\""), + Err(miniconf::Traversal::Invalid(1, "invalid tag").into()) + ); + assert_eq!(s.en, Enum::A(0)); + s.set_json("/en/foo", b"99").unwrap(); + assert_eq!(s.en, Enum::A(99)); + assert_eq!( + s.set_json("/en/B/a", b"99"), + Err(miniconf::Traversal::Absent(2).into()) + ); + s.set_json("/tag", b"\"B\"").unwrap(); + s.set_json("/en/B/a", b"8").unwrap(); + assert_eq!(s.en, Enum::B(Inner { a: 8 })); + + assert_eq!( + Settings::nodes::>() + .exact_size() + .map(|pn| { + let (p, n) = pn.unwrap(); + assert!(n.is_leaf()); + p.into_inner() + }) + .collect::>(), + vec!["/tag", "/en/foo", "/en/B/a"] + ); +} diff --git a/miniconf/tests/index.rs b/miniconf/tests/index.rs index 7c60331b..cdf1f9c4 100644 --- a/miniconf/tests/index.rs +++ b/miniconf/tests/index.rs @@ -1,3 +1,5 @@ +// This follows arrays.rs, just with indices. + use miniconf::{Deserialize, JsonCoreSlash, Serialize, Traversal, Tree}; #[derive(Debug, Copy, Clone, Default, Tree, Deserialize, Serialize)] diff --git a/miniconf/tests/iter.rs b/miniconf/tests/iter.rs index f32ad2c3..1d70517b 100644 --- a/miniconf/tests/iter.rs +++ b/miniconf/tests/iter.rs @@ -29,20 +29,23 @@ fn struct_iter() { #[test] fn struct_iter_indices() { - let mut paths = [ + let paths = [ ([0, 0, 0], 2), ([0, 1, 0], 2), ([1, 0, 0], 2), ([2, 0, 0], 3), ([3, 0, 0], 1), - ] - .into_iter(); - for (have, expect) in Settings::nodes::>().exact_size().zip(&mut paths) { - let (idx, node) = have.unwrap(); - assert_eq!((idx.into_inner(), node.depth()), expect); - } - // Ensure that all fields were iterated. - assert_eq!(paths.next(), None); + ]; + assert_eq!( + Settings::nodes::>() + .exact_size() + .map(|have| { + let (idx, node) = have.unwrap(); + (idx.into_inner(), node.depth()) + }) + .collect::>(), + paths + ); } #[test] diff --git a/miniconf/tests/option.rs b/miniconf/tests/option.rs index 57cedf11..ed353b3a 100644 --- a/miniconf/tests/option.rs +++ b/miniconf/tests/option.rs @@ -11,6 +11,21 @@ struct Settings { value: Option, } +fn nodes, const Y: usize>(want: &[&str]) { + assert_eq!( + M::nodes::>() + .exact_size() + .map(|pn| { + let (p, n) = pn.unwrap(); + assert!(n.is_leaf()); + assert_eq!(p.chars().filter(|c| *c == p.separator()).count(), n.depth()); + p.into_inner() + }) + .collect::>(), + want + ); +} + #[test] fn just_option() { let mut it = Option::::nodes::>().exact_size(); @@ -33,6 +48,7 @@ fn option_get_set_none() { settings.get_json("/value", &mut data), Err(Traversal::Absent(1).into()) ); + // The Absent field indicates at which depth the variant was absent assert_eq!( settings.set_json("/value/data", b"5"), Err(Traversal::Absent(1).into()) @@ -56,21 +72,7 @@ fn option_get_set_some() { #[test] fn option_iterate_some_none() { - let mut settings = Settings::default(); - - // When the value is None, it will still be iterated over as a topic but may not exist at runtime. - settings.value.take(); - let mut iterator = Settings::nodes::>().exact_size(); - let (path, _node) = iterator.next().unwrap().unwrap(); - assert_eq!(path.as_str(), "/value/data"); - assert!(iterator.next().is_none()); - - // When the value is Some, it should be iterated over. - settings.value.replace(Inner { data: 5 }); - let mut iterator = Settings::nodes::>().exact_size(); - let (path, _node) = iterator.next().unwrap().unwrap(); - assert_eq!(path.as_str(), "/value/data"); - assert_eq!(iterator.next(), None); + nodes::(&["/value/data"]); } #[test] @@ -79,23 +81,14 @@ fn option_test_normal_option() { struct S { data: Option, } + nodes::(&["/data"]); let mut s = S::default(); assert!(s.data.is_none()); - let mut iterator = S::nodes::>().exact_size(); - let (path, _node) = iterator.next().unwrap().unwrap(); - assert_eq!(path.as_str(), "/data"); - assert!(iterator.next().is_none()); - s.set_json("/data", b"7").unwrap(); assert_eq!(s.data, Some(7)); - let mut iterator = S::nodes::>().exact_size(); - let (path, _node) = iterator.next().unwrap().unwrap(); - assert_eq!(path.as_str(), "/data"); - assert!(iterator.next().is_none()); - s.set_json("/data", b"null").unwrap(); assert!(s.data.is_none()); } @@ -107,25 +100,16 @@ fn option_test_defer_option() { #[tree(depth = 1)] data: Option, } + nodes::(&["/data"]); let mut s = S::default(); assert!(s.data.is_none()); - let mut iterator = S::nodes::>().exact_size(); - let (path, _node) = iterator.next().unwrap().unwrap(); - assert_eq!(path.as_str(), "/data"); - assert!(iterator.next().is_none()); - assert!(s.set_json("/data", b"7").is_err()); s.data = Some(0); s.set_json("/data", b"7").unwrap(); assert_eq!(s.data, Some(7)); - let mut iterator = S::nodes::>().exact_size(); - let (path, _node) = iterator.next().unwrap().unwrap(); - assert_eq!(path.as_str(), "/data"); - assert!(iterator.next().is_none()); - assert!(s.set_json("/data", b"null").is_err()); } diff --git a/miniconf/tests/packed.rs b/miniconf/tests/packed.rs index 64c46b22..cc27404f 100644 --- a/miniconf/tests/packed.rs +++ b/miniconf/tests/packed.rs @@ -136,7 +136,7 @@ fn size() { // Bit-hungriest type is [T;0] but that doesn't have any keys so won't recurse/consume with any Transcode/Keys // Then [T; 1] which takes one bit per level (not 0 bits, to distinguish empty packed) // Worst case for a 32 bit usize we need 31 array levels (marker bit) but TreeKey is only implemented to 16 - // Easiest 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. + // Easiest 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. // With the unit as type, we need 0 storage but can't do much. type A16 = [[[[[[[[[[[[[[[[(); 3]; 3]; 3]; 3]; 3]; 3]; 3]; 3]; 3]; 3]; 3]; 3]; 3]; 3]; 3]; 1]; assert_eq!(core::mem::size_of::(), 0); diff --git a/miniconf/tests/structs.rs b/miniconf/tests/structs.rs index 918ab053..0fb0c9c3 100644 --- a/miniconf/tests/structs.rs +++ b/miniconf/tests/structs.rs @@ -3,11 +3,10 @@ use miniconf::{ }; #[test] -fn atomic_struct() { - #[derive(Serialize, Deserialize, Default, PartialEq, Debug)] +fn structs() { + #[derive(Serialize, Deserialize, Tree, Default, PartialEq, Debug)] struct Inner { a: u32, - b: u32, } #[derive(Tree, Default, PartialEq, Debug)] @@ -15,6 +14,8 @@ fn atomic_struct() { a: f32, b: bool, c: Inner, + #[tree(depth = 1)] + d: Inner, } let mut settings = Settings::default(); @@ -23,58 +24,30 @@ fn atomic_struct() { assert!(settings.set_json("/c/a", b"4").is_err()); // Inner settings can be updated atomically. - settings.set_json("/c", b"{\"a\": 5, \"b\": 3}").unwrap(); - - let expected = { - let mut expected = Settings::default(); - expected.c.a = 5; - expected.c.b = 3; - expected - }; - - assert_eq!(settings, expected); - - // Check that metadata is correct. - let metadata = Settings::metadata(); - assert_eq!(metadata.max_depth, 1); - assert_eq!(metadata.max_length("/"), "/c".len()); - assert_eq!(metadata.count, 3); -} - -#[test] -fn recursive_struct() { - #[derive(Tree, Default, PartialEq, Debug)] - struct Inner { - a: u32, - } - - #[derive(Tree, Default, PartialEq, Debug)] - struct Settings { - a: f32, - b: bool, - #[tree(depth = 1)] - c: Inner, - } - - let mut settings = Settings::default(); - - settings.set_json("/c/a", b"3").unwrap(); - let expected = { - let mut expected = Settings::default(); - expected.c.a = 3; - expected - }; + settings.set_json("/c", b"{\"a\": 5}").unwrap(); - assert_eq!(settings, expected); + // Deferred inner settings can be updated individually. + settings.set_json("/d/a", b"3").unwrap(); // It is not allowed to set a non-terminal node. - assert!(settings.set_json("/c", b"{\"a\": 5}").is_err()); + assert!(settings.set_json("/d", b"{\"a\": 5").is_err()); + + assert_eq!(settings.c, Inner { a: 5 }); + assert_eq!(settings.d, Inner { a: 3 }); // Check that metadata is correct. let metadata = Settings::metadata(); assert_eq!(metadata.max_depth, 2); - assert_eq!(metadata.max_length("/"), "/c/a".len()); - assert_eq!(metadata.count, 3); + assert_eq!(metadata.max_length("/"), "/d/a".len()); + assert_eq!(metadata.count, 4); + + assert_eq!( + Settings::nodes::>() + .exact_size() + .map(|p| p.unwrap().0.into_inner()) + .collect::>(), + vec!["/a", "/b", "/c", "/d/a"] + ); } #[test] @@ -87,6 +60,26 @@ fn empty_struct() { .is_none()); } +#[test] +fn unit_struct() { + #[derive(Tree, Default)] + struct Settings; + assert!(Settings::nodes::>() + .exact_size() + .next() + .is_none()); +} + +#[test] +fn empty_tuple_struct() { + #[derive(Tree, Default)] + struct Settings(); + assert!(Settings::nodes::>() + .exact_size() + .next() + .is_none()); +} + #[test] fn borrowed() { // Can't derive TreeAny diff --git a/miniconf_derive/Cargo.toml b/miniconf_derive/Cargo.toml index 994d93ba..3fb611da 100644 --- a/miniconf_derive/Cargo.toml +++ b/miniconf_derive/Cargo.toml @@ -8,7 +8,6 @@ description = "Derive macros for `miniconf`" repository = "https://github.com/quartiq/miniconf" keywords = ["settings", "serde", "no_std", "json", "mqtt"] categories = ["no-std", "config", "rust-patterns", "parsing"] -resolver = "2" [lib] proc-macro = true @@ -18,6 +17,3 @@ syn = "2.0" quote = "1.0" proc-macro2 = "1.0" darling = "0.20" - -[dev-dependencies] -syn = { version = "2.0", features = ["extra-traits"] } diff --git a/miniconf_derive/src/field.rs b/miniconf_derive/src/field.rs index ed867bd7..52b74062 100644 --- a/miniconf_derive/src/field.rs +++ b/miniconf_derive/src/field.rs @@ -1,18 +1,13 @@ -use darling::{ - ast::{self, Data}, - util::Flag, - Error, FromDeriveInput, FromField, -}; -use proc_macro2::TokenStream; -use quote::quote; +use darling::{util::Flag, FromField}; +use proc_macro2::{Span, TokenStream}; +use quote::quote_spanned; +use syn::spanned::Spanned; #[derive(Debug, FromField, Clone)] #[darling(attributes(tree))] pub struct TreeField { pub ident: Option, - // pub vis: syn::Visibility, pub ty: syn::Type, - // attrs: Vec, #[darling(default)] pub depth: usize, pub skip: Flag, @@ -24,11 +19,18 @@ pub struct TreeField { } impl TreeField { - pub(crate) fn typ(&self) -> &syn::Type { + fn span(&self) -> Span { + self.ident + .as_ref() + .map(|i| i.span()) + .unwrap_or(self.ty.span()) + } + + pub fn typ(&self) -> &syn::Type { self.typ.as_ref().unwrap_or(&self.ty) } - pub(crate) fn name(&self) -> Option<&syn::Ident> { + pub fn name(&self) -> Option<&syn::Ident> { self.rename.as_ref().or(self.ident.as_ref()) } @@ -36,18 +38,18 @@ impl TreeField { match &self.ident { None => { let index = syn::Index::from(i); - quote! { #index } + quote_spanned!(self.span()=> #index) } - Some(name) => quote! { #name }, + Some(name) => quote_spanned!(self.span()=> #name), } } - pub(crate) fn traverse_by_key(&self, i: usize) -> Option { + pub fn traverse_by_key(&self, i: usize) -> Option { // Quote context is a match of the field index with `traverse_by_key()` args available. let depth = self.depth; if depth > 0 { let typ = self.typ(); - Some(quote! { + Some(quote_spanned! { self.span()=> #i => <#typ as ::miniconf::TreeKey<#depth>>::traverse_by_key(keys, func) }) } else { @@ -55,12 +57,12 @@ impl TreeField { } } - pub(crate) fn metadata(&self, i: usize) -> Option { + pub fn metadata(&self, i: usize) -> Option { // Quote context is a match of the field index with `metadata()` args available. let depth = self.depth; if depth > 0 { let typ = self.typ(); - Some(quote! { + Some(quote_spanned! { self.span()=> #i => <#typ as ::miniconf::TreeKey<#depth>>::metadata() }) } else { @@ -68,50 +70,66 @@ impl TreeField { } } - fn getter(&self, i: usize) -> TokenStream { - let ident = self.ident_or_index(i); - match &self.get { - Some(get) => quote! { + fn getter(&self, i: usize, is_enum: bool) -> TokenStream { + if let Some(get) = &self.get { + quote_spanned! { get.span()=> #get(self).map_err(|msg| ::miniconf::Traversal::Access(0, msg).into()) - }, - None => quote! { Ok(&self.#ident) }, + } + } else if is_enum { + quote_spanned!(self.span()=> Ok(value)) + } else { + let ident = self.ident_or_index(i); + quote_spanned!(self.span()=> Ok(&self.#ident)) } } - fn getter_mut(&self, i: usize) -> TokenStream { - let ident = self.ident_or_index(i); - match &self.get_mut { - Some(get_mut) => quote!( + fn getter_mut(&self, i: usize, is_enum: bool) -> TokenStream { + if let Some(get_mut) = &self.get_mut { + quote_spanned! { get_mut.span()=> #get_mut(self).map_err(|msg| ::miniconf::Traversal::Access(0, msg).into()) - ), - None => quote!( Ok(&mut self.#ident) ), + } + } else if is_enum { + quote_spanned!(self.span()=> Ok(value)) + } else { + let ident = self.ident_or_index(i); + quote_spanned!(self.span()=> Ok(&mut self.#ident)) } } fn validator(&self) -> TokenStream { - match &self.validate { - Some(validate) => quote! { + if let Some(validate) = &self.validate { + quote_spanned! { validate.span()=> .and_then(|value| #validate(self, value) .map_err(|msg| ::miniconf::Traversal::Invalid(0, msg).into()) ) - }, - None => quote! {}, + } + } else { + quote_spanned!(self.span()=> ) + } + } + + fn lhs(&self, i: usize, ident: Option<&syn::Ident>) -> TokenStream { + if let Some(ident) = ident { + quote_spanned!(ident.span()=> (Self::#ident(value), #i)) + } else { + quote_spanned!(self.span()=> #i) } } - pub(crate) fn serialize_by_key(&self, i: usize) -> TokenStream { + pub fn serialize_by_key(&self, i: usize, ident: Option<&syn::Ident>) -> TokenStream { // Quote context is a match of the field index with `serialize_by_key()` args available. + let lhs = self.lhs(i, ident); let depth = self.depth; - let getter = self.getter(i); + let getter = self.getter(i, ident.is_some()); if depth > 0 { - quote! { - #i => #getter + quote_spanned! { self.span()=> + #lhs => #getter .and_then(|value| ::miniconf::TreeSerialize::<#depth>::serialize_by_key(value, keys, ser)) } } else { - quote! { - #i => #getter + quote_spanned! { self.span()=> + #lhs => #getter .and_then(|value| ::miniconf::Serialize::serialize(value, ser) .map_err(|err| ::miniconf::Error::Inner(0, err)) @@ -121,198 +139,66 @@ impl TreeField { } } - pub(crate) fn deserialize_by_key(&self, i: usize) -> TokenStream { + pub fn deserialize_by_key(&self, i: usize, ident: Option<&syn::Ident>) -> TokenStream { // Quote context is a match of the field index with `deserialize_by_key()` args available. + let lhs = self.lhs(i, ident); let depth = self.depth; - let getter_mut = self.getter_mut(i); + let getter_mut = self.getter_mut(i, ident.is_some()); let validator = self.validator(); if depth > 0 { - quote! { - #i => #getter_mut + quote_spanned! { self.span()=> + #lhs => #getter_mut .and_then(|item| ::miniconf::TreeDeserialize::<'de, #depth>::deserialize_by_key(item, keys, de) ) #validator } } else { - quote! { - #i => ::miniconf::Deserialize::deserialize(de) + quote_spanned! { self.span()=> + #lhs => ::miniconf::Deserialize::deserialize(de) .map_err(|err| ::miniconf::Error::Inner(0, err)) #validator - .and_then(|value| - #getter_mut.and_then(|item| { - *item = value; - Ok(0) + .and_then(|new| + #getter_mut.map(|item| { + *item = new; + 0 }) ) } } } - pub(crate) fn ref_any_by_key(&self, i: usize) -> TokenStream { + pub fn ref_any_by_key(&self, i: usize, ident: Option<&syn::Ident>) -> TokenStream { // Quote context is a match of the field index with `get_mut_by_key()` args available. + let lhs = self.lhs(i, ident); let depth = self.depth; - let getter = self.getter(i); + let getter = self.getter(i, ident.is_some()); if depth > 0 { - quote! { - #i => #getter - .and_then(|value| ::miniconf::TreeAny::<#depth>::ref_any_by_key(value, keys)) + quote_spanned! { self.span()=> + #lhs => #getter + .and_then(|item| ::miniconf::TreeAny::<#depth>::ref_any_by_key(item, keys)) } } else { - quote! { - #i => #getter.map(|value| value as &dyn ::core::any::Any) + quote_spanned! { self.span()=> + #lhs => #getter.map(|item| item as &dyn ::core::any::Any) } } } - pub(crate) fn mut_any_by_key(&self, i: usize) -> TokenStream { + pub fn mut_any_by_key(&self, i: usize, ident: Option<&syn::Ident>) -> TokenStream { // Quote context is a match of the field index with `get_mut_by_key()` args available. + let lhs = self.lhs(i, ident); let depth = self.depth; - let getter_mut = self.getter_mut(i); + let getter_mut = self.getter_mut(i, ident.is_some()); if depth > 0 { - quote! { - #i => #getter_mut - .and_then(|value| ::miniconf::TreeAny::<#depth>::mut_any_by_key(value, keys)) + quote_spanned! { self.span()=> + #lhs => #getter_mut + .and_then(|item| ::miniconf::TreeAny::<#depth>::mut_any_by_key(item, keys)) } } else { - quote! { - #i => #getter_mut.map(|value| value as &mut dyn ::core::any::Any) - } - } - } -} - -#[derive(Debug, FromDeriveInput, Clone)] -#[darling(attributes(tree))] -#[darling(supports(struct_any))] -pub struct Tree { - pub ident: syn::Ident, - pub generics: syn::Generics, - // pub vis: syn::Visibility, - pub data: ast::Data<(), TreeField>, - // attrs: Vec, -} - -impl Tree { - pub(crate) fn depth(&self) -> usize { - self.fields() - .iter() - .fold(0usize, |d, field| d.max(field.depth)) - + 1 - } - - pub(crate) fn parse(input: &syn::DeriveInput) -> Result { - let mut t = Self::from_derive_input(input)?; - let fields = t.fields_mut(); - // unnamed fields can only be skipped if they are terminal - while fields - .last() - .map(|f| f.skip.is_present()) - .unwrap_or_default() - { - fields.pop(); - } - fields.retain(|f| { - if f.skip.is_present() { - assert!(f.ident.is_some()); + quote_spanned! { self.span()=> + #lhs => #getter_mut.map(|item| item as &mut dyn ::core::any::Any) } - !f.skip.is_present() - }); - Ok(t) - } - - pub(crate) fn fields(&self) -> &Vec { - let Data::Struct(fields) = &self.data else { - unreachable!() - }; - &fields.fields - } - - pub(crate) fn fields_mut(&mut self) -> &mut Vec { - let Data::Struct(fields) = &mut self.data else { - unreachable!() - }; - &mut fields.fields - } - - pub(crate) fn bound_generics(&mut self, func: &mut F) - where - F: FnMut(usize) -> Option, - { - let Self { - ref mut generics, - data: Data::Struct(ref fields), - .. - } = self - else { - unreachable!() - }; - for f in fields.fields.iter() { - walk_type_params(f.typ(), func, f.depth, generics) } } } - -fn walk_type_params(typ: &syn::Type, func: &mut F, depth: usize, generics: &mut syn::Generics) -where - F: FnMut(usize) -> Option, -{ - match typ { - syn::Type::Path(syn::TypePath { path, .. }) => { - if let Some(ident) = path.get_ident() { - // The type is a single ident (no other path segments, has no generics): - // call back if it is a generic type for us - for generic in &mut generics.params { - if let syn::GenericParam::Type(type_param) = generic { - if &type_param.ident == ident { - if let Some(bound) = func(depth) { - type_param.bounds.push(bound); - } - } - } - } - } else { - // Analyze the type parameters of the type, as they may be generics for us as well - // This tries to reproduce the bounds that field types place on - // their generic types, directly or indirectly. For this the API depth (the const generic - // param to `TreeKey` etc) is determined as follows: - // - // Assume that all types use their generic T at - // relative depth 1, i.e. - // * if `#[tree(depth(Y > 1))] a: S` then `T: Tree{Key,Serialize,Deserialize}` - // * else (that is if `Y = 1` or `a: S` without `#[tree]`) then - // `T: serde::{Serialize,Deserialize}` - // - // And analogously for nested types `S>` and `[[T; ..]; ..]` etc. - // This is correct for all types in this library (Option, array, structs with the derive macro). - // - // The bounds are conservative (might not be required) and - // fragile (might apply the wrong bound). - // This matches the standard derive behavior and its issues - // https://github.com/rust-lang/rust/issues/26925 - // - // To fix this, one would extend the attribute syntax to allow overriding bounds. - for seg in path.segments.iter() { - if let syn::PathArguments::AngleBracketed(args) = &seg.arguments { - for arg in args.args.iter() { - if let syn::GenericArgument::Type(typ) = arg { - // Found type argument in field type: recurse - walk_type_params(typ, func, depth.saturating_sub(1), generics); - } - } - } - } - } - } - syn::Type::Array(syn::TypeArray { elem, .. }) - | syn::Type::Slice(syn::TypeSlice { elem, .. }) => { - // An array or slice places the element exactly one level deeper: recurse. - walk_type_params(elem, func, depth.saturating_sub(1), generics); - } - syn::Type::Reference(syn::TypeReference { elem, .. }) => { - // A reference is transparent - walk_type_params(elem, func, depth, generics); - } - other => panic!("Unsupported type: {:?}", other), - }; -} diff --git a/miniconf_derive/src/lib.rs b/miniconf_derive/src/lib.rs index bef1b9cb..afa06a26 100644 --- a/miniconf_derive/src/lib.rs +++ b/miniconf_derive/src/lib.rs @@ -1,342 +1,64 @@ use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, parse_quote, DeriveInput}; +use syn::{parse_macro_input, DeriveInput}; mod field; - -fn do_derive_tree_key(mut tree: field::Tree) -> TokenStream { - tree.bound_generics(&mut |depth| { - if depth > 0 { - Some(parse_quote!(::miniconf::TreeKey<#depth>)) - } else { - None - } - }); - - let fields = tree.fields(); - let fields_len = fields.len(); - - let (names, name_to_index, index_to_name, index_len) = - if fields.iter().all(|f| f.ident.is_none()) { - ( - None, - quote!(str::parse(value).ok()), - quote!(if index >= #fields_len { - Err(::miniconf::Traversal::NotFound(1))? - } else { - None - }), - quote!(index.checked_ilog10().unwrap_or_default() as usize + 1), - ) - } else { - let names = fields.iter().map(|field| { - // ident is Some - let name = field.name().unwrap(); - quote! { stringify!(#name) } - }); - ( - Some(quote!( - const __MINICONF_NAMES: &'static [&'static str] = &[#(#names ,)*]; - )), - quote!(Self::__MINICONF_NAMES.iter().position(|&n| n == value)), - quote!(Some( - *Self::__MINICONF_NAMES - .get(index) - .ok_or(::miniconf::Traversal::NotFound(1))? - )), - quote!(Self::__MINICONF_NAMES[index].len()), - ) - }; - - let traverse_by_key_arms = fields - .iter() - .enumerate() - .filter_map(|(i, field)| field.traverse_by_key(i)); - let metadata_arms = fields - .iter() - .enumerate() - .filter_map(|(i, field)| field.metadata(i)); - let defers = fields.iter().map(|field| field.depth > 0); - let depth = tree.depth(); - let ident = &tree.ident; - - let (impl_generics, ty_generics, where_clause) = tree.generics.split_for_impl(); - - quote! { - impl #impl_generics #ident #ty_generics #where_clause { - // TODO: can these be hidden and disambiguated w.r.t. collision? - fn __miniconf_lookup(keys: &mut K) -> Result { - const DEFERS: [bool; #fields_len] = [#(#defers ,)*]; - let index = ::miniconf::Keys::next::(keys)?; - let defer = DEFERS.get(index) - .ok_or(::miniconf::Traversal::NotFound(1))?; - if !defer && !keys.finalize() { - Err(::miniconf::Traversal::TooLong(1)) - } else { - Ok(index) - } - } - - #names - } - - impl #impl_generics ::miniconf::KeyLookup for #ident #ty_generics #where_clause { - const LEN: usize = #fields_len; - - #[inline] - fn name_to_index(value: &str) -> Option { - #name_to_index - } - } - - impl #impl_generics ::miniconf::TreeKey<#depth> for #ident #ty_generics #where_clause { - fn metadata() -> ::miniconf::Metadata { - let mut meta = ::miniconf::Metadata::default(); - for index in 0..#fields_len { - let item_meta: ::miniconf::Metadata = match index { - #(#metadata_arms ,)* - _ => { - let mut m = ::miniconf::Metadata::default(); - m.count = 1; - m - } - }; - meta.max_length = meta.max_length.max( - #index_len + - item_meta.max_length - ); - meta.max_depth = meta.max_depth.max( - item_meta.max_depth - ); - meta.count += item_meta.count; - } - meta.max_depth += 1; - meta - } - - fn traverse_by_key( - mut keys: K, - mut func: F, - ) -> Result> - where - K: ::miniconf::Keys, - F: FnMut(usize, Option<&'static str>, usize) -> Result<(), E>, - { - let index = ::miniconf::Keys::next::(&mut keys)?; - let name = #index_to_name; - func(index, name, #fields_len).map_err(|err| ::miniconf::Error::Inner(1, err))?; - ::miniconf::Error::increment_result(match index { - #(#traverse_by_key_arms ,)* - _ => { - if !keys.finalize() { - Err(::miniconf::Traversal::TooLong(0).into()) - } else { - Ok(0) - } - } - }) - } - } - } - .into() -} - -fn do_derive_tree_serialize(mut tree: field::Tree) -> TokenStream { - tree.bound_generics(&mut |depth| { - if depth > 0 { - Some(parse_quote!(::miniconf::TreeSerialize<#depth>)) - } else { - Some(parse_quote!(::miniconf::Serialize)) - } - }); - - let serialize_by_key_arms = tree - .fields() - .iter() - .enumerate() - .map(|(i, field)| field.serialize_by_key(i)); - let depth = tree.depth(); - let ident = &tree.ident; - - let (impl_generics, ty_generics, where_clause) = tree.generics.split_for_impl(); - - quote! { - impl #impl_generics ::miniconf::TreeSerialize<#depth> for #ident #ty_generics #where_clause { - fn serialize_by_key(&self, mut keys: K, ser: S) -> Result> - where - K: ::miniconf::Keys, - S: ::miniconf::Serializer, - { - let index = Self::__miniconf_lookup(&mut keys)?; - // Note(unreachable) empty structs have diverged by now - #[allow(unreachable_code)] - ::miniconf::Error::increment_result(match index { - #(#serialize_by_key_arms ,)* - _ => unreachable!(), - }) - } - } - }.into() -} - -fn do_derive_tree_deserialize(mut tree: field::Tree) -> TokenStream { - tree.bound_generics(&mut |depth| { - if depth > 0 { - Some(parse_quote!(::miniconf::TreeDeserialize<'de, #depth>)) - } else { - Some(parse_quote!(::miniconf::Deserialize<'de>)) - } - }); - - let depth = tree.depth(); - let ident = &tree.ident; - - let orig_generics = tree.generics.clone(); - let (_, ty_generics, where_clause) = orig_generics.split_for_impl(); - let lts: Vec<_> = tree.generics.lifetimes().cloned().collect(); - tree.generics.params.push(parse_quote!('de)); - if let Some(syn::GenericParam::Lifetime(de)) = tree.generics.params.last_mut() { - assert_eq!(de.lifetime.ident, "de"); - for l in lts { - assert!(l.lifetime.ident != "de"); - de.bounds.push(l.lifetime); - } - } - let (impl_generics, _, _) = tree.generics.split_for_impl(); - - let deserialize_by_key_arms = tree - .fields() - .iter() - .enumerate() - .map(|(i, field)| field.deserialize_by_key(i)); - - quote! { - impl #impl_generics ::miniconf::TreeDeserialize<'de, #depth> for #ident #ty_generics #where_clause { - fn deserialize_by_key(&mut self, mut keys: K, de: D) -> Result> - where - K: ::miniconf::Keys, - D: ::miniconf::Deserializer<'de>, - { - let index = Self::__miniconf_lookup(&mut keys)?; - // Note(unreachable) empty structs have diverged by now - #[allow(unreachable_code)] - ::miniconf::Error::increment_result(match index { - #(#deserialize_by_key_arms ,)* - _ => unreachable!(), - }) - } - } - }.into() -} - -fn do_derive_tree_any(mut tree: field::Tree) -> TokenStream { - tree.bound_generics(&mut |depth| { - if depth > 0 { - Some(parse_quote!(::miniconf::TreeAny<#depth>)) - } else { - Some(parse_quote!(::core::any::Any)) - } - }); - - let ref_any_by_key_arms = tree - .fields() - .iter() - .enumerate() - .map(|(i, field)| field.ref_any_by_key(i)); - let mut_any_by_key_arms = tree - .fields() - .iter() - .enumerate() - .map(|(i, field)| field.mut_any_by_key(i)); - let depth = tree.depth(); - let ident = &tree.ident; - - let (impl_generics, ty_generics, where_clause) = tree.generics.split_for_impl(); - - quote! { - impl #impl_generics ::miniconf::TreeAny<#depth> for #ident #ty_generics #where_clause { - fn ref_any_by_key(&self, mut keys: K) -> Result<&dyn ::core::any::Any, ::miniconf::Traversal> - where - K: ::miniconf::Keys, - { - let index = Self::__miniconf_lookup(&mut keys)?; - // Note(unreachable) empty structs have diverged by now - #[allow(unreachable_code)] - { - let ret: Result<_, _> = match index { - #(#ref_any_by_key_arms ,)* - _ => unreachable!() - }; - ret.map_err(::miniconf::Traversal::increment) - } - } - - fn mut_any_by_key(&mut self, mut keys: K) -> Result<&mut dyn ::core::any::Any, ::miniconf::Traversal> - where - K: ::miniconf::Keys, - { - let index = Self::__miniconf_lookup(&mut keys)?; - // Note(unreachable) empty structs have diverged by now - #[allow(unreachable_code)] - { - let ret: Result<_, _> = match index { - #(#mut_any_by_key_arms ,)* - _ => unreachable!() - }; - ret.map_err(::miniconf::Traversal::increment) - } - } - } - }.into() -} +mod tree; +use tree::Tree; /// Derive the `TreeKey` trait for a struct. #[proc_macro_derive(TreeKey, attributes(tree))] pub fn derive_tree_key(input: TokenStream) -> TokenStream { - match field::Tree::parse(&parse_macro_input!(input as DeriveInput)) { - Ok(t) => do_derive_tree_key(t), - Err(e) => e.write_errors().into(), + match Tree::parse(&parse_macro_input!(input as DeriveInput)) { + Ok(t) => t.tree_key(), + Err(e) => e.write_errors(), } + .into() } /// Derive the `TreeSerialize` trait for a struct. #[proc_macro_derive(TreeSerialize, attributes(tree))] pub fn derive_tree_serialize(input: TokenStream) -> TokenStream { - match field::Tree::parse(&parse_macro_input!(input as DeriveInput)) { - Ok(t) => do_derive_tree_serialize(t), - Err(e) => e.write_errors().into(), + match Tree::parse(&parse_macro_input!(input as DeriveInput)) { + Ok(t) => t.tree_serialize(), + Err(e) => e.write_errors(), } + .into() } /// Derive the `TreeDeserialize` trait for a struct. #[proc_macro_derive(TreeDeserialize, attributes(tree))] pub fn derive_tree_deserialize(input: TokenStream) -> TokenStream { - match field::Tree::parse(&parse_macro_input!(input as DeriveInput)) { - Ok(t) => do_derive_tree_deserialize(t), - Err(e) => e.write_errors().into(), + match Tree::parse(&parse_macro_input!(input as DeriveInput)) { + Ok(t) => t.tree_deserialize(), + Err(e) => e.write_errors(), } + .into() } /// Derive the `TreeAny` trait for a struct. #[proc_macro_derive(TreeAny, attributes(tree))] pub fn derive_tree_any(input: TokenStream) -> TokenStream { - match field::Tree::parse(&parse_macro_input!(input as DeriveInput)) { - Ok(t) => do_derive_tree_any(t), - Err(e) => e.write_errors().into(), + match Tree::parse(&parse_macro_input!(input as DeriveInput)) { + Ok(t) => t.tree_any(), + Err(e) => e.write_errors(), } + .into() } /// Shorthand to derive the `TreeKey`, `TreeAny`, `TreeSerialize`, and `TreeDeserialize` traits for a struct. #[proc_macro_derive(Tree, attributes(tree))] pub fn derive_tree(input: TokenStream) -> TokenStream { - match field::Tree::parse(&parse_macro_input!(input as DeriveInput)) { - Ok(t) => { - let mut ts = do_derive_tree_key(t.clone()); - ts.extend(do_derive_tree_any(t.clone())); - ts.extend(do_derive_tree_serialize(t.clone())); - ts.extend(do_derive_tree_deserialize(t)); - ts - } - Err(e) => e.write_errors().into(), + match Tree::parse(&parse_macro_input!(input as DeriveInput)) { + Ok(t) => [ + t.tree_key(), + t.tree_any(), + t.tree_serialize(), + t.tree_deserialize(), + ] + .into_iter() + .flatten() + .collect(), + Err(e) => e.write_errors(), } + .into() } diff --git a/miniconf_derive/src/tree.rs b/miniconf_derive/src/tree.rs new file mode 100644 index 00000000..510fd88c --- /dev/null +++ b/miniconf_derive/src/tree.rs @@ -0,0 +1,537 @@ +use darling::{ + ast::{self, Data}, + util::{Flag, SpannedValue}, + Error, FromDeriveInput, FromVariant, +}; +use proc_macro2::TokenStream; +use quote::{quote, quote_spanned}; +use syn::parse_quote; + +use crate::field::TreeField; + +#[derive(Debug, FromVariant, Clone)] +#[darling(attributes(tree))] +pub struct TreeVariant { + ident: syn::Ident, + rename: Option, + skip: Flag, + fields: ast::Fields>, +} + +impl TreeVariant { + fn field(&self) -> &SpannedValue { + assert!(self.fields.len() == 1); + self.fields.fields.first().unwrap() + } + + fn name(&self) -> &syn::Ident { + self.rename.as_ref().unwrap_or(&self.ident) + } +} + +#[derive(Debug, FromDeriveInput, Clone)] +#[darling(attributes(tree))] +#[darling(supports(any))] +pub struct Tree { + ident: syn::Ident, + // pub flatten: Flag, // FIXME: implement + generics: syn::Generics, + data: Data, SpannedValue>, +} + +impl Tree { + fn depth(&self) -> usize { + match &self.data { + Data::Struct(fields) => fields.fields.iter().fold(0, |d, field| d.max(field.depth)) + 1, + Data::Enum(variants) => { + variants + .iter() + .fold(0, |d, variant| d.max(variant.field().depth)) + + 1 + } + } + } + + pub fn parse(input: &syn::DeriveInput) -> Result { + let mut tree = Self::from_derive_input(input)?; + + match &mut tree.data { + Data::Struct(fields) => { + // unnamed fields can only be skipped if they are terminal + while fields + .fields + .last() + .map(|f| f.skip.is_present()) + .unwrap_or_default() + { + fields.fields.pop(); + } + fields + .fields + .retain(|f| f.ident.is_none() || !f.skip.is_present()); + if let Some(f) = fields.fields.iter().find(|f| f.skip.is_present()) { + return Err( + Error::custom("Can only `skip` terminal tuple struct fields") + .with_span(&f.skip.span()), + ); + } + } + Data::Enum(variants) => { + variants.retain(|v| !(v.skip.is_present() || v.fields.is_unit())); + for v in variants.iter_mut() { + if v.fields.is_struct() { + // Note(design) For tuple or named struct variants we'd have to create proxy + // structs anyway to support KeyLookup on that level. + return Err( + Error::custom("Struct variants not supported").with_span(&v.span()) + ); + } + // unnamed fields can only be skipped if they are terminal + while v + .fields + .fields + .last() + .map(|f| f.skip.is_present()) + .unwrap_or_default() + { + v.fields.fields.pop(); + } + if let Some(f) = v.fields.iter().find(|f| f.skip.is_present()) { + return Err( + Error::custom("Can only `skip` terminal tuple struct fields") + .with_span(&f.skip.span()), + ); + } + } + } + } + Ok(tree) + } + + fn fields(&self) -> Vec<&SpannedValue> { + match &self.data { + Data::Struct(fields) => fields.iter().collect(), + Data::Enum(variants) => variants.iter().map(|v| v.field()).collect(), + } + } + + fn bound_generics(&self, func: &mut F) -> syn::Generics + where + F: FnMut(usize) -> Option, + { + let mut generics = self.generics.clone(); + for f in self.fields() { + walk_type_params(f.typ(), func, f.depth, &mut generics); + } + generics + } + + pub fn tree_key(&self) -> TokenStream { + let depth = self.depth(); + let ident = &self.ident; + let generics = self.bound_generics(&mut |depth| { + (depth > 0).then_some(parse_quote!(::miniconf::TreeKey<#depth>)) + }); + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let fields = self.fields(); + let fields_len = fields.len(); + let metadata_arms = fields.iter().enumerate().filter_map(|(i, f)| f.metadata(i)); + let traverse_arms = fields + .iter() + .enumerate() + .filter_map(|(i, f)| f.traverse_by_key(i)); + let defers = fields.iter().map(|field| field.depth > 0); + let names: Option> = match &self.data { + Data::Struct(fields) if fields.style.is_struct() => Some( + fields + .iter() + .map(|f| { + // ident is Some + let name = f.name().unwrap(); + quote_spanned! { name.span()=> stringify!(#name) } + }) + .collect(), + ), + Data::Enum(variants) => Some( + variants + .iter() + .map(|v| { + let name = v.name(); + quote_spanned! { name.span()=> stringify!(#name) } + }) + .collect(), + ), + _ => None, + }; + let (names, name_to_index, index_to_name, index_len) = if let Some(names) = names { + ( + Some(quote!( + const __MINICONF_NAMES: [&'static str; #fields_len] = [#(#names ,)*]; + )), + quote!(Self::__MINICONF_NAMES.iter().position(|&n| n == value)), + quote!(Some( + *Self::__MINICONF_NAMES + .get(index) + .ok_or(::miniconf::Traversal::NotFound(1))? + )), + quote!(Self::__MINICONF_NAMES[index].len()), + ) + } else { + ( + None, + quote!(str::parse(value).ok()), + quote!(if index >= #fields_len { + Err(::miniconf::Traversal::NotFound(1))? + } else { + None + }), + quote!(index.checked_ilog10().unwrap_or_default() as usize + 1), + ) + }; + + quote! { + #[automatically_derived] + impl #impl_generics #ident #ty_generics #where_clause { + // TODO: can these be hidden and disambiguated w.r.t. collision? + fn __miniconf_lookup(keys: &mut K) -> Result { + const DEFERS: [bool; #fields_len] = [#(#defers ,)*]; + let index = ::miniconf::Keys::next::(keys)?; + let defer = DEFERS.get(index) + .ok_or(::miniconf::Traversal::NotFound(1))?; + if !defer && !keys.finalize() { + Err(::miniconf::Traversal::TooLong(1)) + } else { + Ok(index) + } + } + + #names + } + + #[automatically_derived] + impl #impl_generics ::miniconf::KeyLookup for #ident #ty_generics #where_clause { + const LEN: usize = #fields_len; + + #[inline] + fn name_to_index(value: &str) -> Option { + #name_to_index + } + } + + #[automatically_derived] + impl #impl_generics ::miniconf::TreeKey<#depth> for #ident #ty_generics #where_clause { + fn metadata() -> ::miniconf::Metadata { + let mut meta = ::miniconf::Metadata::default(); + for index in 0..#fields_len { + let item_meta = match index { + #(#metadata_arms ,)* + _ => { + let mut m = ::miniconf::Metadata::default(); + m.count = 1; + m + } + }; + meta.max_length = meta.max_length.max( + #index_len + + item_meta.max_length + ); + meta.max_depth = meta.max_depth.max( + item_meta.max_depth + ); + meta.count += item_meta.count; + } + meta.max_depth += 1; + meta + } + + fn traverse_by_key( + mut keys: K, + mut func: F, + ) -> Result> + where + K: ::miniconf::Keys, + F: FnMut(usize, Option<&'static str>, usize) -> Result<(), E>, + { + let index = ::miniconf::Keys::next::(&mut keys)?; + let name = #index_to_name; + func(index, name, #fields_len).map_err(|err| ::miniconf::Error::Inner(1, err))?; + ::miniconf::Error::increment_result(match index { + #(#traverse_arms ,)* + _ => { + if !keys.finalize() { + Err(::miniconf::Traversal::TooLong(0).into()) + } else { + Ok(0) + } + } + }) + } + } + } + } + + pub fn tree_serialize(&self) -> TokenStream { + let depth = self.depth(); + let ident = &self.ident; + let generics = self.bound_generics(&mut |depth| { + Some(if depth > 0 { + parse_quote!(::miniconf::TreeSerialize<#depth>) + } else { + parse_quote!(::miniconf::Serialize) + }) + }); + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let (mat, arms, default): (_, Vec<_>, _) = match &self.data { + Data::Struct(fields) => ( + quote!(index), + fields + .iter() + .enumerate() + .map(|(i, f)| f.serialize_by_key(i, None)) + .collect(), + quote!(unreachable!()), + ), + Data::Enum(variants) => ( + quote!((self, index)), + variants + .iter() + .enumerate() + .map(|(i, v)| v.field().serialize_by_key(i, Some(&v.ident))) + .collect(), + quote!(Err(::miniconf::Traversal::Absent(0).into())), + ), + }; + + quote! { + #[automatically_derived] + impl #impl_generics ::miniconf::TreeSerialize<#depth> for #ident #ty_generics #where_clause { + fn serialize_by_key(&self, mut keys: K, ser: S) -> Result> + where + K: ::miniconf::Keys, + S: ::miniconf::Serializer, + { + let index = Self::__miniconf_lookup(&mut keys)?; + // Note(unreachable) empty structs have diverged by now + #[allow(unreachable_code)] + ::miniconf::Error::increment_result(match #mat { + #(#arms ,)* + _ => #default + }) + } + } + } + } + + pub fn tree_deserialize(&self) -> TokenStream { + let mut generics = self.bound_generics(&mut |depth| { + Some(if depth > 0 { + parse_quote!(::miniconf::TreeDeserialize<'de, #depth>) + } else { + parse_quote!(::miniconf::Deserialize<'de>) + }) + }); + + let depth = self.depth(); + let ident = &self.ident; + + let orig_generics = generics.clone(); + let (_, ty_generics, where_clause) = orig_generics.split_for_impl(); + let lts: Vec<_> = generics.lifetimes().cloned().collect(); + generics.params.push(parse_quote!('de)); + if let Some(syn::GenericParam::Lifetime(de)) = generics.params.last_mut() { + assert_eq!(de.lifetime.ident, "de"); + for l in lts { + assert!(l.lifetime.ident != "de"); + de.bounds.push(l.lifetime); + } + } + let (impl_generics, _, _) = generics.split_for_impl(); + + let (mat, arms, default): (_, Vec<_>, _) = match &self.data { + Data::Struct(fields) => ( + quote!(index), + fields + .iter() + .enumerate() + .map(|(i, f)| f.deserialize_by_key(i, None)) + .collect(), + quote!(unreachable!()), + ), + Data::Enum(variants) => ( + quote!((self, index)), + variants + .iter() + .enumerate() + .map(|(i, v)| v.field().deserialize_by_key(i, Some(&v.ident))) + .collect(), + quote!(Err(::miniconf::Traversal::Absent(0).into())), + ), + }; + + quote! { + #[automatically_derived] + impl #impl_generics ::miniconf::TreeDeserialize<'de, #depth> for #ident #ty_generics #where_clause { + fn deserialize_by_key(&mut self, mut keys: K, de: D) -> Result> + where + K: ::miniconf::Keys, + D: ::miniconf::Deserializer<'de>, + { + let index = Self::__miniconf_lookup(&mut keys)?; + // Note(unreachable) empty structs have diverged by now + #[allow(unreachable_code)] + ::miniconf::Error::increment_result(match #mat { + #(#arms ,)* + _ => #default + }) + } + } + } + } + + pub fn tree_any(&self) -> TokenStream { + let generics = self.bound_generics(&mut |depth| { + Some(if depth > 0 { + parse_quote!(::miniconf::TreeAny<#depth>) + } else { + parse_quote!(::core::any::Any) + }) + }); + + let depth = self.depth(); + let ident = &self.ident; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let (mat, ref_arms, mut_arms, default): (_, Vec<_>, Vec<_>, _) = match &self.data { + Data::Struct(fields) => ( + quote!(index), + fields + .iter() + .enumerate() + .map(|(i, f)| f.ref_any_by_key(i, None)) + .collect(), + fields + .iter() + .enumerate() + .map(|(i, f)| f.mut_any_by_key(i, None)) + .collect(), + quote!(unreachable!()), + ), + Data::Enum(variants) => ( + quote!((self, index)), + variants + .iter() + .enumerate() + .map(|(i, v)| v.field().ref_any_by_key(i, Some(&v.ident))) + .collect(), + variants + .iter() + .enumerate() + .map(|(i, v)| v.field().mut_any_by_key(i, Some(&v.ident))) + .collect(), + quote!(Err(::miniconf::Traversal::Absent(0).into())), + ), + }; + + quote! { + #[automatically_derived] + impl #impl_generics ::miniconf::TreeAny<#depth> for #ident #ty_generics #where_clause { + fn ref_any_by_key(&self, mut keys: K) -> Result<&dyn ::core::any::Any, ::miniconf::Traversal> + where + K: ::miniconf::Keys, + { + let index = Self::__miniconf_lookup(&mut keys)?; + // Note(unreachable) empty structs have diverged by now + #[allow(unreachable_code)] + { + let ret: Result<_, _> = match #mat { + #(#ref_arms ,)* + _ => #default + }; + ret.map_err(::miniconf::Traversal::increment) + } + } + + fn mut_any_by_key(&mut self, mut keys: K) -> Result<&mut dyn ::core::any::Any, ::miniconf::Traversal> + where + K: ::miniconf::Keys, + { + let index = Self::__miniconf_lookup(&mut keys)?; + // Note(unreachable) empty structs have diverged by now + #[allow(unreachable_code)] + { + let ret: Result<_, _> = match #mat { + #(#mut_arms ,)* + _ => #default + }; + ret.map_err(::miniconf::Traversal::increment) + } + } + } + } + } +} + +fn walk_type_params(typ: &syn::Type, func: &mut F, depth: usize, generics: &mut syn::Generics) +where + F: FnMut(usize) -> Option, +{ + match typ { + syn::Type::Path(syn::TypePath { path, .. }) => { + if let Some(ident) = path.get_ident() { + // The type is a single ident (no other path segments, has no generics): + // call back if it is a generic type for us + for generic in &mut generics.params { + if let syn::GenericParam::Type(type_param) = generic { + if &type_param.ident == ident { + if let Some(bound) = func(depth) { + type_param.bounds.push(bound.into()); + } + } + } + } + } else { + // Analyze the type parameters of the type, as they may be generics for us as well + // This tries to reproduce the bounds that field types place on + // their generic types, directly or indirectly. For this the API depth (the const generic + // param to `TreeKey` etc) is determined as follows: + // + // Assume that all types use their generic T at + // relative depth 1, i.e. + // * if `#[tree(depth(Y > 1))] a: S` then `T: Tree{Key,Serialize,Deserialize}` + // * else (that is if `Y = 1` or `a: S` without `#[tree]`) then + // `T: serde::{Serialize,Deserialize}` + // + // And analogously for nested types `S>` and `[[T; ..]; ..]` etc. + // This is correct for all types in this library (Option, array, structs with the derive macro). + // + // The bounds are conservative (might not be required) and + // fragile (might apply the wrong bound). + // This matches the standard derive behavior and its issues + // https://github.com/rust-lang/rust/issues/26925 + // + // To fix this, one would extend the attribute syntax to allow overriding bounds. + for seg in path.segments.iter() { + if let syn::PathArguments::AngleBracketed(args) = &seg.arguments { + for arg in args.args.iter() { + if let syn::GenericArgument::Type(typ) = arg { + // Found type argument in field type: recurse + walk_type_params(typ, func, depth.saturating_sub(1), generics); + } + } + } + } + } + } + syn::Type::Array(syn::TypeArray { elem, .. }) + | syn::Type::Slice(syn::TypeSlice { elem, .. }) => { + // An array or slice places the element exactly one level deeper: recurse. + walk_type_params(elem, func, depth.saturating_sub(1), generics); + } + syn::Type::Reference(syn::TypeReference { elem, .. }) => { + // A reference is transparent + walk_type_params(elem, func, depth, generics); + } + other => panic!("Unsupported type: {:?}", other), + }; +} diff --git a/miniconf_mqtt/Cargo.toml b/miniconf_mqtt/Cargo.toml index b5d6bae7..0d85452f 100644 --- a/miniconf_mqtt/Cargo.toml +++ b/miniconf_mqtt/Cargo.toml @@ -8,10 +8,6 @@ description = "MQTT interface for `miniconf`, using `minimq`" repository = "https://github.com/quartiq/miniconf" keywords = ["settings", "serde", "no_std", "json", "mqtt"] categories = ["no-std", "config", "rust-patterns", "parsing"] -resolver = "2" - -[features] -std = [] [lib] @@ -30,7 +26,7 @@ name = "mqtt" [dev-dependencies] machine = "0.3" env_logger = "0.11" -std-embedded-nal = { git = "https://gitlab.com/ryan-summers/std-embedded-nal", branch = "feature/0.8" } +std-embedded-nal = "0.3" tokio = { version = "1.9", features = ["rt-multi-thread", "time", "macros"] } std-embedded-time = "0.1" miniconf = { features = ["json-core", "derive"], path = "../miniconf" } diff --git a/miniconf_mqtt/src/lib.rs b/miniconf_mqtt/src/lib.rs index 4245b07a..994a7f6a 100644 --- a/miniconf_mqtt/src/lib.rs +++ b/miniconf_mqtt/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(not(any(test, doctest)), no_std)] +#![no_std] #![deny(rust_2018_compatibility)] #![deny(rust_2018_idioms)] #![warn(missing_docs)] diff --git a/py/miniconf-mqtt/miniconf/__main__.py b/py/miniconf-mqtt/miniconf/__main__.py index aac99630..c3405af4 100644 --- a/py/miniconf-mqtt/miniconf/__main__.py +++ b/py/miniconf-mqtt/miniconf/__main__.py @@ -83,31 +83,42 @@ async def run(): if arg.endswith("?"): path = arg.removesuffix("?") assert path.startswith("/") or not path - for p in await interface.list(path): + paths = await interface.list(path) + # Note: There is no way for the CLI tool to reliably + # distinguish a one-element leaf get responce from a + # one-element inner list response without looking at + # the payload. + # The only way is to note that a JSON payload of a + # get can not start with the / that a list response + # starts with. + if len(paths) == 1 and not paths[0].startswith("/"): + print(f"{path}={paths[0]}") + continue + for p in paths: try: value = await interface.get(p) - print(f"List `{p}` = `{value}`") + print(f"{p}={value}") except MiniconfException as err: - print(f"List `{p}`: {repr(err)}") + print(f"{p}: {repr(err)}") elif arg.endswith("!"): path = arg.removesuffix("!") assert path.startswith("/") or not path await interface.dump(path) - print(f"Dumped `{path}` into namespace") + print(f"{path}: Dumped into MQTT namespace") elif "=" in arg: path, value = arg.split("=", 1) assert path.startswith("/") or not path if not value: await interface.clear(path) - print(f"Cleared retained `{path}`") + print(f"{path}: Cleared retained") else: await interface.set(path, json.loads(value), args.retain) - print(f"Set `{path}` = `{value}`") + print(f"{path}={value}") else: path = arg assert path.startswith("/") or not path value = await interface.get(path) - print(f"Get `{path}` = `{value}`") + print(f"{path}={value}") asyncio.run(run())