diff --git a/bisync/Cargo.toml b/bisync/Cargo.toml index 29b0316..ac446a2 100644 --- a/bisync/Cargo.toml +++ b/bisync/Cargo.toml @@ -9,4 +9,4 @@ repository = "https://github.com/JM4ier/bisync" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bisync_macros = "0.2.3" +bisync_macros = { path = "../bisync_macros" } diff --git a/bisync/src/lib.rs b/bisync/src/lib.rs index 960b277..23d1efc 100644 --- a/bisync/src/lib.rs +++ b/bisync/src/lib.rs @@ -9,6 +9,8 @@ //! #[path = "."] //! pub mod asynchronous { //! use bisync::asynchronous::*; +//! // If you use the `suffix!` macro, it needs to be in scope: +//! // use bisync::suffix; //! mod inner; //! pub use inner::*; //! } @@ -17,6 +19,8 @@ //! #[path = "."] //! pub mod blocking { //! use bisync::synchronous::*; +//! // If you use the `suffix!` macro, it needs to be in scope: +//! // use bisync::suffix; //! mod inner; //! pub use inner::*; //! } @@ -28,7 +32,7 @@ //! // inner.rs //! //! // these are all the available definitions: -//! use super::{bisync, only_sync, only_async, SYNC, ASYNC}; +//! use super::{bisync, only_sync, only_async, SYNC, ASYNC, suffix}; //! //! #[bisync] //! pub async fn foo() -> String { @@ -46,8 +50,23 @@ //! } //! //! baz().await +//! +//! // Example usage of the suffix macro assuming http_get is the base method +//! // and your crate has "async" and "blocking" features. +//! // The suffix macro uses these features for conditional compilation. +//! let _response = suffix!("_async", http_get("https://example.com").await.unwrap()); +//! // This would expand to: +//! // #[cfg(feature = "async")] { http_get_async("https://example.com").await.unwrap() } +//! // #[cfg(all(feature = "blocking", not(feature = "async")))] { http_get("https://example.com").await.unwrap() } +//! // Then #[bisync]'s internal_strip_async would remove .await for the blocking path. //! } -//! +//! +//! +//! +//! // Dummy function for example +//! async fn http_get(_url: &str) -> Result { Ok("dummy sync".to_string()) } +//! async fn http_get_async(_url: &str) -> Result { Ok("dummy async".to_string()) } +//! //! #[only_sync] //! fn baz() -> String { //! ureq::get("https://example.com") @@ -72,6 +91,20 @@ //! As you can see, we prevent duplicate definitions of `foo` and `bar` because they get generated twice, //! once in synchronous form, and once in asynchronous form. //! +//! The `suffix!` macro allows conditional compilation of method calls based on features of the *crate using bisync*, +//! typically "async" and "blocking". It suffixes the method call immediately preceding an `.await`. +//! +//! **Using `suffix!` Macro:** +//! +//! The `suffix!` macro expects the *user's crate* to have features like "async" and "blocking" defined. +//! It's called like this: `suffix!("_async", expression_containing_method().await)`. +//! +//! - If `feature = "async"` is enabled: `expression_containing_method_async().await` +//! - If `feature = "blocking"` (and not "async") is enabled: `expression_containing_method().await` +//! +//! The `#[bisync]` attribute will then further process this: `internal_strip_async` will remove the `.await` +//! from the blocking path. + #![no_std] /// The definitions to use for the synchronous code variation @@ -102,3 +135,5 @@ pub mod asynchronous { pub const ASYNC: bool = true; } +// Re-export the `suffix` procedural macro so users can call `bisync::suffix!` +pub use ::bisync_macros::suffix; diff --git a/bisync_macros/Cargo.toml b/bisync_macros/Cargo.toml index 30862af..09cf40f 100644 --- a/bisync_macros/Cargo.toml +++ b/bisync_macros/Cargo.toml @@ -10,3 +10,6 @@ repository = "https://github.com/JM4ier/bisync" proc-macro = true [dependencies] +syn = { version = "2.0", features = ["full", "visit-mut"] } +quote = "1.0" +proc-macro2 = "1.0" diff --git a/bisync_macros/src/lib.rs b/bisync_macros/src/lib.rs index 6bbd70d..0563ce7 100644 --- a/bisync_macros/src/lib.rs +++ b/bisync_macros/src/lib.rs @@ -1,6 +1,17 @@ //! Do not use this crate directly. -use proc_macro::{Group, TokenStream}; +use proc_macro::{Group, TokenStream, TokenTree}; + +use quote::quote; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, + visit_mut::{self, VisitMut}, + Expr, ExprAwait, Ident, LitStr, Token, +}; + +// Original bisync attribute macros +// ================================ #[proc_macro_attribute] pub fn internal_noop(_attr: TokenStream, item: TokenStream) -> TokenStream { @@ -14,8 +25,6 @@ pub fn internal_delete(_attr: TokenStream, _item: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn internal_strip_async(attr: TokenStream, item: TokenStream) -> TokenStream { - use proc_macro::TokenTree; - let mut new = Vec::new(); let tokens: Vec = item.into_iter().collect(); let mut i = 0; @@ -48,10 +57,79 @@ pub fn internal_strip_async(attr: TokenStream, item: TokenStream) -> TokenStream new.push(tokens[i].clone()); } } - TokenTree::Literal(..) => new.push(tokens[i].clone()), + TokenTree::Literal(..) => { + new.push(tokens[i].clone()); + } } i += 1; } - new.into_iter().collect() } + +// Function-like Proc-Macro: suffix +// ==================================== + +struct SuffixMacroInput { + suffix_str: LitStr, + _comma: Token![,], + expr: Expr, +} + +impl Parse for SuffixMacroInput { + fn parse(input: ParseStream) -> syn::Result { + Ok(SuffixMacroInput { + suffix_str: input.parse()?, + _comma: input.parse()?, + expr: input.parse()?, + }) + } +} + +struct AwaitMethodSuffixer<'a> { + suffix: &'a str, +} + +impl<'a> VisitMut for AwaitMethodSuffixer<'a> { + fn visit_expr_await_mut(&mut self, expr_await: &mut ExprAwait) { + if let Expr::MethodCall(method_call) = &mut *expr_await.base { + let original_method_ident = method_call.method.clone(); + let new_method_name_str = + format!("{}{}", original_method_ident.to_string(), self.suffix); + method_call.method = Ident::new(&new_method_name_str, original_method_ident.span()); + } + visit_mut::visit_expr_await_mut(self, expr_await); + } + + fn visit_expr_mut(&mut self, expr: &mut Expr) { + visit_mut::visit_expr_mut(self, expr); + } +} + +#[proc_macro] +pub fn suffix(input: TokenStream) -> TokenStream { + let parsed_input = parse_macro_input!(input as SuffixMacroInput); + let suffix_value = parsed_input.suffix_str.value(); + + let mut async_expr_transformed = parsed_input.expr.clone(); + let blocking_expr_original = parsed_input.expr; + + let mut suffixer = AwaitMethodSuffixer { + suffix: &suffix_value, + }; + suffixer.visit_expr_mut(&mut async_expr_transformed); + + let output = quote! { + { + #[cfg(feature = "async")] + { + #async_expr_transformed + } + #[cfg(all(feature = "blocking", not(feature = "async")))] + { + #blocking_expr_original + } + } + }; + + output.into() +}