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 | ::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 ] |
151 | macro_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 ] |
209 | macro_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!("\ |
249 | test_derive failed: |
250 | expected: |
251 | ``` |
252 | {} |
253 | ``` |
254 | |
255 | got: |
256 | ``` |
257 | {} |
258 | ``` \n" , |
259 | $crate::unpretty_print(&expected_toks), |
260 | $crate::unpretty_print(&res), |
261 | ); |
262 | } |
263 | } |
264 | }; |
265 | } |
266 | |