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!
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -167,6 +167,7 @@ name = "automation_macro"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itertools",
|
"itertools",
|
||||||
|
"mlua",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.106",
|
||||||
|
|||||||
@@ -11,3 +11,6 @@ itertools = { workspace = true }
|
|||||||
proc-macro2 = { workspace = true }
|
proc-macro2 = { workspace = true }
|
||||||
quote = { workspace = true }
|
quote = { workspace = true }
|
||||||
syn = { workspace = true }
|
syn = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
mlua = { workspace = true }
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use syn::{Attribute, DeriveInput, Token, parenthesized};
|
|||||||
|
|
||||||
enum Attr {
|
enum Attr {
|
||||||
Trait(TraitAttr),
|
Trait(TraitAttr),
|
||||||
|
AddMethods(AddMethodsAttr),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Attr {
|
impl Parse for Attr {
|
||||||
@@ -19,7 +20,13 @@ impl Parse for Attr {
|
|||||||
|
|
||||||
let attr = match ident.to_string().as_str() {
|
let attr = match ident.to_string().as_str() {
|
||||||
"traits" => Attr::Trait(attr.parse()?),
|
"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)
|
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<Self> {
|
||||||
|
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 {
|
struct Implementation {
|
||||||
generics: Option<syn::AngleBracketedGenericArguments>,
|
generics: Option<syn::AngleBracketedGenericArguments>,
|
||||||
traits: Traits,
|
traits: Traits,
|
||||||
}
|
add_methods: Vec<AddMethodsAttr>,
|
||||||
|
|
||||||
impl From<(Option<syn::AngleBracketedGenericArguments>, Traits)> for Implementation {
|
|
||||||
fn from(value: (Option<syn::AngleBracketedGenericArguments>, Traits)) -> Self {
|
|
||||||
Self {
|
|
||||||
generics: value.0,
|
|
||||||
traits: value.1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl quote::ToTokens for Implementation {
|
impl quote::ToTokens for Implementation {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
let Self { generics, traits } = &self;
|
let Self {
|
||||||
|
generics,
|
||||||
|
traits,
|
||||||
|
add_methods,
|
||||||
|
} = &self;
|
||||||
|
|
||||||
tokens.extend(quote! {
|
tokens.extend(quote! {
|
||||||
#generics {
|
#generics {
|
||||||
@@ -135,6 +157,10 @@ impl quote::ToTokens for Implementation {
|
|||||||
methods.add_async_method("get_id", async |_lua, this, _: ()| { Ok(this.get_id()) });
|
methods.add_async_method("get_id", async |_lua, this, _: ()| { Ok(this.get_id()) });
|
||||||
|
|
||||||
#traits
|
#traits
|
||||||
|
|
||||||
|
#(
|
||||||
|
#add_methods(methods);
|
||||||
|
)*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -145,6 +171,7 @@ struct Implementations(Vec<Implementation>);
|
|||||||
|
|
||||||
impl From<Vec<Attr>> for Implementations {
|
impl From<Vec<Attr>> for Implementations {
|
||||||
fn from(attributes: Vec<Attr>) -> Self {
|
fn from(attributes: Vec<Attr>) -> Self {
|
||||||
|
let mut add_methods = Vec::new();
|
||||||
let mut all = Traits::default();
|
let mut all = Traits::default();
|
||||||
let mut implementations: HashMap<_, Traits> = HashMap::new();
|
let mut implementations: HashMap<_, Traits> = HashMap::new();
|
||||||
for attribute in attributes {
|
for attribute in attributes {
|
||||||
@@ -161,6 +188,7 @@ impl From<Vec<Attr>> for Implementations {
|
|||||||
all.extend(&attribute.traits);
|
all.extend(&attribute.traits);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Attr::AddMethods(attribute) => add_methods.push(attribute),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +200,16 @@ impl From<Vec<Attr>> 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(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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`
|
/// 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 `device(traits)` attribute can be used to tell the macro what traits are implemented so that
|
||||||
/// the appropriate methods can automatically be registered.
|
/// the appropriate methods can automatically be registered.
|
||||||
/// If the struct does not have any type parameters the syntax is very simple:
|
/// If the struct does not have any type parameters the syntax is very simple:
|
||||||
/// ```
|
/// ```rust
|
||||||
/// #[device(traits(TraitA, TraitB))]
|
/// #[device(traits(TraitA, TraitB))]
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// If the type does have type parameters you will have to manually specify all variations that
|
/// If the type does have type parameters you will have to manually specify all variations that
|
||||||
/// have the trait available:
|
/// have the trait available:
|
||||||
/// ```
|
/// ```rust
|
||||||
/// #[device(traits(TraitA, TraitB for <StateA>, <StateB>))]
|
/// #[device(traits(TraitA, TraitB for <StateA>, <StateB>))]
|
||||||
/// ```
|
/// ```
|
||||||
/// If multiple of these attributes are specified they will all combined appropriately.
|
/// 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
|
/// 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
|
/// 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
|
/// listed in the other trait attributes. This behavior only applies if there is at least one
|
||||||
/// instance with type parameters specified.
|
/// 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<M: mlua::UserDataMethods<D>>(methods: &mut M) {}
|
||||||
|
/// ```
|
||||||
|
/// It can then be registered with:
|
||||||
|
/// ```rust
|
||||||
|
/// #[device(add_methods(top_secret))]
|
||||||
|
/// ```
|
||||||
#[proc_macro_derive(Device, attributes(device))]
|
#[proc_macro_derive(Device, attributes(device))]
|
||||||
pub fn device(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
pub fn device(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
let ast = parse_macro_input!(input as DeriveInput);
|
let ast = parse_macro_input!(input as DeriveInput);
|
||||||
|
|||||||
Reference in New Issue
Block a user