diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-07-21 07:37:34 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-07-21 07:37:34 +0800 |
| commit | 67ec96406e6f4b8f30bc0a38c9ba95845b3cfb52 (patch) | |
| tree | 43bfb43aeb6f3ec877f754840f380a50b0715b09 | |
| parent | 04ce3a18efe971c5b6893977f0ebe1ce1d9a6947 (diff) | |
feat(macros): add render_route macro and renderify attribute for render
pipeline error routing
| -rw-r--r-- | CHANGELOG.md | 38 | ||||
| -rw-r--r-- | mingling/src/lib.rs | 9 | ||||
| -rw-r--r-- | mingling_macros/src/extensions.rs | 4 | ||||
| -rw-r--r-- | mingling_macros/src/extensions/renderify.rs | 48 | ||||
| -rw-r--r-- | mingling_macros/src/lib.rs | 76 |
5 files changed, 175 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 16ca38a..9977b9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -366,6 +366,44 @@ None Under the hood, `r_append!` (when used in implicit buffer mode inside a `#[buffer]` function) calls `append_other` on the current render buffer. The `From<F>` implementation allows the buffer function's return value (a `RenderResult`) to be seamlessly converted into the parent's buffer via `append_other`. This enables clean separation of render logic into reusable buffer functions that can be composed together. +14. **[`core`]** **[`macros`]** Added the `render_route!` macro and `#[renderify]` extension attribute macro, providing error routing to the rendering pipeline (as opposed to `route!`/`#[routeify]` which route to the chain pipeline). + + The `render_route!` macro is conceptually similar to `route!`, but instead of routing errors through `Routable::to_chain()` (returning `ChainProcess`), it routes them directly to the renderer via `crate::ThisProgram::render(AnyOutput::new(e))` (returning `RenderResult`). This makes it suitable for use in `#[renderer]` and `#[help]` functions where the return type is `RenderResult`. + + ```rust,ignore + use mingling::macros::{renderer, render_route}; + + #[renderer] + fn render_something(prev: SomeType) -> RenderResult { + let data = render_route!(fetch_data().map_err(|e| ErrorEntry::new(e.to_string())))?; + // ... render data + Ok(RenderResult::new()) + } + ``` + + The `#[renderify]` extension attribute is the rendering-pipeline counterpart to `#[routeify]`. It transforms `expr?` into `render_route!(expr)` (instead of `route!(expr)`), enabling concise error routing in renderer and help functions using the `?` operator syntax. + + ```rust,ignore + #[renderer(renderify)] + fn render_greeting(prev: Greeting) -> RenderResult { + let data = load_data()?; // expands to render_route!(load_data()) + r_println!("{data}"); + Ok(RenderResult::new()) + } + ``` + + The `#[renderify]` macro can be used: + - **Standalone** — as a direct attribute: `#[renderify] fn render(...) { ... }` + - **As an extension** — via the extension point system: `#[renderer(renderify)] fn render(...) { ... }` or `#[help(renderify)] fn help(...) { ... }` + + When used as a renderer/help extension, the `renderify` identifier is detected by the extension point mechanism, stripped from the attribute arguments, and `#[renderify]` is applied as an outer attribute on top of `#[renderer]`/`#[help]` — just like `routeify` works with `#[chain]`. + + Both `render_route!` and `#[renderify]` are feature-gated behind `extra_macros` and re-exported as `mingling::macros::render_route` and `mingling::macros::renderify` respectively. + + Internal changes: + - Added `mingling_macros/src/extensions/renderify.rs` with `renderify_impl` implementation. + - Registered `#[proc_macro] pub fn render_route` and `#[proc_macro_attribute] pub fn renderify` in `mingling_macros/src/lib.rs`. + #### **BREAKING CHANGES** (API CHANGES): 1. **[`macros:renderer`]** **[`macros:help`]** Removed `r_println!` and `r_print!` macros from being implicitly injected by `#[renderer]` and `#[help]` macros. These macros still exist, but must now be used **explicitly** — either with an explicit buffer argument, or via the `#[buffer]` extension attribute that re-enables implicit buffer injection. diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs index 457f393..f08e9e4 100644 --- a/mingling/src/lib.rs +++ b/mingling/src/lib.rs @@ -123,8 +123,17 @@ pub mod macros { pub use mingling_macros::register_renderer; #[doc(hidden)] pub use mingling_macros::register_type; + /// `render_route! { /* ... */ }` - Routes errors to the rendering pipeline. + /// Similar to `route!`, but used in `#[renderer]` and `#[help]` functions + /// where the return type is `RenderResult` instead of `ChainProcess`. + #[cfg(feature = "extra_macros")] + pub use mingling_macros::render_route; /// `#[renderer]` - Used to generate a struct implementing the `Renderer` trait via a method pub use mingling_macros::renderer; + /// `#[renderify]` - An extension attribute macro that transforms `expr?` into `render_route!(expr)`. + /// Can be used standalone or as a renderer/help extension: `#[renderer(renderify, ...)]`, `#[help(renderify, ...)]`. + #[cfg(feature = "extra_macros")] + pub use mingling_macros::renderify; /// `route! { /* ... */ }` - Used to generate a route that either returns a successful result or early returns an error. #[cfg(feature = "extra_macros")] pub use mingling_macros::route; diff --git a/mingling_macros/src/extensions.rs b/mingling_macros/src/extensions.rs index 022761b..c2fdb30 100644 --- a/mingling_macros/src/extensions.rs +++ b/mingling_macros/src/extensions.rs @@ -13,6 +13,10 @@ use syn::{Ident, Token}; #[cfg(feature = "extra_macros")] pub(crate) mod routeify; +/// Extension: `#[renderify]` — transforms `expr?` into `render_route!(expr)`. +#[cfg(feature = "extra_macros")] +pub(crate) mod renderify; + /// Extension: `#[buffer]` — wraps a unit-returning function to return `RenderResult`. pub(crate) mod buffer; diff --git a/mingling_macros/src/extensions/renderify.rs b/mingling_macros/src/extensions/renderify.rs new file mode 100644 index 0000000..9be8acc --- /dev/null +++ b/mingling_macros/src/extensions/renderify.rs @@ -0,0 +1,48 @@ +//! The `#[renderify]` extension — transforms `expr?` into `render_route!(expr)`. +//! +//! Designed as an extension for the Mingling attribute macro system, intended +//! to be used with `#[renderer(renderify)]`, `#[help(renderify)]`, +//! or standalone as `#[renderify]`. +//! +//! # How it works +//! +//! The macro parses the function AST and replaces every `Expr::Try` node with an +//! equivalent `render_route!(expr)` invocation, which routes errors to the +//! rendering pipeline via `crate::ThisProgram::render(AnyOutput::new(e))`. + +use proc_macro::TokenStream; +use quote::ToTokens; +use syn::spanned::Spanned; +use syn::visit_mut::VisitMut; +use syn::{Expr, ItemFn, parse_macro_input}; + +struct RenderifyTransform; + +impl VisitMut for RenderifyTransform { + 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(); + + // Set the span of the generated `render_route` ident to the `?` token's span, + // so that rust-analyzer resolves the `?` position to the `render_route!` macro + // instead of the standard Try trait, showing the render_route macro's docs on hover. + let q_span = try_expr.question_token.span(); + let route_ident = proc_macro2::Ident::new("render_route", q_span); + + if let Ok(macro_expr) = syn::parse2::<Expr>(quote::quote! { + ::mingling::macros::#route_ident!(#inner_tokens) + }) { + *expr = macro_expr; + } + } + } +} + +pub(crate) fn renderify_impl(_attr: TokenStream, item: TokenStream) -> TokenStream { + let mut input_fn = parse_macro_input!(item as ItemFn); + RenderifyTransform.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 5d8d7e0..da699da 100644 --- a/mingling_macros/src/lib.rs +++ b/mingling_macros/src/lib.rs @@ -590,6 +590,56 @@ pub fn route(input: TokenStream) -> TokenStream { TokenStream::from(expanded) } +/// Routes errors to the rendering pipeline instead of the chain pipeline. +/// +/// This macro is similar to [`route!`] but instead of routing errors through +/// `Routable::to_chain()` (which returns `ChainProcess`), it routes them +/// directly to the renderer via `crate::ThisProgram::render(AnyOutput::new(e))` +/// (which returns `RenderResult`). +/// +/// This is useful in `#[renderer]` and `#[help]` functions where the return +/// type is `RenderResult` rather than `ChainProcess`. +/// +/// # Syntax +/// +/// ```rust,ignore +/// render_route!(expr) +/// ``` +/// +/// Where `expr` is an expression of type `Result<T, E>`. +/// +/// # Interaction with `#[routeify]` +/// +/// When `#[routeify]` is used on a `#[renderer]` or `#[help]` function (e.g. +/// `#[renderer(routeify)]` or `#[help(routeify)]`), every `expr?` is automatically +/// replaced with `render_route!(expr)` instead of `route!(expr)`. +/// +/// # Example +/// +/// ```rust,ignore +/// use mingling::macros::{renderer, render_route}; +/// use std::io::Write; +/// +/// #[renderer] +/// fn render_something(prev: SomeType) -> RenderResult { +/// let data = render_route!(fetch_data().map_err(|e| ErrorEntry::new(e.to_string())))?; +/// // ... render data +/// Ok(RenderResult::new()) +/// } +/// ``` +#[cfg(feature = "extra_macros")] +#[proc_macro] +pub fn render_route(input: TokenStream) -> TokenStream { + let expr = parse_macro_input!(input as syn::Expr); + let expanded = quote! { + match #expr { + Ok(r) => r, + Err(e) => return crate::ThisProgram::render(::mingling::AnyOutput::new(e)), + } + }; + TokenStream::from(expanded) +} + /// Creates an empty result value wrapped in `ChainProcess` for early return /// from a chain function. /// @@ -1314,6 +1364,32 @@ pub fn routeify(attr: TokenStream, item: TokenStream) -> TokenStream { extensions::routeify::routeify_impl(attr, item) } +/// Extension attribute macro that transforms `expr?` into `render_route!(expr)`. +/// +/// Designed for use with `#[renderer(renderify, ...)]` or `#[help(renderify, ...)]` +/// to enable concise error routing in renderer and help functions using the `?` +/// operator syntax. +/// +/// Unlike `#[routeify]` which routes errors to the chain pipeline via `route!`, +/// `#[renderify]` routes errors to the rendering pipeline via `render_route!`, +/// which matches the `RenderResult` return type of renderer and help functions. +/// +/// # Example +/// +/// ```rust,ignore +/// #[renderer(renderify)] +/// fn render_greeting(prev: Greeting) -> RenderResult { +/// let data = load_data()?; // expands to render_route!(load_data()) +/// r_println!("{data}"); +/// Ok(RenderResult::new()) +/// } +/// ``` +#[cfg(feature = "extra_macros")] +#[proc_macro_attribute] +pub fn renderify(attr: TokenStream, item: TokenStream) -> TokenStream { + extensions::renderify::renderify_impl(attr, item) +} + /// Wraps a unit-returning function to produce a `RenderResult`. /// /// The `#[buffer]` attribute macro injects a local `__render_result_buffer` |
