aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-23 09:22:52 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-23 09:22:52 +0800
commit920db605f34883bd2742bb99f99ced2276f1a7f7 (patch)
treea47ed88a8462ab1a3e68c8cfa42c33bfbd471762
parent4fc27a60620272c6be56f3829268238e74db0a4d (diff)
14
-rw-r--r--doc/args/func.md0
-rw-r--r--doc/args/invoke.md0
-rw-r--r--doc/args/select_arm.md0
-rw-r--r--doc/func.md26
-rw-r--r--doc/invoke.md11
-rw-r--r--doc/lib.md0
-rw-r--r--doc/select.md19
-rw-r--r--src/args.rs51
-rw-r--r--src/func.rs34
-rw-r--r--src/invoke.rs37
-rw-r--r--src/lib.rs65
-rw-r--r--src/select.rs77
12 files changed, 174 insertions, 146 deletions
diff --git a/doc/args/func.md b/doc/args/func.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/doc/args/func.md
diff --git a/doc/args/invoke.md b/doc/args/invoke.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/doc/args/invoke.md
diff --git a/doc/args/select_arm.md b/doc/args/select_arm.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/doc/args/select_arm.md
diff --git a/doc/func.md b/doc/func.md
new file mode 100644
index 0000000..20ef710
--- /dev/null
+++ b/doc/func.md
@@ -0,0 +1,26 @@
+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() { ... }
+```
diff --git a/doc/invoke.md b/doc/invoke.md
new file mode 100644
index 0000000..2f583d8
--- /dev/null
+++ b/doc/invoke.md
@@ -0,0 +1,11 @@
+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));
+```
diff --git a/doc/lib.md b/doc/lib.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/doc/lib.md
diff --git a/doc/select.md b/doc/select.md
new file mode 100644
index 0000000..56fda1b
--- /dev/null
+++ b/doc/select.md
@@ -0,0 +1,19 @@
+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() }];
+```
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)]
diff --git a/src/lib.rs b/src/lib.rs
index 7d82100..b34b3e1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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(&quote! { #not_body }, &quote! { #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(&quote! { #not_body }, &quote! { #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('!')
}