diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/args.rs | 51 | ||||
| -rw-r--r-- | src/func.rs | 34 | ||||
| -rw-r--r-- | src/invoke.rs | 37 | ||||
| -rw-r--r-- | src/lib.rs | 65 | ||||
| -rw-r--r-- | src/select.rs | 77 |
5 files changed, 118 insertions, 146 deletions
diff --git a/src/args.rs b/src/args.rs deleted file mode 100644 index 63854b3..0000000 --- a/src/args.rs +++ /dev/null @@ -1,51 +0,0 @@ -use syn::parse::{Parse, ParseStream}; -use syn::{LitStr, Token}; - -/// Arguments for the `#[func]` attribute. -pub struct FuncArgs { - pub feature_name: String, -} - -impl Default for FuncArgs { - fn default() -> Self { - FuncArgs { - feature_name: "async".to_string(), - } - } -} - -impl Parse for FuncArgs { - fn parse(input: ParseStream) -> syn::Result<Self> { - if input.is_empty() { - return Ok(FuncArgs::default()); - } - - // Parse: "feature_name" - let feat_lit: LitStr = input.parse()?; - let feature_name = feat_lit.value(); - - Ok(FuncArgs { feature_name }) - } -} - -/// Input for the `invoke!` macro. -pub enum InvokeInput { - /// invoke!(expr) — feature name defaults to "async" - Default(proc_macro2::TokenStream), - /// invoke!("feat" => expr) — explicit feature name - Explicit(LitStr, proc_macro2::TokenStream), -} - -impl Parse for InvokeInput { - fn parse(input: ParseStream) -> syn::Result<Self> { - if input.peek(LitStr) { - let feat: LitStr = input.parse()?; - input.parse::<Token![=>]>()?; - let expr: proc_macro2::TokenStream = input.parse()?; - Ok(InvokeInput::Explicit(feat, expr)) - } else { - let expr: proc_macro2::TokenStream = input.parse()?; - Ok(InvokeInput::Default(expr)) - } - } -} diff --git a/src/func.rs b/src/func.rs index 2407f3e..c17ca28 100644 --- a/src/func.rs +++ b/src/func.rs @@ -1,8 +1,38 @@ +use crate::SynResult; use proc_macro::TokenStream; use quote::quote; -use syn::{ItemFn, parse_macro_input}; +use syn::{ + ItemFn, LitStr, + parse::{Parse, ParseStream}, + parse_macro_input, +}; -use crate::args::FuncArgs; +#[doc = include_str!("../doc/args/func.md")] +pub struct FuncArgs { + pub feature_name: String, +} + +impl Default for FuncArgs { + fn default() -> Self { + FuncArgs { + feature_name: "async".to_string(), + } + } +} + +impl Parse for FuncArgs { + fn parse(input: ParseStream) -> SynResult<Self> { + if input.is_empty() { + return Ok(FuncArgs::default()); + } + + // Parse: "feature_name" + let feat_lit: LitStr = input.parse()?; + let feature_name = feat_lit.value(); + + Ok(FuncArgs { feature_name }) + } +} pub(crate) fn func(attr: TokenStream, item: TokenStream) -> TokenStream { let args = parse_macro_input!(attr as FuncArgs); diff --git a/src/invoke.rs b/src/invoke.rs index 4e4a324..a137411 100644 --- a/src/invoke.rs +++ b/src/invoke.rs @@ -1,14 +1,41 @@ +use crate::SynResult; +use crate::TokenStream2; use proc_macro::TokenStream; use quote::quote; -use syn::parse_macro_input; +use syn::{ + LitStr, Token, + parse::{Parse, ParseStream}, + parse_macro_input, +}; -use crate::args::InvokeInput; +#[doc = include_str!("../doc/args/invoke.md")] +pub enum InvokeArgs { + /// invoke!(expr) — feature name defaults to "async" + Default(TokenStream2), + + /// invoke!("feat" => expr) — explicit feature name + Explicit(LitStr, TokenStream2), +} + +impl Parse for InvokeArgs { + fn parse(input: ParseStream) -> SynResult<Self> { + if input.peek(LitStr) { + let feat: LitStr = input.parse()?; + input.parse::<Token![=>]>()?; + let expr: TokenStream2 = input.parse()?; + Ok(InvokeArgs::Explicit(feat, expr)) + } else { + let expr: TokenStream2 = input.parse()?; + Ok(InvokeArgs::Default(expr)) + } + } +} pub(crate) fn invoke(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as InvokeInput); + let input = parse_macro_input!(input as InvokeArgs); let expanded = match input { - InvokeInput::Default(expr) => { + InvokeArgs::Default(expr) => { quote! {{ #[cfg(feature = "async")] { #expr.await } @@ -16,7 +43,7 @@ pub(crate) fn invoke(input: TokenStream) -> TokenStream { { #expr } }} } - InvokeInput::Explicit(feat, expr) => { + InvokeArgs::Explicit(feat, expr) => { let feat_name = &feat; quote! {{ #[cfg(feature = #feat_name)] @@ -1,75 +1,28 @@ +#![doc = include_str!("../doc/lib.md")] +#![deny(missing_docs)] + use proc_macro::TokenStream; -mod args; pub(crate) mod func; pub(crate) mod invoke; pub(crate) mod select; -/// Attribute macro that generates both a sync and an async version of a function, -/// gated by a Cargo feature flag. -/// -/// # Usage -/// -/// ```ignore -/// /// Doc comments are preserved -/// #[might_be_async::func] -/// pub fn my_function<T: Clone>(arg: T) -> ReturnType -/// where T: Debug -/// { -/// // body — written as a regular (non-async) function -/// } -/// ``` -/// -/// Expands to: -/// - `#[cfg(not(feature = "async"))] fn my_function(...)` — sync version -/// - `#[cfg(feature = "async")] async fn my_function(...)` — async version -/// -/// An explicit feature name can be provided: -/// -/// ```ignore -/// #[might_be_async::func("tokio_rt")] -/// pub fn my_function() { ... } -/// ``` +pub(crate) use proc_macro2::TokenStream as TokenStream2; +pub(crate) use syn::Result as SynResult; + +#[doc = include_str!("../doc/func.md")] #[proc_macro_attribute] pub fn func(attr: TokenStream, item: TokenStream) -> TokenStream { func::func(attr, item) } -/// Wraps a call expression, adding `.await` when the async feature is enabled. -/// -/// # Usage -/// -/// ```ignore -/// // Default feature name ("async"): -/// let result = might_be_async::invoke!(some_async_fn(args)); -/// -/// // Explicit feature name: -/// let result = might_be_async::invoke!("tokio_rt" => some_async_fn(args)); -/// ``` +#[doc = include_str!("../doc/invoke.md")] #[proc_macro] pub fn invoke(input: TokenStream) -> TokenStream { invoke::invoke(input) } -/// Select between sync and async expressions based on a Cargo feature flag. -/// -/// # Usage -/// -/// ## Explicit mode (with feature names) -/// -/// ```ignore -/// select!["async" => expr_async().await, "sync" => expr_sync()]; -/// select!["async" => { expr_async().await }, "sync" => { expr_sync() }]; -/// select!["async" => expr_async().await, ! => expr_sync()]; -/// select![! => expr_async().await, "sync" => expr_sync()]; -/// ``` -/// -/// ## Implicit mode (auto-detect `.await`) -/// -/// ```ignore -/// select![expr_async().await, expr_sync()]; -/// select![{ expr_async().await }, { expr_sync() }]; -/// ``` +#[doc = include_str!("../doc/select.md")] #[proc_macro] pub fn select(input: TokenStream) -> TokenStream { select::select(input) diff --git a/src/select.rs b/src/select.rs index d20d2bc..f64944b 100644 --- a/src/select.rs +++ b/src/select.rs @@ -1,50 +1,55 @@ +use crate::SynResult; +use crate::TokenStream2; use proc_macro::TokenStream; -use proc_macro2::{Spacing, TokenStream as TokenStream2, TokenTree}; +use proc_macro2::{Spacing, TokenTree}; use quote::{ToTokens, quote}; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::{Expr, LitStr, Token, parse_macro_input}; -// ─── SelectArm ────────────────────────────────────────────────────────────────────────── - -/// A single arm inside `select!`. -pub enum SelectArm { +#[doc = include_str!("../doc/args/select_arm.md")] +pub enum SelectArmArgs { /// "feat_name" => expr Explicit { feat: LitStr, body: Expr }, + /// ! => expr Not { body: Expr }, + /// expr (no feature name — auto-detect by .await) Implicit { body: Expr }, } -impl Parse for SelectArm { - fn parse(input: ParseStream) -> syn::Result<Self> { +impl Parse for SelectArmArgs { + fn parse(input: ParseStream) -> SynResult<Self> { + // Try to parse an explicit feature name: "feature_name" => expr if input.peek(LitStr) { let feat: LitStr = input.parse()?; input.parse::<Token![=>]>()?; let body: Expr = input.parse()?; - Ok(SelectArm::Explicit { feat, body }) - } else if input.peek(Token![!]) { + Ok(SelectArmArgs::Explicit { feat, body }) + } + // Try to parse a negation arm: ! => expr + else if input.peek(Token![!]) { let _not: Token![!] = input.parse()?; input.parse::<Token![=>]>()?; let body: Expr = input.parse()?; - Ok(SelectArm::Not { body }) - } else { + Ok(SelectArmArgs::Not { body }) + } + // Otherwise treat as an implicit arm (auto-detect whether it contains .await) + else { let body: Expr = input.parse()?; - Ok(SelectArm::Implicit { body }) + Ok(SelectArmArgs::Implicit { body }) } } } -// ─── SelectInput ──────────────────────────────────────────────────────────────────────── - struct SelectInput { - arms: Vec<SelectArm>, + arms: Vec<SelectArmArgs>, } impl Parse for SelectInput { - fn parse(input: ParseStream) -> syn::Result<Self> { - let punctuated: Punctuated<SelectArm, Token![,]> = Punctuated::parse_terminated(input)?; + fn parse(input: ParseStream) -> SynResult<Self> { + let punctuated: Punctuated<SelectArmArgs, Token![,]> = Punctuated::parse_terminated(input)?; if punctuated.len() != 2 { return Err(syn::Error::new( @@ -53,13 +58,11 @@ impl Parse for SelectInput { )); } - let arms: Vec<SelectArm> = punctuated.into_iter().collect(); + let arms: Vec<SelectArmArgs> = punctuated.into_iter().collect(); Ok(SelectInput { arms }) } } -// ─── Select macro ─────────────────────────────────────────────────────────────────────── - pub(crate) fn select(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as SelectInput); let expanded = input.expand(); @@ -74,8 +77,8 @@ impl SelectInput { match (arm0, arm1) { // Both explicit — use cfg!() since no .await to worry about ( - SelectArm::Explicit { feat: f0, body: b0 }, - SelectArm::Explicit { feat: f1, body: b1 }, + SelectArmArgs::Explicit { feat: f0, body: b0 }, + SelectArmArgs::Explicit { feat: f1, body: b1 }, ) => { let f0_str = f0.value(); let f1_str = f1.value(); @@ -93,7 +96,7 @@ impl SelectInput { } // Explicit + Not — cfg!() safe (no .await) - (SelectArm::Explicit { feat, body }, SelectArm::Not { body: not_body }) => { + (SelectArmArgs::Explicit { feat, body }, SelectArmArgs::Not { body: not_body }) => { let feat_str = feat.value(); if has_not_prefix(&feat_str) { let inner = &feat_str[1..]; @@ -102,7 +105,9 @@ impl SelectInput { quote! { if cfg!(feature = #feat_str) { #body } else { #not_body } } } } - (SelectArm::Not { body: not_body }, SelectArm::Explicit { feat, body }) => { + + // Not + Explicit — cfg!() safe (no .await) + (SelectArmArgs::Not { body: not_body }, SelectArmArgs::Explicit { feat, body }) => { let feat_str = feat.value(); if has_not_prefix(&feat_str) { let inner = &feat_str[1..]; @@ -113,7 +118,10 @@ impl SelectInput { } // Explicit + Implicit — cfg!() safe (arms have no .await from this context) - (SelectArm::Explicit { feat, body }, SelectArm::Implicit { body: imp_body }) => { + ( + SelectArmArgs::Explicit { feat, body }, + SelectArmArgs::Implicit { body: imp_body }, + ) => { let feat_str = feat.value(); if has_not_prefix(&feat_str) { let inner = &feat_str[1..]; @@ -122,7 +130,12 @@ impl SelectInput { quote! { if cfg!(feature = #feat_str) { #body } else { #imp_body } } } } - (SelectArm::Implicit { body: imp_body }, SelectArm::Explicit { feat, body }) => { + + // Implicit + Explicit — cfg!() safe (arms have no .await from this context) + ( + SelectArmArgs::Implicit { body: imp_body }, + SelectArmArgs::Explicit { feat, body }, + ) => { let feat_str = feat.value(); if has_not_prefix(&feat_str) { let inner = &feat_str[1..]; @@ -133,7 +146,7 @@ impl SelectInput { } // Both implicit — use #[cfg] blocks to handle .await correctly - (SelectArm::Implicit { body: b0 }, SelectArm::Implicit { body: b1 }) => { + (SelectArmArgs::Implicit { body: b0 }, SelectArmArgs::Implicit { body: b1 }) => { let b0_has_await = token_stream_has_await(&b0.to_token_stream()); let b1_has_await = token_stream_has_await(&b1.to_token_stream()); @@ -152,15 +165,17 @@ impl SelectInput { } // Not + Implicit - (SelectArm::Not { body: not_body }, SelectArm::Implicit { body: imp_body }) => { + (SelectArmArgs::Not { body: not_body }, SelectArmArgs::Implicit { body: imp_body }) => { cfg_block("e! { #not_body }, "e! { #imp_body }) } - (SelectArm::Implicit { body: imp_body }, SelectArm::Not { body: not_body }) => { + + // Implicit + Not — use #[cfg] blocks to handle .await correctly + (SelectArmArgs::Implicit { body: imp_body }, SelectArmArgs::Not { body: not_body }) => { cfg_block("e! { #not_body }, "e! { #imp_body }) } // Two Not - (SelectArm::Not { body: b0 }, SelectArm::Not { body: b1 }) => { + (SelectArmArgs::Not { body: b0 }, SelectArmArgs::Not { body: b1 }) => { quote! { if cfg!(feature = "async") { #b0 } else { #b1 } } } } @@ -178,8 +193,6 @@ fn cfg_block(async_branch: &TokenStream2, sync_branch: &TokenStream2) -> TokenSt }} } -// ─── Helpers ───────────────────────────────────────────────────────────────────────────── - fn has_not_prefix(s: &str) -> bool { s.starts_with('!') } |
