Impl generic From/IntoFfi for Option
This commit is contained in:
84
crates/luaffi/src/option.rs
Normal file
84
crates/luaffi/src/option.rs
Normal file
@@ -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<T> {
|
||||
None, // __tag = 0
|
||||
Some(T), // __tag = 1
|
||||
}
|
||||
|
||||
unsafe impl<T: Type> Type for lua_option<T> {
|
||||
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::<Self>();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Type> Cdef for lua_option<T> {
|
||||
fn build(b: &mut CdefBuilder) {
|
||||
b.field::<c_int>("__tag");
|
||||
(T::ty() != TypeType::Void).then(|| b.field::<T>("__value"));
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: IntoFfi> IntoFfi for Option<T> {
|
||||
type Into = lua_option<T::Into>;
|
||||
|
||||
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; ")
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user