aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-20 01:11:44 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-20 01:11:44 +0800
commit779d7f6b3b5a22406c61d94efc45ad4422e50e0d (patch)
tree9a346a0aa1d8323cc657ff86cb59a57e9a01d959 /mingling_macros
parent1f583f625a7d541dc7d47a8136b31d29a5ed5441 (diff)
feat(extensions): add routeify extension macro for error routing
Diffstat (limited to 'mingling_macros')
-rw-r--r--mingling_macros/src/extensions.rs46
-rw-r--r--mingling_macros/src/extensions/routeify.rs39
-rw-r--r--mingling_macros/src/lib.rs21
3 files changed, 66 insertions, 40 deletions
diff --git a/mingling_macros/src/extensions.rs b/mingling_macros/src/extensions.rs
index 97ef636..aff20e2 100644
--- a/mingling_macros/src/extensions.rs
+++ b/mingling_macros/src/extensions.rs
@@ -3,38 +3,18 @@
//! This module provides a way for attribute macros like `#[chain]`, `#[renderer]`,
//! `#[help]`, and `#[completion]` to accept extension identifiers that are
//! applied as outer attributes before the bare macro.
-//!
-//! # How it works
-//!
-//! When a user writes `#[chain(routeify, other_ext)]`, the extension point mechanism
-//! detects `routeify` and `other_ext` as extension identifiers, and generates a
-//! re-dispatch token stream equivalent to:
-//!
-//! ```ignore
-//! #[other_ext]
-//! #[routeify]
-//! #[chain]
-//! fn handle(...) { ... }
-//! ```
-//!
-//! The original bare macro (`#[chain]` without extensions) sees no arguments
-//! and proceeds as normal — the extensions have already been stripped.
use proc_macro::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{Ident, Token};
+/// Extension: `#[routeify]` — transforms `expr?` into `route!(expr)`.
+#[cfg(feature = "extra_macros")]
+pub(crate) mod routeify;
+
/// Parsed extensions from an attribute macro like `#[chain(routeify, other_ext)]`.
-///
-/// For macros with no fixed arguments (chain, renderer, help):
-/// - `exts` contains all parsed extension identifiers
-/// - `fixed` is empty
-///
-/// For macros with fixed arguments (completion):
-/// - Use `parse_completion_attr_extensions` instead.
pub(crate) struct Extensions {
- /// Extension identifiers (e.g., `routeify`, `other_ext`)
pub(crate) exts: Vec<Ident>,
}
@@ -53,23 +33,16 @@ impl Parse for Extensions {
}
/// Parsed extensions for `#[completion(EntryType, routeify, ...)]`.
-///
-/// The first argument is the entry type path, the rest are extension identifiers.
#[cfg(feature = "comp")]
pub(crate) struct CompletionExt {
- /// The entry type path (first argument, e.g., `HelloEntry` or `crate::Entry`)
pub(crate) entry_type: proc_macro2::TokenStream,
- /// Extension identifiers (e.g., `routeify`, `other_ext`)
pub(crate) exts: Vec<Ident>,
}
#[cfg(feature = "comp")]
impl Parse for CompletionExt {
fn parse(input: ParseStream) -> syn::Result<Self> {
- // Parse the first argument as the entry type path
let entry_type: proc_macro2::TokenStream = input.parse()?;
-
- // Parse remaining arguments as extensions
let mut exts = Vec::new();
while !input.is_empty() {
let _ = input.parse::<Token![,]>();
@@ -79,16 +52,12 @@ impl Parse for CompletionExt {
let ident: Ident = input.parse()?;
exts.push(ident);
}
-
Ok(CompletionExt { entry_type, exts })
}
}
/// Generates a re-dispatch token stream for attribute macros that take **no fixed arguments**
/// (chain, renderer, help).
-///
-/// If `attr` contains extensions, the output is `#[ext1] #[ext2] ...[#bare_attr] #item`.
-/// If `attr` is empty or contains no extensions, returns `None`.
pub(crate) fn try_redispatch_simple(
attr: TokenStream,
item: &TokenStream,
@@ -109,7 +78,7 @@ pub(crate) fn try_redispatch_simple(
Some(
quote! {
- #(#exts)*
+ #(#[#exts])*
#[#bare]
#item
}
@@ -118,9 +87,6 @@ pub(crate) fn try_redispatch_simple(
}
/// Generates a re-dispatch token stream for `#[completion(EntryType, ...)]`.
-///
-/// If extensions are found, the output is `#[ext1] #[ext2] #[completion(EntryType)] #item`.
-/// If no extensions, returns `None`.
#[cfg(feature = "comp")]
pub(crate) fn try_redispatch_completion(
attr: TokenStream,
@@ -141,7 +107,7 @@ pub(crate) fn try_redispatch_completion(
Some(
quote! {
- #(#exts)*
+ #(#[#exts])*
#[::mingling::macros::completion(#entry_type)]
#item
}
diff --git a/mingling_macros/src/extensions/routeify.rs b/mingling_macros/src/extensions/routeify.rs
new file mode 100644
index 0000000..f011fb9
--- /dev/null
+++ b/mingling_macros/src/extensions/routeify.rs
@@ -0,0 +1,39 @@
+//! The `#[routeify]` extension — transforms `expr?` into `route!(expr)`.
+//!
+//! Designed as an extension for the Mingling attribute macro system, intended
+//! to be used with `#[chain(routeify)]` or standalone as `#[routeify]`.
+//!
+//! # How it works
+//!
+//! The macro parses the function AST and replaces every `Expr::Try` node with an
+//! equivalent `route!(expr)` invocation.
+
+use proc_macro::TokenStream;
+use quote::ToTokens;
+use syn::visit_mut::VisitMut;
+use syn::{Expr, ItemFn, parse_macro_input};
+
+struct RouteifyTransform;
+
+impl VisitMut for RouteifyTransform {
+ fn visit_expr_mut(&mut self, expr: &mut Expr) {
+ syn::visit_mut::visit_expr_mut(self, expr);
+
+ if let Expr::Try(try_expr) = expr {
+ let inner = &*try_expr.expr;
+ let inner_tokens = inner.to_token_stream();
+
+ if let Ok(macro_expr) = syn::parse2::<Expr>(quote::quote! {
+ ::mingling::macros::route!(#inner_tokens)
+ }) {
+ *expr = macro_expr;
+ }
+ }
+ }
+}
+
+pub(crate) fn routeify_impl(_attr: TokenStream, item: TokenStream) -> TokenStream {
+ let mut input_fn = parse_macro_input!(item as ItemFn);
+ RouteifyTransform.visit_item_fn_mut(&mut input_fn);
+ input_fn.to_token_stream().into()
+}
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs
index 3743df8..a85648f 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -1239,6 +1239,27 @@ pub fn help(attr: TokenStream, item: TokenStream) -> TokenStream {
help::help_attr(item)
}
+/// Extension attribute macro that transforms `expr?` into `route!(expr)`.
+///
+/// Designed for use with `#[chain(routeify, ...)]` to enable concise error
+/// routing in chain functions using the `?` operator syntax.
+///
+/// # Example
+///
+/// ```rust,ignore
+/// #[chain(routeify)]
+/// fn handle_calc(args: EntryCalculate) -> Next {
+/// let a = args.pick(&arg![f32]).to_result()?;
+/// let op = args.pick(&arg![Operator]).to_result()?;
+/// StateCalculate { number_a: a, operator: op, ... }.to_chain()
+/// }
+/// ```
+#[cfg(feature = "extra_macros")]
+#[proc_macro_attribute]
+pub fn routeify(attr: TokenStream, item: TokenStream) -> TokenStream {
+ extensions::routeify::routeify_impl(attr, item)
+}
+
/// Derive macro for automatically implementing the `Grouped` trait on a struct.
///
/// The `#[derive(Grouped)]` macro: