Skip to content
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

macros: preserve none-delimiters in function body #3583

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ env:
RUST_BACKTRACE: 1
nightly: nightly-2021-04-25
minrust: 1.45.2
# Tokio's tests require a higher minimum Rust version than Tokio itself.
#
# See https://github.com/tokio-rs/tokio/pull/3583 for more info.
minrust-tests: 1.47.0

jobs:
# Depends on all action sthat are required for a "successful" CI run.
Expand Down Expand Up @@ -258,7 +262,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Install Rust
run: rustup update ${{ env.minrust }} && rustup default ${{ env.minrust }}
run: rustup update ${{ env.minrust-tests }} && rustup default ${{ env.minrust-tests }}
- name: Install clippy
run: rustup component add clippy

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);
54 changes: 29 additions & 25 deletions tokio-macros/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,14 +185,32 @@ 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> {
if input.sig.asyncness.take().is_none() {
let mut input: syn::ItemFn = syn::parse(input_tokens.clone())?;

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

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;

if sig.asyncness.take().is_none() {
let msg = "the `async` keyword is missing from the function declaration";
return Err(syn::Error::new_spanned(input.sig.fn_token, msg));
return Err(syn::Error::new_spanned(sig.fn_token, msg));
}

let mut config = Configuration::new(is_test, rt_multi_thread);
Expand Down Expand Up @@ -301,7 +319,12 @@ fn parse_knobs(
quote! {}
};

let body = &input.block;
// 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()));

input.block = syn::parse_quote! {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason test failed after the master was merged is that body was converted to syn ast again here (confirmed locally: 38326a5)

{
#rt
Expand All @@ -322,31 +345,12 @@ 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();
}
}

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())
}