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}