luau-vm 0.732.0

Pure-Rust Luau virtual machine, garbage collector, and standard libraries
Documentation
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;

/// Unstable metamethod lookup capability.
///
/// # Safety
///
/// Values, tables, userdata, and string handles must be live, correctly
/// tagged records belonging to this thread's VM. Cached lookup state must be
/// kept coherent with metatable mutation and the GC barrier protocol.
#[allow(
    clippy::missing_safety_doc,
    reason = "all methods share the capability-level safety contract"
)]
pub trait MetamethodRuntime: crate::handle::sealed::Sealed {
    /// `luaT_init`
    unsafe fn init(&self) -> VmErrorResult;

    /// `fasttm`
    unsafe fn fast_tm(&self, events: Option<Table>, event: TmEvent) -> Option<TValue>;

    /// `fastnotm`
    unsafe fn fast_notm(&self, events: Option<Table>, event: TmEvent) -> bool;

    /// `luaT_gettmbyobj`
    unsafe fn get_tm_by_obj(&self, object: TValue, event: TmEvent) -> Option<TValue>;

    /// `luaT_objtypenamestr`
    unsafe fn obj_type_name_str(&self, object: TValue) -> TString;

    /// `luaT_objtypename`
    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 {
    /// `luaT_init`
    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(())
    }

    /// `fasttm`
    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)) }
    }

    /// `fastnotm`
    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
            }
        }
    }

    /// `luaT_gettmbyobj`
    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) }
        }
    }

    /// `luaT_objtypenamestr`
    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)
        }
    }

    /// `luaT_objtypename`
    unsafe fn obj_type_name(&self, object: TValue) -> LuaString {
        LuaString::from_interned(unsafe { self.obj_type_name_str(object) })
    }
}