aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src/attr
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_macros/src/attr')
-rw-r--r--mingling_macros/src/attr/command.rs6
-rw-r--r--mingling_macros/src/attr/dispatcher_clap.rs4
-rw-r--r--mingling_macros/src/attr/metadata.rs87
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()
+}