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!
6pub use proc_macro2::TokenStream as TokenStream2;
7pub 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))]
13pub 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))]
18pub 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]
88macro_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 ::core::result::Result::Ok(p) => {
99 match $crate::Structure::try_new(&p) {
100 ::core::result::Result::Ok(s) => $crate::MacroResult::into_stream($inner(s)),
101 ::core::result::Result::Err(e) => {
102 ::core::convert::Into::into(e.to_compile_error())
103 }
104 }
105 }
106 ::core::result::Result::Err(e) => {
107 ::core::convert::Into::into(e.to_compile_error())
108 }
109 }
110 }
111 };
112}
113
114/// The `decl_attribute!` macro declares a custom attribute wrapper. It will
115/// parse the incoming `TokenStream` into a `synstructure::Structure` object,
116/// and pass it into the inner function.
117///
118/// Your inner function should have the following type:
119///
120/// ```
121/// fn attribute(
122/// attr: proc_macro2::TokenStream,
123/// structure: synstructure::Structure,
124/// ) -> proc_macro2::TokenStream {
125/// unimplemented!()
126/// }
127/// ```
128///
129/// # Usage
130///
131/// ```
132/// fn attribute_interesting(
133/// _attr: proc_macro2::TokenStream,
134/// _structure: synstructure::Structure,
135/// ) -> proc_macro2::TokenStream {
136/// quote::quote! { ... }
137/// }
138///
139/// # const _IGNORE: &'static str = stringify! {
140/// decl_attribute!([interesting] => attribute_interesting);
141/// # };
142/// ```
143///
144/// *This macro is available if `synstructure` is built with the `"proc-macro"`
145/// feature.*
146#[cfg(all(
147 not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "wasi"))),
148 feature = "proc-macro"
149))]
150#[macro_export]
151macro_rules! decl_attribute {
152 ([$attribute:ident] => $(#[$($attrs:tt)*])* $inner:path) => {
153 #[proc_macro_attribute]
154 $(#[$($attrs)*])*
155 pub fn $attribute(
156 attr: $crate::macros::TokenStream,
157 i: $crate::macros::TokenStream,
158 ) -> $crate::macros::TokenStream {
159 match $crate::macros::parse::<$crate::macros::DeriveInput>(i) {
160 ::core::result::Result::Ok(p) => match $crate::Structure::try_new(&p) {
161 ::core::result::Result::Ok(s) => {
162 $crate::MacroResult::into_stream(
163 $inner(::core::convert::Into::into(attr), s)
164 )
165 }
166 ::core::result::Result::Err(e) => {
167 ::core::convert::Into::into(e.to_compile_error())
168 }
169 },
170 ::core::result::Result::Err(e) => {
171 ::core::convert::Into::into(e.to_compile_error())
172 }
173 }
174 }
175 };
176}
177
178/// Run a test on a custom derive. This macro expands both the original struct
179/// and the expansion to ensure that they compile correctly, and confirms that
180/// feeding the original struct into the named derive will produce the written
181/// output.
182///
183/// You can add `no_build` to the end of the macro invocation to disable
184/// checking that the written code compiles. This is useful in contexts where
185/// the procedural macro cannot depend on the crate where it is used during
186/// tests.
187///
188/// # Usage
189///
190/// ```
191/// fn test_derive_example(_s: synstructure::Structure)
192/// -> Result<proc_macro2::TokenStream, syn::Error>
193/// {
194/// Ok(quote::quote! { const YOUR_OUTPUT: &'static str = "here"; })
195/// }
196///
197/// fn main() {
198/// synstructure::test_derive!{
199/// test_derive_example {
200/// struct A;
201/// }
202/// expands to {
203/// const YOUR_OUTPUT: &'static str = "here";
204/// }
205/// }
206/// }
207/// ```
208#[macro_export]
209macro_rules! test_derive {
210 ($name:path { $($i:tt)* } expands to { $($o:tt)* }) => {
211 {
212 #[allow(dead_code)]
213 fn ensure_compiles() {
214 $($i)*
215 $($o)*
216 }
217
218 $crate::test_derive!($name { $($i)* } expands to { $($o)* } no_build);
219 }
220 };
221
222 ($name:path { $($i:tt)* } expands to { $($o:tt)* } no_build) => {
223 {
224 let i = ::core::stringify!( $($i)* );
225 let parsed = $crate::macros::parse_str::<$crate::macros::DeriveInput>(i)
226 .expect(::core::concat!(
227 "Failed to parse input to `#[derive(",
228 ::core::stringify!($name),
229 ")]`",
230 ));
231
232 let raw_res = $name($crate::Structure::new(&parsed));
233 let res = $crate::MacroResult::into_result(raw_res)
234 .expect(::core::concat!(
235 "Procedural macro failed for `#[derive(",
236 ::core::stringify!($name),
237 ")]`",
238 ));
239
240 let expected = ::core::stringify!( $($o)* )
241 .parse::<$crate::macros::TokenStream2>()
242 .expect("output should be a valid TokenStream");
243 let mut expected_toks = <$crate::macros::TokenStream2
244 as ::core::convert::From<$crate::macros::TokenStream2>>::from(expected);
245 if <$crate::macros::TokenStream2 as ::std::string::ToString>::to_string(&res)
246 != <$crate::macros::TokenStream2 as ::std::string::ToString>::to_string(&expected_toks)
247 {
248 panic!("\
249test_derive failed:
250expected:
251```
252{}
253```
254
255got:
256```
257{}
258```\n",
259 $crate::unpretty_print(&expected_toks),
260 $crate::unpretty_print(&res),
261 );
262 }
263 }
264 };
265}
266