extern crate proc_macro;
use proc_macro::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn::{ImplItem, ItemImpl, PatType, ReturnType, Signature, Type};
enum MluaReturnType {
Void,
Primitive,
Result,
}
enum TakesSelf {
No,
Yes,
Mut,
}
struct ExportedFn {
ret: MluaReturnType,
takes_self: TakesSelf,
is_field: bool,
sig: Signature,
}
#[proc_macro_attribute]
pub fn mlua_bridge(_attr: TokenStream, item: TokenStream) -> TokenStream {
let impl_item = item.clone();
let impl_item = syn::parse_macro_input!(impl_item as ItemImpl);
let mut fns = vec![];
for ele in impl_item.items {
let ImplItem::Fn(f) = ele else {
continue;
};
fns.push(f);
}
let mut exported_fns = vec![];
for ele in fns.into_iter() {
let sig = ele.sig;
let ret = match &sig.output {
ReturnType::Default => MluaReturnType::Void,
ReturnType::Type(_, t) => match t.as_ref() {
Type::Path(r) => {
if r.path.segments.last().is_some_and(|r| r.ident == "Result") {
MluaReturnType::Result
} else {
MluaReturnType::Primitive
}
}
_ => MluaReturnType::Primitive,
},
};
let takes_self = sig
.inputs
.first()
.map(|a| match a {
syn::FnArg::Receiver(r) => {
if r.mutability.is_some() {
TakesSelf::Mut
} else {
TakesSelf::Yes
}
}
syn::FnArg::Typed(_) => TakesSelf::No,
})
.unwrap_or(TakesSelf::No);
let is_field = matches!(&sig.ident.to_string()[..4], "get_" | "set_");
exported_fns.push(ExportedFn {
ret,
takes_self,
is_field,
sig,
});
}
let (fields, funcs): (Vec<_>, Vec<_>) = exported_fns.into_iter().partition(|x| x.is_field);
let mut funcs_impl = quote! {};
let mut fields_impl = quote! {};
for f in funcs {
let name = f.sig.ident.clone();
let self_name = f.sig.ident;
let name = quote! {stringify!(#name)};
let args: Vec<PatType> = f
.sig
.inputs
.iter()
.cloned()
.filter_map(|x| match x {
syn::FnArg::Receiver(_) => None,
syn::FnArg::Typed(pat_type) => Some(pat_type),
})
.collect();
let args_tup = args.iter().map(|x| x.pat.clone());
let args_typ = args.iter().map(|x| x.ty.clone());
let args_tup = quote! {(#(#args_tup),*)};
let args_typ = quote! { (#(#args_typ),*)};
let args = quote! {#args_tup: #args_typ};
let question = match f.ret {
MluaReturnType::Void | MluaReturnType::Primitive => quote! {},
MluaReturnType::Result => quote! {?},
};
let method = match &f.takes_self {
TakesSelf::No => quote! {add_function_mut},
TakesSelf::Yes => quote! {add_method},
TakesSelf::Mut => quote! {add_method_mut},
};
let self_ident = match &f.takes_self {
TakesSelf::No => quote! {Self::},
TakesSelf::Yes | TakesSelf::Mut => quote! {s.},
};
let closure_def = match &f.takes_self {
TakesSelf::No => quote! { |_lua, #args| },
TakesSelf::Yes | TakesSelf::Mut => quote! { |_lua, s, #args| },
};
let t = quote! {
methods.#method(#name, #closure_def Ok(#self_ident #self_name #args_tup #question));
};
t.to_tokens(&mut funcs_impl);
}
for f in fields {
let name = f.sig.ident.clone();
let name = format_ident!("{}", name.to_string()[4..]);
let self_name = f.sig.ident.clone();
let name = quote! {stringify!(#name)};
let question = match &f.ret {
MluaReturnType::Void | MluaReturnType::Primitive => quote! {},
MluaReturnType::Result => quote! {?},
};
let self_ident = match &f.takes_self {
TakesSelf::No => quote! {Self::},
TakesSelf::Yes | TakesSelf::Mut => quote! {s.},
};
let t = match (&f.takes_self, &f.sig.ident.to_string().starts_with("set")) {
(TakesSelf::No, true) => quote! {
fields.add_field_function_set(#name, |_lua, _, v| Ok(#self_ident #self_name(v)#question));
},
(TakesSelf::No, false) => quote! {
fields.add_field_function_get(#name, |_lua, _| Ok(#self_ident #self_name()#question));
},
(_, true) => quote! {
fields.add_field_method_set(#name, |_lua, s, v| Ok(#self_ident #self_name(v)#question));
},
(_, false) => quote! {
fields.add_field_method_get(#name, |_lua, s| Ok(#self_ident #self_name()#question));
},
};
t.to_tokens(&mut fields_impl);
}
let item_ident = impl_item.self_ty.into_token_stream();
let trait_impl = quote! {
impl ::mlua::UserData for #item_ident {
fn add_methods<M: ::mlua::UserDataMethods<Self>>(methods: &mut M) {
#funcs_impl
}
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
#fields_impl
}
}
};
let mut item = proc_macro2::TokenStream::from(item);
trait_impl.to_tokens(&mut item);
item.into()
}