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}