Skip to content

macros: preserve none-delimiters in function body #3583

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ env:
RUSTFLAGS: -Dwarnings
RUST_BACKTRACE: 1
nightly: nightly-2020-09-21
minrust: 1.45.2
minrust: 1.47.0

jobs:
# Depends on all action sthat are required for a "successful" CI run.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ several other libraries, including:

## Supported Rust Versions

Tokio is built against the latest stable release. The minimum supported version is 1.45.
Tokio is built against the latest stable release. The minimum supported version is 1.47.
The current Tokio version is not guaranteed to build on Rust versions earlier than the
minimum supported version.

Expand Down
22 changes: 22 additions & 0 deletions tests-integration/tests/macros_main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,25 @@ fn shell() {
assert_eq!(1, basic_main());
assert_eq!(bool::default(), generic_fun::<bool>())
}

macro_rules! generate_preserve_none_delimiters_tests {
($e:expr) => {
#[test]
#[allow(clippy::redundant_closure_call)]
fn preserve_none_delimiters_in_main() {
#[tokio::main]
async fn f() -> i32 {
$e()
}

assert_eq!(f(), ($e)());
}

#[tokio::test]
#[allow(clippy::redundant_closure_call)]
async fn preserve_none_delimiters_in_test() {
assert_eq!($e(), ($e)());
}
};
}
generate_preserve_none_delimiters_tests!(|| 5);
57 changes: 26 additions & 31 deletions tokio-macros/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,29 @@ fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Erro
}

fn parse_knobs(
mut input: syn::ItemFn,
input_tokens: TokenStream,
args: syn::AttributeArgs,
is_test: bool,
rt_multi_thread: bool,
) -> Result<TokenStream, syn::Error> {
let mut input: syn::ItemFn = syn::parse(input_tokens.clone())?;

if (is_test || input.sig.ident == "main") && !input.sig.inputs.is_empty() {
let function = if is_test { "test" } else { "main" };

return Err(syn::Error::new_spanned(
&input.sig.inputs,
format_args!("the {} function cannot accept arguments", function),
));
}
if is_test {
if let Some(attr) = input.attrs.iter().find(|attr| attr.path.is_ident("test")) {
let msg = "second test attribute is supplied";
return Err(syn::Error::new_spanned(attr, msg));
}
}

let sig = &mut input.sig;
let body = &input.block;
let attrs = &input.attrs;
let vis = input.vis;

Expand Down Expand Up @@ -291,6 +307,12 @@ fn parse_knobs(
}
};

// The last token of a function item is always its body. We use the input token stream directly
// instead of printing the parsed function to make sure that Rust preserves None-delimited
// groups inside the function body; see <https://github.com/tokio-rs/tokio/issues/3579>.
let body =
proc_macro2::TokenStream::from(TokenStream::from(input_tokens.into_iter().last().unwrap()));

let result = quote! {
#header
#(#attrs)*
Expand All @@ -308,38 +330,11 @@ fn parse_knobs(

#[cfg(not(test))] // Work around for rust-lang/rust#62127
pub(crate) fn main(args: TokenStream, item: TokenStream, rt_multi_thread: bool) -> TokenStream {
let input = syn::parse_macro_input!(item as syn::ItemFn);
let args = syn::parse_macro_input!(args as syn::AttributeArgs);

if input.sig.ident == "main" && !input.sig.inputs.is_empty() {
let msg = "the main function cannot accept arguments";
return syn::Error::new_spanned(&input.sig.ident, msg)
.to_compile_error()
.into();
}

parse_knobs(input, args, false, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into())
parse_knobs(item, args, false, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into())
}

pub(crate) fn test(args: TokenStream, item: TokenStream, rt_multi_thread: bool) -> TokenStream {
let input = syn::parse_macro_input!(item as syn::ItemFn);
let args = syn::parse_macro_input!(args as syn::AttributeArgs);

for attr in &input.attrs {
if attr.path.is_ident("test") {
let msg = "second test attribute is supplied";
return syn::Error::new_spanned(&attr, msg)
.to_compile_error()
.into();
}
}

if !input.sig.inputs.is_empty() {
let msg = "the test function cannot accept arguments";
return syn::Error::new_spanned(&input.sig.inputs, msg)
.to_compile_error()
.into();
}

parse_knobs(input, args, true, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into())
parse_knobs(item, args, true, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into())
}
2 changes: 1 addition & 1 deletion tokio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ several other libraries, including:

## Supported Rust Versions

Tokio is built against the latest stable release. The minimum supported version is 1.45.
Tokio is built against the latest stable release. The minimum supported version is 1.47.
The current Tokio version is not guaranteed to build on Rust versions earlier than the
minimum supported version.

Expand Down