diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
commit | 10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch) | |
tree | bdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/maybe-async/src | |
parent | Releasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff) | |
download | rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.tar.xz rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.zip |
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/maybe-async/src')
-rw-r--r-- | vendor/maybe-async/src/lib.rs | 621 | ||||
-rw-r--r-- | vendor/maybe-async/src/parse.rs | 49 | ||||
-rw-r--r-- | vendor/maybe-async/src/visit.rs | 199 |
3 files changed, 869 insertions, 0 deletions
diff --git a/vendor/maybe-async/src/lib.rs b/vendor/maybe-async/src/lib.rs new file mode 100644 index 000000000..b8b43a16a --- /dev/null +++ b/vendor/maybe-async/src/lib.rs @@ -0,0 +1,621 @@ +//! +//! # Maybe-Async Procedure Macro +//! +//! **Why bother writing similar code twice for blocking and async code?** +//! +//! [![Build Status](https://github.com/fMeow/maybe-async-rs/workflows/CI%20%28Linux%29/badge.svg?branch=main)](https://github.com/fMeow/maybe-async-rs/actions) +//! [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) +//! [![Latest Version](https://img.shields.io/crates/v/maybe-async.svg)](https://crates.io/crates/maybe-async) +//! [![maybe-async](https://docs.rs/maybe-async/badge.svg)](https://docs.rs/maybe-async) +//! +//! When implementing both sync and async versions of API in a crate, most API +//! of the two version are almost the same except for some async/await keyword. +//! +//! `maybe-async` help unifying async and sync implementation by **procedural +//! macro**. +//! - Write async code with normal `async`, `await`, and let `maybe_async` +//! handles +//! those `async` and `await` when you need a blocking code. +//! - Switch between sync and async by toggling `is_sync` feature gate in +//! `Cargo.toml`. +//! - use `must_be_async` and `must_be_sync` to keep code in specified version +//! - use `impl_async` and `impl_sync` to only compile code block on specified +//! version +//! - A handy macro to unify unit test code is also provided. +//! +//! These procedural macros can be applied to the following codes: +//! - trait item declaration +//! - trait implmentation +//! - function definition +//! - struct definition +//! +//! **RECOMMENDATION**: Enable **resolver ver2** in your crate, which is +//! introduced in Rust 1.51. If not, two crates in dependency with conflict +//! version (one async and another blocking) can fail complilation. +//! +//! +//! ## Motivation +//! +//! The async/await language feature alters the async world of rust. +//! Comparing with the map/and_then style, now the async code really resembles +//! sync version code. +//! +//! In many crates, the async and sync version of crates shares the same API, +//! but the minor difference that all async code must be awaited prevent the +//! unification of async and sync code. In other words, we are forced to write +//! an async and an sync implementation repectively. +//! +//! ## Macros in Detail +//! +//! `maybe-async` offers 4 set of attribute macros: `maybe_async`, +//! `sync_impl`/`async_impl`, `must_be_sync`/`must_be_async`, and `test`. +//! +//! To use `maybe-async`, we must know which block of codes is only used on +//! blocking implementation, and which on async. These two implementation should +//! share the same function signatures except for async/await keywords, and use +//! `sync_impl` and `async_impl` to mark these implementation. +//! +//! Use `maybe_async` macro on codes that share the same API on both async and +//! blocking code except for async/await keywords. And use feature gate +//! `is_sync` in `Cargo.toml` to toggle between async and blocking code. +//! +//! - `maybe_async` +//! +//! Offers a unified feature gate to provide sync and async conversion on +//! demand by feature gate `is_sync`, with **async first** policy. +//! +//! Want to keep async code? add `maybe_async` in dependencies with default +//! features, which means `maybe_async` is the same as `must_be_async`: +//! +//! ```toml +//! [dependencies] +//! maybe_async = "0.2" +//! ``` +//! +//! Wanna convert async code to sync? Add `maybe_async` to dependencies with +//! an `is_sync` feature gate. In this way, `maybe_async` is the same as +//! `must_be_sync`: +//! +//! ```toml +//! [dependencies] +//! maybe_async = { version = "0.2", features = ["is_sync"] } +//! ``` +//! +//! Not all async traits need futures that are `dyn Future + Send`. +//! To avoid having "Send" and "Sync" bounds placed on the async trait +//! methods, invoke the maybe_async macro as #[maybe_async(?Send)] on both +//! the trait and the impl blocks. +//! +//! +//! - `must_be_async` +//! +//! **Keep async**. Add `async_trait` attribute macro for trait declaration +//! or implementation to bring async fn support in traits. +//! +//! To avoid having "Send" and "Sync" bounds placed on the async trait +//! methods, invoke the maybe_async macro as #[must_be_async(?Send)]. +//! +//! - `must_be_sync` +//! +//! **Convert to sync code**. Convert the async code into sync code by +//! removing all `async move`, `async` and `await` keyword +//! +//! +//! - `sync_impl` +//! +//! An sync implementation should on compile on blocking implementation and +//! must simply disappear when we want async version. +//! +//! Although most of the API are almost the same, there definitely come to a +//! point when the async and sync version should differ greatly. For +//! example, a MongoDB client may use the same API for async and sync +//! verison, but the code to actually send reqeust are quite different. +//! +//! Here, we can use `sync_impl` to mark a synchronous implementation, and a +//! sync implementation shoule disappear when we want async version. +//! +//! - `async_impl` +//! +//! An async implementation should on compile on async implementation and +//! must simply disappear when we want sync version. +//! +//! To avoid having "Send" and "Sync" bounds placed on the async trait +//! methods, invoke the maybe_async macro as #[async_impl(?Send)]. +//! +//! +//! - `test` +//! +//! Handy macro to unify async and sync **unit and e2e test** code. +//! +//! You can specify the condition to compile to sync test code +//! and also the conditions to compile to async test code with given test +//! macro, e.x. `tokio::test`, `async_std::test` and etc. When only sync +//! condition is specified,the test code only compiles when sync condition +//! is met. +//! +//! ```rust +//! # #[maybe_async::maybe_async] +//! # async fn async_fn() -> bool { +//! # true +//! # } +//! +//! ##[maybe_async::test( +//! feature="is_sync", +//! async( +//! all(not(feature="is_sync"), feature="async_std"), +//! async_std::test +//! ), +//! async( +//! all(not(feature="is_sync"), feature="tokio"), +//! tokio::test +//! ) +//! )] +//! async fn test_async_fn() { +//! let res = async_fn().await; +//! assert_eq!(res, true); +//! } +//! ``` +//! +//! ## What's Under the Hook +//! +//! `maybe-async` compiles your code in different way with the `is_sync` feature +//! gate. It remove all `await` and `async` keywords in your code under +//! `maybe_async` macro and conditionally compiles codes under `async_impl` and +//! `sync_impl`. +//! +//! Here is an detailed example on what's going on whe the `is_sync` feature +//! gate set or not. +//! +//! ```rust +//! #[maybe_async::maybe_async(?Send)] +//! trait A { +//! async fn async_fn_name() -> Result<(), ()> { +//! Ok(()) +//! } +//! fn sync_fn_name() -> Result<(), ()> { +//! Ok(()) +//! } +//! } +//! +//! struct Foo; +//! +//! #[maybe_async::maybe_async(?Send)] +//! impl A for Foo { +//! async fn async_fn_name() -> Result<(), ()> { +//! Ok(()) +//! } +//! fn sync_fn_name() -> Result<(), ()> { +//! Ok(()) +//! } +//! } +//! +//! #[maybe_async::maybe_async] +//! async fn maybe_async_fn() -> Result<(), ()> { +//! let a = Foo::async_fn_name().await?; +//! +//! let b = Foo::sync_fn_name()?; +//! Ok(()) +//! } +//! ``` +//! +//! When `maybe-async` feature gate `is_sync` is **NOT** set, the generated code +//! is async code: +//! +//! ```rust +//! // Compiled code when `is_sync` is toggled off. +//! #[async_trait::async_trait(?Send)] +//! trait A { +//! async fn maybe_async_fn_name() -> Result<(), ()> { +//! Ok(()) +//! } +//! fn sync_fn_name() -> Result<(), ()> { +//! Ok(()) +//! } +//! } +//! +//! struct Foo; +//! +//! #[async_trait::async_trait(?Send)] +//! impl A for Foo { +//! async fn maybe_async_fn_name() -> Result<(), ()> { +//! Ok(()) +//! } +//! fn sync_fn_name() -> Result<(), ()> { +//! Ok(()) +//! } +//! } +//! +//! async fn maybe_async_fn() -> Result<(), ()> { +//! let a = Foo::maybe_async_fn_name().await?; +//! let b = Foo::sync_fn_name()?; +//! Ok(()) +//! } +//! ``` +//! +//! When `maybe-async` feature gate `is_sync` is set, all async keyword is +//! ignored and yields a sync version code: +//! +//! ```rust +//! // Compiled code when `is_sync` is toggled on. +//! trait A { +//! fn maybe_async_fn_name() -> Result<(), ()> { +//! Ok(()) +//! } +//! fn sync_fn_name() -> Result<(), ()> { +//! Ok(()) +//! } +//! } +//! +//! struct Foo; +//! +//! impl A for Foo { +//! fn maybe_async_fn_name() -> Result<(), ()> { +//! Ok(()) +//! } +//! fn sync_fn_name() -> Result<(), ()> { +//! Ok(()) +//! } +//! } +//! +//! fn maybe_async_fn() -> Result<(), ()> { +//! let a = Foo::maybe_async_fn_name()?; +//! let b = Foo::sync_fn_name()?; +//! Ok(()) +//! } +//! ``` +//! +//! ## Examples +//! +//! ### rust client for services +//! +//! When implementing rust client for any services, like awz3. The higher level +//! API of async and sync version is almost the same, such as creating or +//! deleting a bucket, retrieving an object and etc. +//! +//! The example `service_client` is a proof of concept that `maybe_async` can +//! actually free us from writing almost the same code for sync and async. We +//! can toggle between a sync AWZ3 client and async one by `is_sync` feature +//! gate when we add `maybe-async` to dependency. +//! +//! +//! # License +//! MIT + +extern crate proc_macro; + +use proc_macro::TokenStream; + +use proc_macro2::{Span, TokenStream as TokenStream2}; +use syn::{ + parse_macro_input, spanned::Spanned, AttributeArgs, ImplItem, Lit, Meta, NestedMeta, TraitItem, +}; + +use quote::quote; + +use crate::{parse::Item, visit::AsyncAwaitRemoval}; + +mod parse; +mod visit; + +fn convert_async(input: &mut Item, send: bool) -> TokenStream2 { + if send { + match input { + Item::Impl(item) => quote!(#[async_trait::async_trait]#item), + Item::Trait(item) => quote!(#[async_trait::async_trait]#item), + Item::Fn(item) => quote!(#item), + Item::Static(item) => quote!(#item), + } + } else { + match input { + Item::Impl(item) => quote!(#[async_trait::async_trait(?Send)]#item), + Item::Trait(item) => quote!(#[async_trait::async_trait(?Send)]#item), + Item::Fn(item) => quote!(#item), + Item::Static(item) => quote!(#item), + } + } + .into() +} + +fn convert_sync(input: &mut Item) -> TokenStream2 { + match input { + Item::Impl(item) => { + for inner in &mut item.items { + if let ImplItem::Method(ref mut method) = inner { + if method.sig.asyncness.is_some() { + method.sig.asyncness = None; + } + } + } + AsyncAwaitRemoval.remove_async_await(quote!(#item)) + } + Item::Trait(item) => { + for inner in &mut item.items { + if let TraitItem::Method(ref mut method) = inner { + if method.sig.asyncness.is_some() { + method.sig.asyncness = None; + } + } + } + AsyncAwaitRemoval.remove_async_await(quote!(#item)) + } + Item::Fn(item) => { + if item.sig.asyncness.is_some() { + item.sig.asyncness = None; + } + AsyncAwaitRemoval.remove_async_await(quote!(#item)) + } + Item::Static(item) => AsyncAwaitRemoval.remove_async_await(quote!(#item)), + } + .into() +} + +/// maybe_async attribute macro +/// +/// Can be applied to trait item, trait impl, functions and struct impls. +#[proc_macro_attribute] +pub fn maybe_async(args: TokenStream, input: TokenStream) -> TokenStream { + let send = match args.to_string().replace(" ", "").as_str() { + "" | "Send" => true, + "?Send" => false, + _ => { + return syn::Error::new(Span::call_site(), "Only accepts `Send` or `?Send`") + .to_compile_error() + .into(); + } + }; + + let mut item = parse_macro_input!(input as Item); + + let token = if cfg!(feature = "is_sync") { + convert_sync(&mut item) + } else { + convert_async(&mut item, send) + }; + token.into() +} + +/// convert marked async code to async code with `async-trait` +#[proc_macro_attribute] +pub fn must_be_async(args: TokenStream, input: TokenStream) -> TokenStream { + let send = match args.to_string().replace(" ", "").as_str() { + "" | "Send" => true, + "?Send" => false, + _ => { + return syn::Error::new(Span::call_site(), "Only accepts `Send` or `?Send`") + .to_compile_error() + .into(); + } + }; + let mut item = parse_macro_input!(input as Item); + convert_async(&mut item, send).into() +} + +/// convert marked async code to sync code +#[proc_macro_attribute] +pub fn must_be_sync(_args: TokenStream, input: TokenStream) -> TokenStream { + let mut item = parse_macro_input!(input as Item); + convert_sync(&mut item).into() +} + +/// mark sync implementation +/// +/// only compiled when `is_sync` feature gate is set. +/// When `is_sync` is not set, marked code is removed. +#[proc_macro_attribute] +pub fn sync_impl(_args: TokenStream, input: TokenStream) -> TokenStream { + let input = TokenStream2::from(input); + let token = if cfg!(feature = "is_sync") { + quote!(#input) + } else { + quote!() + }; + token.into() +} + +/// mark async implementation +/// +/// only compiled when `is_sync` feature gate is not set. +/// When `is_sync` is set, marked code is removed. +#[proc_macro_attribute] +pub fn async_impl(args: TokenStream, _input: TokenStream) -> TokenStream { + let send = match args.to_string().replace(" ", "").as_str() { + "" | "Send" => true, + "?Send" => false, + _ => { + return syn::Error::new(Span::call_site(), "Only accepts `Send` or `?Send`") + .to_compile_error() + .into(); + } + }; + + let token = if cfg!(feature = "is_sync") { + quote!() + } else { + let mut item = parse_macro_input!(_input as Item); + convert_async(&mut item, send) + }; + token.into() +} + +macro_rules! match_nested_meta_to_str_lit { + ($t:expr) => { + match $t { + NestedMeta::Lit(lit) => { + match lit { + Lit::Str(s) => { + s.value().parse::<TokenStream2>().unwrap() + } + _ => { + return syn::Error::new(lit.span(), "expected meta or string literal").to_compile_error().into(); + } + } + } + NestedMeta::Meta(meta) => quote!(#meta) + } + }; +} + +/// Handy macro to unify test code of sync and async code +/// +/// Since the API of both sync and async code are the same, +/// with only difference that async functions must be awaited. +/// So it's tedious to write unit sync and async respectively. +/// +/// This macro helps unify the sync and async unit test code. +/// Pass the condition to treat test code as sync as the first +/// argument. And specify the condition when to treat test code +/// as async and the lib to run async test, e.x. `async-std::test`, +/// `tokio::test`, or any valid attribute macro. +/// +/// **ATTENTION**: do not write await inside a assert macro +/// +/// - Examples +/// +/// ```rust +/// #[maybe_async::maybe_async] +/// async fn async_fn() -> bool { +/// true +/// } +/// +/// #[maybe_async::test( +/// // when to treat the test code as sync version +/// feature="is_sync", +/// // when to run async test +/// async(all(not(feature="is_sync"), feature="async_std"), async_std::test), +/// // you can specify multiple conditions for different async runtime +/// async(all(not(feature="is_sync"), feature="tokio"), tokio::test) +/// )] +/// async fn test_async_fn() { +/// let res = async_fn().await; +/// assert_eq!(res, true); +/// } +/// +/// // Only run test in sync version +/// #[maybe_async::test(feature = "is_sync")] +/// async fn test_sync_fn() { +/// let res = async_fn().await; +/// assert_eq!(res, true); +/// } +/// ``` +/// +/// The above code is transcripted to the following code: +/// +/// ```rust +/// # use maybe_async::{must_be_async, must_be_sync, sync_impl}; +/// # #[maybe_async::maybe_async] +/// # async fn async_fn() -> bool { true } +/// +/// // convert to sync version when sync condition is met, keep in async version when corresponding +/// // condition is met +/// #[cfg_attr(feature = "is_sync", must_be_sync, test)] +/// #[cfg_attr( +/// all(not(feature = "is_sync"), feature = "async_std"), +/// must_be_async, +/// async_std::test +/// )] +/// #[cfg_attr( +/// all(not(feature = "is_sync"), feature = "tokio"), +/// must_be_async, +/// tokio::test +/// )] +/// async fn test_async_fn() { +/// let res = async_fn().await; +/// assert_eq!(res, true); +/// } +/// +/// // force converted to sync function, and only compile on sync condition +/// #[cfg(feature = "is_sync")] +/// #[test] +/// fn test_sync_fn() { +/// let res = async_fn(); +/// assert_eq!(res, true); +/// } +/// ``` +#[proc_macro_attribute] +pub fn test(args: TokenStream, input: TokenStream) -> TokenStream { + let attr_args = parse_macro_input!(args as AttributeArgs); + let input = TokenStream2::from(input); + if attr_args.len() < 1 { + return syn::Error::new( + Span::call_site(), + "Arguments cannot be empty, at least specify the condition for sync code", + ) + .to_compile_error() + .into(); + } + + // The first attributes indicates sync condition + let sync_cond = match_nested_meta_to_str_lit!(attr_args.first().unwrap()); + let mut ts = quote!(#[cfg_attr(#sync_cond, maybe_async::must_be_sync, test)]); + + // The rest attributes indicates async condition and async test macro + // only accepts in the forms of `async(cond, test_macro)`, but `cond` and + // `test_macro` can be either meta attributes or string literal + let mut async_token = Vec::new(); + let mut async_conditions = Vec::new(); + for async_meta in attr_args.into_iter().skip(1) { + match async_meta { + NestedMeta::Meta(meta) => match meta { + Meta::List(list) => { + let name = list.path.segments[0].ident.to_string(); + if name.ne("async") { + return syn::Error::new( + list.path.span(), + format!("Unknown path: `{}`, must be `async`", name), + ) + .to_compile_error() + .into(); + } + if list.nested.len() == 2 { + let async_cond = + match_nested_meta_to_str_lit!(list.nested.first().unwrap()); + let async_test = match_nested_meta_to_str_lit!(list.nested.last().unwrap()); + let attr = quote!( + #[cfg_attr(#async_cond, maybe_async::must_be_async, #async_test)] + ); + async_conditions.push(async_cond); + async_token.push(attr); + } else { + let msg = format!( + "Must pass two metas or string literals like `async(condition, \ + async_test_macro)`, you passed {} metas.", + list.nested.len() + ); + return syn::Error::new(list.span(), msg).to_compile_error().into(); + } + } + _ => { + return syn::Error::new( + meta.span(), + "Must be list of metas like: `async(condition, async_test_macro)`", + ) + .to_compile_error() + .into(); + } + }, + NestedMeta::Lit(lit) => { + return syn::Error::new( + lit.span(), + "Must be list of metas like: `async(condition, async_test_macro)`", + ) + .to_compile_error() + .into(); + } + }; + } + + async_token.into_iter().for_each(|t| ts.extend(t)); + ts.extend(quote!( #input )); + if !async_conditions.is_empty() { + quote! { + #[cfg(any(#sync_cond, #(#async_conditions),*))] + #ts + } + } else { + quote! { + #[cfg(#sync_cond)] + #ts + } + } + .into() +} diff --git a/vendor/maybe-async/src/parse.rs b/vendor/maybe-async/src/parse.rs new file mode 100644 index 000000000..b5a8cf903 --- /dev/null +++ b/vendor/maybe-async/src/parse.rs @@ -0,0 +1,49 @@ +use proc_macro2::Span; +use syn::{ + parse::{discouraged::Speculative, Parse, ParseStream, Result}, + Attribute, Error, ItemFn, ItemImpl, ItemStatic, ItemTrait, +}; + +pub enum Item { + Trait(ItemTrait), + Impl(ItemImpl), + Fn(ItemFn), + Static(ItemStatic), +} + +macro_rules! fork { + ($fork:ident = $input:ident) => {{ + $fork = $input.fork(); + &$fork + }}; +} + +impl Parse for Item { + fn parse(input: ParseStream) -> Result<Self> { + let attrs = input.call(Attribute::parse_outer)?; + let mut fork; + let item = if let Some(mut item) = fork!(fork = input).parse::<ItemImpl>().ok() { + if item.trait_.is_none() { + return Err(Error::new(Span::call_site(), "expected a trait impl")); + } + item.attrs = attrs; + Item::Impl(item) + } else if let Some(mut item) = fork!(fork = input).parse::<ItemTrait>().ok() { + item.attrs = attrs; + Item::Trait(item) + } else if let Some(mut item) = fork!(fork = input).parse::<ItemFn>().ok() { + item.attrs = attrs; + Item::Fn(item) + } else if let Some(mut item) = fork!(fork = input).parse::<ItemStatic>().ok() { + item.attrs = attrs; + Item::Static(item) + } else { + return Err(Error::new( + Span::call_site(), + "expected trait impl, trait or fn", + )); + }; + input.advance_to(&fork); + Ok(item) + } +} diff --git a/vendor/maybe-async/src/visit.rs b/vendor/maybe-async/src/visit.rs new file mode 100644 index 000000000..e33c24273 --- /dev/null +++ b/vendor/maybe-async/src/visit.rs @@ -0,0 +1,199 @@ +use std::iter::FromIterator; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{ + parse_quote, + punctuated::Punctuated, + visit_mut::{self, visit_item_mut, visit_path_segment_mut, VisitMut}, + Expr, ExprBlock, File, GenericArgument, GenericParam, Item, PathArguments, PathSegment, Type, + TypeParamBound, WherePredicate, +}; + +pub struct ReplaceGenericType<'a> { + generic_type: &'a str, + arg_type: &'a PathSegment, +} + +impl<'a> ReplaceGenericType<'a> { + pub fn new(generic_type: &'a str, arg_type: &'a PathSegment) -> Self { + Self { + generic_type, + arg_type, + } + } + + pub fn replace_generic_type(item: &mut Item, generic_type: &'a str, arg_type: &'a PathSegment) { + let mut s = Self::new(generic_type, arg_type); + s.visit_item_mut(item); + } +} + +impl<'a> VisitMut for ReplaceGenericType<'a> { + fn visit_item_mut(&mut self, i: &mut Item) { + if let Item::Fn(item_fn) = i { + // remove generic type from generics <T, F> + let args = item_fn + .sig + .generics + .params + .iter() + .filter_map(|param| { + if let GenericParam::Type(type_param) = ¶m { + if type_param.ident.to_string().eq(self.generic_type) { + None + } else { + Some(param) + } + } else { + Some(param) + } + }) + .collect::<Vec<_>>(); + item_fn.sig.generics.params = + Punctuated::from_iter(args.into_iter().map(|p| p.clone()).collect::<Vec<_>>()); + + // remove generic type from where clause + if let Some(where_clause) = &mut item_fn.sig.generics.where_clause { + let new_where_clause = where_clause + .predicates + .iter() + .filter_map(|predicate| { + if let WherePredicate::Type(predicate_type) = predicate { + if let Type::Path(p) = &predicate_type.bounded_ty { + if p.path.segments[0].ident.to_string().eq(self.generic_type) { + None + } else { + Some(predicate) + } + } else { + Some(predicate) + } + } else { + Some(predicate) + } + }) + .collect::<Vec<_>>(); + + where_clause.predicates = Punctuated::from_iter( + new_where_clause + .into_iter() + .map(|c| c.clone()) + .collect::<Vec<_>>(), + ); + }; + } + visit_item_mut(self, i) + } + fn visit_path_segment_mut(&mut self, i: &mut PathSegment) { + // replace generic type with target type + if i.ident.to_string().eq(&self.generic_type) { + *i = self.arg_type.clone(); + } + visit_path_segment_mut(self, i); + } +} + +pub struct AsyncAwaitRemoval; + +impl AsyncAwaitRemoval { + pub fn remove_async_await(&mut self, item: TokenStream) -> TokenStream { + let mut syntax_tree: File = syn::parse(item.into()).unwrap(); + self.visit_file_mut(&mut syntax_tree); + quote!(#syntax_tree) + } +} + +impl VisitMut for AsyncAwaitRemoval { + fn visit_expr_mut(&mut self, node: &mut Expr) { + // Delegate to the default impl to visit nested expressions. + visit_mut::visit_expr_mut(self, node); + + match node { + Expr::Await(expr) => *node = (*expr.base).clone(), + + Expr::Async(expr) => { + let inner = &expr.block; + let sync_expr = if inner.stmts.len() == 1 { + // remove useless braces when there is only one statement + let stmt = &inner.stmts.get(0).unwrap(); + // convert statement to Expr + parse_quote!(#stmt) + } else { + Expr::Block(ExprBlock { + attrs: expr.attrs.clone(), + block: inner.clone(), + label: None, + }) + }; + *node = sync_expr; + } + _ => {} + } + } + + fn visit_item_mut(&mut self, i: &mut Item) { + // find generic parameter of Future and replace it with its Output type + if let Item::Fn(item_fn) = i { + let mut inputs: Vec<(String, PathSegment)> = vec![]; + + // generic params: <T:Future<Output=()>, F> + for param in &item_fn.sig.generics.params { + // generic param: T:Future<Output=()> + if let GenericParam::Type(type_param) = param { + let generic_type_name = type_param.ident.to_string(); + + // bound: Future<Output=()> + for bound in &type_param.bounds { + inputs.extend(search_trait_bound(&generic_type_name, bound)); + } + } + } + + if let Some(where_clause) = &item_fn.sig.generics.where_clause { + for predicate in &where_clause.predicates { + if let WherePredicate::Type(predicate_type) = predicate { + let generic_type_name = if let Type::Path(p) = &predicate_type.bounded_ty { + p.path.segments[0].ident.to_string() + } else { + panic!("Please submit an issue"); + }; + + for bound in &predicate_type.bounds { + inputs.extend(search_trait_bound(&generic_type_name, bound)); + } + } + } + } + + for (generic_type_name, path_seg) in &inputs { + ReplaceGenericType::replace_generic_type(i, generic_type_name, path_seg); + } + } + visit_item_mut(self, i); + } +} + +fn search_trait_bound( + generic_type_name: &str, + bound: &TypeParamBound, +) -> Vec<(String, PathSegment)> { + let mut inputs = vec![]; + + if let TypeParamBound::Trait(trait_bound) = bound { + let segment = &trait_bound.path.segments[trait_bound.path.segments.len() - 1]; + let name = segment.ident.to_string(); + if name.eq("Future") { + // match Future<Output=Type> + if let PathArguments::AngleBracketed(args) = &segment.arguments { + // binding: Output=Type + if let GenericArgument::Binding(binding) = &args.args[0] { + if let Type::Path(p) = &binding.ty { + inputs.push((generic_type_name.to_owned(), p.path.segments[0].clone())); + } + } + } + } + } + inputs +} |