remove_async_await/
lib.rs

1//! # remove-async-await
2//!
3//! A procedural macro to make an async function blocking by removing async and awaits. Useful for crates with practically identical blocking and async implementations, aside from having to use `.await`
4//! on some function calls.
5//!
6//! ## Adding as a dependency
7//!
8//! It is recommended to point the dependency to 1.0 so you get any bug fixes I have to make:
9//!
10//! ```toml
11//! [dependencies]
12//! remove-async-await = "1.0"
13//! ```
14//!
15//! ## Example
16//!
17//! This example assumes you want to keep your async API behind an optional feature called `async`.
18//!
19//! ```rs
20//! #[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)]
21//! async fn get_string() -> String {
22//!     "hello world".to_owned()
23//! }
24//!
25//! #[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)]
26//! pub async fn print() {
27//!     let string = get_string().await;
28//!     println!("{}", string);
29//! }
30//! ```
31//!
32//! In this example, if the `async` feature is not used, it would expand to this:
33//!
34//! ```rs
35//! fn get_string() -> String {
36//!     "hello world".to_owned()
37//! }
38//!
39//! pub fn print() {
40//!     let string = get_string();
41//!     println!("{}", string);
42//! }
43//! ```
44//!
45//! However, if the `async` feature is used, the code will be unaffected.
46//!
47//! You can find more examples in the [`tests/` directory](https://github.com/naturecodevoid/remove-async-await/tree/main/tests).
48//!
49//! ## `remove_async_await_string`
50//!
51//! There are 2 macros this library provides:
52//!
53//! 1. `remove_async_await`: The one you should almost always use. Uses `syn` to parse rust code and remove async from functions and await from expressions. Currently, it can only take a function as an
54//!    input.
55//! 2. `remove_async_await_string`: You should only use this one if `remove_async_await` doesn't work for your use case. This is the "dumb macro"; it
56//! [literally just removes all occurrences of `async` and `.await` from the string representation of the input](https://github.com/naturecodevoid/remove-async-await/blob/main/src/lib.rs#L192). This
57//! means that while it might work with things other than functions, **you shouldn't use it because if a function or variable name contains "async" or ".await", your code will break.**
58//!
59//! ## Known issues
60//!
61//! Here is a list of known issues/limitations that I probably won't fix (PRs are welcome!):
62//!
63//! -   **Issue**: `.await` is not removed when calling a macro
64//!
65//!     **Workarounds**:
66//!
67//!     -   Move the expression using `.await` to a local variable.
68//!
69//!         Example:
70//!
71//!         ```rs
72//!         #[remove_async_await::remove_async_await)]
73//!         async fn issue() {
74//!             println!("{}", get_string().await); // `.await` will not be removed
75//!         }
76//!
77//!         #[remove_async_await::remove_async_await)]
78//!         async fn workaround() {
79//!             let string = get_string().await; // `.await` **will** be removed
80//!             println!("{}", string);
81//!         }
82//!         ```
83//!
84//!     -   Use [`remove_async_await_string`](#remove_async_await_string) (read docs for more info, such as potential bad side effects)
85//!
86//!         Example:
87//!
88//!         ```rs
89//!         #[remove_async_await::remove_async_await)]
90//!         async fn issue() {
91//!             println!("{}", get_string().await); // `.await` will not be removed
92//!         }
93//!
94//!         #[remove_async_await::remove_async_await_string)]
95//!         async fn workaround() {
96//!             println!("{}", get_string().await); // `.await` **will** be removed
97//!         }
98//!         ```
99//!
100//! If you want me to add an issue to this list (or fix the issue), please [create a GitHub issue](https://github.com/naturecodevoid/remove-async-await/issues/new)!
101
102use proc_macro::TokenStream;
103use quote::{quote, ToTokens};
104use syn::{
105    fold::{self, Fold},
106    Expr, ExprBlock, ItemFn, TraitItemMethod,
107};
108
109struct RemoveAsyncAwait;
110
111impl Fold for RemoveAsyncAwait {
112    fn fold_item_fn(&mut self, mut i: ItemFn) -> ItemFn {
113        // remove async functions
114        i.sig.asyncness = None;
115        fold::fold_item_fn(self, i)
116    }
117
118    fn fold_trait_item_method(&mut self, mut i: TraitItemMethod) -> TraitItemMethod {
119        // remove async trait methods
120        i.sig.asyncness = None;
121        fold::fold_trait_item_method(self, i)
122    }
123
124    fn fold_expr(&mut self, e: Expr) -> Expr {
125        match e {
126            // remove await
127            Expr::Await(e) => self.fold_expr(*e.base),
128            // remove async blocks
129            Expr::Async(e) => self.fold_expr(Expr::Block(ExprBlock {
130                attrs: e.attrs,
131                label: None,
132                block: e.block,
133            })),
134            _ => fold::fold_expr(self, e),
135        }
136    }
137}
138
139#[proc_macro_attribute]
140/// Please see crate level documentation for usage and examples.
141pub fn remove_async_await(_args: TokenStream, input: TokenStream) -> TokenStream {
142    #[cfg(feature = "debug")]
143    {
144        println!();
145        println!("Input: {}", input.to_string());
146    }
147
148    macro_rules! to_token_stream {
149        ($input: expr) => {{
150            #[cfg(feature = "debug")]
151            {
152                println!();
153                println!("Parsed input: {:#?}", input);
154                println!();
155            }
156            TokenStream::from($input.to_token_stream())
157        }};
158    }
159
160    // Attempt to parse as ItemFn, then TraitItemMethod, and finally fail
161    let output = match syn::parse::<ItemFn>(input.clone()) {
162        Ok(item) => to_token_stream!(RemoveAsyncAwait.fold_item_fn(item)),
163        Err(_) => match syn::parse::<TraitItemMethod>(input.clone()) {
164            Ok(item) => to_token_stream!(RemoveAsyncAwait.fold_trait_item_method(item)),
165            Err(_) => TokenStream::from(quote! {
166                compile_error!("remove_async_await currently only supports functions and trait methods. if you are using it on a supported type, parsing probably failed; please ensure the input is valid Rust.")
167            }),
168        },
169    };
170
171    #[cfg(feature = "debug")]
172    {
173        println!();
174        println!("Output: {}", output.to_string());
175        println!();
176    }
177
178    output
179}
180
181#[proc_macro_attribute]
182/// Please see crate level documentation for usage and examples. (Specifically the `remove_async_await_string` section)
183pub fn remove_async_await_string(_args: TokenStream, input: TokenStream) -> TokenStream {
184    #[cfg(feature = "debug")]
185    {
186        println!();
187        println!("Input: {}", input.to_string());
188    }
189
190    let input = input.to_string();
191
192    let output = input.replace("async", "").replace(".await", "");
193    let output: TokenStream = output.parse().unwrap();
194
195    #[cfg(feature = "debug")]
196    {
197        println!();
198        println!("Output: {}", output.to_string());
199        println!();
200    }
201
202    output
203}