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}