Skip to main content

luau_vm/
metamethod.rs

1use core::ptr::NonNull;
2use luau_common::ByteSlice;
3
4use crate::Table;
5use crate::VmErrorResult;
6use crate::handle::RawHandle;
7use crate::state::GlobalState;
8use crate::string::LuaString;
9use crate::string::StringRuntime;
10use crate::string::TString;
11use crate::thread::Thread;
12use crate::types::{
13    LUA_T_COUNT, LUA_TCLASS, LUA_TLIGHTUSERDATA, LUA_TOBJECT, LUA_TTABLE, LUA_TUSERDATA,
14};
15use crate::value::TValue;
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18#[repr(usize)]
19pub enum TmEvent {
20    Index,
21    NewIndex,
22    Mode,
23    NameCall,
24    Call,
25    Iter,
26    Len,
27    Eq,
28    Add,
29    Sub,
30    Mul,
31    Div,
32    IDiv,
33    Mod,
34    Pow,
35    Unm,
36    Lt,
37    Le,
38    Concat,
39    Type,
40    MetaTable,
41}
42
43impl TmEvent {
44    pub const FAST_LIMIT: usize = Self::Eq as usize;
45}
46
47pub const TM_N: usize = TmEvent::MetaTable as usize + 1;
48
49/// Unstable metamethod lookup capability.
50///
51/// # Safety
52///
53/// Values, tables, userdata, and string handles must be live, correctly
54/// tagged records belonging to this thread's VM. Cached lookup state must be
55/// kept coherent with metatable mutation and the GC barrier protocol.
56#[allow(
57    clippy::missing_safety_doc,
58    reason = "all methods share the capability-level safety contract"
59)]
60pub trait MetamethodRuntime: crate::handle::sealed::Sealed {
61    /// `luaT_init`
62    unsafe fn init(&self) -> VmErrorResult;
63
64    /// `fasttm`
65    unsafe fn fast_tm(&self, events: Option<Table>, event: TmEvent) -> Option<TValue>;
66
67    /// `fastnotm`
68    unsafe fn fast_notm(&self, events: Option<Table>, event: TmEvent) -> bool;
69
70    /// `luaT_gettmbyobj`
71    unsafe fn get_tm_by_obj(&self, object: TValue, event: TmEvent) -> Option<TValue>;
72
73    /// `luaT_objtypenamestr`
74    unsafe fn obj_type_name_str(&self, object: TValue) -> TString;
75
76    /// `luaT_objtypename`
77    unsafe fn obj_type_name(&self, object: TValue) -> LuaString;
78}
79
80const TYPE_NAMES: [&[u8]; LUA_T_COUNT] = [
81    b"nil",
82    b"boolean",
83    b"userdata",
84    b"number",
85    b"integer",
86    b"vector",
87    b"string",
88    b"table",
89    b"function",
90    b"userdata",
91    b"thread",
92    b"buffer",
93    b"class",
94    b"object",
95];
96
97const EVENT_NAMES: [&[u8]; TM_N] = [
98    b"__index",
99    b"__newindex",
100    b"__mode",
101    b"__namecall",
102    b"__call",
103    b"__iter",
104    b"__len",
105    b"__eq",
106    b"__add",
107    b"__sub",
108    b"__mul",
109    b"__div",
110    b"__idiv",
111    b"__mod",
112    b"__pow",
113    b"__unm",
114    b"__lt",
115    b"__le",
116    b"__concat",
117    b"__type",
118    b"__metatable",
119];
120
121#[allow(
122    clippy::missing_safety_doc,
123    reason = "GlobalState's shared raw-handle contract is documented on GlobalState"
124)]
125impl GlobalState {
126    pub unsafe fn type_name(&self, tag: usize) -> TString {
127        unsafe {
128            TString::from_raw(NonNull::new_unchecked(
129                self.as_ptr().as_ref().unwrap_unchecked().tt_name[tag],
130            ))
131        }
132    }
133
134    pub unsafe fn tm_name(&self, event: usize) -> TString {
135        unsafe {
136            TString::from_raw(NonNull::new_unchecked(
137                self.as_ptr().as_ref().unwrap_unchecked().tm_name[event],
138            ))
139        }
140    }
141
142    pub fn metatable(&self, tag: usize) -> Option<Table> {
143        unsafe {
144            Some(Table::from_raw(NonNull::new(
145                self.as_ptr().as_ref().unwrap_unchecked().mt[tag],
146            )?))
147        }
148    }
149}
150
151impl MetamethodRuntime for Thread {
152    /// `luaT_init`
153    unsafe fn init(&self) -> VmErrorResult {
154        unsafe {
155            let global = self.global();
156
157            for (index, type_name) in TYPE_NAMES.into_iter().enumerate() {
158                let string = self.intern_string(type_name.as_bstr())?;
159                string.fix();
160                global.as_ptr().as_mut().unwrap_unchecked().tt_name[index] = string.as_ptr();
161            }
162
163            for (index, event_name) in EVENT_NAMES.into_iter().enumerate() {
164                let string = self.intern_string(event_name.as_bstr())?;
165                string.fix();
166                global.as_ptr().as_mut().unwrap_unchecked().tm_name[index] = string.as_ptr();
167            }
168        }
169        Ok(())
170    }
171
172    /// `fasttm`
173    unsafe fn fast_tm(&self, events: Option<Table>, event: TmEvent) -> Option<TValue> {
174        let table = events?;
175        if unsafe { self.fast_notm(Some(table), event) } {
176            return None;
177        }
178
179        unsafe { table.get_tm(event, self.global().tm_name(event as usize)) }
180    }
181
182    /// `fastnotm`
183    unsafe fn fast_notm(&self, events: Option<Table>, event: TmEvent) -> bool {
184        match events {
185            None => true,
186            Some(table) => {
187                (unsafe { table.as_ptr().as_ref().unwrap_unchecked().tm_cache }
188                    & (1u8 << event as usize))
189                    != 0
190            }
191        }
192    }
193
194    /// `luaT_gettmbyobj`
195    unsafe fn get_tm_by_obj(&self, object: TValue, event: TmEvent) -> Option<TValue> {
196        unsafe {
197            let type_tag = object.tt();
198            let metatable = match type_tag {
199                x if x == LUA_TTABLE => object.table_value().metatable(),
200                x if x == LUA_TUSERDATA => object.userdata_value().metatable(),
201                x if x == LUA_TCLASS => object.class_value().metatable(),
202                x if x == LUA_TOBJECT => object.object_value().class().instance_metatable(),
203                _ => self.global().metatable(type_tag as usize),
204            }?;
205
206            let event_name = self.global().tm_name(event as usize);
207            let value = metatable.get_str(event_name);
208            if value.is_nil() { None } else { Some(value) }
209        }
210    }
211
212    /// `luaT_objtypenamestr`
213    unsafe fn obj_type_name_str(&self, object: TValue) -> TString {
214        unsafe {
215            let global = self.global();
216            let type_tag = object.tt() as usize;
217
218            if object.is_userdata() {
219                let userdata = object.userdata_value();
220                if userdata.as_ptr().as_ref().unwrap_unchecked().tag
221                    != crate::userdata::USERDATA_TAG_PROXY as u8
222                    && let Some(metatable) = userdata.metatable()
223                {
224                    let type_name = metatable.get_str(global.tm_name(TmEvent::Type as usize));
225
226                    if type_name.is_string() {
227                        return type_name.string_value();
228                    }
229
230                    return global.type_name(type_tag);
231                }
232            }
233
234            if object.tt() == LUA_TLIGHTUSERDATA {
235                let tag = object.light_userdata_tag();
236                if (tag as u32) < crate::userdata::LIGHT_USERDATA_TAG_LIMIT as u32
237                    && let Some(name) = global.light_userdata_name(tag as usize)
238                {
239                    return name;
240                }
241            }
242
243            if let Some(metatable) = global.metatable(type_tag) {
244                let type_name = metatable.get_str(global.tm_name(TmEvent::Type as usize));
245
246                if type_name.is_string() {
247                    return type_name.string_value();
248                }
249            }
250
251            global.type_name(type_tag)
252        }
253    }
254
255    /// `luaT_objtypename`
256    unsafe fn obj_type_name(&self, object: TValue) -> LuaString {
257        LuaString::from_interned(unsafe { self.obj_type_name_str(object) })
258    }
259}