diff options
author | Joel Galenson <jgalenson@google.com> | 2020-10-26 20:16:26 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-10-26 20:16:26 +0000 |
commit | 384f54f528a26ecebbeaa3e39c0a2c69bdb9e849 (patch) | |
tree | 945168256ae499a842348b28699a47653d6d8eb3 | |
parent | 7c7c291d8ea8d5c68a1e5b7ef8c43d51773ae38b (diff) | |
parent | 064c299c71f7a9d36852ce8ca5c33b1cf5f5904f (diff) | |
download | tokio-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.json | 5 | ||||
-rw-r--r-- | CHANGELOG.md | 31 | ||||
-rw-r--r-- | Cargo.toml | 42 | ||||
-rw-r--r-- | Cargo.toml.orig | 36 | ||||
-rw-r--r-- | LICENSE | 47 | ||||
-rw-r--r-- | README.md | 13 | ||||
-rw-r--r-- | src/entry.rs | 359 | ||||
-rw-r--r-- | src/lib.rs | 213 | ||||
-rw-r--r-- | src/select.rs | 43 |
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 @@ -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; + }) +} |