Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bisync/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
39 changes: 37 additions & 2 deletions bisync/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
//! }
Expand All @@ -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::*;
//! }
Expand All @@ -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 {
Expand All @@ -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<String, ()> { Ok("dummy sync".to_string()) }
//! async fn http_get_async(_url: &str) -> Result<String, ()> { Ok("dummy async".to_string()) }
//!
//! #[only_sync]
//! fn baz() -> String {
//! ureq::get("https://example.com")
Expand All @@ -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
Expand Down Expand Up @@ -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;
3 changes: 3 additions & 0 deletions bisync_macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
88 changes: 83 additions & 5 deletions bisync_macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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<TokenTree> = item.into_iter().collect();
let mut i = 0;
Expand Down Expand Up @@ -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<Self> {
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()
}