use core::ptr::NonNull;
use luau_common::ByteSlice;
use crate::Table;
use crate::VmErrorResult;
use crate::handle::RawHandle;
use crate::state::GlobalState;
use crate::string::LuaString;
use crate::string::StringRuntime;
use crate::string::TString;
use crate::thread::Thread;
use crate::types::{
LUA_T_COUNT, LUA_TCLASS, LUA_TLIGHTUSERDATA, LUA_TOBJECT, LUA_TTABLE, LUA_TUSERDATA,
};
use crate::value::TValue;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(usize)]
pub enum TmEvent {
Index,
NewIndex,
Mode,
NameCall,
Call,
Iter,
Len,
Eq,
Add,
Sub,
Mul,
Div,
IDiv,
Mod,
Pow,
Unm,
Lt,
Le,
Concat,
Type,
MetaTable,
}
impl TmEvent {
pub const FAST_LIMIT: usize = Self::Eq as usize;
}
pub const TM_N: usize = TmEvent::MetaTable as usize + 1;
#[allow(
clippy::missing_safety_doc,
reason = "all methods share the capability-level safety contract"
)]
pub trait MetamethodRuntime: crate::handle::sealed::Sealed {
unsafe fn init(&self) -> VmErrorResult;
unsafe fn fast_tm(&self, events: Option<Table>, event: TmEvent) -> Option<TValue>;
unsafe fn fast_notm(&self, events: Option<Table>, event: TmEvent) -> bool;
unsafe fn get_tm_by_obj(&self, object: TValue, event: TmEvent) -> Option<TValue>;
unsafe fn obj_type_name_str(&self, object: TValue) -> TString;
unsafe fn obj_type_name(&self, object: TValue) -> LuaString;
}
const TYPE_NAMES: [&[u8]; LUA_T_COUNT] = [
b"nil",
b"boolean",
b"userdata",
b"number",
b"integer",
b"vector",
b"string",
b"table",
b"function",
b"userdata",
b"thread",
b"buffer",
b"class",
b"object",
];
const EVENT_NAMES: [&[u8]; TM_N] = [
b"__index",
b"__newindex",
b"__mode",
b"__namecall",
b"__call",
b"__iter",
b"__len",
b"__eq",
b"__add",
b"__sub",
b"__mul",
b"__div",
b"__idiv",
b"__mod",
b"__pow",
b"__unm",
b"__lt",
b"__le",
b"__concat",
b"__type",
b"__metatable",
];
#[allow(
clippy::missing_safety_doc,
reason = "GlobalState's shared raw-handle contract is documented on GlobalState"
)]
impl GlobalState {
pub unsafe fn type_name(&self, tag: usize) -> TString {
unsafe {
TString::from_raw(NonNull::new_unchecked(
self.as_ptr().as_ref().unwrap_unchecked().tt_name[tag],
))
}
}
pub unsafe fn tm_name(&self, event: usize) -> TString {
unsafe {
TString::from_raw(NonNull::new_unchecked(
self.as_ptr().as_ref().unwrap_unchecked().tm_name[event],
))
}
}
pub fn metatable(&self, tag: usize) -> Option<Table> {
unsafe {
Some(Table::from_raw(NonNull::new(
self.as_ptr().as_ref().unwrap_unchecked().mt[tag],
)?))
}
}
}
impl MetamethodRuntime for Thread {
unsafe fn init(&self) -> VmErrorResult {
unsafe {
let global = self.global();
for (index, type_name) in TYPE_NAMES.into_iter().enumerate() {
let string = self.intern_string(type_name.as_bstr())?;
string.fix();
global.as_ptr().as_mut().unwrap_unchecked().tt_name[index] = string.as_ptr();
}
for (index, event_name) in EVENT_NAMES.into_iter().enumerate() {
let string = self.intern_string(event_name.as_bstr())?;
string.fix();
global.as_ptr().as_mut().unwrap_unchecked().tm_name[index] = string.as_ptr();
}
}
Ok(())
}
unsafe fn fast_tm(&self, events: Option<Table>, event: TmEvent) -> Option<TValue> {
let table = events?;
if unsafe { self.fast_notm(Some(table), event) } {
return None;
}
unsafe { table.get_tm(event, self.global().tm_name(event as usize)) }
}
unsafe fn fast_notm(&self, events: Option<Table>, event: TmEvent) -> bool {
match events {
None => true,
Some(table) => {
(unsafe { table.as_ptr().as_ref().unwrap_unchecked().tm_cache }
& (1u8 << event as usize))
!= 0
}
}
}
unsafe fn get_tm_by_obj(&self, object: TValue, event: TmEvent) -> Option<TValue> {
unsafe {
let type_tag = object.tt();
let metatable = match type_tag {
x if x == LUA_TTABLE => object.table_value().metatable(),
x if x == LUA_TUSERDATA => object.userdata_value().metatable(),
x if x == LUA_TCLASS => object.class_value().metatable(),
x if x == LUA_TOBJECT => object.object_value().class().instance_metatable(),
_ => self.global().metatable(type_tag as usize),
}?;
let event_name = self.global().tm_name(event as usize);
let value = metatable.get_str(event_name);
if value.is_nil() { None } else { Some(value) }
}
}
unsafe fn obj_type_name_str(&self, object: TValue) -> TString {
unsafe {
let global = self.global();
let type_tag = object.tt() as usize;
if object.is_userdata() {
let userdata = object.userdata_value();
if userdata.as_ptr().as_ref().unwrap_unchecked().tag
!= crate::userdata::USERDATA_TAG_PROXY as u8
&& let Some(metatable) = userdata.metatable()
{
let type_name = metatable.get_str(global.tm_name(TmEvent::Type as usize));
if type_name.is_string() {
return type_name.string_value();
}
return global.type_name(type_tag);
}
}
if object.tt() == LUA_TLIGHTUSERDATA {
let tag = object.light_userdata_tag();
if (tag as u32) < crate::userdata::LIGHT_USERDATA_TAG_LIMIT as u32
&& let Some(name) = global.light_userdata_name(tag as usize)
{
return name;
}
}
if let Some(metatable) = global.metatable(type_tag) {
let type_name = metatable.get_str(global.tm_name(TmEvent::Type as usize));
if type_name.is_string() {
return type_name.string_value();
}
}
global.type_name(type_tag)
}
}
unsafe fn obj_type_name(&self, object: TValue) -> LuaString {
LuaString::from_interned(unsafe { self.obj_type_name_str(object) })
}
}