Implement metatype proc-macro

This commit is contained in:
2025-06-19 20:38:40 +10:00
parent af81879b42
commit 1c821d8804
8 changed files with 325 additions and 142 deletions

View File

@@ -51,8 +51,8 @@ fn generate_type(ty: &Ident) -> Result<TokenStream> {
#fmt(#cdecl_fmt)
}
fn build(s: &mut #ffi::TypeBuilder) {
s.cdef::<Self>().metatype::<Self>();
fn build(b: &mut #ffi::TypeBuilder) {
b.cdef::<Self>().metatype::<Self>();
}
}
})
@@ -71,7 +71,7 @@ fn generate_cdef_structure(str: &mut ItemStruct) -> Result<TokenStream> {
Ok(quote! {
unsafe impl #ffi::CDef for #ty {
fn build(s: &mut #ffi::CDefBuilder) { #build }
fn build(b: &mut #ffi::CDefBuilder) { #build }
}
})
}
@@ -90,20 +90,19 @@ fn generate_cdef_enum(enu: &mut ItemEnum) -> Result<TokenStream> {
.iter_mut()
.map(|variant| {
let build = generate_build_cdef(&to_cfields(&mut variant.fields)?)?;
Ok(quote! { s.inner_struct(|s| { #build }); })
Ok(quote! { b.inner_struct(|b| { #build }); })
})
.collect::<Result<Vec<_>>>()?;
Ok(quote! {
unsafe impl #ffi::CDef for #ty {
fn build(s: &mut #ffi::CDefBuilder) {
s.field::<::std::ffi::c_int>("__tag").inner_union(|s| { #(#build)* });
fn build(b: &mut #ffi::CDefBuilder) {
b.field::<::std::ffi::c_int>("__tag").inner_union(|b| { #(#build)* });
}
}
})
}
#[derive(Debug)]
struct CField {
name: String,
ty: Type,
@@ -130,7 +129,7 @@ fn to_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
.collect()
}
#[derive(Debug, Default)]
#[derive(Default)]
struct CFieldAttrs {
opaque: bool,
}
@@ -189,14 +188,14 @@ fn generate_build_cdef(fields: &[CField]) -> Result<TokenStream> {
if field.attrs.opaque {
body.push(if i == len - 1 {
let a = offset(i);
quote! { s.field_opaque(size - #a); } // last field
quote! { b.field_opaque(size - #a); } // last field
} else {
let a = offset(i);
let b = offset(i + 1);
quote! { s.field_opaque(#b - #a); }
quote! { b.field_opaque(#b - #a); }
});
} else {
body.push(quote! { s.field::<#ty>(#name); });
body.push(quote! { b.field::<#ty>(#name); });
}
}

View File

@@ -1,20 +1,20 @@
use crate::utils::{ffi_crate, syn_assert, syn_error, ty_name};
use crate::utils::{ffi_crate, is_primitive, is_unit, pat_ident, syn_assert, syn_error, ty_name};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{spanned::*, *};
pub fn transform(mut item: ItemImpl) -> Result<TokenStream> {
pub fn transform(mut imp: ItemImpl) -> Result<TokenStream> {
syn_assert!(
item.generics.params.is_empty(),
item.generics,
imp.generics.params.is_empty(),
imp.generics,
"cannot be generic (not yet implemented)"
);
let impls = generate_impls(&mut item)?;
let mod_name = format_ident!("__metatype__{}", ty_name(&item.self_ty)?);
let impls = generate_impls(&mut imp)?;
let mod_name = format_ident!("__metatype__{}", ty_name(&imp.self_ty)?);
Ok(quote! {
#item
#imp
#[doc(hidden)]
#[allow(unused, non_snake_case)]
@@ -27,23 +27,246 @@ pub fn transform(mut item: ItemImpl) -> Result<TokenStream> {
fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
let ffi = ffi_crate();
let ffi_funcs = get_ffi_functions(imp)?;
let ffi_wrappers: Vec<_> = ffi_funcs
.iter()
.map(generate_ffi_wrapper)
.collect::<Result<_>>()?;
let ffi_register: Vec<_> = ffi_funcs
.iter()
.map(generate_ffi_register)
.collect::<Result<_>>()?;
let lua_funcs = get_lua_functions(imp)?;
let lua_register: Vec<_> = lua_funcs
.iter()
.map(generate_lua_register)
.collect::<Result<_>>()?;
let ty = &*imp.self_ty;
let name = ty_name(&ty)?;
Ok(quote! {
unsafe impl #ffi::Metatype for #ty {
type Target = Self;
fn build(s: &mut #ffi::MetatypeBuilder) {}
fn build(b: &mut #ffi::MetatypeBuilder) {
#(#ffi_register)*
#(#lua_register)*
}
}
impl #ty {
impl #ty { #(#ffi_wrappers)* }
})
}
struct FfiFunction {
name: Ident,
rust_name: Ident,
lua_name: String,
c_name: String,
params: Vec<PatType>,
ret: Type,
ret_out: bool,
}
fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
let mut funcs = vec![];
for item in imp.items.iter_mut() {
if let ImplItem::Fn(func) = item
&& let Some(ref abi) = func.sig.abi
&& let Some(ref abi) = abi.name
&& abi.value() == "Lua-C"
{
func.sig.abi = None;
let params = func
.sig
.inputs
.iter()
.map(|arg| {
Ok(match arg {
FnArg::Receiver(recv) => {
let ty = &recv.ty;
parse_quote! { self: #ty }
}
FnArg::Typed(ty) => ty.clone(),
})
})
.collect::<Result<_>>()?;
let ret = match func.sig.output {
ReturnType::Default => parse_quote!(()),
ReturnType::Type(_, ref ty) => (**ty).clone(),
};
// whether to use out-param for return values
let ret_out = !is_primitive(&ret);
funcs.push(FfiFunction {
name: func.sig.ident.clone(),
rust_name: format_ident!("__ffi_{}", func.sig.ident),
lua_name: format!("{}", func.sig.ident),
c_name: format!("{}_{}", ty_name(&imp.self_ty)?, func.sig.ident),
params,
ret,
ret_out,
});
}
}
Ok(funcs)
}
#[derive(Debug)]
enum FfiArgType {
Default,
}
fn get_ffi_arg_type(ty: &Type) -> FfiArgType {
FfiArgType::Default
}
fn escape_self(name: &Ident) -> Ident {
if name == "self" {
format_ident!("__self")
} else {
name.clone()
}
}
fn generate_ffi_wrapper(func: &FfiFunction) -> Result<TokenStream> {
let ffi = ffi_crate();
let name = &func.name;
let rust_name = &func.rust_name;
let c_name = &func.c_name;
let mut params = vec![];
let mut args = vec![];
for param in func.params.iter() {
let name = escape_self(pat_ident(&param.pat)?);
let ty = &param.ty;
match get_ffi_arg_type(ty) {
FfiArgType::Default => {
params.push(quote! { #name: <#ty as #ffi::FromFfi>::From });
args.push(quote! { <#ty as #ffi::FromFfi>::convert(#name) });
}
}
}
// make return by out-param the first parameter
let (ret, do_ret) = if func.ret_out {
let ret = &func.ret;
params.insert(0, quote! { __ret_out: *mut #ret });
(
quote! { () },
quote! { unsafe { ::std::ptr::write(__ret_out, __ret) }; },
)
} else {
let ret = &func.ret;
(quote! { #ret }, quote! { return __ret; })
};
Ok(quote! {
#[unsafe(export_name = #c_name)]
unsafe extern "C" fn #rust_name(#(#params),*) -> #ret {
let __ret = Self::#name(#(#args),*);
#do_ret
}
})
}
#[derive(Debug)]
struct FfiFunction {}
fn generate_ffi_register(func: &FfiFunction) -> Result<TokenStream> {
let ffi = ffi_crate();
let lua_name = &func.lua_name;
let c_name = &func.c_name;
let mut params = vec![];
let mut asserts = vec![];
#[derive(Debug)]
struct LuaFunction {}
for param in func.params.iter() {
let name = format!("{}", pat_ident(&param.pat)?);
let ty = &param.ty;
params.push(match get_ffi_arg_type(ty) {
FfiArgType::Default => quote! { b.param::<#ty>(#name); },
});
}
let ret = &func.ret;
let ret_conv = if is_unit(ret) {
quote! { #ffi::FfiReturnConvention::Void }
} else if func.ret_out {
asserts.push(quote! { #ffi::__internal::assert_type_ne_all!(#ret, ()); });
quote! { #ffi::FfiReturnConvention::OutParam }
} else {
asserts.push(quote! { #ffi::__internal::assert_type_ne_all!(#ret, ()); });
quote! { #ffi::FfiReturnConvention::ByValue }
};
Ok(quote! {
b.index(#lua_name, |b| {
#(#asserts)*
#(#params)*
b.call::<#ret>(#c_name, #ret_conv);
});
})
}
struct LuaFunction {
name: String,
params: Vec<PatType>,
body: Block,
}
fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
let mut funcs = vec![];
let mut i = 0;
while i < imp.items.len() {
if let ImplItem::Fn(ref mut func) = imp.items[i]
&& let Some(ref abi) = func.sig.abi
&& let Some(ref abi) = abi.name
&& abi.value() == "Lua"
{
let params = func
.sig
.inputs
.iter()
.map(|arg| {
Ok(match arg {
FnArg::Receiver(recv) => {
syn_assert!(ty_name(&recv.ty)? == "Self", recv, "must be `self`");
syn_assert!(recv.mutability.is_none(), recv, "cannot be mut");
parse_quote! { self: cdata }
}
FnArg::Typed(ty) => ty.clone(),
})
})
.collect::<Result<_>>()?;
funcs.push(LuaFunction {
name: format!("{}", func.sig.ident),
body: func.block.clone(),
params,
});
imp.items.remove(i);
} else {
i += 1;
}
}
Ok(funcs)
}
fn generate_lua_register(func: &LuaFunction) -> Result<TokenStream> {
let ffi = ffi_crate();
let name = &func.name;
let params = &func.params;
let body = &func.body;
Ok(quote! {
b.index_raw(#name, #ffi::__internal::luaify!(|#(#params),*| #body));
})
}

View File

@@ -1,5 +1,5 @@
use std::{env, fmt};
use syn::{ext::*, spanned::*, *};
use std::env;
use syn::{spanned::*, *};
macro_rules! syn_error {
($src:expr, $($fmt:expr),+) => {{
@@ -33,3 +33,68 @@ pub fn ty_name(ty: &Type) -> Result<&Ident> {
_ => syn_error!(ty, "expected ident"),
}
}
pub fn pat_ident(pat: &Pat) -> Result<&Ident> {
match pat {
Pat::Ident(ident) => Ok(&ident.ident),
_ => syn_error!(pat, "expected ident"),
}
}
pub fn is_unit(ty: &Type) -> bool {
if let Type::Tuple(tuple) = ty
&& tuple.elems.is_empty()
{
true
} else {
false
}
}
pub fn is_primitive(ty: &Type) -> bool {
match ty {
Type::Tuple(tuple) if tuple.elems.is_empty() => true, // unit type
Type::Reference(_) | Type::Ptr(_) => true,
Type::Paren(paren) => is_primitive(&paren.elem),
Type::Path(path) => {
if let Some(name) = path.path.get_ident() {
matches!(
format!("{name}").as_str(),
"bool"
| "u8"
| "u16"
| "u32"
| "u64"
| "usize"
| "i8"
| "i16"
| "i32"
| "i64"
| "isize"
| "f32"
| "f64"
| "char"
| "c_char"
| "c_schar"
| "c_uchar"
| "c_short"
| "c_ushort"
| "c_int"
| "c_uint"
| "c_long"
| "c_ulong"
| "c_longlong"
| "c_ulonglong"
| "c_float"
| "c_double"
| "c_size_t"
| "c_ssize_t"
| "c_ptrdiff_t"
)
} else {
false
}
}
_ => false,
}
}