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: custom crate name for #[tokio::main] and #[tokio::test] #4613

Merged
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
9 changes: 9 additions & 0 deletions tests-build/tests/fail/macros_invalid_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ async fn test_worker_threads_not_int() {}
#[tokio::test(flavor = "current_thread", worker_threads = 4)]
async fn test_worker_threads_and_current_thread() {}

#[tokio::test(crate = 456)]
async fn test_crate_not_ident_int() {}

#[tokio::test(crate = "456")]
async fn test_crate_not_ident_invalid() {}

#[tokio::test(crate = "abc::edf")]
async fn test_crate_not_ident_path() {}

#[tokio::test]
#[test]
async fn test_has_second_test_attr() {}
Expand Down
32 changes: 25 additions & 7 deletions tests-build/tests/fail/macros_invalid_input.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ error: the `async` keyword is missing from the function declaration
4 | fn main_is_not_async() {}
| ^^

error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`
--> $DIR/macros_invalid_input.rs:6:15
|
6 | #[tokio::main(foo)]
Expand All @@ -22,13 +22,13 @@ error: the `async` keyword is missing from the function declaration
13 | fn test_is_not_async() {}
| ^^

error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`
--> $DIR/macros_invalid_input.rs:15:15
|
15 | #[tokio::test(foo)]
| ^^^

error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`
--> $DIR/macros_invalid_input.rs:18:15
|
18 | #[tokio::test(foo = 123)]
Expand Down Expand Up @@ -64,16 +64,34 @@ error: The `worker_threads` option requires the `multi_thread` runtime flavor. U
33 | #[tokio::test(flavor = "current_thread", worker_threads = 4)]
| ^

error: Failed to parse value of `crate` as ident.
--> $DIR/macros_invalid_input.rs:36:23
|
36 | #[tokio::test(crate = 456)]
| ^^^

error: Failed to parse value of `crate` as ident: "456"
--> $DIR/macros_invalid_input.rs:39:23
|
39 | #[tokio::test(crate = "456")]
| ^^^^^

error: Failed to parse value of `crate` as ident: "abc::edf"
--> $DIR/macros_invalid_input.rs:42:23
|
42 | #[tokio::test(crate = "abc::edf")]
| ^^^^^^^^^^

error: second test attribute is supplied
--> $DIR/macros_invalid_input.rs:37:1
--> $DIR/macros_invalid_input.rs:46:1
|
37 | #[test]
46 | #[test]
| ^^^^^^^

error: duplicated attribute
--> $DIR/macros_invalid_input.rs:37:1
--> $DIR/macros_invalid_input.rs:46:1
|
37 | #[test]
46 | #[test]
| ^^^^^^^
|
= note: `-D duplicate-macro-attributes` implied by `-D warnings`
55 changes: 50 additions & 5 deletions tokio-macros/src/entry.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use proc_macro::TokenStream;
use proc_macro2::Span;
use proc_macro2::{Ident, Span};
use quote::{quote, quote_spanned, ToTokens};
use syn::parse::Parser;

Expand Down Expand Up @@ -29,13 +29,15 @@ struct FinalConfig {
flavor: RuntimeFlavor,
worker_threads: Option<usize>,
start_paused: Option<bool>,
crate_name: Option<String>,
}

/// Config used in case of the attribute not being able to build a valid config
const DEFAULT_ERROR_CONFIG: FinalConfig = FinalConfig {
flavor: RuntimeFlavor::CurrentThread,
worker_threads: None,
start_paused: None,
crate_name: None,
};

struct Configuration {
Expand All @@ -45,6 +47,7 @@ struct Configuration {
worker_threads: Option<(usize, Span)>,
start_paused: Option<(bool, Span)>,
is_test: bool,
crate_name: Option<String>,
}

impl Configuration {
Expand All @@ -59,6 +62,7 @@ impl Configuration {
worker_threads: None,
start_paused: None,
is_test,
crate_name: None,
}
}

Expand Down Expand Up @@ -104,6 +108,15 @@ impl Configuration {
Ok(())
}

fn set_crate_name(&mut self, name: syn::Lit, span: Span) -> Result<(), syn::Error> {
if self.crate_name.is_some() {
return Err(syn::Error::new(span, "`crate` set multiple times."));
}
let name_ident = parse_ident(name, span, "crate")?;
self.crate_name = Some(name_ident.to_string());
Ok(())
}

fn macro_name(&self) -> &'static str {
if self.is_test {
"tokio::test"
Expand Down Expand Up @@ -151,6 +164,7 @@ impl Configuration {
};

Ok(FinalConfig {
crate_name: self.crate_name.clone(),
flavor,
worker_threads,
start_paused,
Expand Down Expand Up @@ -185,6 +199,27 @@ fn parse_string(int: syn::Lit, span: Span, field: &str) -> Result<String, syn::E
}
}

fn parse_ident(lit: syn::Lit, span: Span, field: &str) -> Result<Ident, syn::Error> {
match lit {
syn::Lit::Str(s) => {
let err = syn::Error::new(
span,
format!(
"Failed to parse value of `{}` as ident: \"{}\"",
field,
s.value()
),
);
let path = s.parse::<syn::Path>().map_err(|_| err.clone())?;
path.get_ident().cloned().ok_or(err)
}
_ => Err(syn::Error::new(
span,
format!("Failed to parse value of `{}` as ident.", field),
)),
}
}

fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Error> {
match bool {
syn::Lit::Bool(b) => Ok(b.value),
Expand Down Expand Up @@ -243,9 +278,15 @@ fn build_config(
let msg = "Attribute `core_threads` is renamed to `worker_threads`";
return Err(syn::Error::new_spanned(namevalue, msg));
}
"crate" => {
config.set_crate_name(
namevalue.lit.clone(),
syn::spanned::Spanned::span(&namevalue.lit),
)?;
}
name => {
let msg = format!(
"Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`",
"Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`",
name,
);
return Err(syn::Error::new_spanned(namevalue, msg));
Expand Down Expand Up @@ -275,7 +316,7 @@ fn build_config(
format!("The `{}` attribute requires an argument.", name)
}
name => {
format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`", name)
format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`", name)
}
};
return Err(syn::Error::new_spanned(path, msg));
Expand Down Expand Up @@ -313,12 +354,16 @@ fn parse_knobs(mut input: syn::ItemFn, is_test: bool, config: FinalConfig) -> To
(start, end)
};

