structstruck/
lib.rs

1//! ## Nested struct and enum definitions
2//!
3//! One of the best parts of Rust's ecosystem is `serde`,
4//! and how it allows to comfortably use native Rust types when working with
5//! serialized data in pretty much any format.
6//!
7//! Take this JSON object for example:
8//! ```json
9//! {
10//!   "name": "asdf",
11//!   "storage": {
12//!     "diskSize": "10Gi",
13//!     "storageTypes": {
14//!       "hdd": false,
15//!       "ssd": true
16//!     }
17//!   }
18//! }
19//! ```
20//! If you have some practice, you can probably immediately imagine a set of Rust structs
21//! which the JSON object could deserialize into:
22//! ```no_run
23//! struct Resource {
24//!     name: String,
25//!     storage: Storage,
26//! }
27//! struct Storage {
28//!     disk_size: String,
29//!     storage_types: StorageTypes,
30//! }
31//! struct StorageTypes {
32//!     hdd: bool,
33//!     ssd: bool,
34//! }
35//! ```
36//! Since Rust's structs are "flat", every JSON subobject needs its own struct,
37//! and they need to be typed out one next to the other, and not nested like the JSON object.
38//! This can get unwieldy for large objects with many fields and subobjects.
39//!
40//! What if instead, you could just create your structs in the same nested style?
41//! ```no_run
42//! # // Can't check whether these things are equal in doctests because I can only call public functions
43//! # structstruck::strike!{
44//! struct Resource {
45//!     name: String,
46//!     storage: struct {
47//!         disk_size: String,
48//!         storage_types: struct {
49//!             hdd: bool,
50//!             ssd: bool,
51//!         }
52//!     }
53//! }
54//! # };
55//! ```
56//! This crate allows you to do exactly that, at the expense of one macro.
57//!
58//! ### Usage
59//!
60//! Wrap your nested struct into an invocation of `structstruck::strike!`.
61//! ```no_run
62//! structstruck::strike! {
63//!     struct Outer {
64//!         inner: struct {
65//!             value: usize
66//!         }
67//!     }
68//! }
69//! ```
70//! This will expand to flat struct definitions:
71//! ```no_run
72//! struct Outer {
73//!     inner: Inner,
74//! }
75//! struct Inner {
76//!     value: usize
77//! }
78//! ```
79//! Since the inner struct's name was not given, it was automatically inferred from the field name
80//! (similarly done for tuple enum variants).
81//!
82//! The inferred name can be overwritten if necessary:
83//! ```no_run
84//! structstruck::strike! {
85//!     struct Outer {
86//!         inner: struct InNer {
87//!             value: usize
88//!         }
89//!     }
90//! }
91//! ```
92//!
93//! #### Supported declarations
94//! structstruck, despite its name, works with enums and structs, and with tuple and named variants.
95//! ```no_run
96//! structstruck::strike! {
97//!     struct Outer {
98//!         enum_demo: enum {
99//!             NamedVariant {
100//!                 tuple_struct: struct (usize)
101//!             }
102//!             TupleVariant(struct InsideTupleVariant (isize))
103//!         }
104//!     }
105//! }
106//! ```
107//! This will generate the following declarations:
108//! ```no_run
109//! struct TupleStruct(usize);
110//! struct InsideTupleVariant(isize);
111//! enum EnumDemo {
112//!     NamedVariant { tuple_struct: TupleStruct },
113//!     TupleVariant(InsideTupleVariant),
114//! }
115//! ```
116//!
117//! #### Substructs in generics
118//! Declarations may appear inside generics arguments. (It works "as you would expect".)
119//! ```no_run
120//! structstruck::strike! {
121//!     struct Parent {
122//!         a: Option<struct {
123//!             c: u32,
124//!         }>,
125//!         b: Result<
126//!             struct Then {
127//!                 d: u64,
128//!             },
129//!             struct Else {
130//!                 e: u128,
131//!             },
132//!         >
133//!     }
134//! }
135//! ```
136//! The above results in
137//! ```no_run
138//! struct A {
139//!     c: u32,
140//! }
141//! struct Then {
142//!     d: u64,
143//! }
144//! struct Else {
145//!     e: u128,
146//! }
147//! struct Parent {
148//!     a: Option<A>,
149//!     b: Result<Then, Else>,
150//! }
151//! ```
152//! (The structs themselves being generic is not supported yet(?).)
153//!
154//! #### Attributes
155//! Applying attributes (or doc comments) to a single inner struct would be syntactically awkward:
156//! ```no_run
157//! structstruck::strike! {
158//!     struct Outer {
159//!         documented: /** documentation */ struct {},
160//!         attributed: #[allow(madness)] struct {},
161//!     }
162//! }
163//! ```
164//! Thus, `structstruck` allows to use inner attributes at the start of the struct declarations and automatically transforms them to outer attributes
165//! ```no_run
166//! structstruck::strike! {
167//!     struct Outer {
168//!         documented: struct {
169//!             //! documentation
170//!         },
171//!         attributed: struct {
172//!             #![forbid(madness)]
173//!         },
174//!     }
175//! }
176//! ```
177//!
178//! To quickly apply attributes to all declarations, attributes can be wrapped in the `#[strikethrough[…]]`
179//! pseudoattribute.
180//! ```no_run
181//! structstruck::strike! {
182//!     // It's strikethrough[…], not strikethrough(…)
183//!     // This appears to confuse even the rustdoc syntax highlighter
184//!     #[strikethrough[derive(Debug)]]
185//!     struct Parent {
186//!         a: Option<struct {
187//!             c: u32,
188//!         }>,
189//!         b: Result<
190//!             struct Then {
191//!                 d: u64,
192//!             },
193//!             struct Else {
194//!                 e: u128,
195//!             },
196//!         >
197//!     }
198//! }
199//! println!("{:#?}", Parent { ..todo!("value skipped for brevity") });
200//! ```
201//!
202//! ### Missing features, limitations
203//!  * You can't exclude subtrees from `#[strikethrough[…]]`.
204//!  * Generic parameter constraints need to be repeated for each struct.
205//!  * Usage error handling is minimal, e.g.:
206//!  * No protection against using the name of a field twice as the name of a struct,  
207//!    e.g. with `foo: Result<struct {…}, struct {…}>,`
208//!  * All substructs will be linearized directly next to the parent struct - without any namespacing or modules.  
209//!    Would be interesting to support `foo: struct foo::Foo {…}` or some automatic version of that.
210//!  * rustfmt really doesn't play along.
211
212mod imp;
213#[cfg(test)]
214mod test;
215
216/// Main functionality
217///
218/// See crate level documentation.
219// I would have loved to make this a proc_macro_attribute.
220// But it seems those require that the declarations are actual valid Rust.
221// proc_macro relaxes this to valid TokenTrees.
222#[proc_macro]
223pub fn strike(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
224    let mut ret = Default::default();
225    let item = imp::flatten_empty_groups(item.into());
226    imp::recurse_through_definition(item, vec![], false, &mut ret);
227    ret.into()
228}