aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Galenson <jgalenson@google.com>2020-10-26 20:16:26 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2020-10-26 20:16:26 +0000
commit384f54f528a26ecebbeaa3e39c0a2c69bdb9e849 (patch)
tree945168256ae499a842348b28699a47653d6d8eb3
parent7c7c291d8ea8d5c68a1e5b7ef8c43d51773ae38b (diff)
parent064c299c71f7a9d36852ce8ca5c33b1cf5f5904f (diff)
downloadtokio-macros-384f54f528a26ecebbeaa3e39c0a2c69bdb9e849.tar.gz
Import tokio-macros-0.2.5 am: ea9e45442b am: 76a32ce049 am: 91d84b59d7 am: 064c299c71
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/tokio-macros/+/1472197 Change-Id: Ic6c43ff7a1edf95d6dab9a4777304e20b06bd28d
-rw-r--r--.cargo_vcs_info.json5
-rw-r--r--CHANGELOG.md31
-rw-r--r--Cargo.toml42
-rw-r--r--Cargo.toml.orig36
-rw-r--r--LICENSE47
-rw-r--r--README.md13
-rw-r--r--src/entry.rs359
-rw-r--r--src/lib.rs213
-rw-r--r--src/select.rs43
9 files changed, 789 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..6b0eb4d
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,5 @@
+{
+ "git": {
+ "sha1": "c6fc1db6981733c8bfe90d979132b00cfc03b83b"
+ }
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..d9edc5d
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,31 @@
+# 0.2.5 (February 27, 2019)
+
+### Fixed
+- doc improvements (#2225).
+
+# 0.2.4 (January 27, 2019)
+
+### Fixed
+- generics on `#[tokio::main]` function (#2177).
+
+### Added
+- support for `tokio::select!` (#2152).
+
+# 0.2.3 (January 7, 2019)
+
+### Fixed
+- Revert breaking change.
+
+# 0.2.2 (January 7, 2019)
+
+### Added
+- General refactoring and inclusion of additional runtime options (#2022 and #2038)
+
+# 0.2.1 (December 18, 2019)
+
+### Fixes
+- inherit visibility when wrapping async fn (#1954).
+
+# 0.2.0 (November 26, 2019)
+
+- Initial release
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..75c1c64
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,42 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies
+#
+# If you believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+edition = "2018"
+name = "tokio-macros"
+version = "0.2.5"
+authors = ["Tokio Contributors <team@tokio.rs>"]
+description = "Tokio's proc macros.\n"
+homepage = "https://tokio.rs"
+documentation = "https://docs.rs/tokio-macros/0.2.5/tokio_macros"
+categories = ["asynchronous"]
+license = "MIT"
+repository = "https://github.com/tokio-rs/tokio"
+[package.metadata.docs.rs]
+all-features = true
+
+[lib]
+proc-macro = true
+[dependencies.proc-macro2]
+version = "1.0.7"
+
+[dependencies.quote]
+version = "1"
+
+[dependencies.syn]
+version = "1.0.3"
+features = ["full"]
+[dev-dependencies.tokio]
+version = "0.2.0"
+features = ["full"]
+
+[features]
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..2709212
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,36 @@
+[package]
+name = "tokio-macros"
+# When releasing to crates.io:
+# - Remove path dependencies
+# - Update html_root_url.
+# - Update doc url
+# - Cargo.toml
+# - Update CHANGELOG.md.
+# - Create "v0.1.x" git tag.
+version = "0.2.5"
+edition = "2018"
+authors = ["Tokio Contributors <team@tokio.rs>"]
+license = "MIT"
+repository = "https://github.com/tokio-rs/tokio"
+homepage = "https://tokio.rs"
+documentation = "https://docs.rs/tokio-macros/0.2.5/tokio_macros"
+description = """
+Tokio's proc macros.
+"""
+categories = ["asynchronous"]
+
+[lib]
+proc-macro = true
+
+[features]
+
+[dependencies]
+proc-macro2 = "1.0.7"
+quote = "1"
+syn = { version = "1.0.3", features = ["full"] }
+
+[dev-dependencies]
+tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
+
+[package.metadata.docs.rs]
+all-features = true
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0fbb739
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,47 @@
+Copyright (c) 2019 Tokio Contributors
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+The MIT License (MIT)
+
+Copyright (c) 2019 Yoshua Wuyts
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..988726f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
+# Tokio Macros
+
+Procedural macros for use with Tokio
+
+## License
+
+This project is licensed under the [MIT license](LICENSE).
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in Tokio by you, shall be licensed as MIT, without any additional
+terms or conditions.
diff --git a/src/entry.rs b/src/entry.rs
new file mode 100644
index 0000000..6a58b79
--- /dev/null
+++ b/src/entry.rs
@@ -0,0 +1,359 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use std::num::NonZeroUsize;
+
+#[derive(Clone, Copy, PartialEq)]
+enum Runtime {
+ Basic,
+ Threaded,
+}
+
+fn parse_knobs(
+ mut input: syn::ItemFn,
+ args: syn::AttributeArgs,
+ is_test: bool,
+ rt_threaded: bool,
+) -> Result<TokenStream, syn::Error> {
+ let sig = &mut input.sig;
+ let body = &input.block;
+ let attrs = &input.attrs;
+ let vis = input.vis;
+
+ if sig.asyncness.is_none() {
+ let msg = "the async keyword is missing from the function declaration";
+ return Err(syn::Error::new_spanned(sig.fn_token, msg));
+ }
+
+ sig.asyncness = None;
+
+ let mut runtime = None;
+ let mut core_threads = None;
+ let mut max_threads = None;
+
+ for arg in args {
+ match arg {
+ syn::NestedMeta::Meta(syn::Meta::NameValue(namevalue)) => {
+ let ident = namevalue.path.get_ident();
+ if ident.is_none() {
+ let msg = "Must have specified ident";
+ return Err(syn::Error::new_spanned(namevalue, msg));
+ }
+ match ident.unwrap().to_string().to_lowercase().as_str() {
+ "core_threads" => {
+ if rt_threaded {
+ match &namevalue.lit {
+ syn::Lit::Int(expr) => {
+ let num = expr.base10_parse::<NonZeroUsize>().unwrap();
+ if num.get() > 1 {
+ runtime = Some(Runtime::Threaded);
+ } else {
+ runtime = Some(Runtime::Basic);
+ }
+
+ if let Some(v) = max_threads {
+ if v < num {
+ return Err(syn::Error::new_spanned(
+ namevalue,
+ "max_threads cannot be less than core_threads",
+ ));
+ }
+ }
+
+ core_threads = Some(num);
+ }
+ _ => {
+ return Err(syn::Error::new_spanned(
+ namevalue,
+ "core_threads argument must be an int",
+ ))
+ }
+ }
+ } else {
+ return Err(syn::Error::new_spanned(
+ namevalue,
+ "core_threads can only be set with rt-threaded feature flag enabled",
+ ));
+ }
+ }
+ "max_threads" => match &namevalue.lit {
+ syn::Lit::Int(expr) => {
+ let num = expr.base10_parse::<NonZeroUsize>().unwrap();
+
+ if let Some(v) = core_threads {
+ if num < v {
+ return Err(syn::Error::new_spanned(
+ namevalue,
+ "max_threads cannot be less than core_threads",
+ ));
+ }
+ }
+ max_threads = Some(num);
+ }
+ _ => {
+ return Err(syn::Error::new_spanned(
+ namevalue,
+ "max_threads argument must be an int",
+ ))
+ }
+ },
+ name => {
+ let msg = format!("Unknown attribute pair {} is specified; expected one of: `core_threads`, `max_threads`", name);
+ return Err(syn::Error::new_spanned(namevalue, msg));
+ }
+ }
+ }
+ syn::NestedMeta::Meta(syn::Meta::Path(path)) => {
+ let ident = path.get_ident();
+ if ident.is_none() {
+ let msg = "Must have specified ident";
+ return Err(syn::Error::new_spanned(path, msg));
+ }
+ match ident.unwrap().to_string().to_lowercase().as_str() {
+ "threaded_scheduler" => {
+ runtime = Some(runtime.unwrap_or_else(|| Runtime::Threaded))
+ }
+ "basic_scheduler" => runtime = Some(runtime.unwrap_or_else(|| Runtime::Basic)),
+ name => {
+ let msg = format!("Unknown attribute {} is specified; expected `basic_scheduler` or `threaded_scheduler`", name);
+ return Err(syn::Error::new_spanned(path, msg));
+ }
+ }
+ }
+ other => {
+ return Err(syn::Error::new_spanned(
+ other,
+ "Unknown attribute inside the macro",
+ ));
+ }
+ }
+ }
+
+ let mut rt = quote! { tokio::runtime::Builder::new().basic_scheduler() };
+ if rt_threaded && (runtime == Some(Runtime::Threaded) || (runtime.is_none() && !is_test)) {
+ rt = quote! { #rt.threaded_scheduler() };
+ }
+ if let Some(v) = core_threads.map(|v| v.get()) {
+ rt = quote! { #rt.core_threads(#v) };
+ }
+ if let Some(v) = max_threads.map(|v| v.get()) {
+ rt = quote! { #rt.max_threads(#v) };
+ }
+
+ let header = {
+ if is_test {
+ quote! {
+ #[test]
+ }
+ } else {
+ quote! {}
+ }
+ };
+
+ let result = quote! {
+ #header
+ #(#attrs)*
+ #vis #sig {
+ #rt
+ .enable_all()
+ .build()
+ .unwrap()
+ .block_on(async { #body })
+ }
+ };
+
+ Ok(result.into())
+}
+
+#[cfg(not(test))] // Work around for rust-lang/rust#62127
+pub(crate) fn main(args: TokenStream, item: TokenStream, rt_threaded: 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.inputs, msg)
+ .to_compile_error()
+ .into();
+ }
+
+ parse_knobs(input, args, false, rt_threaded).unwrap_or_else(|e| e.to_compile_error().into())
+}
+
+pub(crate) fn test(args: TokenStream, item: TokenStream, rt_threaded: 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_threaded).unwrap_or_else(|e| e.to_compile_error().into())
+}
+
+pub(crate) mod old {
+ use proc_macro::TokenStream;
+ use quote::quote;
+
+ enum Runtime {
+ Basic,
+ Threaded,
+ Auto,
+ }
+
+ #[cfg(not(test))] // Work around for rust-lang/rust#62127
+ pub(crate) fn main(args: TokenStream, item: TokenStream) -> TokenStream {
+ let mut input = syn::parse_macro_input!(item as syn::ItemFn);
+ let args = syn::parse_macro_input!(args as syn::AttributeArgs);
+
+ let sig = &mut input.sig;
+ let name = &sig.ident;
+ let inputs = &sig.inputs;
+ let body = &input.block;
+ let attrs = &input.attrs;
+ let vis = input.vis;
+
+ if sig.asyncness.is_none() {
+ let msg = "the async keyword is missing from the function declaration";
+ return syn::Error::new_spanned(sig.fn_token, msg)
+ .to_compile_error()
+ .into();
+ } else if name == "main" && !inputs.is_empty() {
+ let msg = "the main function cannot accept arguments";
+ return syn::Error::new_spanned(&sig.inputs, msg)
+ .to_compile_error()
+ .into();
+ }
+
+ sig.asyncness = None;
+
+ let mut runtime = Runtime::Auto;
+
+ for arg in args {
+ if let syn::NestedMeta::Meta(syn::Meta::Path(path)) = arg {
+ let ident = path.get_ident();
+ if ident.is_none() {
+ let msg = "Must have specified ident";
+ return syn::Error::new_spanned(path, msg).to_compile_error().into();
+ }
+ match ident.unwrap().to_string().to_lowercase().as_str() {
+ "threaded_scheduler" => runtime = Runtime::Threaded,
+ "basic_scheduler" => runtime = Runtime::Basic,
+ name => {
+ let msg = format!("Unknown attribute {} is specified; expected `basic_scheduler` or `threaded_scheduler`", name);
+ return syn::Error::new_spanned(path, msg).to_compile_error().into();
+ }
+ }
+ }
+ }
+
+ let result = match runtime {
+ Runtime::Threaded | Runtime::Auto => quote! {
+ #(#attrs)*
+ #vis #sig {
+ tokio::runtime::Runtime::new().unwrap().block_on(async { #body })
+ }
+ },
+ Runtime::Basic => quote! {
+ #(#attrs)*
+ #vis #sig {
+ tokio::runtime::Builder::new()
+ .basic_scheduler()
+ .enable_all()
+ .build()
+ .unwrap()
+ .block_on(async { #body })
+ }
+ },
+ };
+
+ result.into()
+ }
+
+ pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream {
+ let input = syn::parse_macro_input!(item as syn::ItemFn);
+ let args = syn::parse_macro_input!(args as syn::AttributeArgs);
+
+ let ret = &input.sig.output;
+ let name = &input.sig.ident;
+ let body = &input.block;
+ let attrs = &input.attrs;
+ let vis = input.vis;
+
+ for attr in 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.asyncness.is_none() {
+ let msg = "the async keyword is missing from the function declaration";
+ return syn::Error::new_spanned(&input.sig.fn_token, msg)
+ .to_compile_error()
+ .into();
+ } else 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();
+ }
+
+ let mut runtime = Runtime::Auto;
+
+ for arg in args {
+ if let syn::NestedMeta::Meta(syn::Meta::Path(path)) = arg {
+ let ident = path.get_ident();
+ if ident.is_none() {
+ let msg = "Must have specified ident";
+ return syn::Error::new_spanned(path, msg).to_compile_error().into();
+ }
+ match ident.unwrap().to_string().to_lowercase().as_str() {
+ "threaded_scheduler" => runtime = Runtime::Threaded,
+ "basic_scheduler" => runtime = Runtime::Basic,
+ name => {
+ let msg = format!("Unknown attribute {} is specified; expected `basic_scheduler` or `threaded_scheduler`", name);
+ return syn::Error::new_spanned(path, msg).to_compile_error().into();
+ }
+ }
+ }
+ }
+
+ let result = match runtime {
+ Runtime::Threaded => quote! {
+ #[test]
+ #(#attrs)*
+ #vis fn #name() #ret {
+ tokio::runtime::Runtime::new().unwrap().block_on(async { #body })
+ }
+ },
+ Runtime::Basic | Runtime::Auto => quote! {
+ #[test]
+ #(#attrs)*
+ #vis fn #name() #ret {
+ tokio::runtime::Builder::new()
+ .basic_scheduler()
+ .enable_all()
+ .build()
+ .unwrap()
+ .block_on(async { #body })
+ }
+ },
+ };
+
+ result.into()
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..9fdfb5b
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,213 @@
+#![doc(html_root_url = "https://docs.rs/tokio-macros/0.2.5")]
+#![allow(clippy::needless_doctest_main)]
+#![warn(
+ missing_debug_implementations,
+ missing_docs,
+ rust_2018_idioms,
+ unreachable_pub
+)]
+#![deny(intra_doc_link_resolution_failure)]
+#![doc(test(
+ no_crate_inject,
+ attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
+))]
+
+//! Macros for use with Tokio
+
+// This `extern` is required for older `rustc` versions but newer `rustc`
+// versions warn about the unused `extern crate`.
+#[allow(unused_extern_crates)]
+extern crate proc_macro;
+
+mod entry;
+mod select;
+
+use proc_macro::TokenStream;
+
+/// Marks async function to be executed by selected runtime.
+///
+/// ## Options:
+///
+///
+/// - `core_threads=n` - Sets core threads to `n` (requires `rt-threaded` feature).
+/// - `max_threads=n` - Sets max threads to `n` (requires `rt-core` or `rt-threaded` feature).
+///
+/// ## Function arguments:
+///
+/// Arguments are allowed for any functions aside from `main` which is special
+///
+/// ## Usage
+///
+/// ### Using default
+///
+/// ```rust
+/// #[tokio::main]
+/// async fn main() {
+/// println!("Hello world");
+/// }
+/// ```
+///
+/// ### Set number of core threads
+///
+/// ```rust
+/// #[tokio::main(core_threads = 1)]
+/// async fn main() {
+/// println!("Hello world");
+/// }
+/// ```
+#[proc_macro_attribute]
+#[cfg(not(test))] // Work around for rust-lang/rust#62127
+pub fn main_threaded(args: TokenStream, item: TokenStream) -> TokenStream {
+ entry::main(args, item, true)
+}
+
+/// Marks async function to be executed by selected runtime.
+///
+/// ## Options:
+///
+/// - `basic_scheduler` - All tasks are executed on the current thread.
+/// - `threaded_scheduler` - Uses the multi-threaded scheduler. Used by default (requires `rt-threaded` feature).
+///
+/// ## Function arguments:
+///
+/// Arguments are allowed for any functions aside from `main` which is special
+///
+/// ## Usage
+///
+/// ### Using default
+///
+/// ```rust
+/// #[tokio::main]
+/// async fn main() {
+/// println!("Hello world");
+/// }
+/// ```
+///
+/// ### Select runtime
+///
+/// ```rust
+/// #[tokio::main(basic_scheduler)]
+/// async fn main() {
+/// println!("Hello world");
+/// }
+/// ```
+#[proc_macro_attribute]
+#[cfg(not(test))] // Work around for rust-lang/rust#62127
+pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
+ entry::old::main(args, item)
+}
+
+/// Marks async function to be executed by selected runtime.
+///
+/// ## Options:
+///
+/// - `max_threads=n` - Sets max threads to `n`.
+///
+/// ## Function arguments:
+///
+/// Arguments are allowed for any functions aside from `main` which is special
+///
+/// ## Usage
+///
+/// ### Using default
+///
+/// ```rust
+/// #[tokio::main]
+/// async fn main() {
+/// println!("Hello world");
+/// }
+/// ```
+#[proc_macro_attribute]
+#[cfg(not(test))] // Work around for rust-lang/rust#62127
+pub fn main_basic(args: TokenStream, item: TokenStream) -> TokenStream {
+ entry::main(args, item, false)
+}
+
+/// Marks async function to be executed by runtime, suitable to test environment
+///
+/// ## Options:
+///
+/// - `core_threads=n` - Sets core threads to `n` (requires `rt-threaded` feature).
+/// - `max_threads=n` - Sets max threads to `n` (requires `rt-core` or `rt-threaded` feature).
+///
+/// ## Usage
+///
+/// ### Select runtime
+///
+/// ```no_run
+/// #[tokio::test(core_threads = 1)]
+/// async fn my_test() {
+/// assert!(true);
+/// }
+/// ```
+///
+/// ### Using default
+///
+/// ```no_run
+/// #[tokio::test]
+/// async fn my_test() {
+/// assert!(true);
+/// }
+/// ```
+#[proc_macro_attribute]
+pub fn test_threaded(args: TokenStream, item: TokenStream) -> TokenStream {
+ entry::test(args, item, true)
+}
+
+/// Marks async function to be executed by runtime, suitable to test environment
+///
+/// ## Options:
+///
+/// - `basic_scheduler` - All tasks are executed on the current thread. Used by default.
+/// - `threaded_scheduler` - Use multi-threaded scheduler (requires `rt-threaded` feature).
+///
+/// ## Usage
+///
+/// ### Select runtime
+///
+/// ```no_run
+/// #[tokio::test(threaded_scheduler)]
+/// async fn my_test() {
+/// assert!(true);
+/// }
+/// ```
+///
+/// ### Using default
+///
+/// ```no_run
+/// #[tokio::test]
+/// async fn my_test() {
+/// assert!(true);
+/// }
+/// ```
+#[proc_macro_attribute]
+pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
+ entry::old::test(args, item)
+}
+
+/// Marks async function to be executed by runtime, suitable to test environment
+///
+/// ## Options:
+///
+/// - `max_threads=n` - Sets max threads to `n`.
+///
+/// ## Usage
+///
+/// ```no_run
+/// #[tokio::test]
+/// async fn my_test() {
+/// assert!(true);
+/// }
+/// ```
+#[proc_macro_attribute]
+pub fn test_basic(args: TokenStream, item: TokenStream) -> TokenStream {
+ entry::test(args, item, false)
+}
+
+/// Implementation detail of the `select!` macro. This macro is **not** intended
+/// to be used as part of the public API and is permitted to change.
+#[proc_macro]
+#[doc(hidden)]
+pub fn select_priv_declare_output_enum(input: TokenStream) -> TokenStream {
+ select::declare_output_enum(input)
+}
diff --git a/src/select.rs b/src/select.rs
new file mode 100644
index 0000000..ddb2e6a
--- /dev/null
+++ b/src/select.rs
@@ -0,0 +1,43 @@
+use proc_macro::{TokenStream, TokenTree};
+use proc_macro2::Span;
+use quote::quote;
+use syn::Ident;
+
+pub(crate) fn declare_output_enum(input: TokenStream) -> TokenStream {
+ // passed in is: `(_ _ _)` with one `_` per branch
+ let branches = match input.into_iter().next() {
+ Some(TokenTree::Group(group)) => group.stream().into_iter().count(),
+ _ => panic!("unexpected macro input"),
+ };
+
+ let variants = (0..branches)
+ .map(|num| Ident::new(&format!("_{}", num), Span::call_site()))
+ .collect::<Vec<_>>();
+
+ // Use a bitfield to track which futures completed
+ let mask = Ident::new(
+ if branches <= 8 {
+ "u8"
+ } else if branches <= 16 {
+ "u16"
+ } else if branches <= 32 {
+ "u32"
+ } else if branches <= 64 {
+ "u64"
+ } else {
+ panic!("up to 64 branches supported");
+ },
+ Span::call_site(),
+ );
+
+ TokenStream::from(quote! {
+ pub(super) enum Out<#( #variants ),*> {
+ #( #variants(#variants), )*
+ // Include a `Disabled` variant signifying that all select branches
+ // failed to resolve.
+ Disabled,
+ }
+
+ pub(super) type Mask = #mask;
+ })
+}