aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src
diff options
context:
space:
mode:
authorWeicao-CatilGrass <1992414357@qq.com>2026-05-22 08:33:46 +0800
committerWeicao-CatilGrass <1992414357@qq.com>2026-05-22 08:33:46 +0800
commit7adbe2715285d7baedfb91f5e81f5ea64f7d1a5a (patch)
treeb9e4985c5b2980613523eea8ad49974c5d1d1070 /mingling_macros/src
parent7eed97fe690f214eba43b4784bc2dee3a71a1498 (diff)
Extract resource injection into shared module and add to #[renderer]
Diffstat (limited to 'mingling_macros/src')
-rw-r--r--mingling_macros/src/chain.rs199
-rw-r--r--mingling_macros/src/lib.rs5
-rw-r--r--mingling_macros/src/renderer.rs132
-rw-r--r--mingling_macros/src/res_injection.rs197
4 files changed, 277 insertions, 256 deletions
diff --git a/mingling_macros/src/chain.rs b/mingling_macros/src/chain.rs
index 60e44e9..9666c51 100644
--- a/mingling_macros/src/chain.rs
+++ b/mingling_macros/src/chain.rs
@@ -1,143 +1,13 @@
#![allow(clippy::too_many_arguments)]
+use crate::res_injection::{
+ ResourceInjection, extract_args_info, generate_immut_resource_bindings,
+ wrap_body_with_mut_resources,
+};
use proc_macro::TokenStream;
use quote::{ToTokens, quote};
use syn::spanned::Spanned;
-use syn::{
- FnArg, Ident, ItemFn, Pat, PatType, ReturnType, Signature, Type, TypePath, parse_macro_input,
-};
-
-/// Extracted information about a resource injection parameter
-struct ResourceInjection {
- var_name: Ident,
- full_type: Type,
- inner_type: TypePath,
- is_ref: bool,
- is_mut: bool,
-}
-
-/// Extracts the previous type and parameter name from function arguments,
-fn extract_args_info(sig: &Signature) -> syn::Result<(Pat, TypePath, Vec<ResourceInjection>)> {
- if sig.inputs.is_empty() {
- return Err(syn::Error::new(
- sig.span(),
- "Chain function must have at least one parameter",
- ));
- }
-
- // First parameter: required, the previous chain type (must be owned, not a reference)
- let first_arg = &sig.inputs[0];
- let (prev_param, previous_type) = match first_arg {
- FnArg::Typed(PatType { pat, ty, .. }) => {
- let param_pat = (**pat).clone();
- match &**ty {
- Type::Path(type_path) => {
- // Check that the type is a single-segment type (no `::`)
- if type_path.path.segments.len() > 1 {
- return Err(syn::Error::new(
- type_path.span(),
- format!(
- "The type `{}` in #[chain] function must be a simple single-segment type, \
- e.g. `Empty` instead of `other::Empty`. \
- Qualified paths with `::` are not allowed here.",
- quote! { #type_path }
- ),
- ));
- }
- (param_pat, type_path.clone())
- }
- Type::Reference(_) => {
- return Err(syn::Error::new(
- ty.span(),
- "The first parameter (previous type) must be taken by move, \
- not by reference. \
- Use `prev: SomeEntry` instead of `prev: &SomeEntry`.",
- ));
- }
- _ => {
- return Err(syn::Error::new(
- ty.span(),
- "First parameter type must be a type path",
- ));
- }
- }
- }
- FnArg::Receiver(_) => {
- return Err(syn::Error::new(
- first_arg.span(),
- "Chain function cannot have self parameter",
- ));
- }
- };
-
- // 2nd to Nth parameters: optional, for resource injection
- let mut resources = Vec::new();
- for arg in sig.inputs.iter().skip(1) {
- match arg {
- FnArg::Typed(PatType { pat, ty, .. }) => {
- // Extract the variable name – must be a simple identifier
- let var_name = match &**pat {
- Pat::Ident(pat_ident) => pat_ident.ident.clone(),
- _ => {
- return Err(syn::Error::new(
- pat.span(),
- "Resource injection parameter must be a simple identifier (e.g., `age: &Age`)",
- ));
- }
- };
-
- let full_type = *(*ty).clone();
-
- // Try to extract inner type for reference patterns like `&Age` -> `Age`
- // and `&mut Age` -> `Age`
- let (inner_type, is_ref, is_mut) = match &full_type {
- Type::Reference(ref_type) => match &*ref_type.elem {
- Type::Path(type_path) => {
- let is_mut = ref_type.mutability.is_some();
- (type_path.clone(), true, is_mut)
- }
- _ => {
- return Err(syn::Error::new(
- ty.span(),
- "Reference resource type must be a type path (e.g., `age: &Age`)",
- ));
- }
- },
- Type::Path(_) => {
- return Err(syn::Error::new(
- ty.span(),
- "Resource injection parameter must be a reference (`&T` or `&mut T`), \
- not an owned value. Use `age: &Age` instead of `age: Age`.",
- ));
- }
- _ => {
- return Err(syn::Error::new(
- ty.span(),
- "Resource injection type must be a type path or reference to one \
- (e.g., `age: Age` or `age: &Age`)",
- ));
- }
- };
-
- resources.push(ResourceInjection {
- var_name,
- full_type,
- inner_type,
- is_ref,
- is_mut,
- });
- }
- FnArg::Receiver(_) => {
- return Err(syn::Error::new(
- arg.span(),
- "Resource injection parameter cannot be self",
- ));
- }
- }
- }
-
- Ok((prev_param, previous_type, resources))
-}
+use syn::{Ident, ItemFn, Pat, ReturnType, Signature, Type, TypePath, parse_macro_input};
/// Parses the `#[chain(...)]` attribute arguments.
///
@@ -202,65 +72,6 @@ fn validate_return_type(sig: &Signature) -> Result<(), proc_macro2::TokenStream>
Ok(())
}
-/// Generates `let` binding statements for immutable resource injection parameters.
-///
-/// Each immutable reference parameter gets a `_binding` variable that holds the
-/// `res_or_default` result, then a shadowing `let` that borrows from it via `.as_ref()`.
-fn generate_immut_resource_bindings<'a>(
- resources: impl Iterator<Item = &'a ResourceInjection>,
- program_type: &proc_macro2::TokenStream,
-) -> Vec<proc_macro2::TokenStream> {
- resources
- .filter(|r| !r.is_mut)
- .map(|res| {
- let var_binding_name = syn::Ident::new(
- &format!("{}_binding", &res.var_name.to_string()),
- res.var_name.span(),
- );
- let var_name = &res.var_name;
- let full_type = &res.full_type;
- let inner_type = &res.inner_type;
- if res.is_ref {
- quote! {
- let #var_binding_name = ::mingling::this::<#program_type>()
- .res_or_default::<#inner_type>();
- let #var_name: #full_type = #var_binding_name.as_ref();
- }
- } else {
- quote! {
- let #var_name: #full_type = ::mingling::this::<#program_type>()
- .res_or_default::<#full_type>();
- }
- }
- })
- .collect()
-}
-
-/// Wraps the function body in nested `__modify_res_and_return_route` closures for
-/// each mutable resource parameter. The innermost closure gets the original body,
-/// and each mutable parameter wraps outward from last to first.
-fn wrap_body_with_mut_resources(
- fn_body_stmts: &[syn::Stmt],
- mut_resources: &[&ResourceInjection],
- program_type: &proc_macro2::TokenStream,
-) -> proc_macro2::TokenStream {
- let mut wrapped = quote! {
- #(#fn_body_stmts)*
- };
-
- for res in mut_resources.iter() {
- let var_name = &res.var_name;
- let inner_type = &res.inner_type;
- wrapped = quote! {
- ::mingling::this::<#program_type>().__modify_res_and_return_route(|#var_name: &mut #inner_type| {
- #wrapped
- }).into()
- };
- }
-
- wrapped
-}
-
/// Builds the `proc` function implementation that serves as the actual chain
/// entry point inside the generated `Chain` impl.
///
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs
index 1733470..8c98ba3 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -41,6 +41,7 @@ mod pack;
mod program_setup;
mod render;
mod renderer;
+mod res_injection;
#[cfg(feature = "comp")]
mod suggest;
@@ -616,8 +617,8 @@ pub fn chain(attr: TokenStream, item: TokenStream) -> TokenStream {
/// }
/// ```
#[proc_macro_attribute]
-pub fn renderer(_attr: TokenStream, item: TokenStream) -> TokenStream {
- renderer::renderer_attr(item)
+pub fn renderer(attr: TokenStream, item: TokenStream) -> TokenStream {
+ renderer::renderer_attr(attr, item)
}
/// Declares a completion suggestion provider for a command entry type.
diff --git a/mingling_macros/src/renderer.rs b/mingling_macros/src/renderer.rs
index 39ea594..db04c19 100644
--- a/mingling_macros/src/renderer.rs
+++ b/mingling_macros/src/renderer.rs
@@ -1,42 +1,12 @@
use proc_macro::TokenStream;
use quote::{ToTokens, quote};
use syn::spanned::Spanned;
-use syn::{FnArg, ItemFn, Pat, PatType, ReturnType, Signature, Type, TypePath, parse_macro_input};
+use syn::{ItemFn, ReturnType, Signature, Type, TypePath, parse_macro_input};
use crate::get_global_set;
-
-/// Extracts the previous type and parameter name from function arguments
-fn extract_previous_info(sig: &Signature) -> syn::Result<(Pat, TypePath)> {
- // The function should have exactly one parameter
- if sig.inputs.len() != 1 {
- return Err(syn::Error::new(
- sig.inputs.span(),
- "Renderer function must have exactly one parameter (the previous type)",
- ));
- }
-
- // First and only parameter is the previous type
- let arg = &sig.inputs[0];
- match arg {
- FnArg::Typed(PatType { pat, ty, .. }) => {
- // Extract the pattern (parameter name)
- let param_pat = (**pat).clone();
-
- // Extract the type
- match &**ty {
- Type::Path(type_path) => Ok((param_pat, type_path.clone())),
- _ => Err(syn::Error::new(
- ty.span(),
- "Parameter type must be a type path",
- )),
- }
- }
- FnArg::Receiver(_) => Err(syn::Error::new(
- arg.span(),
- "Renderer function cannot have self parameter",
- )),
- }
-}
+use crate::res_injection::{
+ extract_args_info, generate_immut_resource_bindings, wrap_body_with_mut_resources,
+};
/// Extracts and returns the return type from the function signature (or None for `()` / no return type).
fn extract_return_type(sig: &Signature) -> syn::Result<Option<syn::Type>> {
@@ -53,7 +23,11 @@ fn extract_return_type(sig: &Signature) -> syn::Result<Option<syn::Type>> {
}
}
-pub fn renderer_attr(item: TokenStream) -> TokenStream {
+pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
+ // Parse attribute arguments for program path (e.g. #[renderer(my_crate::Program)])
+ let (program_path, _use_crate_prefix) = parse_renderer_attr_args(attr);
+ let program_type = &program_path;
+
// Parse the function item
let input_fn = parse_macro_input!(item as ItemFn);
@@ -64,8 +38,8 @@ pub fn renderer_attr(item: TokenStream) -> TokenStream {
.into();
}
- // Extract the previous type and parameter name from function arguments
- let (prev_param, previous_type) = match extract_previous_info(&input_fn.sig) {
+ // Extract the previous type, parameter name, and resource injection params
+ let (prev_param, previous_type, resources) = match extract_args_info(&input_fn.sig) {
Ok(info) => info,
Err(e) => return e.to_compile_error().into(),
};
@@ -81,8 +55,8 @@ pub fn renderer_attr(item: TokenStream) -> TokenStream {
Err(e) => return e.to_compile_error().into(),
};
- // Get the function body
- let fn_body = &input_fn.block;
+ // Get function body statements
+ let fn_body_stmts: Vec<syn::Stmt> = input_fn.block.stmts.clone();
// Get function attributes (excluding the renderer attribute)
let mut fn_attrs = input_fn.attrs.clone();
@@ -101,6 +75,13 @@ pub fn renderer_attr(item: TokenStream) -> TokenStream {
"__internal_renderer_{}",
just_fmt::snake_case!(fn_name.to_string())
);
+ let struct_name = syn::Ident::new(&internal_name, fn_name.span());
+
+ let has_resources = !resources.is_empty();
+
+ // Generate resource bindings for immutable resources
+ let immut_resource_stmts = generate_immut_resource_bindings(resources.iter(), program_type);
+ let mut_resources: Vec<_> = resources.iter().filter(|r| r.is_mut).collect();
// Determine public return type and the expression to return dummy_r
let (public_return_type, result_return) = match &return_type {
@@ -121,10 +102,46 @@ pub fn renderer_attr(item: TokenStream) -> TokenStream {
(ret_ty, expr)
}
};
- let struct_name = syn::Ident::new(&internal_name, fn_name.span());
- // Generate the struct and implementation
- // We need to create a wrapper function that adds the r parameter
+ // Build the Renderer::render body with resource injection
+ let render_fn_body = if has_resources {
+ let wrapped_body =
+ wrap_body_with_mut_resources(&fn_body_stmts, &mut_resources, program_type);
+ quote! {
+ #(#immut_resource_stmts)*
+ #wrapped_body
+ }
+ } else {
+ quote! { #(#fn_body_stmts)* }
+ };
+
+ // Build the original function with resource injection (no `.into()` — signature is exactly as user wrote)
+ let original_fn_body = if has_resources {
+ let wrapped_body =
+ wrap_body_with_mut_resources(&fn_body_stmts, &mut_resources, program_type);
+ quote! {
+ let mut dummy_r = ::mingling::RenderResult::default();
+ {
+ let __renderer_inner_result = &mut dummy_r;
+ #(#immut_resource_stmts)*
+ #wrapped_body
+ }
+ #result_return
+ }
+ } else {
+ quote! {
+ let mut dummy_r = ::mingling::RenderResult::default();
+ {
+ let __renderer_inner_result = &mut dummy_r;
+ #(#fn_body_stmts)*
+ }
+ #result_return
+ }
+ };
+
+ // Keep the original function signature unchanged (same params as user wrote)
+ let original_inputs = input_fn.sig.inputs.clone();
+
let expanded = quote! {
#(#fn_attrs)*
#[doc(hidden)]
@@ -137,33 +154,28 @@ pub fn renderer_attr(item: TokenStream) -> TokenStream {
type Previous = #previous_type;
fn render(#prev_param: Self::Previous, __renderer_inner_result: &mut ::mingling::RenderResult) {
- // Create a local wrapper function that includes `__renderer_inner_result` parameter
- // This allows r_println! to access `__renderer_inner_result`
- #[allow(non_snake_case)]
- fn render_wrapper(#prev_param: #previous_type, __renderer_inner_result: &mut ::mingling::RenderResult) {
- #fn_body
- }
-
- // Call the wrapper function
- render_wrapper(#prev_param, __renderer_inner_result);
+ #render_fn_body
}
}
// Keep the original function for internal use (without r parameter)
#(#fn_attrs)*
- #vis fn #fn_name(#prev_param: impl Into<#previous_type>) -> #public_return_type {
- let #prev_param = #prev_param.into();
- let mut dummy_r = ::mingling::RenderResult::default();
- {
- let __renderer_inner_result = &mut dummy_r;
- #fn_body
- }
- #result_return
+ #vis fn #fn_name(#original_inputs) -> #public_return_type {
+ #original_fn_body
}
};
expanded.into()
}
+fn parse_renderer_attr_args(attr: TokenStream) -> (proc_macro2::TokenStream, bool) {
+ if attr.is_empty() {
+ (crate::default_program_path(), true)
+ } else {
+ let path: syn::Path =
+ syn::parse(attr).expect("Expected a path argument for #[renderer(path)]");
+ (quote! { #path }, false)
+ }
+}
/// Builds the renderer entry for the global renderers list
pub fn build_renderer_entry(
@@ -201,7 +213,7 @@ pub fn register_renderer(input: TokenStream) -> TokenStream {
// Parse the input as a comma-separated list of arguments
let input_parsed = syn::parse_macro_input!(input with syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>::parse_terminated);
- // Check that we have exactly two elements
+ // Check that there are exactly two elements
if input_parsed.len() != 2 {
return syn::Error::new(
input_parsed.span(),
diff --git a/mingling_macros/src/res_injection.rs b/mingling_macros/src/res_injection.rs
new file mode 100644
index 0000000..0f180db
--- /dev/null
+++ b/mingling_macros/src/res_injection.rs
@@ -0,0 +1,197 @@
+use quote::quote;
+use syn::spanned::Spanned;
+use syn::{FnArg, Ident, Pat, PatType, Signature, Type, TypePath};
+
+/// Extracted information about a resource injection parameter
+pub(crate) struct ResourceInjection {
+ pub(crate) var_name: Ident,
+ pub(crate) full_type: Type,
+ pub(crate) inner_type: TypePath,
+ pub(crate) is_ref: bool,
+ pub(crate) is_mut: bool,
+}
+
+/// Extracts the previous type and parameter name from function arguments,
+/// and collects resource injection parameters from the 2nd argument onward.
+pub(crate) fn extract_args_info(
+ sig: &Signature,
+) -> syn::Result<(Pat, TypePath, Vec<ResourceInjection>)> {
+ if sig.inputs.is_empty() {
+ return Err(syn::Error::new(
+ sig.span(),
+ "Function must have at least one parameter",
+ ));
+ }
+
+ // First parameter: required, the previous type (must be owned, not a reference)
+ let first_arg = &sig.inputs[0];
+ let (prev_param, previous_type) = match first_arg {
+ FnArg::Typed(PatType { pat, ty, .. }) => {
+ let param_pat = (**pat).clone();
+ match &**ty {
+ Type::Path(type_path) => {
+ // Check that the type is a single-segment type (no `::`)
+ if type_path.path.segments.len() > 1 {
+ return Err(syn::Error::new(
+ type_path.span(),
+ format!(
+ "The type `{}` must be a simple single-segment type, \
+ e.g. `Empty` instead of `other::Empty`. \
+ Qualified paths with `::` are not allowed here.",
+ quote! { #type_path }
+ ),
+ ));
+ }
+ (param_pat, type_path.clone())
+ }
+ Type::Reference(_) => {
+ return Err(syn::Error::new(
+ ty.span(),
+ "The first parameter (previous type) must be taken by move, \
+ not by reference. \
+ Use `prev: SomeEntry` instead of `prev: &SomeEntry`.",
+ ));
+ }
+ _ => {
+ return Err(syn::Error::new(
+ ty.span(),
+ "First parameter type must be a type path",
+ ));
+ }
+ }
+ }
+ FnArg::Receiver(_) => {
+ return Err(syn::Error::new(
+ first_arg.span(),
+ "Function cannot have self parameter",
+ ));
+ }
+ };
+
+ // 2nd to Nth parameters: optional, for resource injection
+ let mut resources = Vec::new();
+ for arg in sig.inputs.iter().skip(1) {
+ match arg {
+ FnArg::Typed(PatType { pat, ty, .. }) => {
+ // Extract the variable name – must be a simple identifier
+ let var_name = match &**pat {
+ Pat::Ident(pat_ident) => pat_ident.ident.clone(),
+ _ => {
+ return Err(syn::Error::new(
+ pat.span(),
+ "Resource injection parameter must be a simple identifier (e.g., `age: &Age`)",
+ ));
+ }
+ };
+
+ let full_type = *(*ty).clone();
+
+ // Try to extract inner type for reference patterns like `&Age` -> `Age`
+ // and `&mut Age` -> `Age`
+ let (inner_type, is_ref, is_mut) = match &full_type {
+ Type::Reference(ref_type) => match &*ref_type.elem {
+ Type::Path(type_path) => {
+ let is_mut = ref_type.mutability.is_some();
+ (type_path.clone(), true, is_mut)
+ }
+ _ => {
+ return Err(syn::Error::new(
+ ty.span(),
+ "Reference resource type must be a type path (e.g., `age: &Age`)",
+ ));
+ }
+ },
+ Type::Path(_) => {
+ return Err(syn::Error::new(
+ ty.span(),
+ "Resource injection parameter must be a reference (`&T` or `&mut T`), \
+ not an owned value. Use `age: &Age` instead of `age: Age`.",
+ ));
+ }
+ _ => {
+ return Err(syn::Error::new(
+ ty.span(),
+ "Resource injection type must be a type path or reference to one \
+ (e.g., `age: Age` or `age: &Age`)",
+ ));
+ }
+ };
+
+ resources.push(ResourceInjection {
+ var_name,
+ full_type,
+ inner_type,
+ is_ref,
+ is_mut,
+ });
+ }
+ FnArg::Receiver(_) => {
+ return Err(syn::Error::new(
+ arg.span(),
+ "Resource injection parameter cannot be self",
+ ));
+ }
+ }
+ }
+
+ Ok((prev_param, previous_type, resources))
+}
+
+/// Generates `let` binding statements for immutable resource injection parameters.
+///
+/// Each immutable reference parameter gets a `_binding` variable that holds the
+/// `res_or_default` result, then a shadowing `let` that borrows from it via `.as_ref()`.
+pub(crate) fn generate_immut_resource_bindings<'a>(
+ resources: impl Iterator<Item = &'a ResourceInjection>,
+ program_type: &proc_macro2::TokenStream,
+) -> Vec<proc_macro2::TokenStream> {
+ resources
+ .filter(|r| !r.is_mut)
+ .map(|res| {
+ let var_binding_name = syn::Ident::new(
+ &format!("{}_binding", &res.var_name.to_string()),
+ res.var_name.span(),
+ );
+ let var_name = &res.var_name;
+ let full_type = &res.full_type;
+ let inner_type = &res.inner_type;
+ if res.is_ref {
+ quote! {
+ let #var_binding_name = ::mingling::this::<#program_type>()
+ .res_or_default::<#inner_type>();
+ let #var_name: #full_type = #var_binding_name.as_ref();
+ }
+ } else {
+ quote! {
+ let #var_name: #full_type = ::mingling::this::<#program_type>()
+ .res_or_default::<#full_type>();
+ }
+ }
+ })
+ .collect()
+}
+
+/// Wraps the function body in nested `__modify_res_and_return_route` closures for
+/// each mutable resource parameter. The innermost closure gets the original body,
+/// and each mutable parameter wraps outward from last to first.
+pub(crate) fn wrap_body_with_mut_resources(
+ fn_body_stmts: &[syn::Stmt],
+ mut_resources: &[&ResourceInjection],
+ program_type: &proc_macro2::TokenStream,
+) -> proc_macro2::TokenStream {
+ let mut wrapped = quote! {
+ #(#fn_body_stmts)*
+ };
+
+ for res in mut_resources.iter() {
+ let var_name = &res.var_name;
+ let inner_type = &res.inner_type;
+ wrapped = quote! {
+ ::mingling::this::<#program_type>().__modify_res_and_return_route(|#var_name: &mut #inner_type| {
+ #wrapped
+ }).into()
+ };
+ }
+
+ wrapped
+}