let crate_name = config.crate_name.as_deref().unwrap_or("tokio");

let crate_ident = Ident::new(crate_name, last_stmt_start_span);

let mut rt = match config.flavor {
RuntimeFlavor::CurrentThread => quote_spanned! {last_stmt_start_span=>
tokio::runtime::Builder::new_current_thread()
#crate_ident::runtime::Builder::new_current_thread()
},
RuntimeFlavor::Threaded => quote_spanned! {last_stmt_start_span=>
tokio::runtime::Builder::new_multi_thread()
#crate_ident::runtime::Builder::new_multi_thread()
},
};
if let Some(v) = config.worker_threads {
Expand Down
81 changes: 59 additions & 22 deletions tokio-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,32 @@ use proc_macro::TokenStream;
///
/// Note that `start_paused` requires the `test-util` feature to be enabled.
///
/// ### NOTE:
/// ### Rename package
///
/// If you rename the Tokio crate in your dependencies this macro will not work.
/// If you must rename the current version of Tokio because you're also using an
/// older version of Tokio, you _must_ make the current version of Tokio
/// available as `tokio` in the module where this macro is expanded.
/// ```rust
/// use tokio as tokio1;
///
/// #[tokio1::main(crate = "tokio1")]
/// async fn main() {
/// println!("Hello world");
/// }
/// ```
///
/// Equivalent code not using `#[tokio::main]`
///
/// ```rust
/// use tokio as tokio1;
///
/// fn main() {
/// tokio1::runtime::Builder::new_multi_thread()
/// .enable_all()
/// .build()
/// .unwrap()
/// .block_on(async {
/// println!("Hello world");
/// })
/// }
/// ```
#[proc_macro_attribute]
#[cfg(not(test))] // Work around for rust-lang/rust#62127
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
Expand Down Expand Up @@ -213,12 +233,32 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
/// }
/// ```
///
/// ### NOTE:
/// ### Rename package
///
/// ```rust
/// use tokio as tokio1;
///
/// #[tokio1::main(crate = "tokio1")]
/// async fn main() {
/// println!("Hello world");
/// }
/// ```
///
/// Equivalent code not using `#[tokio::main]`
///
/// ```rust
/// use tokio as tokio1;
///
/// If you rename the Tokio crate in your dependencies this macro will not work.
/// If you must rename the current version of Tokio because you're also using an
/// older version of Tokio, you _must_ make the current version of Tokio
/// available as `tokio` in the module where this macro is expanded.
/// fn main() {
/// tokio1::runtime::Builder::new_multi_thread()
/// .enable_all()
/// .build()
/// .unwrap()
/// .block_on(async {
/// println!("Hello world");
/// })
/// }
/// ```
#[proc_macro_attribute]
#[cfg(not(test))] // Work around for rust-lang/rust#62127
pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
Expand Down Expand Up @@ -260,12 +300,16 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
///
/// Note that `start_paused` requires the `test-util` feature to be enabled.
///
/// ### NOTE:
/// ### Rename package
///
/// ```rust
/// use tokio as tokio1;
///
/// If you rename the Tokio crate in your dependencies this macro will not work.
/// If you must rename the current version of Tokio because you're also using an
/// older version of Tokio, you _must_ make the current version of Tokio
/// available as `tokio` in the module where this macro is expanded.
/// #[tokio1::test(crate = "tokio1")]
/// async fn my_test() {
/// println!("Hello world");
/// }
/// ```
#[proc_macro_attribute]
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
entry::test(args, item, true)
Expand All @@ -281,13 +325,6 @@ pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
/// assert!(true);
/// }
/// ```
///
/// ### NOTE:
///
/// If you rename the Tokio crate in your dependencies this macro will not work.
/// If you must rename the current version of Tokio because you're also using an
/// older version of Tokio, you _must_ make the current version of Tokio
/// available as `tokio` in the module where this macro is expanded.
#[proc_macro_attribute]
pub fn test_rt(args: TokenStream, item: TokenStream) -> TokenStream {
entry::test(args, item, false)
Expand Down
26 changes: 26 additions & 0 deletions tokio/tests/macros_rename_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#![cfg(feature = "full")]

#[allow(unused_imports)]
use std as tokio;

use ::tokio as tokio1;

async fn compute() -> usize {
let join = tokio1::spawn(async { 1 });
join.await.unwrap()
}

#[tokio1::main(crate = "tokio1")]
async fn compute_main() -> usize {
compute().await
}

#[test]
fn crate_rename_main() {
assert_eq!(1, compute_main());
}

#[tokio1::test(crate = "tokio1")]
async fn crate_rename_test() {
assert_eq!(1, compute().await);
}