From 30596d93312aea60b305bc853034d566bf240cb7 Mon Sep 17 00:00:00 2001 From: luaneko Date: Wed, 25 Jun 2025 21:24:24 +1000 Subject: [PATCH] Impl generic From/IntoFfi for Option --- crates/luaffi/Cargo.toml | 4 ++ crates/luaffi/src/lib.rs | 8 ++- crates/luaffi/src/option.rs | 84 ++++++++++++++++++++++++++++ crates/luaffi/src/result.rs | 10 ++-- crates/luaffi/src/string.rs | 108 ++++++++++++++++++++---------------- 5 files changed, 157 insertions(+), 57 deletions(-) create mode 100644 crates/luaffi/src/option.rs diff --git a/crates/luaffi/Cargo.toml b/crates/luaffi/Cargo.toml index 27abe62..209f426 100644 --- a/crates/luaffi/Cargo.toml +++ b/crates/luaffi/Cargo.toml @@ -7,6 +7,10 @@ authors.workspace = true homepage.workspace = true repository.workspace = true +[features] +option_ref_abi = [] +option_string_abi = [] + [dependencies] bstr = "1.12.0" luaffi_impl = { path = "../luaffi_impl" } diff --git a/crates/luaffi/src/lib.rs b/crates/luaffi/src/lib.rs index baaab24..a98bc87 100644 --- a/crates/luaffi/src/lib.rs +++ b/crates/luaffi/src/lib.rs @@ -11,13 +11,13 @@ use std::{ mem, }; -pub mod future; -pub mod string; - #[doc(hidden)] #[path = "./internal.rs"] pub mod __internal; +pub mod future; +pub mod option; pub mod result; +pub mod string; // Dummy function to ensure that strings passed to Rust via wrapper objects will not be // garbage-collected until the end of the function (used in string.rs when string marshalling is @@ -857,7 +857,9 @@ macro_rules! impl_ptr_intoabi { impl_ptr_intoabi!(*const T); impl_ptr_intoabi!(*mut T); +#[cfg(feature = "option_ref_abi")] // disabled because it conflicts with the generic Option impl impl_ptr_intoabi!(Option<&'static T>); +#[cfg(feature = "option_ref_abi")] impl_ptr_intoabi!(Option<&'static mut T>); // diff --git a/crates/luaffi/src/option.rs b/crates/luaffi/src/option.rs new file mode 100644 index 0000000..672e327 --- /dev/null +++ b/crates/luaffi/src/option.rs @@ -0,0 +1,84 @@ +use crate::{ + __internal::{disp, display}, + Cdef, CdefBuilder, IntoFfi, KEEP_FN, Type, TypeBuilder, TypeType, +}; +use std::{ffi::c_int, fmt::Display}; + +#[repr(C)] +#[allow(non_camel_case_types)] +pub enum lua_option { + None, // __tag = 0 + Some(T), // __tag = 1 +} + +unsafe impl Type for lua_option { + fn name() -> impl Display { + display!("option__{}", T::name()) + } + + fn ty() -> TypeType { + TypeType::Aggregate + } + + fn cdecl(name: impl Display) -> impl Display { + display!("struct {} {name}", Self::name()) + } + + fn build(b: &mut TypeBuilder) { + b.cdef::(); + } +} + +unsafe impl Cdef for lua_option { + fn build(b: &mut CdefBuilder) { + b.field::("__tag"); + (T::ty() != TypeType::Void).then(|| b.field::("__value")); + } +} + +unsafe impl IntoFfi for Option { + type Into = lua_option; + + fn convert(self) -> Self::Into { + match self { + Some(value) => lua_option::Some(T::convert(value)), + None => lua_option::None, + } + } + + fn require_owned() -> bool { + // lua_option is only used to transmit information about whether we have a value or not and + // is forgotten immediately after use, so there is no need for an owned option + false + } + + fn postlude(ret: &str) -> impl Display { + disp(move |f| { + write!(f, "if {ret}.__tag ~= 0 then ")?; + match T::Into::ty() { + TypeType::Void => write!(f, "{ret} = nil; ")?, // for void options, we don't have a __value + TypeType::Primitive => { + // can always copy primitives to stack + write!(f, "{ret} = {ret}.__value; {}", T::postlude(ret))?; + } + TypeType::Aggregate => { + let ct = T::Into::name(); + if T::require_owned() { + // inner value requires ownership; copy it into its own cdata and forget + // option. + write!(f, "{ret} = __new(__ct.{ct}, {ret}.__value); ")?; + write!(f, "{}", T::postlude(ret))?; + } else { + // inner value is a "temporary" like an option itself and doesn't require + // full ownership of itself. we just need to keep the option object alive + // until its postlude completes. + write!(f, "local {ret}_keep = {ret}; {ret} = {ret}.__value; ")?; + write!(f, "do {}end; ", T::postlude(ret))?; + write!(f, "__C.{KEEP_FN}({ret}_keep); ")?; // keep original option alive + } + } + } + write!(f, "else {ret} = nil; end; ") + }) + } +} diff --git a/crates/luaffi/src/result.rs b/crates/luaffi/src/result.rs index 280a436..c747673 100644 --- a/crates/luaffi/src/result.rs +++ b/crates/luaffi/src/result.rs @@ -72,12 +72,12 @@ unsafe impl IntoFfi for Result { write!(f, "{ret} = __new(__ct.{ct}, {ret}.__value); ")?; write!(f, "{}", T::postlude(ret))?; } else { - // inner value is a "temporary" itself and doesn't require full ownership of - // itself. we just need to keep the result object alive until its postlude - // completes. - write!(f, "local __{ret} = {ret}; {ret} = {ret}.__value; ")?; + // inner value is a "temporary" like a result itself and doesn't require + // full ownership of itself. we just need to keep the result object alive + // until its postlude completes. + write!(f, "local {ret}_keep = {ret}; {ret} = {ret}.__value; ")?; write!(f, "do {}end; ", T::postlude(ret))?; - write!(f, "__C.{KEEP_FN}(__{ret}); ")?; // keep original result alive + write!(f, "__C.{KEEP_FN}({ret}_keep); ")?; // keep original result alive } } } diff --git a/crates/luaffi/src/string.rs b/crates/luaffi/src/string.rs index 8c59c97..40646bb 100644 --- a/crates/luaffi/src/string.rs +++ b/crates/luaffi/src/string.rs @@ -4,7 +4,7 @@ use crate::{ }; use bstr::{BStr, BString}; use luaffi_impl::{cdef, metatype}; -use std::{fmt::Display, mem::ManuallyDrop, ptr, slice}; +use std::{fmt::Display, mem::ManuallyDrop, slice}; pub(crate) const IS_UTF8_FN: &str = "luaffi_is_utf8"; pub(crate) const DROP_BUFFER_FN: &str = "luaffi_drop_buffer"; @@ -42,6 +42,7 @@ impl lua_buf { } } + #[cfg(feature = "option_string_ffi")] pub(crate) fn null() -> Self { Self { __ptr: ptr::null(), @@ -71,6 +72,7 @@ impl lua_buffer { } } + #[cfg(feature = "option_string_ffi")] pub(crate) fn null() -> Self { Self { __ptr: ptr::null_mut(), @@ -228,56 +230,64 @@ impl_into_via!(&'static str, &'static BStr); impl_into_via!(BString, Vec); 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; +// `Option: From/IntoFfi` isn't implemented because it conflicts with the generic +// `Option: From/IntoFfi` impl and rust doesn't have specialisation yet (and probably not anytime +// soon). this is fine for now because we have specialisation for string-like parameters implemented +// in the #[metatype] macro already, and string returns wrapped in `Option` isn't much additional +// overhead. +#[cfg(feature = "option_string_abi")] +mod impl_option_string { + macro_rules! impl_optional_from { + ($ty:ty) => { + unsafe impl<'s> FromFfi for Option<$ty> { + type From = <$ty as FromFfi>::From; - fn require_keepalive() -> bool { - <$ty as FromFfi>::require_keepalive() - } + fn require_keepalive() -> bool { + <$ty as FromFfi>::require_keepalive() + } - fn prelude(arg: &str) -> impl Display { - // just pass a null pointer if argument is nil - display!( - "if {arg} ~= nil then {}end; ", - <$ty as FromFfi>::prelude(arg) - ) - } + fn prelude(arg: &str) -> impl Display { + // just pass a null pointer if argument is nil + display!( + "if {arg} ~= nil then {}end; ", + <$ty as FromFfi>::prelude(arg) + ) + } - fn convert(from: Self::From) -> Self { - from.map(|s| <$ty as FromFfi>::convert(Some(s))) + fn convert(from: Self::From) -> Self { + from.map(|s| <$ty as FromFfi>::convert(Some(s))) + } } - } - }; + }; + } + + impl_optional_from!(&'s [u8]); + impl_optional_from!(&'s BStr); + impl_optional_from!(&'s str); + + macro_rules! impl_optional_into { + ($ty:ty, $null:expr) => { + unsafe impl IntoFfi for Option<$ty> { + type Into = <$ty as IntoFfi>::Into; + + fn convert(self) -> Self::Into { + self.map_or($null, <$ty as IntoFfi>::convert) + } + + fn postlude(ret: &str) -> impl Display { + display!( + "if {ret}.__ptr == nil then {ret} = nil; else {}end; ", + <$ty as IntoFfi>::postlude(ret) + ) + } + } + }; + } + + 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, lua_buffer::null()); + impl_optional_into!(BString, lua_buffer::null()); + impl_optional_into!(String, lua_buffer::null()); } - -impl_optional_from!(&'s [u8]); -impl_optional_from!(&'s BStr); -impl_optional_from!(&'s str); - -macro_rules! impl_optional_into { - ($ty:ty, $null:expr) => { - unsafe impl IntoFfi for Option<$ty> { - type Into = <$ty as IntoFfi>::Into; - - fn convert(self) -> Self::Into { - self.map_or($null, <$ty as IntoFfi>::convert) - } - - fn postlude(ret: &str) -> impl Display { - display!( - "if {ret}.__ptr == nil then {ret} = nil; else {}end; ", - <$ty as IntoFfi>::postlude(ret) - ) - } - } - }; -} - -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, lua_buffer::null()); -impl_optional_into!(BString, lua_buffer::null()); -impl_optional_into!(String, lua_buffer::null());