From 19cdb37dfbdf8aafda1fdc25102e67d402a9e7e1 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Tue, 9 Sep 2025 04:00:52 +0200 Subject: [PATCH] feat: Added attribute to easily register additional lua methods Previously this could only be done by implementing a trait, like `AddAdditionalMethods`, that that has an add_methods function where you can put your custom methods. With this new attribute you can pass in a register function directly! --- Cargo.lock | 1 + automation_macro/Cargo.toml | 3 ++ automation_macro/src/device.rs | 61 +++++++++++++++++++++++++++------- automation_macro/src/lib.rs | 20 +++++++++-- 4 files changed, 70 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db3b990..6099456 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -167,6 +167,7 @@ name = "automation_macro" version = "0.1.0" dependencies = [ "itertools", + "mlua", "proc-macro2", "quote", "syn 2.0.106", diff --git a/automation_macro/Cargo.toml b/automation_macro/Cargo.toml index d8f8318..21d5f33 100644 --- a/automation_macro/Cargo.toml +++ b/automation_macro/Cargo.toml @@ -11,3 +11,6 @@ itertools = { workspace = true } proc-macro2 = { workspace = true } quote = { workspace = true } syn = { workspace = true } + +[dev-dependencies] +mlua = { workspace = true } diff --git a/automation_macro/src/device.rs b/automation_macro/src/device.rs index 6fa8014..99058d3 100644 --- a/automation_macro/src/device.rs +++ b/automation_macro/src/device.rs @@ -8,6 +8,7 @@ use syn::{Attribute, DeriveInput, Token, parenthesized}; enum Attr { Trait(TraitAttr), + AddMethods(AddMethodsAttr), } impl Parse for Attr { @@ -19,7 +20,13 @@ impl Parse for Attr { let attr = match ident.to_string().as_str() { "traits" => Attr::Trait(attr.parse()?), - _ => return Err(syn::Error::new(ident.span(), "Expected 'traits'")), + "add_methods" => Attr::AddMethods(attr.parse()?), + _ => { + return Err(syn::Error::new( + ident.span(), + "Expected 'traits' or 'add_methods'", + )); + } }; Ok(attr) @@ -98,23 +105,38 @@ impl Parse for Generics { } } +#[derive(Clone)] +struct AddMethodsAttr(syn::Path); + +impl Parse for AddMethodsAttr { + fn parse(input: ParseStream) -> syn::Result { + Ok(Self(input.parse()?)) + } +} + +impl ToTokens for AddMethodsAttr { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let Self(path) = self; + + tokens.extend(quote! { + #path + }); + } +} + struct Implementation { generics: Option, traits: Traits, -} - -impl From<(Option, Traits)> for Implementation { - fn from(value: (Option, Traits)) -> Self { - Self { - generics: value.0, - traits: value.1, - } - } + add_methods: Vec, } impl quote::ToTokens for Implementation { fn to_tokens(&self, tokens: &mut TokenStream2) { - let Self { generics, traits } = &self; + let Self { + generics, + traits, + add_methods, + } = &self; tokens.extend(quote! { #generics { @@ -135,6 +157,10 @@ impl quote::ToTokens for Implementation { methods.add_async_method("get_id", async |_lua, this, _: ()| { Ok(this.get_id()) }); #traits + + #( + #add_methods(methods); + )* } } }); @@ -145,6 +171,7 @@ struct Implementations(Vec); impl From> for Implementations { fn from(attributes: Vec) -> Self { + let mut add_methods = Vec::new(); let mut all = Traits::default(); let mut implementations: HashMap<_, Traits> = HashMap::new(); for attribute in attributes { @@ -161,6 +188,7 @@ impl From> for Implementations { all.extend(&attribute.traits); } } + Attr::AddMethods(attribute) => add_methods.push(attribute), } } @@ -172,7 +200,16 @@ impl From> for Implementations { } } - Self(implementations.into_iter().map(Into::into).collect()) + Self( + implementations + .into_iter() + .map(|(generics, traits)| Implementation { + generics, + traits, + add_methods: add_methods.clone(), + }) + .collect(), + ) } } diff --git a/automation_macro/src/lib.rs b/automation_macro/src/lib.rs index 2268de7..3e03abe 100644 --- a/automation_macro/src/lib.rs +++ b/automation_macro/src/lib.rs @@ -32,26 +32,40 @@ pub fn lua_serialize(input: proc_macro::TokenStream) -> proc_macro::TokenStream /// Derive macro generating an impl for the trait `::mlua::UserData` /// +/// # Device traits /// The `device(traits)` attribute can be used to tell the macro what traits are implemented so that /// the appropriate methods can automatically be registered. /// If the struct does not have any type parameters the syntax is very simple: -/// ``` +/// ```rust /// #[device(traits(TraitA, TraitB))] /// ``` /// /// If the type does have type parameters you will have to manually specify all variations that /// have the trait available: -/// ``` +/// ```rust /// #[device(traits(TraitA, TraitB for , ))] /// ``` /// If multiple of these attributes are specified they will all combined appropriately. /// /// -/// # NOTE +/// ## NOTE /// If your type _has_ type parameters any instance of the traits attribute that does not specify /// any type parameters will have the traits applied to _all_ other type parameter variations /// listed in the other trait attributes. This behavior only applies if there is at least one /// instance with type parameters specified. +/// +/// # Additional methods +/// Additional methods can be added by using the `device(add_methods)` attribute. This attribute +/// takes the path to a function with the following signature that can register the additional methods: +/// +/// ```rust +/// # struct D; +/// fn top_secret>(methods: &mut M) {} +/// ``` +/// It can then be registered with: +/// ```rust +/// #[device(add_methods(top_secret))] +/// ``` #[proc_macro_derive(Device, attributes(device))] pub fn device(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = parse_macro_input!(input as DeriveInput);