Fix __eq metamethods throwing on type mismatch

This commit is contained in:
2025-06-28 11:10:29 +10:00
parent db9d611ff7
commit 36300b07d3
2 changed files with 131 additions and 63 deletions

View File

@@ -458,6 +458,7 @@ pub struct MetatypeFunctionBuilder<'r, 'm> {
cargs: String, // C call arguments
prelude: String, // lua function body prelude
postlude: String, // lua function body postlude
is_eqmm: bool,
}
impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
@@ -469,9 +470,15 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
cargs: String::new(),
prelude: String::new(),
postlude: String::new(),
is_eqmm: false,
}
}
pub fn set_eqmm(&mut self, eq: bool) -> &mut Self {
self.is_eqmm = eq; // are we building an __eq metamethod?
self
}
pub fn param<T: FromFfi>(&mut self, name: impl Display) -> &mut Self {
let Self {
metatype: MetatypeBuilder { reg, .. },
@@ -510,6 +517,26 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
self
}
pub fn param_ctchecked<T: FromFfi, U: Type + Metatype<Target = U>>(
&mut self,
name: impl Display,
) -> &mut Self {
let ct = U::name();
if self.is_eqmm {
// special case for building __eq metamethods which are expected to return false if the
// arguments aren't of the expected type, because otherwise the `==` and `~=` operators
// could throw instead of returning false.
write!(
self.prelude,
r#"if not __istype(__ct.{ct}, {name}) then return false; end; "#
)
} else {
write!(self.prelude, r#"assert(__istype(__ct.{ct}, {name})); "#)
}
.unwrap();
self.param::<T>(name)
}
pub fn param_str(
&mut self,
name: impl Display,
@@ -520,7 +547,9 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
//
// this passes one lua `string` argument as two C `const uint8_t *ptr` and `uintptr_t len`
// arguments, bypassing the slower generic `&[u8]: FromFfi` path which constructs a
// temporary cdata to pass the string and its length in one argument.
// temporary cdata to pass the string and its length in one argument. this is used when the
// #[metatype] macro detects a string-like parameter, currently defined to be &[u8], &str,
// &BStr (from the bstr crate), or those types wrapped in Option<T>.
let Self {
lparams,
cparams,
@@ -780,8 +809,7 @@ macro_rules! impl_bigint_intoabi {
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 )
// precision here and that 53 bits of precision for big integers are enough.
display!("{ret} = tonumber({ret}); ")
}
}
@@ -837,22 +865,6 @@ impl_ptr!(*mut T, true);
impl_ptr!(Option<&T>, false);
impl_ptr!(Option<&mut T>, true);
macro_rules! impl_ptr_annotation {
($ty:ty) => {
impl<T> Annotation for $ty
where
T: Annotation,
{
fn annotation() -> impl Display {
"lightuserdata"
}
}
};
}
impl_ptr_annotation!(*const T);
impl_ptr_annotation!(*mut T);
macro_rules! impl_ref_annotation {
($ty:ty) => {
impl<T> Annotation for $ty