1 //! This module provides two utility macros for testing custom derives. They can
2 //! be used together to eliminate some of the boilerplate required in order to
3 //! declare and test custom derive implementations.
4 
5 // Re-exports used by the decl_derive! and test_derive!
6 pub use proc_macro2::TokenStream as TokenStream2;
7 pub use syn::{parse_str, DeriveInput};
8 
9 #[cfg(all(
10     not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "wasi"))),
11     feature = "proc-macro"
12 ))]
13 pub use proc_macro::TokenStream;
14 #[cfg(all(
15     not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "wasi"))),
16     feature = "proc-macro"
17 ))]
18 pub use syn::parse;
19 
20 /// The `decl_derive!` macro declares a custom derive wrapper. It will parse the
21 /// incoming `TokenStream` into a `synstructure::Structure` object, and pass it
22 /// into the inner function.
23 ///
24 /// Your inner function should take a `synstructure::Structure` by value, and
25 /// return a type implementing `synstructure::MacroResult`, for example:
26 ///
27 /// ```
28 /// fn derive_simple(input: synstructure::Structure) -> proc_macro2::TokenStream {
29 ///     unimplemented!()
30 /// }
31 ///
32 /// fn derive_result(input: synstructure::Structure)
33 ///     -> syn::Result<proc_macro2::TokenStream>
34 /// {
35 ///     unimplemented!()
36 /// }
37 /// ```
38 ///
39 /// # Usage
40 ///
41 /// ### Without Attributes
42 /// ```
43 /// fn derive_interesting(_input: synstructure::Structure) -> proc_macro2::TokenStream {
44 ///     quote::quote! { ... }
45 /// }
46 ///
47 /// # const _IGNORE: &'static str = stringify! {
48 /// decl_derive!([Interesting] => derive_interesting);
49 /// # };
50 /// ```
51 ///
52 /// ### With Attributes
53 /// ```
54 /// # fn main() {}
55 /// fn derive_interesting(_input: synstructure::Structure) -> proc_macro2::TokenStream {
56 ///     quote::quote! { ... }
57 /// }
58 ///
59 /// # const _IGNORE: &'static str = stringify! {
60 /// decl_derive!([Interesting, attributes(interesting_ignore)] => derive_interesting);
61 /// # };
62 /// ```
63 ///
64 /// ### Decl Attributes & Doc Comments
65 /// ```
66 /// # fn main() {}
67 /// fn derive_interesting(_input: synstructure::Structure) -> proc_macro2::TokenStream {
68 ///     quote::quote! { ... }
69 /// }
70 ///
71 /// # const _IGNORE: &'static str = stringify! {
72 /// decl_derive! {
73 ///     [Interesting] =>
74 ///     #[allow(some_lint)]
75 ///     /// Documentation Comments
76 ///     derive_interesting
77 /// }
78 /// # };
79 /// ```
80 ///
81 /// *This macro is available if `synstructure` is built with the `"proc-macro"`
82 /// feature.*
83 #[cfg(all(
84     not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "wasi"))),
85     feature = "proc-macro"
86 ))]
87 #[macro_export]
88 macro_rules! decl_derive {
89     // XXX: Switch to using this variant everywhere?
90     ([$derives:ident $($derive_t:tt)*] => $(#[$($attrs:tt)*])* $inner:path) => {
91         #[proc_macro_derive($derives $($derive_t)*)]
92         #[allow(non_snake_case)]
93         $(#[$($attrs)*])*
94         pub fn $derives(
95             i: $crate::macros::TokenStream
96         ) -> $crate::macros::TokenStream {
97             match $crate::macros::parse::<$crate::macros::DeriveInput>(i) {
98                 Ok(p) => {
99                     match $crate::Structure::try_new(&p) {
100                         Ok(s) => $crate::MacroResult::into_stream($inner(s)),
101                         Err(e) => e.to_compile_error().into(),
102                     }
103                 }
104                 Err(e) => e.to_compile_error().into(),
105             }
106         }
107     };
108 }
109 
110 /// The `decl_attribute!` macro declares a custom attribute wrapper. It will
111 /// parse the incoming `TokenStream` into a `synstructure::Structure` object,
112 /// and pass it into the inner function.
113 ///
114 /// Your inner function should have the following type:
115 ///
116 /// ```
117 /// fn attribute(
118 ///     attr: proc_macro2::TokenStream,
119 ///     structure: synstructure::Structure,
120 /// ) -> proc_macro2::TokenStream {
121 ///     unimplemented!()
122 /// }
123 /// ```
124 ///
125 /// # Usage
126 ///
127 /// ```
128 /// fn attribute_interesting(
129 ///     _attr: proc_macro2::TokenStream,
130 ///     _structure: synstructure::Structure,
131 /// ) -> proc_macro2::TokenStream {
132 ///     quote::quote! { ... }
133 /// }
134 ///
135 /// # const _IGNORE: &'static str = stringify! {
136 /// decl_attribute!([interesting] => attribute_interesting);
137 /// # };
138 /// ```
139 ///
140 /// *This macro is available if `synstructure` is built with the `"proc-macro"`
141 /// feature.*
142 #[cfg(all(
143     not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "wasi"))),
144     feature = "proc-macro"
145 ))]
146 #[macro_export]
147 macro_rules! decl_attribute {
148     ([$attribute:ident] => $(#[$($attrs:tt)*])* $inner:path) => {
149         #[proc_macro_attribute]
150         $(#[$($attrs)*])*
151         pub fn $attribute(
152             attr: $crate::macros::TokenStream,
153             i: $crate::macros::TokenStream,
154         ) -> $crate::macros::TokenStream {
155             match $crate::macros::parse::<$crate::macros::DeriveInput>(i) {
156                 Ok(p) => match $crate::Structure::try_new(&p) {
157                     Ok(s) => $crate::MacroResult::into_stream($inner(attr.into(), s)),
158                     Err(e) => e.to_compile_error().into(),
159                 },
160                 Err(e) => e.to_compile_error().into(),
161             }
162         }
163     };
164 }
165 
166 /// Run a test on a custom derive. This macro expands both the original struct
167 /// and the expansion to ensure that they compile correctly, and confirms that
168 /// feeding the original struct into the named derive will produce the written
169 /// output.
170 ///
171 /// You can add `no_build` to the end of the macro invocation to disable
172 /// checking that the written code compiles. This is useful in contexts where
173 /// the procedural macro cannot depend on the crate where it is used during
174 /// tests.
175 ///
176 /// # Usage
177 ///
178 /// ```
179 /// fn test_derive_example(_s: synstructure::Structure)
180 ///     -> Result<proc_macro2::TokenStream, syn::Error>
181 /// {
182 ///     Ok(quote::quote! { const YOUR_OUTPUT: &'static str = "here"; })
183 /// }
184 ///
185 /// fn main() {
186 ///     synstructure::test_derive!{
187 ///         test_derive_example {
188 ///             struct A;
189 ///         }
190 ///         expands to {
191 ///             const YOUR_OUTPUT: &'static str = "here";
192 ///         }
193 ///     }
194 /// }
195 /// ```
196 #[macro_export]
197 macro_rules! test_derive {
198     ($name:path { $($i:tt)* } expands to { $($o:tt)* }) => {
199         {
200             #[allow(dead_code)]
201             fn ensure_compiles() {
202                 $($i)*
203                 $($o)*
204             }
205 
206             $crate::test_derive!($name { $($i)* } expands to { $($o)* } no_build);
207         }
208     };
209 
210     ($name:path { $($i:tt)* } expands to { $($o:tt)* } no_build) => {
211         {
212             let i = stringify!( $($i)* );
213             let parsed = $crate::macros::parse_str::<$crate::macros::DeriveInput>(i)
214                 .expect(concat!(
215                     "Failed to parse input to `#[derive(",
216                     stringify!($name),
217                     ")]`",
218                 ));
219 
220             let raw_res = $name($crate::Structure::new(&parsed));
221             let res = $crate::MacroResult::into_result(raw_res)
222                 .expect(concat!(
223                     "Procedural macro failed for `#[derive(",
224                     stringify!($name),
225                     ")]`",
226                 ));
227 
228             let expected = stringify!( $($o)* )
229                 .parse::<$crate::macros::TokenStream2>()
230                 .expect("output should be a valid TokenStream");
231             let mut expected_toks = $crate::macros::TokenStream2::from(expected);
232             if res.to_string() != expected_toks.to_string() {
233                 panic!("\
234 test_derive failed:
235 expected:
236 ```
237 {}
238 ```
239 
240 got:
241 ```
242 {}
243 ```\n",
244                     $crate::unpretty_print(&expected_toks),
245                     $crate::unpretty_print(&res),
246                 );
247             }
248         }
249     };
250 }
251