proc_macro_tester/lib.rs
1//! This crate provides macros for testing procedural macros.
2//!
3//! Only attribute macros are supported at the moment.
4
5#[doc(hidden)]
6pub use quote;
7
8/// Asserts that the first code block with attribute macro
9/// expands to the second code block.
10///
11/// The first code block must consist of
12/// a attribute macro and an item.
13/// The implementation function of the macro must
14/// * present in the same scope with the same name as the attribute macro
15/// * have a signature of `(macro_attributes: A, applied_item: I) -> Result<R, _>`,
16/// where `A` and `I` implement [`From<proc_macro2::TokenStream>`](proc_macro2::TokenStream),
17/// and `R` implements [`Into<proc_macro2::TokenStream>`](proc_macro2::TokenStream)
18///
19/// The second code block may consist of arbitrary tokens.
20///
21/// See also [`assert_yields!`] for macros
22/// that does not modify the original input.
23///
24/// # Examples
25/// ```
26/// use proc_macro_tester::assert_expands;
27///
28/// assert_expands!(
29/// {
30/// #[add_id_field(u32)]
31/// struct Person {
32/// name: String,
33/// }
34/// },
35/// {
36/// struct Person {
37/// id: u32,
38/// name: String,
39/// }
40/// }
41/// );
42///
43/// # // macro implementation
44/// # // usage: #[add_id_field(id_type)]
45/// # use syn::spanned::Spanned;
46/// # fn add_id_field(
47/// # attrs: proc_macro2::TokenStream,
48/// # item: proc_macro2::TokenStream,
49/// # ) -> syn::Result<proc_macro2::TokenStream> {
50/// # // get id type from macro attributes
51/// # let id_type: syn::Type = syn::parse2(attrs)?;
52/// # // get input struct
53/// # let mut item_struct: syn::ItemStruct = syn::parse2(item)?;
54/// #
55/// # // get struct fields
56/// # let syn::Fields::Named(fields) = &mut item_struct.fields else {
57/// # return Err(syn::Error::new(
58/// # item_struct.fields.span(),
59/// # "The struct must have named fields",
60/// # ));
61/// # };
62/// # // prepend an id field
63/// # fields.named.insert(
64/// # 0,
65/// # syn::parse_quote! {
66/// # id: #id_type
67/// # },
68/// # );
69/// #
70/// # // return the modified struct
71/// # Ok(quote::quote! {
72/// # #item_struct
73/// # })
74/// # }
75/// ```
76#[macro_export]
77macro_rules! assert_expands {
78 (
79 {
80 #[ $macro_name:ident $(( $($attrs:tt)* ))? ]
81 $item:item
82 },
83 {
84 $($expanded:tt)*
85 }
86 ) => {
87 ::core::assert_eq!(
88 ::std::string::ToString::to_string(
89 &$macro_name(
90 ::core::convert::Into::into($crate::quote::quote!($( $($attrs)* )?)),
91 ::core::convert::Into::into($crate::quote::quote!($item))
92 ).unwrap()
93 ),
94 ::std::string::ToString::to_string(
95 &$crate::quote::quote!($($expanded)*)
96 )
97 );
98 };
99 (
100 {
101 #[ $(::)? $_:ident :: $($macro_path:ident)::* $(( $($attrs:tt)* ))? ]
102 $item:item
103 },
104 {
105 $($expanded:tt)*
106 }
107 ) => {
108 $crate::assert_expands!(
109 {
110 #[ $($macro_path)::* $(( $($attrs)* ))? ]
111 $item
112 },
113 {
114 $($expanded)*
115 }
116 );
117 };
118}
119
120/// Asserts that the first code block with attribute macro
121/// yields the second code block.
122///
123/// The first code block must consist of
124/// a attribute macro and an item.
125/// The implementation function of the macro must
126/// * present in the same scope with the same name as the attribute macro
127/// * have a signature of `(macro_attributes: A, applied_item: I) -> Result<R, _>`,
128/// where `A` and `I` implement [`From<proc_macro2::TokenStream>`](proc_macro2::TokenStream),
129/// and `R` implements [`Into<proc_macro2::TokenStream>`](proc_macro2::TokenStream)
130///
131/// The second code block may consist of arbitrary tokens.
132///
133/// See also [`assert_expands!`] for macros
134/// that modify the original input.
135///
136/// # Examples
137/// ```
138/// use proc_macro_tester::assert_yields;
139///
140/// assert_yields!(
141/// {
142/// #[create_struct_without_id(NewPerson)]
143/// struct Person {
144/// id: u32,
145/// name: String,
146/// }
147/// },
148/// {
149/// // do not write the original item here
150/// // struct Person {
151/// // id: u32,
152/// // name: String,
153/// // }
154/// struct NewPerson {
155/// name: String,
156/// }
157/// }
158/// );
159///
160/// # // macro implementation
161/// # // usage: #[struct_without_id(NewStructName)]
162/// # use syn::spanned::Spanned;
163/// # fn create_struct_without_id(
164/// # attrs: proc_macro2::TokenStream,
165/// # item: proc_macro2::TokenStream,
166/// # ) -> syn::Result<proc_macro2::TokenStream> {
167/// # // get new struct name from macro attributes
168/// # let struct_name: syn::Ident = syn::parse2(attrs)?;
169/// # // get input struct
170/// # let item_struct: syn::ItemStruct = syn::parse2(item)?;
171/// #
172/// # // get struct fields
173/// # let syn::Fields::Named(fields_named_orig) = &item_struct.fields else {
174/// # return Err(syn::Error::new(
175/// # item_struct.fields.span(),
176/// # "The struct must have named fields",
177/// # ));
178/// # };
179/// # // create fields for new struct without id field
180/// # let mut found_id = false;
181/// # let fields_new = fields_named_orig
182/// # .named
183/// # .clone()
184/// # .into_pairs()
185/// # .filter(|punct_field| match punct_field.value().ident {
186/// # Some(ref ident) if ident == "id" => {
187/// # found_id = true;
188/// # false
189/// # }
190/// # _ => true,
191/// # })
192/// # .collect();
193/// # if !found_id {
194/// # return Err(syn::Error::new(
195/// # item_struct.fields.span(),
196/// # "The struct does not have an `id` field",
197/// # ));
198/// # }
199/// #
200/// # // generated struct
201/// # let new_struct = syn::ItemStruct {
202/// # ident: struct_name,
203/// # fields: syn::Fields::Named(syn::FieldsNamed {
204/// # brace_token: fields_named_orig.brace_token,
205/// # named: fields_new,
206/// # }),
207/// # ..item_struct.clone()
208/// # };
209/// #
210/// # // return the modified struct
211/// # Ok(quote::quote! {
212/// # #item_struct
213/// # #new_struct
214/// # })
215/// # }
216/// ```
217#[macro_export]
218macro_rules! assert_yields {
219 ({
220 #[ $(::)? $macro_path1:ident $(:: $macro_path2:ident)* $(( $($attrs:tt)* ))? ]
221 $item:item
222 }, {
223 $($expanded:tt)*
224 }) => {
225 $crate::assert_expands!(
226 {
227 #[ $macro_path1 $(:: $macro_path2)* $(( $($attrs)* ))? ]
228 $item
229 },
230 {
231 $item
232 $($expanded)*
233 }
234 );
235 };
236}
237
238#[cfg(test)]
239mod unit_test {
240 use super::*;
241
242 fn macro_debugger(
243 attrs: proc_macro2::TokenStream,
244 item: proc_macro2::TokenStream,
245 ) -> Result<proc_macro2::TokenStream, ()> {
246 Ok(quote::quote! {
247 attrs = { #attrs }
248 item = { #item }
249 })
250 }
251
252 fn thrice(
253 _attrs: proc_macro2::TokenStream,
254 item: proc_macro2::TokenStream,
255 ) -> Result<proc_macro2::TokenStream, ()> {
256 Ok(quote::quote! {
257 #item
258 #item
259 #item
260 })
261 }
262
263 fn identity_macro(
264 _attrs: proc_macro2::TokenStream,
265 item: proc_macro2::TokenStream,
266 ) -> Result<proc_macro2::TokenStream, ()> {
267 Ok(item)
268 }
269
270 #[test]
271 fn assert_expands() {
272 assert_expands!(
273 {
274 #[macro_debugger(any token here (as attrs))]
275 struct Some(Item);
276 },
277 {
278 attrs = {
279 any token here (as attrs)
280 }
281 item = {
282 struct Some(Item);
283 }
284 }
285 );
286 assert_expands!(
287 {
288 #[thrice(ignored attrs)]
289 some_item!();
290 },
291 {
292 some_item!();
293 some_item!();
294 some_item!();
295 }
296 );
297 assert_expands!(
298 {
299 #[identity_macro(ignored attrs)]
300 mod some_item;
301 },
302 {
303 mod some_item;
304 }
305 );
306 }
307
308 #[test]
309 fn assert_yields() {
310 assert_yields!(
311 {
312 #[thrice(ignored attrs)]
313 some_item!();
314 },
315 {
316 some_item!();
317 some_item!();
318 }
319 );
320 assert_yields!(
321 {
322 #[identity_macro(ignored attrs)]
323 mod some_item;
324 },
325 {
326 }
327 );
328 }
329
330 #[test]
331 fn multiple_segments_macro_path() {
332 assert_expands!(
333 {
334 #[path::to::identity_macro(ignored attrs)]
335 mod some_item;
336 },
337 {
338 mod some_item;
339 }
340 );
341
342 assert_yields!(
343 {
344 #[path::to::identity_macro(ignored attrs)]
345 mod some_item;
346 },
347 {
348 }
349 );
350 }
351
352 #[test]
353 fn macro_path_with_leading_colons() {
354 assert_expands!(
355 {
356 #[::path::to::identity_macro(ignored attrs)]
357 mod some_item;
358 },
359 {
360 mod some_item;
361 }
362 );
363
364 assert_yields!(
365 {
366 #[::path::to::identity_macro(ignored attrs)]
367 mod some_item;
368 },
369 {
370 }
371 );
372 }
373}