proc_macro_utils/assert.rs
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 separate 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)]
151macro_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
184/// Asserts that the `lhs` matches the tokens wrapped in braces on the `rhs`.
185///
186/// `lhs` needs to be an expression implementing `IntoIterator<Item=TokenTree>`
187/// e.g. [`TokenStream`](proc_macro2::TokenStream) or
188/// [`TokenParser`](crate::TokenParser).
189/// ```
190/// # use quote::quote;
191/// # use proc_macro_utils::assert_tokens;
192/// let some_tokens = quote! { ident, { group } };
193///
194/// assert_tokens! {some_tokens, { ident, {group} }};
195/// ```
196#[macro_export]
197#[cfg(doc)]
198macro_rules! assert_tokens {
199 ($lhs:expr, { $($rhs:tt)* }) => {};
200}
201
202#[macro_export]
203#[cfg(not(doc))]
204#[doc(hidden)]
205#[allow(clippy::module_name_repetitions)]
206macro_rules! assert_tokens {
207 ($lhs:expr, {$($rhs:tt)*}) => {{
208 let mut lhs = $crate::TokenParser::new_generic::<3, _, _>($lhs);
209 $crate::assert_tokens!(@O lhs, "", $($rhs)*);
210 }};
211 (@E $prefix:expr, $expected:tt, $found:tt) => {
212 panic!("expected\n {}\nfound\n {}\nat\n {} {}", stringify!$expected, $found, $prefix, $found);
213 };
214 (@E $prefix:expr, $expected:tt) => {
215 panic!("unexpected end, expected\n {}\nafter\n {}", stringify!$expected, $prefix);
216 };
217 (@G $lhs:ident, $fn:ident, $aggr:expr, $sym:literal, $group:tt, {$($inner:tt)*}, $($rhs:tt)*) => {
218 if let Some(lhs) = $lhs.$fn() {
219 let mut lhs = $crate::TokenParser::<_, 3>::from($crate::__private::proc_macro2::Group::stream(&lhs));
220 $crate::assert_tokens!(@O lhs, concat!($aggr, ' ', $sym), $($inner)*);
221 } else if let Some(lhs) = $lhs.next() {
222 $crate::assert_tokens!(@E $aggr, ($group), lhs);
223 } else {
224 $crate::assert_tokens!(@E $aggr, ($group));
225 }
226 $crate::assert_tokens!(@O $lhs, $crate::assert_tokens!(@C $aggr, $group), $($rhs)*);
227 };
228 // These don't add a whitespace in front
229 (@C $lhs:expr, ,) => {
230 concat!($lhs, ',')
231 };
232 (@C $lhs:expr, :) => {
233 concat!($lhs, ':')
234 };
235 (@C $lhs:expr, ;) => {
236 concat!($lhs, ';')
237 };
238 (@C $lhs:expr, .) => {
239 concat!($lhs, '.')
240 };
241 // All other tokens do
242 (@C $lhs:expr, $rhs:tt) => {
243 concat!($lhs, ' ', stringify!($rhs))
244 };
245 (@O $lhs:ident, $aggr:expr,) => { assert!($lhs.is_empty(), "unexpected left over tokens `{}`", $lhs.into_token_stream()); };
246 (@O $lhs:ident, $aggr:expr, ( $($inner:tt)* ) $($rhs:tt)*) => {
247 $crate::assert_tokens!(@G $lhs, next_parenthesized, $aggr, '(', { $($inner)* }, { $($inner)* }, $($rhs)*);
248 };
249 (@O $lhs:ident, $aggr:expr, { $($inner:tt)* } $($rhs:tt)*) => {
250 $crate::assert_tokens!(@G $lhs, next_braced, $aggr, '{', { $($inner)* }, { $($inner)* }, $($rhs)*);
251 };
252 (@O $lhs:ident, $aggr:expr, [ $($inner:tt)* ] $($rhs:tt)*) => {
253 $crate::assert_tokens!(@G $lhs, next_bracketed, $aggr, '[', [ $($inner)* ], { $($inner)* }, $($rhs)*);
254 };
255 (@O $lhs:ident, $aggr:expr, $token:tt $($rhs:tt)*) => {
256 if let Some(lhs) = $lhs.next_macro_rules_tt().map(|t|t.to_string()).or_else(|| $lhs.next().map(|t|t.to_string())) {
257 if(lhs != stringify!($token)) {
258 $crate::assert_tokens!(@E $aggr, ($token), lhs);
259 }
260 } else {
261 $crate::assert_tokens!(@E $aggr, ($token));
262 }
263 $crate::assert_tokens!(@O $lhs, $crate::assert_tokens!(@C $aggr, $token), $($rhs)*);
264 };
265}
266
267#[test]
268fn test() {
269 // TODO testing with quote is incomplete `":::"` can be joint joint alone if
270 // produced directly not with quote.
271 use quote::quote;
272 assert_tokens!(quote!(ident ident, { group/test, vec![a, (a + b)] }, "literal" $), {
273 ident ident, { group /test, vec![a,(a+b)] }, "literal" $
274 });
275 assert_tokens!(quote!(:::), {
276 :::
277 });
278 assert_tokens!(quote!(more:::test::test:: hello :-D $$$ It should just work), {
279 more ::: test ::test:: hello :-D $$$ It should just work
280 });
281
282 assert_tokens!(quote!(:$), {: $});
283}