Modify procedural macro to allow more flexible function signatures; Refactor code

This commit is contained in:
2025-12-31 18:21:38 +02:00
parent 498cb43390
commit 221f75e976
6 changed files with 173 additions and 223 deletions

View File

@@ -2,7 +2,7 @@
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{FnArg, ImplItem, ItemImpl, Pat, Type, parse_macro_input};
use syn::{FnArg, ImplItem, ItemImpl, ReturnType, Type, parse_macro_input};
#[proc_macro_attribute]
pub fn component(attr: TokenStream, item: TokenStream) -> TokenStream {
@@ -23,14 +23,12 @@ pub fn component(attr: TokenStream, item: TokenStream) -> TokenStream {
for item in &mut input.items {
if let ImplItem::Fn(method) = item {
// Check for #[action]
let has_action = method
.attrs
.iter()
.any(|attr| attr.path().is_ident("action"));
if has_action {
// Remove the #[action] attribute so it doesn't cause compilation errors
method.attrs.retain(|attr| !attr.path().is_ident("action"));
let method_name = &method.sig.ident;
@@ -39,44 +37,61 @@ pub fn component(attr: TokenStream, item: TokenStream) -> TokenStream {
snake_to_pascal(&method_name.to_string())
);
let returns_result = match &method.sig.output {
ReturnType::Default => false,
ReturnType::Type(_, ty) => {
quote!(#ty).to_string().contains("Result")
}
};
let mut variant_fields = Vec::new();
let mut call_args = Vec::new();
let mut variant_arg_names = Vec::new();
// Inspect arguments to decide what goes into the Enum and what is injected
for arg in method.sig.inputs.iter().skip(1) {
// Skip 'self'
if let FnArg::Typed(pat_type) = arg {
if is_sender_type(&pat_type.ty) {
// Inject the 'tx' from handle_action, don't add to Enum
call_args.push(quote!(tx));
continue;
}
} else {
// This is a data argument, add to Enum
let ty = &pat_type.ty;
variant_fields.push(quote!(#ty));
let ty = &pat_type.ty;
variant_fields.push(quote!(#ty));
if let Pat::Ident(pi) = &*pat_type.pat {
let arg_ident = &pi.ident;
call_args.push(quote!(#arg_ident));
let arg_id = format_ident!(
"arg_{}",
variant_arg_names.len()
);
variant_arg_names.push(arg_id.clone());
call_args.push(quote!(#arg_id));
}
}
}
// Generate Enum Variant
if variant_fields.is_empty() {
enum_variants.push(quote!(#variant_name));
match_arms.push(quote! {
#enum_name::#variant_name => self.#method_name(tx)
});
} else {
enum_variants
.push(quote!(#variant_name(#(#variant_fields),*)));
// Extracting bindings for the match arm: Enum::Var(a, b) -> self.method(a, b, tx)
let pattern_args = (0..variant_fields.len())
.map(|i| format_ident!("arg_{}", i));
let pattern_args_clone = pattern_args.clone();
match_arms.push(quote! {
#enum_name::#variant_name(#(#pattern_args),*) => self.#method_name(#(#pattern_args_clone,)* tx)
});
}
// Generate Match Arm
let pattern = if variant_arg_names.is_empty() {
quote!(#enum_name::#variant_name)
} else {
quote!(#enum_name::#variant_name(#(#variant_arg_names),*))
};
let call = if returns_result {
quote!(self.#method_name(#(#call_args),*))
} else {
quote!({ self.#method_name(#(#call_args),*); ::core::result::Result::Ok(()) })
};
match_arms.push(quote!(#pattern => #call));
}
}
}
@@ -105,6 +120,11 @@ pub fn component(attr: TokenStream, item: TokenStream) -> TokenStream {
TokenStream::from(expanded)
}
fn is_sender_type(ty: &Type) -> bool {
let s = quote!(#ty).to_string();
s.contains("UnboundedSender")
}
fn snake_to_pascal(s: &str) -> String {
s.split('_')
.map(|word| {
@@ -118,9 +138,3 @@ fn snake_to_pascal(s: &str) -> String {
})
.collect()
}
// Helper to detect if an argument is the Sender to inject it from handle_action
fn is_sender_type(ty: &Type) -> bool {
let s = quote!(#ty).to_string();
s.contains("UnboundedSender")
}