diff --git a/tests-build/tests/fail/macros_dead_code.rs b/tests-build/tests/fail/macros_dead_code.rs new file mode 100644 index 00000000000..f2ada6f835d --- /dev/null +++ b/tests-build/tests/fail/macros_dead_code.rs @@ -0,0 +1,8 @@ +#![deny(dead_code)] + +use tests_build::tokio; + +#[tokio::main] +async fn f() {} + +fn main() {} diff --git a/tests-build/tests/fail/macros_dead_code.stderr b/tests-build/tests/fail/macros_dead_code.stderr new file mode 100644 index 00000000000..816c294bd31 --- /dev/null +++ b/tests-build/tests/fail/macros_dead_code.stderr @@ -0,0 +1,11 @@ +error: function is never used: `f` + --> $DIR/macros_dead_code.rs:6:10 + | +6 | async fn f() {} + | ^ + | +note: the lint level is defined here + --> $DIR/macros_dead_code.rs:1:9 + | +1 | #![deny(dead_code)] + | ^^^^^^^^^ diff --git a/tests-build/tests/fail/macros_type_mismatch.rs b/tests-build/tests/fail/macros_type_mismatch.rs new file mode 100644 index 00000000000..086244c9fd3 --- /dev/null +++ b/tests-build/tests/fail/macros_type_mismatch.rs @@ -0,0 +1,32 @@ +use tests_build::tokio; + +#[tokio::main] +async fn missing_semicolon_or_return_type() { + Ok(()) +} + +#[tokio::main] +async fn missing_return_type() { + /* TODO(taiki-e): one of help messages still wrong + help: consider using a semicolon here + | + 16 | return Ok(());; + | + */ + return Ok(()); +} + +#[tokio::main] +async fn extra_semicolon() -> Result<(), ()> { + /* TODO(taiki-e): help message still wrong + help: try using a variant of the expected enum + | + 29 | Ok(Ok(());) + | + 29 | Err(Ok(());) + | + */ + Ok(()); +} + +fn main() {} diff --git a/tests-build/tests/fail/macros_type_mismatch.stderr b/tests-build/tests/fail/macros_type_mismatch.stderr new file mode 100644 index 00000000000..4d573181523 --- /dev/null +++ b/tests-build/tests/fail/macros_type_mismatch.stderr @@ -0,0 +1,51 @@ +error[E0308]: mismatched types + --> $DIR/macros_type_mismatch.rs:5:5 + | +5 | Ok(()) + | ^^^^^^ expected `()`, found enum `Result` + | + = note: expected unit type `()` + found enum `Result<(), _>` +help: consider using a semicolon here + | +5 | Ok(()); + | ^ +help: try adding a return type + | +4 | async fn missing_semicolon_or_return_type() -> Result<(), _> { + | ^^^^^^^^^^^^^^^^ + +error[E0308]: mismatched types + --> $DIR/macros_type_mismatch.rs:16:5 + | +16 | return Ok(()); + | ^^^^^^^^^^^^^^ expected `()`, found enum `Result` + | + = note: expected unit type `()` + found enum `Result<(), _>` +help: consider using a semicolon here + | +16 | return Ok(());; + | ^ +help: try adding a return type + | +9 | async fn missing_return_type() -> Result<(), _> { + | ^^^^^^^^^^^^^^^^ + +error[E0308]: mismatched types + --> $DIR/macros_type_mismatch.rs:29:5 + | +20 | async fn extra_semicolon() -> Result<(), ()> { + | -------------- expected `Result<(), ()>` because of return type +... +29 | Ok(()); + | ^^^^^^^ expected enum `Result`, found `()` + | + = note: expected enum `Result<(), ()>` + found unit type `()` +help: try using a variant of the expected enum + | +29 | Ok(Ok(());) + | +29 | Err(Ok(());) + | diff --git a/tests-build/tests/macros.rs b/tests-build/tests/macros.rs index baed23117ea..d2330cb6e18 100644 --- a/tests-build/tests/macros.rs +++ b/tests-build/tests/macros.rs @@ -8,6 +8,12 @@ fn compile_fail_full() { #[cfg(feature = "full")] t.compile_fail("tests/fail/macros_invalid_input.rs"); + #[cfg(feature = "full")] + t.compile_fail("tests/fail/macros_dead_code.rs"); + + #[cfg(feature = "full")] + t.compile_fail("tests/fail/macros_type_mismatch.rs"); + #[cfg(all(feature = "rt", not(feature = "full")))] t.compile_fail("tests/fail/macros_core_no_default.rs"); diff --git a/tokio-macros/Cargo.toml b/tokio-macros/Cargo.toml index e8d0f5142d6..7ab38dc2189 100644 --- a/tokio-macros/Cargo.toml +++ b/tokio-macros/Cargo.toml @@ -26,7 +26,7 @@ proc-macro = true [dependencies] proc-macro2 = "1.0.7" quote = "1" -syn = { version = "1.0.3", features = ["full"] } +syn = { version = "1.0.56", features = ["full"] } [dev-dependencies] tokio = { version = "1.0.0", path = "../tokio", features = ["full"] } diff --git a/tokio-macros/src/entry.rs b/tokio-macros/src/entry.rs index 0967317cefe..ddc19585d7b 100644 --- a/tokio-macros/src/entry.rs +++ b/tokio-macros/src/entry.rs @@ -1,6 +1,6 @@ use proc_macro::TokenStream; use proc_macro2::Span; -use quote::quote; +use quote::{quote, quote_spanned, ToTokens}; #[derive(Clone, Copy, PartialEq)] enum RuntimeFlavor { @@ -278,11 +278,29 @@ fn parse_knobs( let config = config.build()?; + // If type mismatch occurs, the current rustc points to the last statement. + let (last_stmt_start_span, last_stmt_end_span) = { + let mut last_stmt = input + .block + .stmts + .last() + .map(ToTokens::into_token_stream) + .unwrap_or_default() + .into_iter(); + // `Span` on stable Rust has a limitation that only points to the first + // token, not the whole tokens. We can work around this limitation by + // using the first/last span of the tokens like + // `syn::Error::new_spanned` does. + let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span()); + let end = last_stmt.last().map_or(start, |t| t.span()); + (start, end) + }; + let mut rt = match config.flavor { - RuntimeFlavor::CurrentThread => quote! { + RuntimeFlavor::CurrentThread => quote_spanned! {last_stmt_start_span=> tokio::runtime::Builder::new_current_thread() }, - RuntimeFlavor::Threaded => quote! { + RuntimeFlavor::Threaded => quote_spanned! {last_stmt_start_span=> tokio::runtime::Builder::new_multi_thread() }, }; @@ -302,7 +320,8 @@ fn parse_knobs( }; let body = &input.block; - input.block = syn::parse_quote! { + let brace_token = input.block.brace_token; + input.block = syn::parse2(quote_spanned! {last_stmt_end_span=> { #rt .enable_all() @@ -310,7 +329,9 @@ fn parse_knobs( .unwrap() .block_on(async #body) } - }; + }) + .unwrap(); + input.block.brace_token = brace_token; let result = quote! { #header diff --git a/tokio/tests/macros_test.rs b/tokio/tests/macros_test.rs index 83963981416..f5bc5a0330f 100644 --- a/tokio/tests/macros_test.rs +++ b/tokio/tests/macros_test.rs @@ -21,7 +21,20 @@ async fn test_macro_is_resilient_to_shadowing() { // https://github.com/tokio-rs/tokio/issues/3403 #[rustfmt::skip] // this `rustfmt::skip` is necessary because unused_braces does not warn if the block contains newline. #[tokio::main] -async fn unused_braces_main() { println!("hello") } +pub async fn unused_braces_main() { println!("hello") } #[rustfmt::skip] // this `rustfmt::skip` is necessary because unused_braces does not warn if the block contains newline. #[tokio::test] async fn unused_braces_test() { assert_eq!(1 + 1, 2) } + +// https://github.com/tokio-rs/tokio/pull/3766#issuecomment-835508651 +#[std::prelude::v1::test] +fn trait_method() { + trait A { + fn f(self); + } + impl A for () { + #[tokio::main] + async fn f(self) {} + } + ().f() +}