diff options
Diffstat (limited to 'mingling_macros/src')
| -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 |
3 files changed, 128 insertions, 0 deletions
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` |
