Refactor luaffi proc-macro
This commit is contained in:
parent
f8e7b8ae62
commit
3dd375b071
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
__internal::{display, type_id},
|
||||
Cdef, CdefBuilder, FfiReturnConvention, IntoFfi, Metatype, MetatypeBuilder, Type, TypeBuilder,
|
||||
Cdef, CdefBuilder, IntoFfi, Metatype, MetatypeBuilder, Type, TypeBuilder, TypeType,
|
||||
UnsafeExternCFn,
|
||||
};
|
||||
use luaify::luaify;
|
||||
@ -43,7 +43,7 @@ pub struct lua_future<F: Future<Output: IntoFfi>> {
|
||||
sig: Signature,
|
||||
poll: fn(Pin<&mut Self>, cx: &mut Context) -> Poll<()>,
|
||||
state: State<F>,
|
||||
take: unsafe extern "C" fn(&mut Self) -> <F::Output as IntoFfi>::To,
|
||||
take: unsafe extern "C" fn(&mut Self) -> <F::Output as IntoFfi>::Into,
|
||||
drop: unsafe extern "C" fn(&mut Self),
|
||||
}
|
||||
|
||||
@ -94,7 +94,7 @@ impl<F: Future<Output: IntoFfi>> lua_future<F> {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn take(&mut self) -> <F::Output as IntoFfi>::To {
|
||||
unsafe extern "C" fn take(&mut self) -> <F::Output as IntoFfi>::Into {
|
||||
// `fut:__take()` returns the fulfilled value by-value (not by out-param) because if we
|
||||
// preallocate a cdata for the out-param and the thread for some reason gets dropped and
|
||||
// never resumed, the GC could call the destructor on an uninitialised cdata.
|
||||
@ -136,8 +136,12 @@ unsafe impl<F: Future<Output: IntoFfi> + 'static> Type for lua_future<F> {
|
||||
display!("future__{:x}", type_id::<F>())
|
||||
}
|
||||
|
||||
fn ty() -> TypeType {
|
||||
TypeType::Aggregate
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
display!("struct future__{:x} {name}", type_id::<F>())
|
||||
display!("struct {} {name}", Self::name())
|
||||
}
|
||||
|
||||
fn build(s: &mut TypeBuilder) {
|
||||
@ -148,7 +152,7 @@ unsafe impl<F: Future<Output: IntoFfi> + 'static> Type for lua_future<F> {
|
||||
unsafe impl<F: Future<Output: IntoFfi> + 'static> Cdef for lua_future<F> {
|
||||
fn build(s: &mut CdefBuilder) {
|
||||
s.field_opaque(mem::offset_of!(Self, take)) // opaque .sig, .poll and .state
|
||||
.field::<UnsafeExternCFn<(&mut Self,), <F::Output as IntoFfi>::To>>("__take")
|
||||
.field::<UnsafeExternCFn<(&mut Self,), <F::Output as IntoFfi>::Into>>("__take")
|
||||
.field::<UnsafeExternCFn<(&mut Self,), ()>>("__drop");
|
||||
}
|
||||
}
|
||||
@ -162,24 +166,25 @@ unsafe impl<F: Future<Output: IntoFfi> + 'static> Metatype for lua_future<F> {
|
||||
}
|
||||
|
||||
unsafe impl<F: Future<Output: IntoFfi> + 'static> IntoFfi for lua_future<F> {
|
||||
type To = lua_future<F>;
|
||||
type Into = lua_future<F>;
|
||||
|
||||
fn convert(self) -> Self::To {
|
||||
fn convert(self) -> Self::Into {
|
||||
self
|
||||
}
|
||||
|
||||
fn postlude(ret: &str, _conv: FfiReturnConvention) -> impl Display {
|
||||
fn postlude(ret: &str) -> impl Display {
|
||||
// When returning a future from Rust to Lua, yield it immediately to the runtime which will
|
||||
// poll it to completion in the background, then take the fulfilled value once the thread
|
||||
// gets resumed. Lua user code should never to worry about awaiting futures.
|
||||
//
|
||||
// Once the current thread gets resumed and we take the future's fulfilled value, we clear
|
||||
// the finaliser on the future and forget it (there is nothing to call drop on).
|
||||
// the finaliser on the future and forget it (there is nothing to drop once the value is
|
||||
// taken).
|
||||
//
|
||||
// `coroutine.yield` is cached as `yield` and `ffi.gc` as `gc` in locals (see lib.rs)
|
||||
// `coroutine.yield` is cached as `__yield` and `ffi.gc` as `__gc` in locals (see lib.rs)
|
||||
display!(
|
||||
"yield({ret}); {ret} = gc({ret}, nil):__take(); {}",
|
||||
<F::Output as IntoFfi>::postlude(ret, FfiReturnConvention::ByValue)
|
||||
"__yield({ret}); {ret} = __gc({ret}, nil):__take(); {}",
|
||||
<F::Output as IntoFfi>::postlude(ret)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ pub mod result;
|
||||
// `ffi.keep(obj)`.
|
||||
//
|
||||
// https://github.com/LuaJIT/LuaJIT/issues/1167
|
||||
pub const KEEP_FN: &str = "luaffi_keep";
|
||||
pub(crate) const KEEP_FN: &str = "luaffi_keep";
|
||||
#[unsafe(export_name = "luaffi_keep")]
|
||||
extern "C" fn __keep(_ptr: *const c_void) {}
|
||||
export![__keep];
|
||||
@ -151,6 +151,7 @@ impl Registry {
|
||||
}
|
||||
|
||||
pub fn declare<T: Type>(&mut self, name: impl Display) -> &mut Self {
|
||||
assert!(T::ty() != TypeType::Void, "cannot declare void type");
|
||||
self.include::<T>()
|
||||
.funcs
|
||||
.insert(name.to_string())
|
||||
@ -159,6 +160,7 @@ impl Registry {
|
||||
}
|
||||
|
||||
pub fn preload<T: Type>(&mut self, name: impl Display) -> &mut Self {
|
||||
assert!(T::ty() != TypeType::Void, "cannot declare void type");
|
||||
self.include::<T>();
|
||||
let ct = T::name();
|
||||
writeln!(
|
||||
@ -189,6 +191,8 @@ impl Display for Registry {
|
||||
|
||||
pub unsafe trait Type {
|
||||
fn name() -> impl Display;
|
||||
fn ty() -> TypeType;
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display;
|
||||
fn extern_cdecl(name: impl Display) -> impl Display {
|
||||
Self::cdecl(name)
|
||||
@ -197,6 +201,13 @@ pub unsafe trait Type {
|
||||
fn build(b: &mut TypeBuilder);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum TypeType {
|
||||
Void,
|
||||
Primitive,
|
||||
Aggregate,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TypeBuilder<'r> {
|
||||
registry: &'r mut Registry,
|
||||
@ -253,6 +264,7 @@ impl<'r> CdefBuilder<'r> {
|
||||
}
|
||||
|
||||
pub fn field<T: Type>(&mut self, name: impl Display) -> &mut Self {
|
||||
assert!(T::ty() != TypeType::Void, "cannot declare void field");
|
||||
self.registry.include::<T>();
|
||||
self.field_raw(T::cdecl(name))
|
||||
}
|
||||
@ -390,21 +402,13 @@ pub unsafe trait FromFfi: Sized {
|
||||
}
|
||||
|
||||
pub unsafe trait IntoFfi: Sized {
|
||||
type To: Type + Sized;
|
||||
type Into: Type + Sized;
|
||||
|
||||
fn postlude(_ret: &str, _conv: FfiReturnConvention) -> impl Display {
|
||||
fn postlude(_ret: &str) -> impl Display {
|
||||
""
|
||||
}
|
||||
|
||||
fn convert(self) -> Self::To;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum FfiReturnConvention {
|
||||
Void,
|
||||
#[default]
|
||||
ByValue,
|
||||
ByOutParam,
|
||||
fn convert(self) -> Self::Into;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -428,6 +432,11 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
|
||||
}
|
||||
|
||||
pub fn param<T: FromFfi>(&mut self, name: impl Display) -> &mut Self {
|
||||
assert!(
|
||||
T::From::ty() != TypeType::Void,
|
||||
"cannot declare void parameter"
|
||||
);
|
||||
|
||||
(!self.params.is_empty()).then(|| self.params.push_str(", "));
|
||||
(!self.args.is_empty()).then(|| self.args.push_str(", "));
|
||||
write!(self.params, "{name}").unwrap();
|
||||
@ -468,7 +477,7 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn call<T: IntoFfi>(&mut self, func: impl Display, ret: FfiReturnConvention) {
|
||||
pub fn call<T: IntoFfi>(&mut self, func: impl Display) {
|
||||
let Self {
|
||||
metatype,
|
||||
params,
|
||||
@ -480,21 +489,21 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
|
||||
let lua = &mut metatype.lua;
|
||||
write!(lua, "function({params}) {prelude}").unwrap();
|
||||
|
||||
match ret {
|
||||
FfiReturnConvention::Void => {
|
||||
match T::Into::ty() {
|
||||
TypeType::Void => {
|
||||
write!(lua, "__C.{func}({args}); {postlude}end").unwrap();
|
||||
}
|
||||
FfiReturnConvention::ByValue => {
|
||||
let check = T::postlude("__res", ret);
|
||||
TypeType::Primitive => {
|
||||
let check = T::postlude("__res");
|
||||
write!(
|
||||
lua,
|
||||
"local __res = __C.{func}({args}); {check}{postlude}return __res; end"
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
FfiReturnConvention::ByOutParam => {
|
||||
let ct = T::To::name();
|
||||
let check = T::postlude("__res", ret);
|
||||
TypeType::Aggregate => {
|
||||
let ct = T::Into::name();
|
||||
let check = T::postlude("__res");
|
||||
write!(lua, "local __res = __new(__ct.{ct}); __C.{func}(__res").unwrap();
|
||||
if !args.is_empty() {
|
||||
write!(lua, ", {args}").unwrap();
|
||||
@ -505,15 +514,51 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_primitive {
|
||||
($rtype:ty, $ctype:expr) => {
|
||||
unsafe impl Type for $rtype {
|
||||
//
|
||||
// SAFETY: Unit type return maps to a C void return, which is a nil return in lua. There is no
|
||||
// equivalent to passing a unit type as an argument in C.
|
||||
//
|
||||
macro_rules! impl_void {
|
||||
($rty:ty) => {
|
||||
unsafe impl Type for $rty {
|
||||
fn name() -> impl Display {
|
||||
$ctype
|
||||
"void"
|
||||
}
|
||||
|
||||
fn ty() -> TypeType {
|
||||
TypeType::Void
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
display!("{} {name}", $ctype)
|
||||
display!("void {name}")
|
||||
}
|
||||
|
||||
fn build(_b: &mut TypeBuilder) {}
|
||||
}
|
||||
|
||||
unsafe impl IntoFfi for $rty {
|
||||
type Into = ();
|
||||
fn convert(self) -> Self::Into {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_void!(());
|
||||
impl_void!(c_void);
|
||||
|
||||
macro_rules! impl_primitive {
|
||||
($rty:ty, $cty:expr) => {
|
||||
unsafe impl Type for $rty {
|
||||
fn name() -> impl Display {
|
||||
$cty
|
||||
}
|
||||
|
||||
fn ty() -> TypeType {
|
||||
TypeType::Primitive
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
display!("{} {name}", $cty)
|
||||
}
|
||||
|
||||
fn build(_b: &mut TypeBuilder) {}
|
||||
@ -521,8 +566,6 @@ macro_rules! impl_primitive {
|
||||
};
|
||||
}
|
||||
|
||||
impl_primitive!((), "void");
|
||||
impl_primitive!(c_void, "void");
|
||||
impl_primitive!(bool, "bool");
|
||||
impl_primitive!(u8, "uint8_t");
|
||||
impl_primitive!(u16, "uint16_t");
|
||||
@ -537,24 +580,6 @@ impl_primitive!(isize, "intptr_t");
|
||||
impl_primitive!(c_float, "float");
|
||||
impl_primitive!(c_double, "double");
|
||||
|
||||
unsafe impl IntoFfi for () {
|
||||
//
|
||||
// SAFETY: Unit type return maps to a C void return, which is a nil return in lua. There is no
|
||||
// equivalent to passing a unit type as an argument in C. `c_void` cannot be returned from rust
|
||||
// so it should return the unit type instead.
|
||||
//
|
||||
type To = ();
|
||||
|
||||
fn convert(self) -> Self::To {}
|
||||
fn postlude(_ret: &str, conv: FfiReturnConvention) -> impl Display {
|
||||
assert!(
|
||||
conv == FfiReturnConvention::Void,
|
||||
"void type cannot be instantiated"
|
||||
);
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl FromFfi for bool {
|
||||
type From = bool;
|
||||
|
||||
@ -570,24 +595,16 @@ unsafe impl FromFfi for bool {
|
||||
}
|
||||
|
||||
unsafe impl IntoFfi for bool {
|
||||
type To = bool;
|
||||
type Into = bool;
|
||||
|
||||
fn convert(self) -> Self::To {
|
||||
fn convert(self) -> Self::Into {
|
||||
self
|
||||
}
|
||||
|
||||
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
|
||||
disp(move |f| match conv {
|
||||
FfiReturnConvention::Void => unreachable!(),
|
||||
FfiReturnConvention::ByValue => Ok(()),
|
||||
FfiReturnConvention::ByOutParam => write!(f, "{ret} = {ret} ~= 0; "),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_number_fromabi {
|
||||
($rtype:ty) => {
|
||||
unsafe impl FromFfi for $rtype {
|
||||
($rty:ty) => {
|
||||
unsafe impl FromFfi for $rty {
|
||||
type From = Self;
|
||||
|
||||
fn prelude(arg: &str) -> impl Display {
|
||||
@ -615,21 +632,13 @@ impl_number_fromabi!(f32);
|
||||
impl_number_fromabi!(f64);
|
||||
|
||||
macro_rules! impl_number_intoabi {
|
||||
($rtype:ty) => {
|
||||
unsafe impl IntoFfi for $rtype {
|
||||
type To = Self;
|
||||
($rty:ty) => {
|
||||
unsafe impl IntoFfi for $rty {
|
||||
type Into = Self;
|
||||
|
||||
fn convert(self) -> Self::To {
|
||||
fn convert(self) -> Self::Into {
|
||||
self
|
||||
}
|
||||
|
||||
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
|
||||
disp(move |f| match conv {
|
||||
FfiReturnConvention::Void => unreachable!(),
|
||||
FfiReturnConvention::ByValue => Ok(()),
|
||||
FfiReturnConvention::ByOutParam => write!(f, "{ret} = tonumber({ret}); "),
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -648,21 +657,20 @@ impl_number_intoabi!(c_float);
|
||||
impl_number_intoabi!(c_double);
|
||||
|
||||
macro_rules! impl_bigint_intoabi {
|
||||
($rtype:ty) => {
|
||||
unsafe impl IntoFfi for $rtype {
|
||||
type To = Self;
|
||||
($rty:ty) => {
|
||||
unsafe impl IntoFfi for $rty {
|
||||
type Into = Self;
|
||||
|
||||
fn convert(self) -> Self::To {
|
||||
fn convert(self) -> Self::Into {
|
||||
self
|
||||
}
|
||||
|
||||
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
|
||||
disp(move |f| match conv {
|
||||
FfiReturnConvention::Void => unreachable!(),
|
||||
FfiReturnConvention::ByValue | FfiReturnConvention::ByOutParam => {
|
||||
write!(f, "{ret} = tonumber({ret}); ")
|
||||
}
|
||||
})
|
||||
fn postlude(ret: &str) -> impl Display {
|
||||
// this isn't "correct" per se, but it's much more ergonomic to work with numbers in
|
||||
// lua than with long longs wrapped in cdata. we gracefully accept the loss of
|
||||
// precision here and that 53 bits of precision for big integers are enough. (the
|
||||
// vain of Lua 5.3 integer subtype ;D )
|
||||
display!("{ret} = tonumber({ret}); ")
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -682,6 +690,10 @@ macro_rules! impl_const_ptr {
|
||||
display!("const_{}_ptr", T::name())
|
||||
}
|
||||
|
||||
fn ty() -> TypeType {
|
||||
TypeType::Primitive
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
T::cdecl(display!("const *{name}"))
|
||||
}
|
||||
@ -704,6 +716,10 @@ macro_rules! impl_mut_ptr {
|
||||
display!("{}_ptr", T::name())
|
||||
}
|
||||
|
||||
fn ty() -> TypeType {
|
||||
TypeType::Primitive
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
T::cdecl(display!("*{name}"))
|
||||
}
|
||||
@ -753,13 +769,13 @@ impl_ptr_fromabi!(Option<&mut T>);
|
||||
macro_rules! impl_ptr_intoabi {
|
||||
($ty:ty) => {
|
||||
unsafe impl<T: Type> IntoFfi for $ty {
|
||||
type To = Self;
|
||||
type Into = Self;
|
||||
|
||||
fn convert(self) -> Self::To {
|
||||
fn convert(self) -> Self::Into {
|
||||
self
|
||||
}
|
||||
|
||||
fn postlude(ret: &str, _conv: FfiReturnConvention) -> impl Display {
|
||||
fn postlude(ret: &str) -> impl Display {
|
||||
display!("if {ret} == nil then {ret} = nil; end; ")
|
||||
}
|
||||
}
|
||||
@ -822,9 +838,9 @@ impl_ref_fromabi!(&'s mut T);
|
||||
macro_rules! impl_ref_intoabi {
|
||||
($ty:ty) => {
|
||||
unsafe impl<T: Type> IntoFfi for $ty {
|
||||
type To = Self;
|
||||
type Into = Self;
|
||||
|
||||
fn convert(self) -> Self::To {
|
||||
fn convert(self) -> Self::Into {
|
||||
self
|
||||
}
|
||||
}
|
||||
@ -845,6 +861,10 @@ unsafe impl<T: Type> Type for [T] {
|
||||
display!("{}_arr", T::name())
|
||||
}
|
||||
|
||||
fn ty() -> TypeType {
|
||||
TypeType::Aggregate
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
display!("{name}[]")
|
||||
}
|
||||
@ -859,6 +879,10 @@ unsafe impl<T: Type, const N: usize> Type for [T; N] {
|
||||
display!("{}_arr{N}", T::name())
|
||||
}
|
||||
|
||||
fn ty() -> TypeType {
|
||||
TypeType::Aggregate
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
display!("{name}[{N}]")
|
||||
}
|
||||
@ -891,6 +915,10 @@ macro_rules! impl_function {
|
||||
}))
|
||||
}
|
||||
|
||||
fn ty() -> TypeType {
|
||||
TypeType::Primitive
|
||||
}
|
||||
|
||||
fn cdecl(name: impl Display) -> impl Display {
|
||||
$ret::cdecl(disp(move |f| Ok({
|
||||
let mut _n = 0;
|
||||
|
@ -1,13 +1,13 @@
|
||||
use crate::{
|
||||
__internal::{disp, display, export},
|
||||
FfiReturnConvention, FromFfi, IntoFfi,
|
||||
FromFfi, IntoFfi,
|
||||
};
|
||||
use bstr::{BStr, BString};
|
||||
use luaffi_impl::{cdef, metatype};
|
||||
use std::{fmt::Display, mem::ManuallyDrop, ptr, slice};
|
||||
|
||||
pub const IS_UTF8_FN: &str = "luaffi_is_utf8";
|
||||
pub const DROP_BUFFER_FN: &str = "luaffi_drop_buffer";
|
||||
pub(crate) const IS_UTF8_FN: &str = "luaffi_is_utf8";
|
||||
pub(crate) const DROP_BUFFER_FN: &str = "luaffi_drop_buffer";
|
||||
|
||||
#[unsafe(export_name = "luaffi_is_utf8")]
|
||||
unsafe extern "C" fn __is_utf8(ptr: *const u8, len: usize) -> bool {
|
||||
@ -33,7 +33,16 @@ pub struct lua_buf {
|
||||
|
||||
#[metatype]
|
||||
impl lua_buf {
|
||||
fn null() -> Self {
|
||||
// this takes a slice and decomposes it into its raw parts. caller should ensure the result is
|
||||
// used only as long as the original buffer is still alive.
|
||||
pub(crate) fn new(s: &[u8]) -> Self {
|
||||
Self {
|
||||
__ptr: s.as_ptr(),
|
||||
__len: s.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn null() -> Self {
|
||||
Self {
|
||||
__ptr: ptr::null(),
|
||||
__len: 0,
|
||||
@ -41,6 +50,36 @@ impl lua_buf {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[cdef]
|
||||
pub struct lua_buffer {
|
||||
__ptr: *mut u8,
|
||||
__len: usize,
|
||||
__cap: usize,
|
||||
}
|
||||
|
||||
#[metatype]
|
||||
impl lua_buffer {
|
||||
// this takes ownership of the Vec and decomposes it into its raw parts. the result must be
|
||||
// dropped by `__drop_buffer` (see [`DROP_BUFFER_FN`]).
|
||||
pub(crate) fn new(s: impl Into<Vec<u8>>) -> Self {
|
||||
let s = s.into();
|
||||
Self {
|
||||
__cap: s.capacity(),
|
||||
__len: s.len(),
|
||||
__ptr: ManuallyDrop::new(s).as_mut_ptr(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn null() -> Self {
|
||||
Self {
|
||||
__ptr: ptr::null_mut(),
|
||||
__len: 0,
|
||||
__cap: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'s> FromFfi for &'s [u8] {
|
||||
type From = Option<&'s lua_buf>;
|
||||
|
||||
@ -55,6 +94,8 @@ unsafe impl<'s> FromFfi for &'s [u8] {
|
||||
f,
|
||||
r#"assert(type({arg}) == "string", "string expected in argument '{arg}', got " .. type({arg})); "#
|
||||
)?;
|
||||
// SAFETY: the lua_buf is only valid for as long as the string is alive. we've ensured
|
||||
// that it is alive for at least the duration of the ffi call via `require_keepalive()`.
|
||||
write!(f, "{arg} = __new(__ct.lua_buf, {arg}, #{arg}); end; ")
|
||||
})
|
||||
}
|
||||
@ -68,22 +109,6 @@ unsafe impl<'s> FromFfi for &'s [u8] {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'s> FromFfi for &'s BStr {
|
||||
type From = <&'s [u8] as FromFfi>::From;
|
||||
|
||||
fn require_keepalive() -> bool {
|
||||
<&[u8] as FromFfi>::require_keepalive()
|
||||
}
|
||||
|
||||
fn prelude(arg: &str) -> impl Display {
|
||||
<&[u8] as FromFfi>::prelude(arg)
|
||||
}
|
||||
|
||||
fn convert(from: Self::From) -> Self {
|
||||
<&[u8] as FromFfi>::convert(from).into()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'s> FromFfi for &'s str {
|
||||
type From = Option<&'s lua_buf>;
|
||||
|
||||
@ -121,7 +146,77 @@ unsafe impl<'s> FromFfi for &'s str {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_optional_ref_from {
|
||||
unsafe impl IntoFfi for &'static [u8] {
|
||||
type Into = lua_buf;
|
||||
|
||||
fn convert(self) -> Self::Into {
|
||||
// SAFETY: the slice is 'static so the resulting lua_buf is always valid
|
||||
lua_buf::new(self)
|
||||
}
|
||||
|
||||
fn postlude(ret: &str) -> impl Display {
|
||||
display!("{ret} = __intern({ret}.__ptr, {ret}.__len)")
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl IntoFfi for Vec<u8> {
|
||||
type Into = lua_buffer;
|
||||
|
||||
fn convert(self) -> Self::Into {
|
||||
lua_buffer::new(self)
|
||||
}
|
||||
|
||||
fn postlude(ret: &str) -> impl Display {
|
||||
display!(
|
||||
"do local __{ret} = {ret}; {ret} = __intern({ret}.__ptr, {ret}.__len); __C.{DROP_BUFFER_FN}(__{ret}); end; "
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_from_via {
|
||||
($ty:ty, $via:ty) => {
|
||||
unsafe impl<'s> FromFfi for $ty {
|
||||
type From = <$via as FromFfi>::From;
|
||||
|
||||
fn require_keepalive() -> bool {
|
||||
<$via as FromFfi>::require_keepalive()
|
||||
}
|
||||
|
||||
fn prelude(arg: &str) -> impl Display {
|
||||
<$via as FromFfi>::prelude(arg)
|
||||
}
|
||||
|
||||
fn convert(from: Self::From) -> Self {
|
||||
<$via as FromFfi>::convert(from).into()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_from_via!(&'s BStr, &'s [u8]);
|
||||
|
||||
macro_rules! impl_into_via {
|
||||
($ty:ty, $via:ty) => {
|
||||
unsafe impl IntoFfi for $ty {
|
||||
type Into = <$via as IntoFfi>::Into;
|
||||
|
||||
fn convert(self) -> Self::Into {
|
||||
<$via as IntoFfi>::convert(self.into())
|
||||
}
|
||||
|
||||
fn postlude(ret: &str) -> impl Display {
|
||||
<$via as IntoFfi>::postlude(ret)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_into_via!(&'static BStr, &'static [u8]);
|
||||
impl_into_via!(&'static str, &'static BStr);
|
||||
impl_into_via!(BString, Vec<u8>);
|
||||
impl_into_via!(String, BString);
|
||||
|
||||
macro_rules! impl_optional_from {
|
||||
($ty:ty) => {
|
||||
unsafe impl<'s> FromFfi for Option<$ty> {
|
||||
type From = <$ty as FromFfi>::From;
|
||||
@ -131,7 +226,7 @@ macro_rules! impl_optional_ref_from {
|
||||
}
|
||||
|
||||
fn prelude(arg: &str) -> impl Display {
|
||||
// avoid constructing a `lua_buf` at all for nil arguments
|
||||
// just pass a null pointer if argument is nil
|
||||
display!(
|
||||
"if {arg} ~= nil then {}end; ",
|
||||
<$ty as FromFfi>::prelude(arg)
|
||||
@ -145,152 +240,32 @@ macro_rules! impl_optional_ref_from {
|
||||
};
|
||||
}
|
||||
|
||||
impl_optional_ref_from!(&'s [u8]);
|
||||
impl_optional_ref_from!(&'s BStr);
|
||||
impl_optional_ref_from!(&'s str);
|
||||
|
||||
unsafe impl IntoFfi for &'static [u8] {
|
||||
type To = lua_buf;
|
||||
|
||||
fn convert(self) -> Self::To {
|
||||
lua_buf {
|
||||
__ptr: self.as_ptr(),
|
||||
__len: self.len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn postlude(ret: &str, _conv: FfiReturnConvention) -> impl Display {
|
||||
display!("{ret} = __intern({ret}.__ptr, {ret}.__len)")
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl IntoFfi for &'static BStr {
|
||||
type To = <&'static [u8] as IntoFfi>::To;
|
||||
|
||||
fn convert(self) -> Self::To {
|
||||
<&[u8] as IntoFfi>::convert(self.as_ref())
|
||||
}
|
||||
|
||||
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
|
||||
<&[u8] as IntoFfi>::postlude(ret, conv)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl IntoFfi for &'static str {
|
||||
type To = <&'static [u8] as IntoFfi>::To;
|
||||
|
||||
fn convert(self) -> Self::To {
|
||||
<&[u8] as IntoFfi>::convert(self.as_bytes())
|
||||
}
|
||||
|
||||
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
|
||||
<&[u8] as IntoFfi>::postlude(ret, conv)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_optional_ref_into {
|
||||
($ty:ty) => {
|
||||
unsafe impl IntoFfi for Option<$ty> {
|
||||
type To = <$ty as IntoFfi>::To;
|
||||
|
||||
fn convert(self) -> Self::To {
|
||||
self.map_or(lua_buf::null(), <$ty as IntoFfi>::convert)
|
||||
}
|
||||
|
||||
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
|
||||
display!(
|
||||
"if {ret}.__ptr == nil then {ret} = nil; else {}end; ",
|
||||
<$ty as IntoFfi>::postlude(ret, conv)
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_optional_ref_into!(&'static [u8]);
|
||||
impl_optional_ref_into!(&'static BStr);
|
||||
impl_optional_ref_into!(&'static str);
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[cdef]
|
||||
pub struct lua_buffer {
|
||||
__ptr: *mut u8,
|
||||
__len: usize,
|
||||
__cap: usize,
|
||||
}
|
||||
|
||||
#[metatype]
|
||||
impl lua_buffer {
|
||||
fn null() -> Self {
|
||||
Self {
|
||||
__ptr: ptr::null_mut(),
|
||||
__len: 0,
|
||||
__cap: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl IntoFfi for Vec<u8> {
|
||||
type To = lua_buffer;
|
||||
|
||||
fn convert(self) -> Self::To {
|
||||
lua_buffer {
|
||||
__cap: self.capacity(),
|
||||
__len: self.len(),
|
||||
__ptr: ManuallyDrop::new(self).as_mut_ptr(),
|
||||
}
|
||||
}
|
||||
|
||||
fn postlude(ret: &str, _conv: FfiReturnConvention) -> impl Display {
|
||||
display!(
|
||||
"do local __{ret} = {ret}; {ret} = __intern({ret}.__ptr, {ret}.__len); __C.{DROP_BUFFER_FN}(__{ret}); end; "
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl IntoFfi for BString {
|
||||
type To = <Vec<u8> as IntoFfi>::To;
|
||||
|
||||
fn convert(self) -> Self::To {
|
||||
<Vec<u8> as IntoFfi>::convert(self.into())
|
||||
}
|
||||
|
||||
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
|
||||
<Vec<u8> as IntoFfi>::postlude(ret, conv)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl IntoFfi for String {
|
||||
type To = <Vec<u8> as IntoFfi>::To;
|
||||
|
||||
fn convert(self) -> Self::To {
|
||||
<Vec<u8> as IntoFfi>::convert(self.into_bytes())
|
||||
}
|
||||
|
||||
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
|
||||
<Vec<u8> as IntoFfi>::postlude(ret, conv)
|
||||
}
|
||||
}
|
||||
impl_optional_from!(&'s [u8]);
|
||||
impl_optional_from!(&'s BStr);
|
||||
impl_optional_from!(&'s str);
|
||||
|
||||
macro_rules! impl_optional_into {
|
||||
($ty:ty) => {
|
||||
($ty:ty, $null:expr) => {
|
||||
unsafe impl IntoFfi for Option<$ty> {
|
||||
type To = <$ty as IntoFfi>::To;
|
||||
type Into = <$ty as IntoFfi>::Into;
|
||||
|
||||
fn convert(self) -> Self::To {
|
||||
self.map_or(lua_buffer::null(), <$ty as IntoFfi>::convert)
|
||||
fn convert(self) -> Self::Into {
|
||||
self.map_or($null, <$ty as IntoFfi>::convert)
|
||||
}
|
||||
|
||||
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
|
||||
fn postlude(ret: &str) -> impl Display {
|
||||
display!(
|
||||
"if {ret}.__ptr == nil then {ret} = nil; else {}end; ",
|
||||
<$ty as IntoFfi>::postlude(ret, conv)
|
||||
<$ty as IntoFfi>::postlude(ret)
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_optional_into!(Vec<u8>);
|
||||
impl_optional_into!(BString);
|
||||
impl_optional_into!(String);
|
||||
impl_optional_into!(&'static [u8], lua_buf::null());
|
||||
impl_optional_into!(&'static BStr, lua_buf::null());
|
||||
impl_optional_into!(&'static str, lua_buf::null());
|
||||
impl_optional_into!(Vec<u8>, lua_buffer::null());
|
||||
impl_optional_into!(BString, lua_buffer::null());
|
||||
impl_optional_into!(String, lua_buffer::null());
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::utils::{ffi_crate, syn_assert, syn_error};
|
||||
use darling::FromMeta;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{ext::IdentExt, *};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use syn::{ext::IdentExt, spanned::Spanned, *};
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
pub struct Args {}
|
||||
@ -24,35 +24,39 @@ pub fn transform(_args: Args, mut item: Item) -> Result<TokenStream> {
|
||||
|
||||
let mod_name = format_ident!("__{name}_cdef");
|
||||
|
||||
Ok(quote! {
|
||||
Ok(quote!(
|
||||
#[repr(C)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#item
|
||||
|
||||
#[doc(hidden)]
|
||||
#[allow(unused, non_snake_case)]
|
||||
/// Automatically generated by luaffi.
|
||||
mod #mod_name {
|
||||
use super::*;
|
||||
#impl_type
|
||||
#impl_cdef
|
||||
}
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
fn generate_type(ty: &Ident) -> Result<TokenStream> {
|
||||
let ffi = ffi_crate();
|
||||
let fmt = quote!(::std::format!);
|
||||
let name = LitStr::new(&format!("{}", ty.unraw()), ty.span());
|
||||
let cdecl_fmt = LitStr::new(&format!("struct {} {{}}", ty.unraw()), ty.span());
|
||||
let span = ty.span();
|
||||
let name = LitStr::new(&ty.unraw().to_string(), span);
|
||||
|
||||
Ok(quote! {
|
||||
Ok(quote_spanned!(span =>
|
||||
unsafe impl #ffi::Type for #ty {
|
||||
fn name() -> impl ::std::fmt::Display {
|
||||
#name
|
||||
}
|
||||
|
||||
fn ty() -> #ffi::TypeType {
|
||||
#ffi::TypeType::Aggregate
|
||||
}
|
||||
|
||||
fn cdecl(name: impl ::std::fmt::Display) -> impl ::std::fmt::Display {
|
||||
#fmt(#cdecl_fmt, name)
|
||||
::std::format!("struct {} {name}", #name)
|
||||
}
|
||||
|
||||
fn build(b: &mut #ffi::TypeBuilder) {
|
||||
@ -62,10 +66,10 @@ fn generate_type(ty: &Ident) -> Result<TokenStream> {
|
||||
|
||||
// SAFETY: we can always implement `IntoFfi` because it transfers ownership from Rust to Lua
|
||||
unsafe impl #ffi::IntoFfi for #ty {
|
||||
type To = Self;
|
||||
fn convert(self) -> Self::To { self }
|
||||
type Into = Self;
|
||||
fn convert(self) -> Self::Into { self }
|
||||
}
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
fn generate_cdef_structure(str: &mut ItemStruct) -> Result<TokenStream> {
|
||||
@ -77,13 +81,14 @@ fn generate_cdef_structure(str: &mut ItemStruct) -> Result<TokenStream> {
|
||||
|
||||
let ffi = ffi_crate();
|
||||
let ty = &str.ident;
|
||||
let build = generate_build_cdef(&to_cfields(&mut str.fields)?)?;
|
||||
let span = ty.span();
|
||||
let build = generate_cdef_build(&get_cfields(&mut str.fields)?)?;
|
||||
|
||||
Ok(quote! {
|
||||
Ok(quote_spanned!(span =>
|
||||
unsafe impl #ffi::Cdef for #ty {
|
||||
fn build(b: &mut #ffi::CdefBuilder) { #build }
|
||||
}
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
fn generate_cdef_enum(enu: &mut ItemEnum) -> Result<TokenStream> {
|
||||
@ -95,22 +100,24 @@ fn generate_cdef_enum(enu: &mut ItemEnum) -> Result<TokenStream> {
|
||||
|
||||
let ffi = ffi_crate();
|
||||
let ty = &enu.ident;
|
||||
let span = ty.span();
|
||||
let build = enu
|
||||
.variants
|
||||
.iter_mut()
|
||||
.map(|variant| {
|
||||
let build = generate_build_cdef(&to_cfields(&mut variant.fields)?)?;
|
||||
Ok(quote! { b.inner_struct(|b| { #build }); })
|
||||
let span = variant.span();
|
||||
let build = generate_cdef_build(&get_cfields(&mut variant.fields)?)?;
|
||||
Ok(quote_spanned!(span => b.inner_struct(|b| { #build })))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
Ok(quote! {
|
||||
Ok(quote_spanned!(span =>
|
||||
unsafe impl #ffi::Cdef for #ty {
|
||||
fn build(b: &mut #ffi::CdefBuilder) {
|
||||
b.field::<::std::ffi::c_int>("__tag").inner_union(|b| { #(#build)* });
|
||||
b.field::<::std::ffi::c_int>("__tag").inner_union(|b| { #(#build;)* });
|
||||
}
|
||||
}
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
struct CField {
|
||||
@ -124,7 +131,7 @@ struct CFieldAttrs {
|
||||
opaque: bool,
|
||||
}
|
||||
|
||||
fn to_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
|
||||
fn get_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
|
||||
match fields {
|
||||
Fields::Named(fields) => fields.named.iter_mut(),
|
||||
Fields::Unnamed(fields) => fields.unnamed.iter_mut(),
|
||||
@ -134,17 +141,17 @@ fn to_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
|
||||
.map(|(i, field)| {
|
||||
Ok(CField {
|
||||
name: match field.ident {
|
||||
Some(ref name) => format!("{}", name.unraw()),
|
||||
Some(ref name) => name.unraw().to_string(),
|
||||
None => format!("__{i}"),
|
||||
},
|
||||
ty: field.ty.clone(),
|
||||
attrs: parse_attrs(&mut field.attrs)?,
|
||||
attrs: parse_cfield_attrs(&mut field.attrs)?,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse_attrs(attrs: &mut Vec<Attribute>) -> Result<CFieldAttrs> {
|
||||
fn parse_cfield_attrs(attrs: &mut Vec<Attribute>) -> Result<CFieldAttrs> {
|
||||
let mut parsed = CFieldAttrs::default();
|
||||
let mut i = 0;
|
||||
while let Some(attr) = attrs.get(i) {
|
||||
@ -161,11 +168,11 @@ fn parse_attrs(attrs: &mut Vec<Attribute>) -> Result<CFieldAttrs> {
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
fn generate_build_cdef(fields: &[CField]) -> Result<TokenStream> {
|
||||
let mut body = vec![quote! {
|
||||
fn generate_cdef_build(fields: &[CField]) -> Result<TokenStream> {
|
||||
let mut body = vec![quote!(
|
||||
let mut offset = 0;
|
||||
let mut align = 1;
|
||||
}];
|
||||
)];
|
||||
|
||||
fn offset(i: usize) -> Ident {
|
||||
format_ident!("offset{i}")
|
||||
@ -174,40 +181,41 @@ fn generate_build_cdef(fields: &[CField]) -> Result<TokenStream> {
|
||||
let max = quote!(::std::cmp::Ord::max);
|
||||
let size_of = quote!(::std::mem::size_of);
|
||||
let align_of = quote!(::std::mem::align_of);
|
||||
|
||||
for (i, field) in fields.iter().enumerate() {
|
||||
let ty = &field.ty;
|
||||
let offset = offset(i);
|
||||
body.push(quote! {
|
||||
body.push(quote_spanned!(ty.span() =>
|
||||
// round up current offset to the alignment of field type for field offset
|
||||
offset = (offset + #align_of::<#ty>() - 1) & !(#align_of::<#ty>() - 1);
|
||||
align = #max(align, #align_of::<#ty>());
|
||||
let #offset = offset;
|
||||
offset += #size_of::<#ty>();
|
||||
});
|
||||
));
|
||||
}
|
||||
|
||||
body.push(quote! {
|
||||
body.push(quote!(
|
||||
// round up final offset to the total alignment of struct for struct size
|
||||
let size = (offset + align - 1) & !(align - 1);
|
||||
});
|
||||
));
|
||||
|
||||
let len = fields.len();
|
||||
for (i, field) in fields.iter().enumerate() {
|
||||
let name = &field.name;
|
||||
let ty = &field.ty;
|
||||
if field.attrs.opaque {
|
||||
body.push(if i == len - 1 {
|
||||
body.push(if field.attrs.opaque {
|
||||
if i == len - 1 {
|
||||
let a = offset(i);
|
||||
quote! { b.field_opaque(size - #a); } // last field
|
||||
quote_spanned!(ty.span() => b.field_opaque(size - #a);) // last field
|
||||
} else {
|
||||
let a = offset(i);
|
||||
let b = offset(i + 1);
|
||||
quote! { b.field_opaque(#b - #a); }
|
||||
});
|
||||
quote_spanned!(ty.span() => b.field_opaque(#b - #a);)
|
||||
}
|
||||
} else {
|
||||
body.push(quote! { b.field::<#ty>(#name); });
|
||||
}
|
||||
quote_spanned!(ty.span() => b.field::<#ty>(#name);)
|
||||
});
|
||||
}
|
||||
|
||||
Ok(quote! { #(#body)* })
|
||||
Ok(quote!(#(#body)*))
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
use crate::utils::{ffi_crate, is_primitive, is_unit, pat_ident, syn_assert, ty_name};
|
||||
use crate::utils::{
|
||||
ffi_crate, is_primitivelike, is_unit, pat_ident, syn_assert, syn_error, ty_name,
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{ext::IdentExt, *};
|
||||
use quote::{ToTokens, format_ident, quote, quote_spanned};
|
||||
use std::{collections::HashSet, fmt};
|
||||
use syn::{ext::IdentExt, punctuated::Punctuated, spanned::Spanned, *};
|
||||
|
||||
pub fn transform(mut imp: ItemImpl) -> Result<TokenStream> {
|
||||
syn_assert!(
|
||||
@ -13,94 +16,76 @@ pub fn transform(mut imp: ItemImpl) -> Result<TokenStream> {
|
||||
let impls = generate_impls(&mut imp)?;
|
||||
let mod_name = format_ident!("__{}_metatype", ty_name(&imp.self_ty)?);
|
||||
|
||||
Ok(quote! {
|
||||
Ok(quote!(
|
||||
#imp
|
||||
|
||||
#[doc(hidden)]
|
||||
#[allow(unused, non_snake_case)]
|
||||
/// Automatically generated by luaffi.
|
||||
mod #mod_name {
|
||||
use super::*;
|
||||
#impls
|
||||
}
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
|
||||
let ffi = ffi_crate();
|
||||
let ty = imp.self_ty.clone();
|
||||
let ty_name = ty_name(&ty)?;
|
||||
let mut ffi_funcs = FfiRegistry::new(ty_name.clone());
|
||||
let mut lua_funcs = LuaRegistry::new(ty_name.clone());
|
||||
let mut mms = HashSet::new();
|
||||
let mut lua_drop = None;
|
||||
|
||||
let ffi = ffi_crate();
|
||||
let ffi_funcs = get_ffi_functions(imp)?;
|
||||
|
||||
// wrapper extern "C" functions that call the actual implementation
|
||||
let ffi_wrappers: Vec<_> = ffi_funcs
|
||||
.iter()
|
||||
.map(generate_ffi_wrapper)
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
// ffi function registration code
|
||||
let ffi_register: Vec<_> = ffi_funcs
|
||||
.iter()
|
||||
.map(generate_ffi_register)
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
let ffi_register_new = match ffi_funcs
|
||||
.iter()
|
||||
.find(|f| f.attrs.metatable.as_deref() == Some("new"))
|
||||
{
|
||||
Some(_) => None,
|
||||
None => Some({
|
||||
// fallback error constructor to prevent creating uninitialised ctypes
|
||||
let err = format!(r#"function() error("type '{ty_name}' has no constructor"); end"#);
|
||||
quote! { b.metatable_raw("new", #err); }
|
||||
}),
|
||||
};
|
||||
|
||||
let ffi_drop_rname = format_ident!("__ffi_drop");
|
||||
let ffi_drop_cname = format!("{ty_name}_drop");
|
||||
let ffi_wrapper_drop = quote! {
|
||||
#[unsafe(export_name = #ffi_drop_cname)]
|
||||
unsafe extern "C" fn #ffi_drop_rname(ptr: *mut Self) {
|
||||
unsafe { ::std::ptr::drop_in_place(ptr) }
|
||||
for func in get_ffi_functions(imp)? {
|
||||
if let Some(mm) = func.attrs.metamethod {
|
||||
syn_assert!(
|
||||
mms.insert(mm),
|
||||
func.name,
|
||||
"metamethod `{mm}` already defined"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let ffi_register_drop = quote! {
|
||||
if ::std::mem::needs_drop::<Self>() {
|
||||
b.declare::<#ffi::UnsafeExternCFn<(*mut Self,), ()>>(#ffi_drop_cname);
|
||||
b.metatable_raw("gc", ::std::format_args!("__C.{}", #ffi_drop_cname));
|
||||
add_ffi_function(&mut ffi_funcs, &func)?;
|
||||
}
|
||||
|
||||
for func in get_lua_functions(imp)? {
|
||||
if let Some(mm) = func.attrs.metamethod {
|
||||
syn_assert!(
|
||||
mms.insert(mm),
|
||||
func.name,
|
||||
"metamethod `{mm}` already defined"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// ffi function symbol export code
|
||||
let ffi_exports = {
|
||||
let mut names = vec![&ffi_drop_rname];
|
||||
names.extend(ffi_funcs.iter().map(|f| &f.rust_name));
|
||||
generate_ffi_exports(&ty, names.into_iter())?
|
||||
};
|
||||
if func.attrs.metamethod == Some(Metamethod::Gc) {
|
||||
lua_drop = Some(func);
|
||||
} else {
|
||||
add_lua_function(&mut lua_funcs, &func)?;
|
||||
}
|
||||
}
|
||||
|
||||
// lua function registration code
|
||||
let lua_funcs = get_lua_functions(imp)?;
|
||||
let lua_register: Vec<_> = lua_funcs
|
||||
.iter()
|
||||
.map(generate_lua_register)
|
||||
.collect::<Result<_>>()?;
|
||||
if !mms.contains(&Metamethod::New) {
|
||||
inject_fallback_new(&mut lua_funcs)?;
|
||||
}
|
||||
|
||||
inject_merged_drop(&mut ffi_funcs, lua_drop.as_ref())?;
|
||||
|
||||
let ffi_shims = &ffi_funcs.shims;
|
||||
let ffi_build = &ffi_funcs.build;
|
||||
let lua_build = &lua_funcs.build;
|
||||
let ffi_exports = generate_ffi_exports(&ffi_funcs)?;
|
||||
|
||||
Ok(quote! {
|
||||
impl #ty {
|
||||
#(#ffi_wrappers)*
|
||||
#ffi_wrapper_drop
|
||||
}
|
||||
impl #ty { #(#ffi_shims)* }
|
||||
|
||||
unsafe impl #ffi::Metatype for #ty {
|
||||
type Target = Self;
|
||||
|
||||
fn build(b: &mut #ffi::MetatypeBuilder) {
|
||||
#(#ffi_register)*
|
||||
#(#lua_register)*
|
||||
|
||||
#ffi_register_new
|
||||
#ffi_register_drop
|
||||
#(#ffi_build)*
|
||||
#(#lua_build)*
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,20 +93,102 @@ fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
enum Metamethod {
|
||||
// known luajit metamethods (see lj_obj.h)
|
||||
// index, newindex, mode and metatable are not included
|
||||
Gc,
|
||||
Eq,
|
||||
Len,
|
||||
Lt,
|
||||
Le,
|
||||
Concat,
|
||||
Call,
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
Mod,
|
||||
Pow,
|
||||
Unm,
|
||||
ToString,
|
||||
New,
|
||||
Pairs,
|
||||
Ipairs,
|
||||
}
|
||||
|
||||
impl TryFrom<&Ident> for Metamethod {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &Ident) -> Result<Self> {
|
||||
Ok(match value.to_string().as_str() {
|
||||
"gc" => Self::Gc,
|
||||
"eq" => Self::Eq,
|
||||
"len" => Self::Len,
|
||||
"lt" => Self::Lt,
|
||||
"le" => Self::Le,
|
||||
"concat" => Self::Concat,
|
||||
"call" => Self::Call,
|
||||
"add" => Self::Add,
|
||||
"sub" => Self::Sub,
|
||||
"mul" => Self::Mul,
|
||||
"div" => Self::Div,
|
||||
"mod" => Self::Mod,
|
||||
"pow" => Self::Pow,
|
||||
"unm" => Self::Unm,
|
||||
"tostring" => Self::ToString,
|
||||
"new" => Self::New,
|
||||
"pairs" => Self::Pairs,
|
||||
"ipairs" => Self::Ipairs,
|
||||
_ => syn_error!(value, "unknown metamethod"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Metamethod {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let name = match self {
|
||||
Self::Gc => "gc",
|
||||
Self::Eq => "eq",
|
||||
Self::Len => "len",
|
||||
Self::Lt => "lt",
|
||||
Self::Le => "le",
|
||||
Self::Concat => "concat",
|
||||
Self::Call => "call",
|
||||
Self::Add => "add",
|
||||
Self::Sub => "sub",
|
||||
Self::Mul => "mul",
|
||||
Self::Div => "div",
|
||||
Self::Mod => "mod",
|
||||
Self::Pow => "pow",
|
||||
Self::Unm => "unm",
|
||||
Self::ToString => "tostring",
|
||||
Self::New => "new",
|
||||
Self::Pairs => "pairs",
|
||||
Self::Ipairs => "ipairs",
|
||||
};
|
||||
|
||||
write!(f, "{name}")
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Metamethod {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let name = self.to_string();
|
||||
tokens.extend(quote!(#name));
|
||||
}
|
||||
}
|
||||
|
||||
struct FfiFunction {
|
||||
name: Ident,
|
||||
rust_name: Ident,
|
||||
lua_name: String,
|
||||
c_name: String,
|
||||
params: Vec<PatType>,
|
||||
ret: Type,
|
||||
ret_by_out: bool,
|
||||
attrs: FfiFunctionAttrs,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct FfiFunctionAttrs {
|
||||
metatable: Option<String>,
|
||||
metamethod: Option<Metamethod>,
|
||||
}
|
||||
|
||||
fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
|
||||
@ -136,49 +203,24 @@ fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
|
||||
func.sig.abi = None;
|
||||
|
||||
// normalise inputs to PatType
|
||||
let params = func
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
Ok(match arg {
|
||||
funcs.push(FfiFunction {
|
||||
name: func.sig.ident.clone(),
|
||||
params: func
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|arg| match arg {
|
||||
FnArg::Receiver(recv) => {
|
||||
let ty = &recv.ty;
|
||||
parse_quote! { self: #ty }
|
||||
parse_quote_spanned!(ty.span() => self: #ty)
|
||||
}
|
||||
FnArg::Typed(ty) => ty.clone(),
|
||||
})
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
// normalise output to Type
|
||||
let ret = match func.sig.output {
|
||||
ReturnType::Default => parse_quote!(()),
|
||||
ReturnType::Type(_, ref ty) => (**ty).clone(),
|
||||
};
|
||||
|
||||
// whether to use out-param for return values.
|
||||
//
|
||||
// out-param return convention isn't strictly necessary (luajit can handle them fine),
|
||||
// but luajit doesn't jit compile aggregate returns yet, so this is more of a
|
||||
// performance optimisation. https://luajit.org/ext_ffi_semantics.html#status
|
||||
//
|
||||
// right now this just heuristically looks for common primitive identifiers like `i32`
|
||||
// and `usize` which has its limitations when it comes to type aliases (proc-macro can't
|
||||
// see them), but the worst thing that can happen with a false detection is an
|
||||
// unnecessarily boxed primitive that gets just unwrapped, or an aggregate suboptimally
|
||||
// returned by-value. it should be correct for 99% of rust code that isn't doing
|
||||
// anything weird.
|
||||
let ret_by_out = !is_primitive(&ret);
|
||||
|
||||
funcs.push(FfiFunction {
|
||||
name: func.sig.ident.clone(),
|
||||
rust_name: format_ident!("__ffi_{}", func.sig.ident.unraw()),
|
||||
lua_name: format!("{}", func.sig.ident.unraw()),
|
||||
c_name: format!("{}_{}", ty_name(&imp.self_ty)?, func.sig.ident.unraw()),
|
||||
params,
|
||||
ret,
|
||||
ret_by_out,
|
||||
.collect(),
|
||||
ret: match func.sig.output {
|
||||
ReturnType::Default => parse_quote_spanned!(func.sig.span() => ()),
|
||||
ReturnType::Type(_, ref ty) => (**ty).clone(),
|
||||
},
|
||||
attrs: parse_ffi_function_attrs(&mut func.attrs)?,
|
||||
});
|
||||
}
|
||||
@ -191,150 +233,201 @@ fn parse_ffi_function_attrs(attrs: &mut Vec<Attribute>) -> Result<FfiFunctionAtt
|
||||
let mut parsed = FfiFunctionAttrs::default();
|
||||
let mut i = 0;
|
||||
while let Some(attr) = attrs.get(i) {
|
||||
if let Some(name) = attr.path().get_ident() {
|
||||
if name == "metatable" {
|
||||
parsed.metatable = Some(attr.parse_args::<LitStr>()?.value());
|
||||
attrs.remove(i);
|
||||
continue;
|
||||
} else if name == "new" {
|
||||
parsed.metatable = Some("new".into());
|
||||
attrs.remove(i);
|
||||
continue;
|
||||
if let Some(name) = attr.path().get_ident()
|
||||
&& let Ok(method) = Metamethod::try_from(name)
|
||||
{
|
||||
match method {
|
||||
Metamethod::Gc => syn_error!(attr, "implement `Drop` instead"),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
parsed.metamethod = Some(method);
|
||||
attrs.remove(i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum FfiArgType {
|
||||
enum FfiParameterType {
|
||||
Default,
|
||||
}
|
||||
|
||||
fn get_ffi_arg_type(_ty: &Type) -> FfiArgType {
|
||||
FfiArgType::Default
|
||||
fn get_ffi_param_type(_ty: &Type) -> FfiParameterType {
|
||||
FfiParameterType::Default
|
||||
}
|
||||
|
||||
fn generate_ffi_wrapper(func: &FfiFunction) -> Result<TokenStream> {
|
||||
enum FfiReturnType {
|
||||
Void,
|
||||
Primitive,
|
||||
Aggregate,
|
||||
}
|
||||
|
||||
fn get_ffi_ret_type(ty: &Type) -> FfiReturnType {
|
||||
// aggregate type returns use an out-param instead of return by-value.
|
||||
//
|
||||
// out-param isn't strictly necessary (luajit can handle them fine), but luajit doesn't jit
|
||||
// compile aggregate returns yet, so this is more of a performance optimisation.
|
||||
// https://luajit.org/ext_ffi_semantics.html#status
|
||||
//
|
||||
// right now this just heuristically looks for common primitive identifiers like `i32` and
|
||||
// `usize` which has its limitations when it comes to type aliases (proc-macro can't see them),
|
||||
// but the worst thing that can happen with a false detection is an unnecessarily boxed
|
||||
// primitive that gets just unwrapped, or an aggregate suboptimally returned by-value. it should
|
||||
// be correct for 99% of rust code that isn't doing anything weird.
|
||||
//
|
||||
// the builder below has additional assertions to confirm whether our detection was correct.
|
||||
if is_unit(ty) {
|
||||
FfiReturnType::Void
|
||||
} else if is_primitivelike(ty) {
|
||||
FfiReturnType::Primitive
|
||||
} else {
|
||||
FfiReturnType::Aggregate
|
||||
}
|
||||
}
|
||||
|
||||
struct FfiRegistry {
|
||||
ty: Ident,
|
||||
shims: Vec<ImplItemFn>,
|
||||
build: Vec<TokenStream>,
|
||||
}
|
||||
|
||||
impl FfiRegistry {
|
||||
fn new(ty: Ident) -> Self {
|
||||
Self {
|
||||
ty,
|
||||
shims: vec![],
|
||||
build: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_ffi_function(registry: &mut FfiRegistry, func: &FfiFunction) -> Result<()> {
|
||||
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![];
|
||||
let ty = ®istry.ty;
|
||||
let func_name = &func.name;
|
||||
let shim_name = format_ident!("__ffi_{}", func_name.unraw());
|
||||
let lua_name = format!("{}", func_name.unraw());
|
||||
let c_name = format!("{}_{}", ty.unraw(), func_name.unraw());
|
||||
|
||||
for (i, param) in func.params.iter().enumerate() {
|
||||
let name = format_ident!("arg{i}");
|
||||
let ty = ¶m.ty;
|
||||
let func_params = &func.params; // target function parameters
|
||||
let func_ret = &func.ret; // target function return type
|
||||
let mut func_args = vec![]; // target function arguments
|
||||
|
||||
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) });
|
||||
let mut shim_params = vec![]; // shim function parameters
|
||||
let mut shim_ret = quote_spanned!(func_ret.span() => // shim function return type
|
||||
<#func_ret as #ffi::IntoFfi>::Into
|
||||
);
|
||||
|
||||
let mut asserts = vec![]; // compile-time builder asserts
|
||||
let mut build = vec![]; // ffi builder body
|
||||
|
||||
// for __new metamethods, ignore the first argument (ctype of self, for which there is no
|
||||
// equivalent in C)
|
||||
if func.attrs.metamethod == Some(Metamethod::New) {
|
||||
build.push(quote!(
|
||||
b.param_ignored();
|
||||
));
|
||||
}
|
||||
|
||||
for (i, param) in func_params.iter().enumerate() {
|
||||
let func_param = ¶m.ty;
|
||||
let shim_param = format_ident!("arg{i}");
|
||||
let name = pat_ident(¶m.pat)?.unraw().to_string();
|
||||
|
||||
match get_ffi_param_type(func_param) {
|
||||
FfiParameterType::Default => {
|
||||
shim_params.push(quote_spanned!(param.span() =>
|
||||
#shim_param: <#func_param as #ffi::FromFfi>::From
|
||||
));
|
||||
func_args.push(quote_spanned!(param.span() =>
|
||||
<#func_param as #ffi::FromFfi>::convert(#shim_param)
|
||||
));
|
||||
build.push(quote_spanned!(param.span() =>
|
||||
b.param::<#func_param>(#name);
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (ret, call) = if func.ret_by_out {
|
||||
// make return by out-param the first parameter
|
||||
let ret = &func.ret;
|
||||
params.insert(0, quote! { out: *mut <#ret as #ffi::IntoFfi>::To });
|
||||
(
|
||||
quote!(()),
|
||||
quote! { ::std::ptr::write(out, <#ret as #ffi::IntoFfi>::convert(Self::#name(#(#args),*))) },
|
||||
)
|
||||
} else {
|
||||
let ret = &func.ret;
|
||||
(
|
||||
quote! { <#ret as #ffi::IntoFfi>::To },
|
||||
quote! { <#ret as #ffi::IntoFfi>::convert(Self::#name(#(#args),*)) },
|
||||
)
|
||||
let mut shim_body = quote_spanned!(func_name.span() => // shim function body
|
||||
<#func_ret as #ffi::IntoFfi>::convert(Self::#func_name(#(#func_args),*))
|
||||
);
|
||||
|
||||
match get_ffi_ret_type(func_ret) {
|
||||
FfiReturnType::Void => {
|
||||
asserts.push(quote_spanned!(func_ret.span() =>
|
||||
<<#func_ret as #ffi::IntoFfi>::Into as #ffi::Type>::ty() == #ffi::TypeType::Void
|
||||
));
|
||||
}
|
||||
FfiReturnType::Primitive => {
|
||||
asserts.push(quote_spanned!(func_ret.span() =>
|
||||
<<#func_ret as #ffi::IntoFfi>::Into as #ffi::Type>::ty() == #ffi::TypeType::Primitive
|
||||
));
|
||||
}
|
||||
FfiReturnType::Aggregate => {
|
||||
asserts.push(quote_spanned!(func_ret.span() =>
|
||||
<<#func_ret as #ffi::IntoFfi>::Into as #ffi::Type>::ty() == #ffi::TypeType::Aggregate
|
||||
));
|
||||
|
||||
shim_params.insert(0, quote!(out: *mut #shim_ret));
|
||||
(shim_body, shim_ret) = (quote!(::std::ptr::write(out, #shim_body)), quote!(()));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
build.push(quote_spanned!(func_name.span() =>
|
||||
b.call::<#func_ret>(#c_name);
|
||||
));
|
||||
|
||||
let shim_params_ty = {
|
||||
let tys: Punctuated<PatType, Token![,]> = parse_quote!(#(#shim_params),*);
|
||||
tys.iter().map(|pat| (*pat.ty).clone()).collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
registry.build.push(quote!(
|
||||
#(::std::assert!(#asserts);)*
|
||||
b.declare::<#ffi::UnsafeExternCFn<(#(#shim_params_ty,)*), #shim_ret>>(#c_name);
|
||||
));
|
||||
|
||||
registry.build.push(match func.attrs.metamethod {
|
||||
Some(ref mm) => quote!(b.metatable(#mm, |b| { #(#build)* });),
|
||||
None => quote!(b.index(#lua_name, |b| { #(#build)* });),
|
||||
});
|
||||
|
||||
registry.shims.push(parse_quote_spanned!(func_name.span() =>
|
||||
#[unsafe(export_name = #c_name)]
|
||||
unsafe extern "C" fn #rust_name(#(#params),*) -> #ret { #call }
|
||||
})
|
||||
unsafe extern "C" fn #shim_name(#(#shim_params),*) -> #shim_ret { #shim_body }
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_ffi_register(func: &FfiFunction) -> Result<TokenStream> {
|
||||
let ffi = ffi_crate();
|
||||
let lua_name = &func.lua_name;
|
||||
let c_name = &func.c_name;
|
||||
fn generate_ffi_exports(registry: &FfiRegistry) -> Result<TokenStream> {
|
||||
let ty = ®istry.ty;
|
||||
let names = registry.shims.iter().map(|f| &f.sig.ident);
|
||||
|
||||
let mut params = vec![];
|
||||
let mut register = vec![];
|
||||
|
||||
// for __new metamethods, ignore the first argument (ctype of self)
|
||||
if func.attrs.metatable.as_deref() == Some("new") {
|
||||
register.push(quote! { b.param_ignored(); });
|
||||
}
|
||||
|
||||
for param in func.params.iter() {
|
||||
let name = format!("{}", pat_ident(¶m.pat)?);
|
||||
let ty = ¶m.ty;
|
||||
|
||||
match get_ffi_arg_type(ty) {
|
||||
FfiArgType::Default => {
|
||||
params.push(quote! { <#ty as #ffi::FromFfi>::From });
|
||||
register.push(quote! { b.param::<#ty>(#name); })
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let ret = &func.ret;
|
||||
let ret_conv = if is_unit(ret) {
|
||||
quote! { #ffi::FfiReturnConvention::Void }
|
||||
} else if func.ret_by_out {
|
||||
quote! { #ffi::FfiReturnConvention::ByOutParam }
|
||||
} else {
|
||||
quote! { #ffi::FfiReturnConvention::ByValue }
|
||||
};
|
||||
|
||||
let declare = if func.ret_by_out {
|
||||
quote! { b.declare::<#ffi::UnsafeExternCFn<(*mut <#ret as #ffi::IntoFfi>::To, #(#params,)*), ()>>(#c_name); }
|
||||
} else {
|
||||
quote! { b.declare::<#ffi::UnsafeExternCFn<(#(#params,)*), <#ret as #ffi::IntoFfi>::To>>(#c_name); }
|
||||
};
|
||||
|
||||
let register = match func.attrs.metatable {
|
||||
Some(ref mt) => quote! {
|
||||
b.metatable(#mt, |b| {
|
||||
#(#register)*
|
||||
b.call::<#ret>(#c_name, #ret_conv);
|
||||
});
|
||||
},
|
||||
None => quote! {
|
||||
b.index(#lua_name, |b| {
|
||||
#(#register)*
|
||||
b.call::<#ret>(#c_name, #ret_conv);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
Ok(quote! { #declare #register })
|
||||
}
|
||||
|
||||
fn generate_ffi_exports<'a>(
|
||||
ty: &Type,
|
||||
names: impl Iterator<Item = &'a Ident>,
|
||||
) -> Result<TokenStream> {
|
||||
Ok(quote! {
|
||||
Ok(quote_spanned!(ty.span() =>
|
||||
// this ensures ffi function symbol exports are actually present in the resulting binary,
|
||||
// otherwise they may get dead code-eliminated before it reaches the linker
|
||||
#[used]
|
||||
static __FFI_EXPORTS: &[fn()] = unsafe {
|
||||
&[#(::std::mem::transmute(#ty::#names as *const ())),*]
|
||||
};
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
struct LuaFunction {
|
||||
name: String,
|
||||
name: Ident,
|
||||
params: Vec<Pat>,
|
||||
body: Block,
|
||||
attrs: LuaFunctionAttrs,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct LuaFunctionAttrs {
|
||||
metamethod: Option<Metamethod>,
|
||||
}
|
||||
|
||||
fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
|
||||
@ -357,15 +450,15 @@ fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
|
||||
FnArg::Receiver(recv) => {
|
||||
syn_assert!(ty_name(&recv.ty)? == "Self", recv, "must be `self`");
|
||||
syn_assert!(recv.mutability.is_none(), recv, "cannot be mut");
|
||||
Pat::Type(parse_quote! { self: cdata })
|
||||
Pat::Type(parse_quote_spanned!(recv.span() => self: cdata))
|
||||
}
|
||||
FnArg::Typed(ty) => Pat::Type(ty.clone()),
|
||||
})
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
if let Some(_) = func.sig.variadic {
|
||||
params.push(parse_quote!(variadic!()));
|
||||
if let Some(ref variadic) = func.sig.variadic {
|
||||
params.push(parse_quote_spanned!(variadic.span() => variadic!()));
|
||||
}
|
||||
|
||||
// shouldn't specify an output type
|
||||
@ -376,9 +469,10 @@ fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
|
||||
);
|
||||
|
||||
funcs.push(LuaFunction {
|
||||
name: format!("{}", func.sig.ident.unraw()),
|
||||
body: func.block.clone(),
|
||||
name: func.sig.ident.clone(),
|
||||
params,
|
||||
body: func.block.clone(),
|
||||
attrs: parse_lua_function_attrs(&mut func.attrs)?,
|
||||
});
|
||||
|
||||
imp.items.remove(i);
|
||||
@ -390,13 +484,122 @@ fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
|
||||
Ok(funcs)
|
||||
}
|
||||
|
||||
fn generate_lua_register(func: &LuaFunction) -> Result<TokenStream> {
|
||||
fn parse_lua_function_attrs(attrs: &mut Vec<Attribute>) -> Result<LuaFunctionAttrs> {
|
||||
let mut parsed = LuaFunctionAttrs::default();
|
||||
let mut i = 0;
|
||||
while let Some(attr) = attrs.get(i) {
|
||||
if let Some(name) = attr.path().get_ident()
|
||||
&& let Ok(method) = Metamethod::try_from(name)
|
||||
{
|
||||
match method {
|
||||
Metamethod::New => syn_error!(attr, r#"cannot be applied to a lua function"#),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
parsed.metamethod = Some(method);
|
||||
attrs.remove(i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
struct LuaRegistry {
|
||||
ty: Ident,
|
||||
build: Vec<TokenStream>,
|
||||
}
|
||||
|
||||
impl LuaRegistry {
|
||||
fn new(ty: Ident) -> Self {
|
||||
Self { ty, build: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
fn add_lua_function(registry: &mut LuaRegistry, func: &LuaFunction) -> Result<()> {
|
||||
let ffi = ffi_crate();
|
||||
let name = &func.name;
|
||||
let luaify = quote!(#ffi::__internal::luaify!);
|
||||
let name = func.name.unraw().to_string();
|
||||
let params = &func.params;
|
||||
let body = &func.body;
|
||||
|
||||
Ok(quote! {
|
||||
b.index_raw(#name, #ffi::__internal::luaify!(|#(#params),*| #body));
|
||||
})
|
||||
registry.build.push(match func.attrs.metamethod {
|
||||
Some(ref mm) => quote!(b.metatable_raw(#mm, #luaify(|#(#params),*| #body));),
|
||||
None => quote!(b.index_raw(#name, #luaify(|#(#params),*| #body));),
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn inject_fallback_new(registry: &mut LuaRegistry) -> Result<()> {
|
||||
let ty = ®istry.ty;
|
||||
let lua = format!(
|
||||
r#"function() error("type '{}' has no constructor"); end"#,
|
||||
ty.unraw(),
|
||||
);
|
||||
|
||||
registry.build.push(quote!(
|
||||
b.metatable_raw("new", #lua);
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn inject_merged_drop(registry: &mut FfiRegistry, lua: Option<&LuaFunction>) -> Result<()> {
|
||||
let ffi = ffi_crate();
|
||||
let luaify = quote!(#ffi::__internal::luaify!);
|
||||
let ty = ®istry.ty;
|
||||
let shim_name = format_ident!("__ffi_drop");
|
||||
let c_name = format_ident!("{}_drop", ty.unraw());
|
||||
let c_name_str = c_name.to_string();
|
||||
|
||||
if let Some(lua) = lua {
|
||||
syn_assert!(
|
||||
lua.params.len() == 1,
|
||||
lua.name,
|
||||
"finaliser must take exactly one parameter"
|
||||
);
|
||||
|
||||
syn_assert!(
|
||||
pat_ident(&lua.params[0])? == "self",
|
||||
lua.params[0],
|
||||
"finaliser parameter must be `self`"
|
||||
);
|
||||
|
||||
let params = &lua.params;
|
||||
let body = &lua.body;
|
||||
|
||||
registry.build.push(quote_spanned!(ty.span() =>
|
||||
if ::std::mem::needs_drop::<Self>() {
|
||||
// if we have both a lua-side finaliser and a rust drop, then merge the finalisers
|
||||
// by doing the lua part first then drop rust
|
||||
b.declare::<#ffi::UnsafeExternCFn<(*mut Self,), ()>>(#c_name_str);
|
||||
b.metatable_raw("gc", #luaify(|self| {
|
||||
raw!(#luaify(#body)); // embed the lua part inside a do block
|
||||
__C::#c_name(self);
|
||||
}));
|
||||
} else {
|
||||
// we only have a lua-side finaliser
|
||||
b.metatable_raw("gc", #luaify(|#(#params),*| #body));
|
||||
}
|
||||
));
|
||||
} else {
|
||||
registry.build.push(quote_spanned!(ty.span() =>
|
||||
if ::std::mem::needs_drop::<Self>() {
|
||||
// we only have a rust drop
|
||||
b.declare::<#ffi::UnsafeExternCFn<(*mut Self,), ()>>(#c_name_str);
|
||||
b.metatable_raw("gc", #luaify(|self| { __C::#c_name(self); }));
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
registry.shims.push(parse_quote_spanned!(ty.span() =>
|
||||
#[unsafe(export_name = #c_name_str)]
|
||||
unsafe extern "C" fn #shim_name(ptr: *mut Self) {
|
||||
unsafe { ::std::ptr::drop_in_place(ptr) }
|
||||
}
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ use syn::{spanned::Spanned, *};
|
||||
|
||||
macro_rules! syn_error {
|
||||
($src:expr, $($fmt:expr),+) => {{
|
||||
use syn::spanned::*;
|
||||
return Err(syn::Error::new($src.span(), format!($($fmt),*)));
|
||||
}};
|
||||
}
|
||||
@ -56,11 +55,11 @@ pub fn is_unit(ty: &Type) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_primitive(ty: &Type) -> bool {
|
||||
pub fn is_primitivelike(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::Paren(paren) => is_primitivelike(&paren.elem),
|
||||
Type::Path(path) => {
|
||||
if let Some(name) = path.path.get_ident() {
|
||||
matches!(
|
||||
|
Loading…
x
Reference in New Issue
Block a user