1+ /// Allows simple unit testing of proc macro implementations.
2+ ///
3+ /// This macro only works with functions taking [`proc_macro2::TokenStream`] due
4+ /// to the [`proc_macro`] api not being available in unit tests. This can be
5+ /// achieved either by manually creating a seperate function:
6+ /// ```ignore
7+ /// use proc_macro::TokenStream;
8+ /// use proc_macro2::TokenStream as TokenStream2;
9+ /// #[proc_macro]
10+ /// pub fn actual_macro(input: TokenStream) -> TokenStream {
11+ /// macro_impl(input.into()).into()
12+ /// }
13+ /// fn macro_impl(input: TokenStream2) -> TokenStream2 {
14+ /// // ...
15+ /// }
16+ /// ```
17+ /// or use a crate like [`manyhow`](https://docs.rs/manyhow/):
18+ /// ```ignore
19+ /// use proc_macro2::TokenStream as TokenStream2;
20+ /// #[manyhow(impl_fn)] // generates `fn actual_macro_impl`
21+ /// pub fn actual_macro(input: TokenStream2) -> TokenStream2 {
22+ /// // ...
23+ /// }
24+ /// ```
25+ ///
26+ /// # Function like macros
27+ /// ```
28+ /// # use proc_macro_utils::assert_expansion;
29+ /// # use proc_macro2::TokenStream;
30+ /// # use quote::quote;
31+ /// // Dummy attribute macro impl
32+ /// fn macro_impl(input: TokenStream) -> TokenStream {
33+ /// quote!(#input)
34+ /// }
35+ /// fn macro_impl_result(input: TokenStream) -> Result<TokenStream, ()> {
36+ /// Ok(quote!(#input))
37+ /// }
38+ /// assert_expansion!(
39+ /// macro_impl!(something test),
40+ /// { something test }
41+ /// );
42+ /// assert_expansion!(
43+ /// macro_impl![1, 2, 3],
44+ /// { 1, 2, 3 }
45+ /// );
46+ /// assert_expansion!(
47+ /// macro_impl!{ braced },
48+ /// { braced }
49+ /// );
50+ /// // adding a single function call (without arguments) is also allowed e.g. `unwrap()`
51+ /// assert_expansion!(
52+ /// macro_impl_result!(result).unwrap(),
53+ /// { result }
54+ /// );
55+ /// ```
56+ ///
57+ /// # Derive macros
58+ /// ```
59+ /// # use proc_macro_utils::assert_expansion;
60+ /// # use proc_macro2::TokenStream;
61+ /// # use quote::quote;
62+ /// // Dummy derive macro impl
63+ /// fn macro_impl(item: TokenStream) -> TokenStream {
64+ /// quote!(#item)
65+ /// }
66+ /// fn macro_impl_result(item: TokenStream) -> Result<TokenStream, ()> {
67+ /// Ok(quote!(#item))
68+ /// }
69+ /// assert_expansion!(
70+ /// #[derive(macro_impl)]
71+ /// struct A; // the comma after items is optional
72+ /// { struct A; }
73+ /// );
74+ /// assert_expansion!(
75+ /// #[derive(macro_impl)]
76+ /// struct A {}
77+ /// { struct A {} }
78+ /// );
79+ /// // adding a single function call (without arguments) is also allowed e.g. `unwrap()`
80+ /// assert_expansion!(
81+ /// #[derive(macro_impl_result)]
82+ /// struct A {}.unwrap()
83+ /// { struct A {} }
84+ /// );
85+ /// // alternatively the proc_macro syntax is compatible
86+ /// assert_expansion!(
87+ /// macro_impl!{ struct A {} },
88+ /// { struct A {} }
89+ /// );
90+ /// ```
91+ ///
92+ /// # Attribute macros
93+ /// ```
94+ /// # use proc_macro_utils::assert_expansion;
95+ /// # use proc_macro2::TokenStream;
96+ /// # use quote::quote;
97+ /// // Dummy attribute macro impl
98+ /// fn macro_impl(input: TokenStream, item: TokenStream) -> TokenStream {
99+ /// quote!(#input, #item)
100+ /// }
101+ /// fn macro_impl_result(input: TokenStream, item: TokenStream) -> Result<TokenStream, ()> {
102+ /// Ok(quote!(#input, #item))
103+ /// }
104+ /// assert_expansion!(
105+ /// #[macro_impl]
106+ /// struct A;
107+ /// { , struct A; }
108+ /// );
109+ /// assert_expansion!(
110+ /// #[macro_impl = "hello"]
111+ /// fn test() { }, // the comma after items is optional
112+ /// { "hello", fn test() {} }
113+ /// );
114+ /// assert_expansion!(
115+ /// #[macro_impl(a = 10)]
116+ /// impl Hello for World {},
117+ /// { a = 10, impl Hello for World {} }
118+ /// );
119+ /// // adding a single function call (without arguments) is also allowed e.g. `unwrap()`
120+ /// assert_expansion!(
121+ /// #[macro_impl_result(a = 10)]
122+ /// impl Hello for World {}.unwrap(),
123+ /// { a = 10, impl Hello for World {} }
124+ /// );
125+ /// ```
126+ ///
127+ /// # Generic usage
128+ /// On top of the normal macro inputs a generic input is also supported.
129+ /// ```
130+ /// # use proc_macro_utils::assert_expansion;
131+ /// # use proc_macro2::TokenStream;
132+ /// # use quote::quote;
133+ /// fn macro_impl(first: TokenStream, second: TokenStream, third: TokenStream) -> TokenStream {
134+ /// quote!(#first, #second, #third)
135+ /// }
136+ /// fn macro_impl_result(first: TokenStream, second: TokenStream, third: TokenStream) -> Result<TokenStream, ()> {
137+ /// Ok(quote!(#first, #second, #third))
138+ /// }
139+ /// assert_expansion!(
140+ /// macro_impl({ 1 }, { something }, { ":)" }),
141+ /// { 1, something, ":)" }
142+ /// );
143+ /// // adding a single function call (without arguments) is also allowed e.g. `unwrap()`
144+ /// assert_expansion!(
145+ /// macro_impl_result({ 1 }, { something }, { ":)" }).unwrap(),
146+ /// { 1, something, ":)" }
147+ /// );
148+ /// ```
149+ #[ macro_export]
150+ #[ allow( clippy:: module_name_repetitions) ]
151+ macro_rules! assert_expansion {
152+ ( $macro: ident!( $( $input: tt) * ) $( . $fn: ident( ) ) ?, { $( $rhs: tt) * } ) => {
153+ $crate:: assert_expansion!( $macro( { $( $input) * } ) $( . $fn( ) ) ?, { $( $rhs) * } )
154+ } ;
155+ ( $macro: ident![ $( $input: tt) * ] $( . $fn: ident( ) ) ?, { $( $rhs: tt) * } ) => {
156+ $crate:: assert_expansion!( $macro( { $( $input) * } ) $( . $fn( ) ) ?, { $( $rhs) * } )
157+ } ;
158+ ( $macro: ident!{ $( $input: tt) * } $( . $fn: ident( ) ) ?, { $( $rhs: tt) * } ) => {
159+ $crate:: assert_expansion!( $macro( { $( $input) * } ) $( . $fn( ) ) ?, { $( $rhs) * } )
160+ } ;
161+ ( #[ derive( $macro: ident) ] $item: item$( . $fn: ident( ) ) ?$( , ) ? { $( $rhs: tt) * } ) => {
162+ $crate:: assert_expansion!( $macro( { $item} ) $( . $fn( ) ) ?, { $( $rhs) * } )
163+ } ;
164+ ( #[ $macro: ident] $item: item$( . $fn: ident( ) ) ?$( , ) ? { $( $rhs: tt) * } ) => {
165+ $crate:: assert_expansion!( $macro( { } , { $item} ) $( . $fn( ) ) ?, { $( $rhs) * } )
166+ } ;
167+ ( #[ $macro: ident = $input: expr] $item: item$( . $fn: ident( ) ) ?$( , ) ? { $( $rhs: tt) * } ) => {
168+ $crate:: assert_expansion!( $macro( { $input} , { $item} ) $( . $fn( ) ) ?, { $( $rhs) * } )
169+ } ;
170+ ( #[ $macro: ident( $( $input: tt) * ) ] $item: item$( . $fn: ident( ) ) ?$( , ) ? { $( $rhs: tt) * } ) => {
171+ $crate:: assert_expansion!( $macro( { $( $input) * } , { $item} ) $( . $fn( ) ) ?, { $( $rhs) * } )
172+ } ;
173+ ( $macro: ident( $( { $( $input: tt) * } ) ,+$( , ) ?) $( . $fn: ident( ) ) ?, { $( $rhs: tt) * } ) => {
174+ $crate:: assert_tokens!(
175+ $crate:: __private:: proc_macro2:: TokenStream :: from( $macro(
176+ $( <$crate:: __private:: proc_macro2:: TokenStream as :: core:: str :: FromStr >
177+ :: from_str( :: core:: stringify!( $( $input) * ) ) . unwrap( ) . into( ) ) ,+
178+ ) $( . $fn( ) ) ?) ,
179+ { $( $rhs) * }
180+ )
181+ } ;
182+ }
183+
1184/// Asserts that the `lhs` matches the tokens wrapped in braces on the `rhs`.
2185///
3186/// `lhs` needs to be an expression implementing `IntoIterator<Item=TokenTree>`
@@ -21,10 +204,10 @@ macro_rules! assert_tokens {
21204#[ doc( hidden) ]
22205#[ allow( clippy:: module_name_repetitions) ]
23206macro_rules! assert_tokens {
24- ( $lhs: expr, { $( $rhs: tt) * } ) => {
207+ ( $lhs: expr, { $( $rhs: tt) * } ) => { {
25208 let mut lhs = $crate:: TokenParser :: new_generic:: <3 , _, _>( $lhs) ;
26- assert_tokens!( @O lhs, "" , $( $rhs) * ) ;
27- } ;
209+ $crate :: assert_tokens!( @O lhs, "" , $( $rhs) * ) ;
210+ } } ;
28211 ( @E $prefix: expr, $expected: tt, $found: tt) => {
29212 panic!( "expected\n {}\n found\n {}\n at\n {} {}" , stringify!$expected, $found, $prefix, $found) ;
30213 } ;
@@ -34,13 +217,13 @@ macro_rules! assert_tokens {
34217 ( @G $lhs: ident, $fn: ident, $aggr: expr, $sym: literal, $group: tt, { $( $inner: tt) * } , $( $rhs: tt) * ) => {
35218 if let Some ( lhs) = $lhs. $fn( ) {
36219 let mut lhs = $crate:: TokenParser :: <_, 3 >:: from( $crate:: __private:: proc_macro2:: Group :: stream( & lhs) ) ;
37- assert_tokens!( @O lhs, concat!( $aggr, ' ' , $sym) , $( $inner) * ) ;
220+ $crate :: assert_tokens!( @O lhs, concat!( $aggr, ' ' , $sym) , $( $inner) * ) ;
38221 } else if let Some ( lhs) = $lhs. next( ) {
39- assert_tokens!( @E $aggr, ( $group) , lhs) ;
222+ $crate :: assert_tokens!( @E $aggr, ( $group) , lhs) ;
40223 } else {
41- assert_tokens!( @E $aggr, ( $group) ) ;
224+ $crate :: assert_tokens!( @E $aggr, ( $group) ) ;
42225 }
43- assert_tokens!( @O $lhs, assert_tokens!( @C $aggr, $group) , $( $rhs) * ) ;
226+ $crate :: assert_tokens!( @O $lhs, $crate :: assert_tokens!( @C $aggr, $group) , $( $rhs) * ) ;
44227 } ;
45228 // These don't add a whitespace in front
46229 ( @C $lhs: expr, , ) => {
@@ -61,28 +244,30 @@ macro_rules! assert_tokens {
61244 } ;
62245 ( @O $lhs: ident, $aggr: expr, ) => { assert!( $lhs. is_empty( ) , "unexpected left over tokens `{}`" , $lhs. into_token_stream( ) ) ; } ;
63246 ( @O $lhs: ident, $aggr: expr, ( $( $inner: tt) * ) $( $rhs: tt) * ) => {
64- assert_tokens!( @G $lhs, next_parenthesized, $aggr, '(' , { $( $inner) * } , { $( $inner) * } , $( $rhs) * ) ;
247+ $crate :: assert_tokens!( @G $lhs, next_parenthesized, $aggr, '(' , { $( $inner) * } , { $( $inner) * } , $( $rhs) * ) ;
65248 } ;
66249 ( @O $lhs: ident, $aggr: expr, { $( $inner: tt) * } $( $rhs: tt) * ) => {
67- assert_tokens!( @G $lhs, next_braced, $aggr, '{' , { $( $inner) * } , { $( $inner) * } , $( $rhs) * ) ;
250+ $crate :: assert_tokens!( @G $lhs, next_braced, $aggr, '{' , { $( $inner) * } , { $( $inner) * } , $( $rhs) * ) ;
68251 } ;
69252 ( @O $lhs: ident, $aggr: expr, [ $( $inner: tt) * ] $( $rhs: tt) * ) => {
70- assert_tokens!( @G $lhs, next_bracketed, $aggr, '[' , [ $( $inner) * ] , { $( $inner) * } , $( $rhs) * ) ;
253+ $crate :: assert_tokens!( @G $lhs, next_bracketed, $aggr, '[' , [ $( $inner) * ] , { $( $inner) * } , $( $rhs) * ) ;
71254 } ;
72255 ( @O $lhs: ident, $aggr: expr, $token: tt $( $rhs: tt) * ) => {
73256 if let Some ( lhs) = $lhs. next_punctuation_group( ) . map( |t|t. to_string( ) ) . or_else( || $lhs. next( ) . map( |t|t. to_string( ) ) ) {
74257 if ( lhs != stringify!( $token) ) {
75- assert_tokens!( @E $aggr, ( $token) , lhs) ;
258+ $crate :: assert_tokens!( @E $aggr, ( $token) , lhs) ;
76259 }
77260 } else {
78- assert_tokens!( @E $aggr, ( $token) ) ;
261+ $crate :: assert_tokens!( @E $aggr, ( $token) ) ;
79262 }
80- assert_tokens!( @O $lhs, assert_tokens!( @C $aggr, $token) , $( $rhs) * ) ;
263+ $crate :: assert_tokens!( @O $lhs, $crate :: assert_tokens!( @C $aggr, $token) , $( $rhs) * ) ;
81264 } ;
82265}
83266
84267#[ test]
85268fn test ( ) {
269+ // TODO testing with quote is incomplete `":::"` can be joint joint alone if
270+ // produced directly not with quote.
86271 use quote:: quote;
87272 assert_tokens ! ( quote!( ident ident, { group/test, vec![ a, ( a + b) ] } , "literal" $) , {
88273 ident ident, { group /test, vec![ a, ( a+b) ] } , "literal" $
0 commit comments