async-generic-1.1.0/.cargo_vcs_info.json0000644000000001440000000000100135610ustar { "git": { "sha1": "a822ea837b38362cee0d14f06ad26a1d461866a4" }, "path_in_vcs": "macros" }async-generic-1.1.0/Cargo.toml0000644000000025600000000000100115630ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.70.0" name = "async-generic" version = "1.1.0" authors = ["Eric Scouten "] description = "Write code that can be both async and synchronous without duplicating it." homepage = "https://github.com/scouten/async-generic" documentation = "https://docs.rs/async-generic" readme = "README.md" keywords = [ "async", "generic", "futures", "macros", "proc_macro", ] license = "MIT OR Apache-2.0" repository = "https://github.com/scouten/async-generic" [package.metadata.cargo-udeps.ignore] development = [ "async-std", "async-trait", ] [lib] proc-macro = true [dependencies.proc-macro2] version = "1.0" [dependencies.quote] version = "1.0" [dependencies.syn] version = "2.0" features = [ "visit-mut", "full", ] [dev-dependencies.async-std] version = "1.0" features = ["attributes"] [dev-dependencies.async-trait] version = "0.1" async-generic-1.1.0/Cargo.toml.orig000064400000000000000000000014651046102023000152470ustar 00000000000000[package] name = "async-generic" version = "1.1.0" description = "Write code that can be both async and synchronous without duplicating it." authors = ["Eric Scouten "] license = "MIT OR Apache-2.0" documentation = "https://docs.rs/async-generic" homepage = "https://github.com/scouten/async-generic" repository = "https://github.com/scouten/async-generic" readme = "../README.md" keywords = ["async", "generic", "futures", "macros", "proc_macro"] edition = "2021" rust-version = "1.70.0" [lib] proc-macro = true [package.metadata.cargo-udeps.ignore] development = ["async-std", "async-trait"] [dependencies] proc-macro2 = "1.0" quote = "1.0" syn = { version = "2.0", features = ["visit-mut", "full"] } [dev-dependencies] async-std = { version = "1.0", features = ["attributes"] } async-trait = "0.1" async-generic-1.1.0/README.md000064400000000000000000000003201046102023000136240ustar 00000000000000# About this crate This crate is derived from the [`maybe-async` crate](https://github.com/fMeow/maybe-async-rs) as of September 2023. Many thanks to Guoli Lyu for their initial work on this problem space. async-generic-1.1.0/src/desugar_if_async.rs000064400000000000000000000040541046102023000170170ustar 00000000000000use proc_macro2::TokenStream; use quote::quote; use syn::{ parse_quote, visit_mut::{self, VisitMut}, Block, Expr, ExprBlock, File, }; pub struct DesugarIfAsync { pub is_async: bool, } impl DesugarIfAsync { pub fn desugar_if_async(&mut self, item: TokenStream) -> TokenStream { let mut syntax_tree: File = syn::parse(item.into()).unwrap(); self.visit_file_mut(&mut syntax_tree); quote!(#syntax_tree) } fn rewrite_if_async( &self, node: &mut Expr, then_branch: Block, else_branch: Option, expr_is_async: bool, ) { if expr_is_async == self.is_async { *node = Expr::Block(ExprBlock { attrs: vec![], label: None, block: then_branch, }); } else if let Some(else_expr) = else_branch { *node = else_expr; } else { *node = parse_quote! {{}}; } } } impl VisitMut for DesugarIfAsync { fn visit_expr_mut(&mut self, node: &mut Expr) { visit_mut::visit_expr_mut(self, node); if let Expr::If(expr_if) = &node { if let Expr::Path(ref var) = expr_if.cond.as_ref() { if let Some(first_segment) = var.path.segments.first() { if var.path.segments.len() == 1 { let name = first_segment.ident.to_string(); let then_branch = expr_if.then_branch.clone(); let else_branch = expr_if.else_branch.as_ref().map(|eb| *eb.1.clone()); match name.as_str() { "_async" => { self.rewrite_if_async(node, then_branch, else_branch, true); } "_sync" => { self.rewrite_if_async(node, then_branch, else_branch, false); } _ => {} } } } } } } } async-generic-1.1.0/src/lib.rs000064400000000000000000000073661046102023000142710ustar 00000000000000#![deny(warnings)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg, doc_cfg_hide))] use proc_macro::{TokenStream, TokenTree}; use proc_macro2::{Ident, Span, TokenStream as TokenStream2, TokenTree as TokenTree2}; use quote::quote; use syn::{ parse::{Parse, ParseStream, Result}, parse_macro_input, Attribute, Error, ItemFn, Token, }; use crate::desugar_if_async::DesugarIfAsync; mod desugar_if_async; fn convert_sync_async( input: &mut Item, is_async: bool, alt_sig: Option, ) -> TokenStream2 { let item = &mut input.0; if is_async { item.sig.asyncness = Some(Token![async](Span::call_site())); item.sig.ident = Ident::new(&format!("{}_async", item.sig.ident), Span::call_site()); } let tokens = quote!(#item); let tokens = if let Some(alt_sig) = alt_sig { let mut found_fn = false; let mut found_args = false; let old_tokens = tokens.into_iter().map(|token| match &token { TokenTree2::Ident(i) => { found_fn = found_fn || &i.to_string() == "fn"; token } TokenTree2::Group(g) => { if found_fn && !found_args && g.delimiter() == proc_macro2::Delimiter::Parenthesis { found_args = true; return TokenTree2::Group(proc_macro2::Group::new( proc_macro2::Delimiter::Parenthesis, alt_sig.clone().into(), )); } token } _ => token, }); TokenStream2::from_iter(old_tokens) } else { tokens }; let mut dia = DesugarIfAsync { is_async }; dia.desugar_if_async(tokens) } #[proc_macro_attribute] pub fn async_generic(args: TokenStream, input: TokenStream) -> TokenStream { let mut async_signature: Option = None; if !args.to_string().is_empty() { let mut atokens = args.into_iter(); loop { if let Some(TokenTree::Ident(i)) = atokens.next() { if i.to_string() != *"async_signature" { break; } } else { break; } if let Some(TokenTree::Group(g)) = atokens.next() { if atokens.next().is_none() && g.delimiter() == proc_macro::Delimiter::Parenthesis { async_signature = Some(g.stream()); } } } if async_signature.is_none() { return syn::Error::new( Span::call_site(), "async_generic can only take a async_signature argument", ) .to_compile_error() .into(); } }; let input_clone = input.clone(); let mut item = parse_macro_input!(input_clone as Item); let sync_tokens = convert_sync_async(&mut item, false, None); let mut item = parse_macro_input!(input as Item); let async_tokens = convert_sync_async(&mut item, true, async_signature); let mut tokens = sync_tokens; tokens.extend(async_tokens); tokens.into() } struct Item(ItemFn); impl Parse for Item { fn parse(input: ParseStream) -> Result { let attrs = input.call(Attribute::parse_outer)?; if let Ok(mut item) = input.parse::() { item.attrs = attrs; if item.sig.asyncness.is_some() { return Err(Error::new( Span::call_site(), "an async_generic function should not be declared as async", )); } Ok(Item(item)) } else { Err(Error::new( Span::call_site(), "async_generic can only be used with functions", )) } } }