diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2021-11-20 13:04:30 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2021-11-20 13:04:30 +0000 |
commit | 979e226f9f428b1fcdb48288a1127c6ec985f3b1 (patch) | |
tree | df9aca0c1c4ce95b6cb43419037865e058623e3c | |
parent | aceee5b2699335365cdd732d72d5a02dfabb8218 (diff) | |
parent | 64b8d17e470ab5c45278ee9023cdc46017bc38e3 (diff) | |
download | tokio-macros-android12-mainline-media-release.tar.gz |
Snap for 7934409 from 64b8d17e470ab5c45278ee9023cdc46017bc38e3 to mainline-media-releaseandroid-mainline-12.0.0_r89android-mainline-12.0.0_r74android-mainline-12.0.0_r62android-mainline-12.0.0_r119android-mainline-12.0.0_r104android12-mainline-media-release
Change-Id: Ib8b69344320b56af357888fae72164fe73783722
-rw-r--r-- | .cargo_vcs_info.json | 2 | ||||
-rw-r--r-- | Android.bp | 26 | ||||
-rw-r--r-- | CHANGELOG.md | 55 | ||||
-rw-r--r-- | Cargo.toml | 15 | ||||
-rw-r--r-- | Cargo.toml.orig | 7 | ||||
-rw-r--r-- | LICENSE | 2 | ||||
-rw-r--r-- | METADATA | 8 | ||||
-rw-r--r-- | TEST_MAPPING | 218 | ||||
-rw-r--r-- | src/entry.rs | 269 | ||||
-rw-r--r-- | src/lib.rs | 15 | ||||
-rw-r--r-- | src/select.rs | 67 |
11 files changed, 546 insertions, 138 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index 482e2e5..92e2ebe 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,5 +1,5 @@ { "git": { - "sha1": "d41882e2a18ea4459d8f3a3c7ba61c4c20b806b4" + "sha1": "623c09c52c2c38a8d75e94c166593547e8477707" } } @@ -1,4 +1,5 @@ // This file is generated by cargo2android.py --run --dependencies --tests. +// Do not modify this file as changes will be overridden on upgrade. package { default_applicable_licenses: ["external_rust_crates_tokio-macros_license"], @@ -46,28 +47,3 @@ rust_test_host { "libtokio", ], } - -// dependent_library ["feature_list"] -// autocfg-1.0.1 -// bytes-1.0.1 "default,std" -// cfg-if-1.0.0 -// instant-0.1.9 -// libc-0.2.86 "align,default,std" -// lock_api-0.4.2 -// log-0.4.14 -// memchr-2.3.4 "default,std" -// mio-0.7.7 "default,net,os-ext,os-poll,os-util,tcp,udp,uds" -// num_cpus-1.13.0 -// once_cell-1.5.2 "alloc,default,std" -// parking_lot-0.11.1 "default" -// parking_lot_core-0.8.2 -// pin-project-lite-0.2.4 -// proc-macro2-1.0.24 "default,proc-macro" -// quote-1.0.8 "default,proc-macro" -// scopeguard-1.1.0 -// signal-hook-registry-1.3.0 -// smallvec-1.6.1 -// syn-1.0.60 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote" -// tokio-1.2.0 "bytes,default,fs,full,io-std,io-util,libc,macros,memchr,mio,net,num_cpus,once_cell,parking_lot,process,rt,rt-multi-thread,signal,signal-hook-registry,sync,time,tokio-macros,winapi" -// tokio-macros-1.1.0 -// unicode-xid-0.2.1 "default" diff --git a/CHANGELOG.md b/CHANGELOG.md index 6760b17..eb5504c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,58 @@ +# 1.6.0 (November 16th, 2021) + +- macros: fix mut patterns in `select!` macro ([#4211]) + +[#4211]: https://github.com/tokio-rs/tokio/pull/4211 + +# 1.5.1 (October 29th, 2021) + +- macros: fix type resolution error in `#[tokio::main]` ([#4176]) + +[#4176]: https://github.com/tokio-rs/tokio/pull/4176 + +# 1.5.0 (October 13th, 2021) + +- macros: make tokio-macros attributes more IDE friendly ([#4162]) + +[#4162]: https://github.com/tokio-rs/tokio/pull/4162 + +# 1.4.1 (September 30th, 2021) + +Reverted: run `current_thread` inside `LocalSet` ([#4027]) + +# 1.4.0 (September 29th, 2021) + +(yanked) + +### Changed + +- macros: run `current_thread` inside `LocalSet` ([#4027]) +- macros: explicitly relaxed clippy lint for `.expect()` in runtime entry macro ([#4030]) + +### Fixed + +- macros: fix invalid error messages in functions wrapped with `#[main]` or `#[test]` ([#4067]) + +[#4027]: https://github.com/tokio-rs/tokio/pull/4027 +[#4030]: https://github.com/tokio-rs/tokio/pull/4030 +[#4067]: https://github.com/tokio-rs/tokio/pull/4067 + +# 1.3.0 (July 7, 2021) + +- macros: don't trigger `clippy::unwrap_used` ([#3926]) + +[#3926]: https://github.com/tokio-rs/tokio/pull/3926 + +# 1.2.0 (May 14, 2021) + +- macros: forward input arguments in `#[tokio::test]` ([#3691]) +- macros: improve diagnostics on type mismatch ([#3766]) +- macros: various error message improvements ([#3677]) + +[#3677]: https://github.com/tokio-rs/tokio/pull/3677 +[#3691]: https://github.com/tokio-rs/tokio/pull/3691 +[#3766]: https://github.com/tokio-rs/tokio/pull/3766 + # 1.1.0 (February 5, 2021) - add `start_paused` option to macros ([#3492]) @@ -3,21 +3,20 @@ # 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 +# 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) +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "tokio-macros" -version = "1.1.0" +version = "1.6.0" authors = ["Tokio Contributors <team@tokio.rs>"] description = "Tokio's proc macros.\n" homepage = "https://tokio.rs" -documentation = "https://docs.rs/tokio-macros/1.1.0/tokio_macros" +documentation = "https://docs.rs/tokio-macros/1.6.0/tokio_macros" categories = ["asynchronous"] license = "MIT" repository = "https://github.com/tokio-rs/tokio" @@ -33,7 +32,7 @@ version = "1.0.7" version = "1" [dependencies.syn] -version = "1.0.3" +version = "1.0.56" features = ["full"] [dev-dependencies.tokio] version = "1.0.0" diff --git a/Cargo.toml.orig b/Cargo.toml.orig index ea7b2de..2114cd2 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -2,18 +2,17 @@ name = "tokio-macros" # When releasing to crates.io: # - Remove path dependencies -# - Update html_root_url. # - Update doc url # - Cargo.toml # - Update CHANGELOG.md. # - Create "tokio-macros-1.0.x" git tag. -version = "1.1.0" +version = "1.6.0" 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/1.1.0/tokio_macros" +documentation = "https://docs.rs/tokio-macros/1.6.0/tokio_macros" description = """ Tokio's proc macros. """ @@ -27,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"] } @@ -1,4 +1,4 @@ -Copyright (c) 2020 Tokio Contributors +Copyright (c) 2021 Tokio Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated @@ -7,13 +7,13 @@ third_party { } url { type: ARCHIVE - value: "https://static.crates.io/crates/tokio-macros/tokio-macros-1.1.0.crate" + value: "https://static.crates.io/crates/tokio-macros/tokio-macros-1.6.0.crate" } - version: "1.1.0" + version: "1.6.0" license_type: NOTICE last_upgrade_date { year: 2021 - month: 2 - day: 9 + month: 11 + day: 16 } } diff --git a/TEST_MAPPING b/TEST_MAPPING new file mode 100644 index 0000000..6c266ef --- /dev/null +++ b/TEST_MAPPING @@ -0,0 +1,218 @@ +// Generated by update_crate_tests.py for tests that depend on this crate. +{ + "presubmit": [ + { + "name": "doh_unit_test" + }, + { + "name": "futures-util_device_test_src_lib" + }, + { + "name": "tokio-test_device_test_src_lib" + }, + { + "name": "tokio-test_device_test_tests_block_on" + }, + { + "name": "tokio-test_device_test_tests_io" + }, + { + "name": "tokio-test_device_test_tests_macros" + }, + { + "name": "tokio_device_test_tests__require_full" + }, + { + "name": "tokio_device_test_tests_buffered" + }, + { + "name": "tokio_device_test_tests_io_async_fd" + }, + { + "name": "tokio_device_test_tests_io_async_read" + }, + { + "name": "tokio_device_test_tests_io_chain" + }, + { + "name": "tokio_device_test_tests_io_copy" + }, + { + "name": "tokio_device_test_tests_io_copy_bidirectional" + }, + { + "name": "tokio_device_test_tests_io_driver" + }, + { + "name": "tokio_device_test_tests_io_driver_drop" + }, + { + "name": "tokio_device_test_tests_io_lines" + }, + { + "name": "tokio_device_test_tests_io_mem_stream" + }, + { + "name": "tokio_device_test_tests_io_read" + }, + { + "name": "tokio_device_test_tests_io_read_buf" + }, + { + "name": "tokio_device_test_tests_io_read_exact" + }, + { + "name": "tokio_device_test_tests_io_read_line" + }, + { + "name": "tokio_device_test_tests_io_read_to_end" + }, + { + "name": "tokio_device_test_tests_io_read_to_string" + }, + { + "name": "tokio_device_test_tests_io_read_until" + }, + { + "name": "tokio_device_test_tests_io_split" + }, + { + "name": "tokio_device_test_tests_io_take" + }, + { + "name": "tokio_device_test_tests_io_write" + }, + { + "name": "tokio_device_test_tests_io_write_all" + }, + { + "name": "tokio_device_test_tests_io_write_buf" + }, + { + "name": "tokio_device_test_tests_io_write_int" + }, + { + "name": "tokio_device_test_tests_macros_join" + }, + { + "name": "tokio_device_test_tests_macros_pin" + }, + { + "name": "tokio_device_test_tests_macros_select" + }, + { + "name": "tokio_device_test_tests_macros_test" + }, + { + "name": "tokio_device_test_tests_macros_try_join" + }, + { + "name": "tokio_device_test_tests_net_bind_resource" + }, + { + "name": "tokio_device_test_tests_net_lookup_host" + }, + { + "name": "tokio_device_test_tests_no_rt" + }, + { + "name": "tokio_device_test_tests_process_kill_on_drop" + }, + { + "name": "tokio_device_test_tests_rt_basic" + }, + { + "name": "tokio_device_test_tests_rt_common" + }, + { + "name": "tokio_device_test_tests_rt_threaded" + }, + { + "name": "tokio_device_test_tests_sync_barrier" + }, + { + "name": "tokio_device_test_tests_sync_broadcast" + }, + { + "name": "tokio_device_test_tests_sync_errors" + }, + { + "name": "tokio_device_test_tests_sync_mpsc" + }, + { + "name": "tokio_device_test_tests_sync_mutex" + }, + { + "name": "tokio_device_test_tests_sync_mutex_owned" + }, + { + "name": "tokio_device_test_tests_sync_notify" + }, + { + "name": "tokio_device_test_tests_sync_oneshot" + }, + { + "name": "tokio_device_test_tests_sync_rwlock" + }, + { + "name": "tokio_device_test_tests_sync_semaphore" + }, + { + "name": "tokio_device_test_tests_sync_semaphore_owned" + }, + { + "name": "tokio_device_test_tests_sync_watch" + }, + { + "name": "tokio_device_test_tests_task_abort" + }, + { + "name": "tokio_device_test_tests_task_blocking" + }, + { + "name": "tokio_device_test_tests_task_local" + }, + { + "name": "tokio_device_test_tests_task_local_set" + }, + { + "name": "tokio_device_test_tests_tcp_accept" + }, + { + "name": "tokio_device_test_tests_tcp_connect" + }, + { + "name": "tokio_device_test_tests_tcp_echo" + }, + { + "name": "tokio_device_test_tests_tcp_into_split" + }, + { + "name": "tokio_device_test_tests_tcp_into_std" + }, + { + "name": "tokio_device_test_tests_tcp_peek" + }, + { + "name": "tokio_device_test_tests_tcp_shutdown" + }, + { + "name": "tokio_device_test_tests_tcp_socket" + }, + { + "name": "tokio_device_test_tests_tcp_split" + }, + { + "name": "tokio_device_test_tests_time_rt" + }, + { + "name": "tokio_device_test_tests_udp" + }, + { + "name": "tokio_device_test_tests_uds_cred" + }, + { + "name": "tokio_device_test_tests_uds_split" + } + ] +} diff --git a/src/entry.rs b/src/entry.rs index f82a329..01f8ee4 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -1,7 +1,10 @@ use proc_macro::TokenStream; use proc_macro2::Span; -use quote::quote; -use syn::spanned::Spanned; +use quote::{quote, quote_spanned, ToTokens}; +use syn::parse::Parser; + +// syn::AttributeArgs does not implement syn::Parse +type AttributeArgs = syn::punctuated::Punctuated<syn::NestedMeta, syn::Token![,]>; #[derive(Clone, Copy, PartialEq)] enum RuntimeFlavor { @@ -28,12 +31,20 @@ struct FinalConfig { start_paused: Option<bool>, } +/// 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, +}; + struct Configuration { rt_multi_thread_available: bool, default_flavor: RuntimeFlavor, flavor: Option<RuntimeFlavor>, worker_threads: Option<(usize, Span)>, start_paused: Option<(bool, Span)>, + is_test: bool, } impl Configuration { @@ -47,6 +58,7 @@ impl Configuration { flavor: None, worker_threads: None, start_paused: None, + is_test, } } @@ -92,16 +104,25 @@ impl Configuration { Ok(()) } + fn macro_name(&self) -> &'static str { + if self.is_test { + "tokio::test" + } else { + "tokio::main" + } + } + fn build(&self) -> Result<FinalConfig, syn::Error> { let flavor = self.flavor.unwrap_or(self.default_flavor); use RuntimeFlavor::*; let worker_threads = match (flavor, self.worker_threads) { (CurrentThread, Some((_, worker_threads_span))) => { - return Err(syn::Error::new( - worker_threads_span, - "The `worker_threads` option requires the `multi_thread` runtime flavor.", - )) + let msg = format!( + "The `worker_threads` option requires the `multi_thread` runtime flavor. Use `#[{}(flavor = \"multi_thread\")]`", + self.macro_name(), + ); + return Err(syn::Error::new(worker_threads_span, msg)); } (CurrentThread, None) => None, (Threaded, worker_threads) if self.rt_multi_thread_available => { @@ -119,10 +140,11 @@ impl Configuration { let start_paused = match (flavor, self.start_paused) { (Threaded, Some((_, start_paused_span))) => { - return Err(syn::Error::new( - start_paused_span, - "The `start_paused` option requires the `current_thread` runtime flavor.", - )); + let msg = format!( + "The `start_paused` option requires the `current_thread` runtime flavor. Use `#[{}(flavor = \"current_thread\")]`", + self.macro_name(), + ); + return Err(syn::Error::new(start_paused_span, msg)); } (CurrentThread, Some((start_paused, _))) => Some(start_paused), (_, None) => None, @@ -142,12 +164,12 @@ fn parse_int(int: syn::Lit, span: Span, field: &str) -> Result<usize, syn::Error Ok(value) => Ok(value), Err(e) => Err(syn::Error::new( span, - format!("Failed to parse {} as integer: {}", field, e), + format!("Failed to parse value of `{}` as integer: {}", field, e), )), }, _ => Err(syn::Error::new( span, - format!("Failed to parse {} as integer.", field), + format!("Failed to parse value of `{}` as integer.", field), )), } } @@ -158,7 +180,7 @@ fn parse_string(int: syn::Lit, span: Span, field: &str) -> Result<String, syn::E syn::Lit::Verbatim(s) => Ok(s.to_string()), _ => Err(syn::Error::new( span, - format!("Failed to parse {} as string.", field), + format!("Failed to parse value of `{}` as string.", field), )), } } @@ -168,71 +190,74 @@ fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Erro syn::Lit::Bool(b) => Ok(b.value), _ => Err(syn::Error::new( span, - format!("Failed to parse {} as bool.", field), + format!("Failed to parse value of `{}` as bool.", field), )), } } -fn parse_knobs( - mut input: syn::ItemFn, - args: syn::AttributeArgs, +fn build_config( + input: syn::ItemFn, + args: AttributeArgs, is_test: bool, rt_multi_thread: 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)); +) -> Result<FinalConfig, syn::Error> { + if input.sig.asyncness.is_none() { + let msg = "the `async` keyword is missing from the function declaration"; + return Err(syn::Error::new_spanned(input.sig.fn_token, msg)); } - sig.asyncness = None; - - let macro_name = if is_test { - "tokio::test" - } else { - "tokio::main" - }; let mut config = Configuration::new(is_test, rt_multi_thread); + let macro_name = config.macro_name(); 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() { + let ident = namevalue + .path + .get_ident() + .ok_or_else(|| { + syn::Error::new_spanned(&namevalue, "Must have specified ident") + })? + .to_string() + .to_lowercase(); + match ident.as_str() { "worker_threads" => { - config.set_worker_threads(namevalue.lit.clone(), namevalue.span())?; + config.set_worker_threads( + namevalue.lit.clone(), + syn::spanned::Spanned::span(&namevalue.lit), + )?; } "flavor" => { - config.set_flavor(namevalue.lit.clone(), namevalue.span())?; + config.set_flavor( + namevalue.lit.clone(), + syn::spanned::Spanned::span(&namevalue.lit), + )?; } "start_paused" => { - config.set_start_paused(namevalue.lit.clone(), namevalue.span())?; + config.set_start_paused( + namevalue.lit.clone(), + syn::spanned::Spanned::span(&namevalue.lit), + )?; } "core_threads" => { let msg = "Attribute `core_threads` is renamed to `worker_threads`"; return Err(syn::Error::new_spanned(namevalue, msg)); } name => { - let msg = format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`", name); + let msg = format!( + "Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`", + 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)); - } - let name = ident.unwrap().to_string().to_lowercase(); + let name = path + .get_ident() + .ok_or_else(|| syn::Error::new_spanned(&path, "Must have specified ident"))? + .to_string() + .to_lowercase(); let msg = match name.as_str() { "threaded_scheduler" | "multi_thread" => { format!( @@ -264,13 +289,35 @@ fn parse_knobs( } } - let config = config.build()?; + config.build() +} + +fn parse_knobs(mut input: syn::ItemFn, is_test: bool, config: FinalConfig) -> TokenStream { + input.sig.asyncness = None; + + // 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() }, }; @@ -281,65 +328,101 @@ fn parse_knobs( rt = quote! { #rt.start_paused(#v) }; } - let header = { - if is_test { - quote! { - #[::core::prelude::v1::test] - } - } else { - quote! {} + let header = if is_test { + quote! { + #[::core::prelude::v1::test] } + } else { + quote! {} }; - let result = quote! { - #header - #(#attrs)* - #vis #sig { - #rt + let body = &input.block; + let brace_token = input.block.brace_token; + let (tail_return, tail_semicolon) = match body.stmts.last() { + Some(syn::Stmt::Semi(expr, _)) => match expr { + syn::Expr::Return(_) => (quote! { return }, quote! { ; }), + _ => match &input.sig.output { + syn::ReturnType::Type(_, ty) if matches!(&**ty, syn::Type::Tuple(ty) if ty.elems.is_empty()) => + { + (quote! {}, quote! { ; }) // unit + } + syn::ReturnType::Default => (quote! {}, quote! { ; }), // unit + syn::ReturnType::Type(..) => (quote! {}, quote! {}), // ! or another + }, + }, + _ => (quote! {}, quote! {}), + }; + input.block = syn::parse2(quote_spanned! {last_stmt_end_span=> + { + let body = async #body; + #[allow(clippy::expect_used)] + #tail_return #rt .enable_all() .build() - .unwrap() - .block_on(async #body) + .expect("Failed building the Runtime") + .block_on(body)#tail_semicolon } + }) + .expect("Parsing failure"); + input.block.brace_token = brace_token; + + let result = quote! { + #header + #input }; - Ok(result.into()) + result.into() +} + +fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream { + tokens.extend(TokenStream::from(error.into_compile_error())); + tokens } #[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 any of the steps for this macro fail, we still want to expand to an item that is as close + // to the expected output as possible. This helps out IDEs such that completions and other + // related features keep working. + let input: syn::ItemFn = match syn::parse(item.clone()) { + Ok(it) => it, + Err(e) => return token_stream_with_error(item, e), + }; - if input.sig.ident == "main" && !input.sig.inputs.is_empty() { + let config = 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(); - } + Err(syn::Error::new_spanned(&input.sig.ident, msg)) + } else { + AttributeArgs::parse_terminated + .parse(args) + .and_then(|args| build_config(input.clone(), args, false, rt_multi_thread)) + }; - parse_knobs(input, args, false, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into()) + match config { + Ok(config) => parse_knobs(input, false, config), + Err(e) => token_stream_with_error(parse_knobs(input, false, DEFAULT_ERROR_CONFIG), e), + } } 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(); - } - } + // If any of the steps for this macro fail, we still want to expand to an item that is as close + // to the expected output as possible. This helps out IDEs such that completions and other + // related features keep working. + let input: syn::ItemFn = match syn::parse(item.clone()) { + Ok(it) => it, + Err(e) => return token_stream_with_error(item, e), + }; + let config = if let Some(attr) = input.attrs.iter().find(|attr| attr.path.is_ident("test")) { + let msg = "second test attribute is supplied"; + Err(syn::Error::new_spanned(&attr, msg)) + } else { + AttributeArgs::parse_terminated + .parse(args) + .and_then(|args| build_config(input.clone(), args, true, rt_multi_thread)) + }; - 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(); + match config { + Ok(config) => parse_knobs(input, true, config), + Err(e) => token_stream_with_error(parse_knobs(input, true, DEFAULT_ERROR_CONFIG), e), } - - parse_knobs(input, args, true, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into()) } @@ -1,4 +1,3 @@ -#![doc(html_root_url = "https://docs.rs/tokio-macros/1.1.0")] #![allow(clippy::needless_doctest_main)] #![warn( missing_debug_implementations, @@ -6,7 +5,7 @@ rust_2018_idioms, unreachable_pub )] -#![cfg_attr(docsrs, deny(broken_intra_doc_links))] +#![cfg_attr(docsrs, deny(rustdoc::broken_intra_doc_links))] #![doc(test( no_crate_inject, attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) @@ -168,6 +167,8 @@ use proc_macro::TokenStream; /// } /// ``` /// +/// Note that `start_paused` requires the `test-util` feature to be enabled. +/// /// ### NOTE: /// /// If you rename the Tokio crate in your dependencies this macro will not work. @@ -258,6 +259,8 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream { /// } /// ``` /// +/// Note that `start_paused` requires the `test-util` feature to be enabled. +/// /// ### NOTE: /// /// If you rename the Tokio crate in your dependencies this macro will not work. @@ -326,3 +329,11 @@ pub fn test_fail(_args: TokenStream, _item: TokenStream) -> TokenStream { pub fn select_priv_declare_output_enum(input: TokenStream) -> TokenStream { select::declare_output_enum(input) } + +/// 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_clean_pattern(input: TokenStream) -> TokenStream { + select::clean_pattern_macro(input) +} diff --git a/src/select.rs b/src/select.rs index ddb2e6a..23e280a 100644 --- a/src/select.rs +++ b/src/select.rs @@ -41,3 +41,70 @@ pub(crate) fn declare_output_enum(input: TokenStream) -> TokenStream { pub(super) type Mask = #mask; }) } + +pub(crate) fn clean_pattern_macro(input: TokenStream) -> TokenStream { + // If this isn't a pattern, we return the token stream as-is. The select! + // macro is using it in a location requiring a pattern, so an error will be + // emitted there. + let mut input: syn::Pat = match syn::parse(input.clone()) { + Ok(it) => it, + Err(_) => return input, + }; + + clean_pattern(&mut input); + quote::ToTokens::into_token_stream(input).into() +} + +// Removes any occurrences of ref or mut in the provided pattern. +fn clean_pattern(pat: &mut syn::Pat) { + match pat { + syn::Pat::Box(_box) => {} + syn::Pat::Lit(_literal) => {} + syn::Pat::Macro(_macro) => {} + syn::Pat::Path(_path) => {} + syn::Pat::Range(_range) => {} + syn::Pat::Rest(_rest) => {} + syn::Pat::Verbatim(_tokens) => {} + syn::Pat::Wild(_underscore) => {} + syn::Pat::Ident(ident) => { + ident.by_ref = None; + ident.mutability = None; + if let Some((_at, pat)) = &mut ident.subpat { + clean_pattern(&mut *pat); + } + } + syn::Pat::Or(or) => { + for case in or.cases.iter_mut() { + clean_pattern(case); + } + } + syn::Pat::Slice(slice) => { + for elem in slice.elems.iter_mut() { + clean_pattern(elem); + } + } + syn::Pat::Struct(struct_pat) => { + for field in struct_pat.fields.iter_mut() { + clean_pattern(&mut field.pat); + } + } + syn::Pat::Tuple(tuple) => { + for elem in tuple.elems.iter_mut() { + clean_pattern(elem); + } + } + syn::Pat::TupleStruct(tuple) => { + for elem in tuple.pat.elems.iter_mut() { + clean_pattern(elem); + } + } + syn::Pat::Reference(reference) => { + reference.mutability = None; + clean_pattern(&mut *reference.pat); + } + syn::Pat::Type(type_pat) => { + clean_pattern(&mut *type_pat.pat); + } + _ => {} + } +} |