infinitree_macros/
lib.rs

1#![forbid(unsafe_code, unused_crate_dependencies)]
2#![deny(clippy::all)]
3
4// these are used in generated code, so tests will fail unless we have
5// them
6#[cfg(test)]
7use infinitree as _;
8#[cfg(test)]
9use serde as _;
10
11use proc_macro::TokenStream;
12use syn::{parse_macro_input, DeriveInput};
13
14mod derive_index;
15
16/// Example use of the derive macro:
17///
18/// ```
19/// use infinitree::fields::{Serialized, VersionedMap};
20/// use serde::{Serialize, Deserialize};
21///
22/// #[derive(Serialize, Deserialize)]
23/// struct PlantHealth {
24///     id: usize,
25///     air_humidity: usize,
26///     soil_humidity: usize,
27///     temperature: f32
28/// }
29///
30/// #[derive(infinitree::Index, Default, Clone)]
31/// pub struct Measurements {
32///     // rename the field when serializing
33///     #[infinitree(name = "last_time")]
34///     _old_last_time: Serialized<String>,
35///
36///     #[infinitree(name = "last_time2")]
37///     last_time: Serialized<usize>,
38///
39///     // only store the keys in the index, not the values
40///     #[infinitree(strategy = "infinitree::fields::SparseField")]
41///     measurements: VersionedMap<usize, PlantHealth>,
42///
43///     // skip the next field when loading & serializing
44///     #[infinitree(skip)]
45///     current_time: usize,
46/// }
47/// ```
48#[proc_macro_derive(Index, attributes(infinitree))]
49pub fn derive_index_macro(input: TokenStream) -> TokenStream {
50    let input = parse_macro_input!(input as DeriveInput);
51    derive_index::expand(derive_index::crate_name_token(), input)
52        .unwrap_or_else(syn::Error::into_compile_error)
53        .into()
54}
55
56#[cfg(test)]
57mod tests {
58    #[test]
59    fn test_macro() {
60        use quote::quote;
61        use syn::parse_quote;
62
63        let input = parse_quote! {
64        #[derive(Default, Index)]
65        pub struct TestStruct<T> {
66            /// A field with both an accessor method and serialized to storage
67            unattributed: ChunkIndex,
68
69            /// Rename the field to `renamed_chunks` both in serialized form
70            /// and accessor method
71            #[infinitree(name = "renamed_chunks")]
72            chunks: ChunkIndex,
73
74            /// Skip generating accessors and exclude from on-disk structure
75            #[infinitree(skip)]
76            _unreferenced: ChunkIndex,
77
78            /// Skip generating accessors and exclude from on-disk structure
79            #[infinitree(strategy = "infinitree::fields::SparseField")]
80            strategizing: ChunkIndex,
81
82            #[infinitree(skip)]
83            _ph: PhantomData<T>
84        }
85        };
86
87        let result = super::derive_index::expand(quote::quote!(::infinitree), input).unwrap();
88
89        #[rustfmt::skip]
90        let expected = quote! {
91        #[automatically_derived]
92        impl<T> TestStruct<T> {
93            #[inline]
94            pub fn unattributed(&'_ self) -> ::infinitree::fields::Intent<Box<::infinitree::fields::LocalField<ChunkIndex>>> {
95                use ::infinitree::fields::{Intent, strategy::Strategy};
96                Intent::new(
97                    "unattributed",
98                    Box::new(::infinitree::fields::LocalField::for_field(
99			&self.unattributed,
100		    )),
101                )
102            }
103            #[inline]
104            pub fn renamed_chunks(&'_ self) -> ::infinitree::fields::Intent<Box<::infinitree::fields::LocalField<ChunkIndex>>> {
105                use ::infinitree::fields::{Intent, strategy::Strategy};
106                Intent::new(
107                    "renamed_chunks",
108                    Box::new(::infinitree::fields::LocalField::for_field(
109			&self.chunks,
110		    )),
111                )
112            }
113            #[inline]
114            pub fn strategizing(&'_ self) -> ::infinitree::fields::Intent<Box<infinitree::fields::SparseField<ChunkIndex>>> {
115                use ::infinitree::fields::{Intent, strategy::Strategy};
116                Intent::new(
117                    "strategizing",
118                    Box::new(infinitree::fields::SparseField::for_field(
119                        &self.strategizing,
120                    )),
121                )
122            }
123            pub fn fields(&self) -> Vec<String> {
124                vec!["unattributed".into(),
125		     "renamed_chunks".into(),
126		     "strategizing".into(),
127		]
128            }
129        }
130        impl<T> ::infinitree::Index for TestStruct<T> {
131            fn store_all(&'_ self) -> ::infinitree::anyhow::Result<Vec<::infinitree::fields::Intent<Box<dyn ::infinitree::fields::Store>>>> {
132                Ok(vec![
133                    self.unattributed().into(),
134                    self.renamed_chunks().into(),
135                    self.strategizing().into(),
136                ])
137            }
138            fn load_all(&'_ self) -> ::infinitree::anyhow::Result<Vec<::infinitree::fields::Intent<Box<dyn ::infinitree::fields::Load>>>> {
139                Ok(vec![
140                    self.unattributed().into(),
141                    self.renamed_chunks().into(),
142                    self.strategizing().into(),
143                ])
144            }
145        }
146            };
147
148        assert_eq!(result.to_string(), expected.to_string());
149    }
150}