Skip to content

Commit 8c4a931

Browse files
committed
macros: preserve none-delimiters in function body
When user-generated token streams are returned from proc macros, all none-delimited groups are flattened. This can cause compilation errors as shown in the issue below. By instead passing the function body token stream straight through from the input of the proc macro to its output, it does not get deconstructed and reconstructed, and so the none-delimited groups are not lost. Fixes: #3579
1 parent e06b257 commit 8c4a931

File tree

2 files changed

+44
-31
lines changed

2 files changed

+44
-31
lines changed

tests-integration/tests/macros_main.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,21 @@ fn shell() {
2626
assert_eq!(1, basic_main());
2727
assert_eq!(bool::default(), generic_fun::<bool>())
2828
}
29+
30+
macro_rules! generate_preserve_none_delimiters_tests {
31+
($e:expr) => {
32+
#[test]
33+
fn preserve_none_delimiters_in_main() {
34+
#[tokio::main]
35+
async fn f() -> i32 { $e() }
36+
37+
assert_eq!(f(), ($e)());
38+
}
39+
40+
#[tokio::test]
41+
async fn preserve_none_delimiters_in_test() {
42+
assert_eq!($e(), ($e)());
43+
}
44+
}
45+
}
46+
generate_preserve_none_delimiters_tests!(|| 5);

tokio-macros/src/entry.rs

Lines changed: 26 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,29 @@ fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Erro
174174
}
175175

176176
fn parse_knobs(
177-
mut input: syn::ItemFn,
177+
input_tokens: TokenStream,
178178
args: syn::AttributeArgs,
179179
is_test: bool,
180180
rt_multi_thread: bool,
181181
) -> Result<TokenStream, syn::Error> {
182+
let mut input: syn::ItemFn = syn::parse(input_tokens.clone())?;
183+
184+
if (is_test || input.sig.ident == "main") && !input.sig.inputs.is_empty() {
185+
let function = if is_test { "test" } else { "main" };
186+
187+
return Err(syn::Error::new_spanned(
188+
&input.sig.inputs,
189+
format_args!("the {} function cannot accept arguments", function),
190+
));
191+
}
192+
if is_test {
193+
if let Some(attr) = input.attrs.iter().find(|attr| attr.path.is_ident("test")) {
194+
let msg = "second test attribute is supplied";
195+
return Err(syn::Error::new_spanned(attr, msg));
196+
}
197+
}
198+
182199
let sig = &mut input.sig;
183-
let body = &input.block;
184200
let attrs = &input.attrs;
185201
let vis = input.vis;
186202

@@ -291,6 +307,12 @@ fn parse_knobs(
291307
}
292308
};
293309

310+
// The last token of a function item is always its body. We use the input token stream directly
311+
// instead of printing the parsed function to make sure that Rust preserves None-delimited
312+
// groups inside the function body; see <https://github.com/tokio-rs/tokio/issues/3579>.
313+
let body =
314+
proc_macro2::TokenStream::from(TokenStream::from(input_tokens.into_iter().last().unwrap()));
315+
294316
let result = quote! {
295317
#header
296318
#(#attrs)*
@@ -308,38 +330,11 @@ fn parse_knobs(
308330

309331
#[cfg(not(test))] // Work around for rust-lang/rust#62127
310332
pub(crate) fn main(args: TokenStream, item: TokenStream, rt_multi_thread: bool) -> TokenStream {
311-
let input = syn::parse_macro_input!(item as syn::ItemFn);
312333
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
313-
314-
if input.sig.ident == "main" && !input.sig.inputs.is_empty() {
315-
let msg = "the main function cannot accept arguments";
316-
return syn::Error::new_spanned(&input.sig.ident, msg)
317-
.to_compile_error()
318-
.into();
319-
}
320-
321-
parse_knobs(input, args, false, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into())
334+
parse_knobs(item, args, false, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into())
322335
}
323336

324337
pub(crate) fn test(args: TokenStream, item: TokenStream, rt_multi_thread: bool) -> TokenStream {
325-
let input = syn::parse_macro_input!(item as syn::ItemFn);
326338
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
327-
328-
for attr in &input.attrs {
329-
if attr.path.is_ident("test") {
330-
let msg = "second test attribute is supplied";
331-
return syn::Error::new_spanned(&attr, msg)
332-
.to_compile_error()
333-
.into();
334-
}
335-
}
336-
337-
if !input.sig.inputs.is_empty() {
338-
let msg = "the test function cannot accept arguments";
339-
return syn::Error::new_spanned(&input.sig.inputs, msg)
340-
.to_compile_error()
341-
.into();
342-
}
343-
344-
parse_knobs(input, args, true, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into())
339+
parse_knobs(item, args, true, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into())
345340
}

0 commit comments

Comments
 (0)