-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: use dlc-macros for async dlc-manager
- Loading branch information
Showing
9 changed files
with
324 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
[package] | ||
name = "dlc-macros" | ||
authors = ["benny b <[email protected]>"] | ||
version = "0.1.0" | ||
edition = "2018" | ||
description = "Procedural macros for writing optionally asynchronous code for traits and functions." | ||
homepage = "https://github.com/p2pderivatives/rust-dlc" | ||
license-file = "../LICENSE" | ||
repository = "https://github.com/p2pderivatives/rust-dlc/tree/master/dlc-macros" | ||
|
||
[dependencies] | ||
proc-macro2 = "1.0.87" | ||
quote = "1.0.37" | ||
syn = { version = "2.0.79", features = ["full", "extra-traits"] } | ||
tokio = { version = "1.40.0", features = ["macros", "test-util"] } | ||
|
||
[lib] | ||
proc-macro = true | ||
|
||
[dev-dependencies] | ||
trybuild = "1.0.99" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
//! Procedural macros for writing optionally asynchronous code for traits and functions. | ||
//! Inspires by [`bdk-macros`](https://github.com/bitcoindevkit/bdk/blob/v0.29.0/macros/src/lib.rs) | ||
#![crate_name = "dlc_macros"] | ||
// Coding conventions | ||
#![forbid(unsafe_code)] | ||
#![deny(non_upper_case_globals)] | ||
#![deny(non_camel_case_types)] | ||
#![deny(non_snake_case)] | ||
#![deny(unused_mut)] | ||
#![deny(dead_code)] | ||
#![deny(unused_imports)] | ||
#![deny(missing_docs)] | ||
|
||
use proc_macro::TokenStream; | ||
use quote::quote; | ||
use syn::spanned::Spanned; | ||
use syn::{ | ||
parse_macro_input, Attribute, Expr, ImplItem, Item, ItemFn, ItemImpl, ItemTrait, TraitItem, | ||
}; | ||
|
||
// Check if the function attributes contains #[maybe_async]. | ||
// For conditional compilation of member functions. | ||
fn is_maybe_async_attr(attr: &Attribute) -> bool { | ||
// Check if the attribute path is exactly "maybe_async" | ||
if attr.path().is_ident("maybe_async") { | ||
return true; | ||
} | ||
|
||
// Check if the attribute path is of the form "module::maybe_async" | ||
if let Some(last_segment) = attr.path().segments.last() { | ||
return last_segment.ident == "maybe_async"; | ||
} | ||
false | ||
} | ||
|
||
// Add async to a standalone function. | ||
fn add_async_to_fn(mut func: ItemFn) -> TokenStream { | ||
// For standalone functions, we'll always make them potentially async | ||
let sync_version = func.clone(); | ||
func.sig.asyncness = Some(syn::Token)); | ||
|
||
quote! { | ||
#[cfg(not(feature = "async"))] | ||
#sync_version | ||
|
||
#[cfg(feature = "async")] | ||
#func | ||
} | ||
.into() | ||
} | ||
|
||
// Adds the `async_trait` macro to the trait and appends async to all of | ||
// the member functions marked `#[maybe_async]`. | ||
fn add_async_to_trait(mut trait_item: ItemTrait) -> TokenStream { | ||
// Check if the trait itself has the `#[maybe_async]` attribute | ||
let is_trait_async = trait_item.attrs.iter().any(is_maybe_async_attr); | ||
trait_item.attrs.retain(|attr| !is_maybe_async_attr(attr)); // Remove the attribute from the trait | ||
|
||
let mut async_trait_item = trait_item.clone(); | ||
|
||
for item in &mut async_trait_item.items { | ||
if let TraitItem::Fn(method) = item { | ||
if is_trait_async || method.attrs.iter().any(is_maybe_async_attr) { | ||
method.sig.asyncness = Some(syn::Token)); | ||
method.attrs.retain(is_maybe_async_attr); | ||
} | ||
} | ||
} | ||
|
||
quote! { | ||
#[cfg(not(feature = "async"))] | ||
#trait_item | ||
|
||
#[cfg(feature = "async")] | ||
#[async_trait::async_trait] | ||
#async_trait_item | ||
} | ||
.into() | ||
} | ||
|
||
// Adds async to a member of a struct implementation method. | ||
fn add_async_to_impl(impl_item: ItemImpl) -> TokenStream { | ||
let mut async_impl_item = impl_item.clone(); | ||
|
||
for item in &mut async_impl_item.items { | ||
if let ImplItem::Fn(method) = item { | ||
if method.attrs.iter().any(is_maybe_async_attr) { | ||
method.sig.asyncness = Some(syn::Token)); | ||
method.attrs.retain(|attr| !is_maybe_async_attr(attr)); | ||
} | ||
} | ||
} | ||
|
||
quote! { | ||
#[cfg(not(feature = "async"))] | ||
#impl_item | ||
|
||
#[cfg(feature = "async")] | ||
#[async_trait::async_trait] | ||
#async_impl_item | ||
} | ||
.into() | ||
} | ||
|
||
/// Makes a method or every method of a trait `async`, if the `async` feature is enabled. | ||
/// | ||
/// Requires the `async-trait` crate as a dependency whenever this attribute is used on a trait | ||
/// definition or trait implementation. | ||
#[proc_macro_attribute] | ||
pub fn maybe_async(_attr: TokenStream, item: TokenStream) -> TokenStream { | ||
let input = parse_macro_input!(item as Item); | ||
|
||
match input { | ||
Item::Fn(func) => add_async_to_fn(func), | ||
Item::Trait(trait_item) => add_async_to_trait(trait_item), | ||
Item::Impl(impl_item) => add_async_to_impl(impl_item), | ||
Item::Verbatim(verbatim) => { | ||
// This case handles unexpected verbatim content, like doc comments | ||
quote! { | ||
#verbatim | ||
} | ||
.into() | ||
} | ||
other => { | ||
let item_type = format!("{:?}", other); | ||
let error_msg = format!( | ||
"#[maybe_async] can only be used on functions, traits, or impl blocks, not on: {}", | ||
item_type | ||
); | ||
quote! { | ||
compile_error!(#error_msg); | ||
} | ||
.into() | ||
} | ||
} | ||
} | ||
|
||
/// Awaits, if the `async` feature is enabled. | ||
#[proc_macro] | ||
pub fn maybe_await(input: TokenStream) -> TokenStream { | ||
let expr = parse_macro_input!(input as Expr); | ||
let quoted = quote! { | ||
{ | ||
#[cfg(not(feature = "async"))] | ||
{ | ||
#expr | ||
} | ||
|
||
#[cfg(feature = "async")] | ||
{ | ||
#expr.await | ||
} | ||
} | ||
}; | ||
|
||
quoted.into() | ||
} | ||
|
||
/// Awaits, if the `async` feature is enabled, uses `tokio::Runtime::block_on()` otherwise | ||
/// | ||
/// Requires the `tokio` crate as a dependecy with `rt-core` or `rt-threaded` to build. | ||
#[proc_macro] | ||
pub fn await_or_block(expr: TokenStream) -> TokenStream { | ||
let expr = parse_macro_input!(expr as Expr); | ||
let quoted = quote! { | ||
{ | ||
#[cfg(not(feature = "async"))] | ||
{ | ||
tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(#expr) | ||
} | ||
|
||
#[cfg(feature = "async")] | ||
{ | ||
#expr.await | ||
} | ||
} | ||
}; | ||
|
||
quoted.into() | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
#[test] | ||
fn test_async_trait() { | ||
let t = trybuild::TestCases::new(); | ||
t.pass("tests/sync.rs"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
use dlc_macros::*; | ||
|
||
#[maybe_async] | ||
trait TestTrait { | ||
fn test_method(&self) -> Result<(), std::io::Error>; | ||
} | ||
|
||
struct TestStruct; | ||
|
||
#[maybe_async] | ||
impl TestTrait for TestStruct { | ||
fn test_method(&self) -> Result<(), std::io::Error> { | ||
Ok(()) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_sync_implementation() { | ||
let test_struct = TestStruct; | ||
let test = maybe_await!(test_struct.test_method()); | ||
assert!(test.is_ok()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
use dlc_macros::maybe_async; | ||
|
||
/// Documentation | ||
#[maybe_async] | ||
pub trait Example { | ||
/// Documentation | ||
#[maybe_async] | ||
fn example_fn(&self); | ||
} | ||
|
||
struct Test; | ||
|
||
impl Example for Test { | ||
fn example_fn(&self) {} | ||
} | ||
|
||
fn main() { | ||
let test = Test; | ||
test.example_fn(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.