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#[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 unsafe fn init(&self) -> VmErrorResult;
63
64 unsafe fn fast_tm(&self, events: Option<Table>, event: TmEvent) -> Option<TValue>;
66
67 unsafe fn fast_notm(&self, events: Option<Table>, event: TmEvent) -> bool;
69
70 unsafe fn get_tm_by_obj(&self, object: TValue, event: TmEvent) -> Option<TValue>;
72
73 unsafe fn obj_type_name_str(&self, object: TValue) -> TString;
75
76 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 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 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 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 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 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 unsafe fn obj_type_name(&self, object: TValue) -> LuaString {
257 LuaString::from_interned(unsafe { self.obj_type_name_str(object) })
258 }
259}