diff options
Diffstat (limited to 'mingling_macros/src/attr')
| -rw-r--r-- | mingling_macros/src/attr/command.rs | 6 | ||||
| -rw-r--r-- | mingling_macros/src/attr/dispatcher_clap.rs | 4 | ||||
| -rw-r--r-- | mingling_macros/src/attr/metadata.rs | 87 |
3 files changed, 92 insertions, 5 deletions
diff --git a/mingling_macros/src/attr/command.rs b/mingling_macros/src/attr/command.rs index 35c5c30..9598fa8 100644 --- a/mingling_macros/src/attr/command.rs +++ b/mingling_macros/src/attr/command.rs @@ -179,7 +179,7 @@ fn build_ext_attrs(exts: &[syn::Path]) -> Vec<TokenStream2> { /// Returns `true` if the function has a first non-reference (owned) parameter that /// serves as the "args" input. fn has_args_param(sig: &syn::Signature) -> bool { - sig.inputs.first().map_or(false, |arg| { + sig.inputs.first().is_some_and(|arg| { if let FnArg::Typed(pat_type) = arg { !matches!(&*pat_type.ty, Type::Reference(_)) } else { @@ -202,10 +202,10 @@ fn build_wrapper_params( // First param is owned (args) -> replace its type with entry type let mut params = sig.inputs.clone(); if let Some(FnArg::Typed(first)) = params.first_mut() { - first.ty = Box::new(Type::Path(syn::TypePath { + *first.ty = Type::Path(syn::TypePath { qself: None, path: syn::Path::from(entry_type.clone()), - })); + }); } params } else { diff --git a/mingling_macros/src/attr/dispatcher_clap.rs b/mingling_macros/src/attr/dispatcher_clap.rs index 40f7d47..218750c 100644 --- a/mingling_macros/src/attr/dispatcher_clap.rs +++ b/mingling_macros/src/attr/dispatcher_clap.rs @@ -39,7 +39,7 @@ impl Parse for ClapOptions { error_struct = Some(value); } else if key == "help" { let value: LitBool = input.parse()?; - if value.value() == false { + if !value.value() { // help = false is allowed but does nothing help_enabled = false; } else { @@ -171,7 +171,7 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke }; let dispatch_tree_entry = - get_dispatch_tree_entry(&command_name_str, dispatcher_struct, &struct_name); + get_dispatch_tree_entry(&command_name_str, dispatcher_struct, struct_name); let expanded = quote! { // Keep the original struct definition diff --git a/mingling_macros/src/attr/metadata.rs b/mingling_macros/src/attr/metadata.rs new file mode 100644 index 0000000..b96e319 --- /dev/null +++ b/mingling_macros/src/attr/metadata.rs @@ -0,0 +1,87 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::spanned::Spanned; +use syn::{Attribute, ItemFn, ReturnType, TypePath, parse_macro_input}; + +/// Implements the `#[metadata(EntryVariant)]` attribute macro. +/// +/// It takes the enum variant ident to attach metadata to, and rewrites the +/// annotated function into: +/// - an `impl ::mingling::Metadata<ReturnType> for EntryVariant` that calls the +/// original function, +/// - a `::mingling::macros::register_metadata!(EntryVariant, ReturnType)` call, +/// - the preserved original function. +pub(crate) fn metadata_attr(attr: TokenStream, item: TokenStream) -> TokenStream { + let entry_variant = parse_macro_input!(attr as syn::Ident); + + let input_fn = parse_macro_input!(item as ItemFn); + + // The metadata type is the function's return type. + let metadata_type = match &input_fn.sig.output { + ReturnType::Type(_, ty) => match syn::parse2::<TypePath>(quote! { #ty }) { + Ok(ty) => ty, + Err(e) => return e.to_compile_error().into(), + }, + ReturnType::Default => { + return syn::Error::new( + input_fn.sig.span(), + "#[metadata] requires the function to have an explicit return type", + ) + .to_compile_error() + .into(); + } + }; + + // Preserve the original return type exactly as written, so the original + // function signature is reproduced unchanged. + let original_return_type = match &input_fn.sig.output { + ReturnType::Type(_, ty) => quote! { #ty }, + ReturnType::Default => quote! { () }, + }; + + // Reject async metadata functions: `Metadata::init_metadata` is synchronous. + if input_fn.sig.asyncness.is_some() { + return syn::Error::new(input_fn.sig.span(), "Metadata function cannot be async") + .to_compile_error() + .into(); + } + + let fn_name = &input_fn.sig.ident; + let vis = &input_fn.vis; + let original_inputs = input_fn.sig.inputs.clone(); + let fn_body_stmts = &input_fn.block.stmts; + + // Function attributes, excluding the metadata attribute itself. + let fn_attrs: Vec<&Attribute> = input_fn + .attrs + .iter() + .filter(|attr| !attr.path().is_ident("metadata")) + .collect(); + + // A metadata provider is a zero-argument function. + if !original_inputs.is_empty() { + return syn::Error::new( + input_fn.sig.span(), + "#[metadata] function cannot take any parameters", + ) + .to_compile_error() + .into(); + } + + let expanded = quote! { + impl ::mingling::Metadata<#metadata_type> for #entry_variant { + fn init_metadata() -> #metadata_type { + #fn_name() + } + } + + ::mingling::macros::register_metadata!(#entry_variant, #metadata_type); + + #(#fn_attrs)* + #vis fn #fn_name(#original_inputs) -> #original_return_type { + #(#fn_body_stmts)* + } + }; + + expanded.into() +} |
