use crate::{ __internal::{disp, display, type_id}, Cdef, CdefBuilder, IntoFfi, KEEP_FN, Metatype, MetatypeBuilder, 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_{:x}", type_id::()) } fn ty() -> TypeType { TypeType::Aggregate } fn cdecl(name: impl Display) -> impl Display { display!("struct {} {name}", Self::name()) } fn build(b: &mut TypeBuilder) { b.cdef::().metatype::(); } } unsafe impl Cdef for lua_option { fn build(b: &mut CdefBuilder) { b.field::("__tag"); (T::ty() != TypeType::Void).then(|| b.field::("__value")); } } unsafe impl Metatype for lua_option { type Target = Self; fn build(_b: &mut MetatypeBuilder) {} } 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; ") }) } }