lua_vm/tagmethods.rs
1//! Tag methods (metamethods) — ported from `ltm.c` / `ltm.h`.
2//!
3//! Every metamethod name (`__index`, `__add`, …) is interned on `GlobalState`
4//! during `init()`. Lookup helpers (`get_tm`, `get_tm_by_obj`) return
5//! `LuaValue::Nil` when no metamethod is present; callers check with
6//! `value.is_nil()` (the `notm` macro in C).
7
8use std::borrow::Cow;
9
10#[allow(unused_imports)]
11use crate::prelude::*;
12use crate::state::LuaState;
13use lua_types::{CallInfoIdx, GcRef, LuaError, LuaType, LuaValue, StackIdx};
14
15// ── TagMethod enum (from ltm.h `TMS`) ────────────────────────────────────────
16
17/// Metamethod selector; one variant per `__xxx` event, in ORDER TM.
18///
19/// The discriminant values are load-bearing: they index into
20/// `GlobalState.tmname` and are used as bit positions in `Table.flags`.
21/// Do **not** reorder without grepping ORDER TM / ORDER OP.
22///
23#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
24#[repr(u8)]
25pub(crate) enum TagMethod {
26 Index = 0,
27 NewIndex,
28 Gc,
29 Mode,
30 Len,
31 Eq,
32 Add,
33 Sub,
34 Mul,
35 Mod,
36 Pow,
37 Div,
38 IDiv,
39 BAnd,
40 BOr,
41 BXor,
42 Shl,
43 Shr,
44 Unm,
45 BNot,
46 Lt,
47 Le,
48 Concat,
49 Call,
50 Close,
51 N,
52}
53
54impl TagMethod {
55 /// Convert a raw u8 discriminant to a `TagMethod`.
56 /// Returns `TagMethod::N` (sentinel) if `v >= TM_N`.
57 ///
58 /// C casts freely between the integer and the enum; here that requires
59 /// an explicit map.
60 pub(crate) fn from_u8(v: u8) -> Self {
61 match v {
62 0 => TagMethod::Index,
63 1 => TagMethod::NewIndex,
64 2 => TagMethod::Gc,
65 3 => TagMethod::Mode,
66 4 => TagMethod::Len,
67 5 => TagMethod::Eq,
68 6 => TagMethod::Add,
69 7 => TagMethod::Sub,
70 8 => TagMethod::Mul,
71 9 => TagMethod::Mod,
72 10 => TagMethod::Pow,
73 11 => TagMethod::Div,
74 12 => TagMethod::IDiv,
75 13 => TagMethod::BAnd,
76 14 => TagMethod::BOr,
77 15 => TagMethod::BXor,
78 16 => TagMethod::Shl,
79 17 => TagMethod::Shr,
80 18 => TagMethod::Unm,
81 19 => TagMethod::BNot,
82 20 => TagMethod::Lt,
83 21 => TagMethod::Le,
84 22 => TagMethod::Concat,
85 23 => TagMethod::Call,
86 24 => TagMethod::Close,
87 _ => TagMethod::N,
88 }
89 }
90}
91
92/// Number of real metamethods (= `TagMethod::N as usize`).
93///
94pub(crate) const TM_N: usize = TagMethod::N as usize;
95
96// ── Type-name table (from ltm.h / ltm.c `luaT_typenames_`) ──────────────────
97
98// Indexed as `luaT_typenames_[(x) + 1]` where x is the raw LuaType integer
99// (LUA_TNONE = -1, LUA_TNIL = 0, …, LUA_TPROTO = 10).
100// LUA_TOTALTYPES = LUA_TPROTO + 2 = 12 entries.
101pub(crate) static TYPE_NAMES: &[&[u8]] = &[
102 b"no value", // index 0 → LUA_TNONE (-1 + 1)
103 b"nil", // index 1 → LUA_TNIL ( 0 + 1)
104 b"boolean", // index 2 → LUA_TBOOLEAN
105 b"userdata", // index 3 → LUA_TLIGHTUSERDATA
106 b"number", // index 4 → LUA_TNUMBER
107 b"string", // index 5 → LUA_TSTRING
108 b"table", // index 6 → LUA_TTABLE
109 b"function", // index 7 → LUA_TFUNCTION
110 b"userdata", // index 8 → LUA_TUSERDATA
111 b"thread", // index 9 → LUA_TTHREAD
112 b"upvalue", // index 10 → LUA_TUPVAL
113 b"proto", // index 11 → LUA_TPROTO
114];
115
116/// Return the human-readable type name for a `LuaType`.
117///
118///
119/// Panics in debug builds if `t` is out of the expected range (shouldn't
120/// happen with a well-formed `LuaType`).
121pub(crate) fn type_name(t: LuaType) -> &'static [u8] {
122 let idx = (t as i32 + 1) as usize;
123 TYPE_NAMES.get(idx).copied().unwrap_or(b"?")
124}
125
126// ── luaT_init ────────────────────────────────────────────────────────────────
127
128/// Intern all metamethod name strings and pin them in the GC.
129///
130/// Must be called exactly once during `LuaState` initialization, before any
131/// metamethod lookup. After this call, `GlobalState.tmname[i]` holds the
132/// interned `LuaString` for metamethod `i`.
133pub(crate) fn init(state: &mut LuaState) -> Result<(), LuaError> {
134 const EVENT_NAMES: &[&[u8]] = &[
135 b"__index",
136 b"__newindex",
137 b"__gc",
138 b"__mode",
139 b"__len",
140 b"__eq",
141 b"__add",
142 b"__sub",
143 b"__mul",
144 b"__mod",
145 b"__pow",
146 b"__div",
147 b"__idiv",
148 b"__band",
149 b"__bor",
150 b"__bxor",
151 b"__shl",
152 b"__shr",
153 b"__unm",
154 b"__bnot",
155 b"__lt",
156 b"__le",
157 b"__concat",
158 b"__call",
159 b"__close",
160 ];
161 debug_assert!(EVENT_NAMES.len() == TM_N);
162
163 // C's `tmname[TM_N]` is a fixed-size array on `global_State`; here
164 // `Vec<GcRef<LuaString>>` is initialized empty in `lua_state_init`, so it
165 // must grow to `TM_N` before indexed assignment.
166 if state.global().tmname.len() < TM_N {
167 let pad = state.intern_str(b"")?;
168 state.global_mut().tmname.resize(TM_N, pad);
169 }
170
171 for (i, &name) in EVENT_NAMES.iter().enumerate() {
172 let interned = state.intern_str(name)?;
173 state.global_mut().tmname[i] = interned.clone();
174 // Pin the string so the GC never collects it. `fix_object` is a
175 // no-op today; these names stay reachable through `GlobalState`
176 // regardless for the life of the state.
177 state.gc().fix_object(&interned);
178 }
179 Ok(())
180}
181
182// ── luaT_gettmbyobj ──────────────────────────────────────────────────────────
183
184/// Look up a metamethod for any Lua value by dispatching on its type.
185///
186/// Tables and full userdata have per-object metatables; all other types use
187/// the per-type metatables on `GlobalState`. Returns `LuaValue::Nil` when
188/// neither the object nor its type has a metatable, or when the metatable
189/// does not contain the requested metamethod.
190pub(crate) fn get_tm_by_obj(state: &mut LuaState, o: &LuaValue, event: TagMethod) -> LuaValue {
191 let mt: Option<GcRef<lua_types::value::LuaTable>> = match o {
192 LuaValue::Table(t) => t.metatable(),
193 LuaValue::UserData(u) => u.metatable(),
194 _ => {
195 let type_idx = o.base_type() as usize;
196 state.global().mt[type_idx].clone()
197 }
198 };
199
200 match mt {
201 Some(mt_ref) => {
202 // Clone the name string before the table lookup to avoid borrow conflict.
203 let ename = state.global().tmname[event as usize].clone();
204 mt_ref.get_short_str(&ename)
205 }
206 None => LuaValue::Nil,
207 }
208}
209
210// ── luaT_objtypename ─────────────────────────────────────────────────────────
211
212/// Return the human-readable type name for a Lua value without any heap
213/// allocation in the common case.
214///
215/// For tables and full userdata whose metatable defines `__name` as a string,
216/// returns `Cow::Owned` with the custom name bytes. For every other case
217/// returns `Cow::Borrowed` pointing into the static `TYPE_NAMES` table —
218/// no allocation, no interning, no `LuaState` access required.
219///
220/// C returns `const char*` — either a pointer into a GC-managed `LuaString`
221/// or a pointer into the static `luaT_typenames_` array. This models that as
222/// `Cow<'static, [u8]>`: `Borrowed` for static names, `Owned` only when the
223/// metatable `__name` field overrides the default. Uses
224/// `LuaTable::get_str_bytes` (linear byte-scan) instead of `intern_str` +
225/// `get_short_str` so the lookup is infallible and requires no mutable state
226/// access.
227pub(crate) fn obj_type_name_cow(o: &LuaValue, honors_name: bool) -> Cow<'static, [u8]> {
228 if matches!(o, LuaValue::LightUserData(_)) {
229 return Cow::Borrowed(b"light userdata");
230 }
231 let mt: Option<GcRef<lua_types::value::LuaTable>> = match o {
232 LuaValue::Table(t) => t.metatable(),
233 LuaValue::UserData(u) => u.metatable(),
234 _ => None,
235 };
236 if honors_name {
237 if let Some(mt_ref) = mt {
238 // Uses get_str_bytes (raw byte scan) rather than intern_str + get_short_str
239 // so no mutable state is needed and no error can propagate.
240 let name_val = mt_ref.get_str_bytes(b"__name");
241 if let LuaValue::Str(s) = name_val {
242 return Cow::Owned(s.as_bytes().to_vec());
243 }
244 }
245 }
246 Cow::Borrowed(type_name(o.base_type()))
247}
248
249/// Compatibility wrapper returning `Vec<u8>` for callers that have not yet
250/// migrated to `obj_type_name_cow`. Always allocates; prefer
251/// `obj_type_name_cow` for allocation-free lookup in error-path code.
252///
253/// `state` supplies the active version: the `__name` metafield override is a
254/// 5.3 addition, so 5.1/5.2 always report the primitive type name (VM finding
255/// F2). Fallibility (`Result`) is retained for API compatibility.
256pub(crate) fn obj_type_name(state: &mut LuaState, o: &LuaValue) -> Result<Vec<u8>, LuaError> {
257 let honors_name = state.global().lua_version.honors_name_metafield();
258 Ok(obj_type_name_cow(o, honors_name).into_owned())
259}
260
261// ── luaT_callTM ──────────────────────────────────────────────────────────────
262
263// const TValue *p2, const TValue *p3)
264/// Call tag method `f` with three arguments, discarding any return values.
265///
266/// Used for metamethods like `__gc` and `__close` that take three operands
267/// and whose return value is irrelevant.
268///
269/// If the current call frame is Lua bytecode (`isLuacode`), the metamethod
270/// may yield; otherwise yielding is suppressed (`callnoyield`).
271pub(crate) fn call_tm(
272 state: &mut LuaState,
273 f: LuaValue,
274 p1: LuaValue,
275 p2: LuaValue,
276 p3: LuaValue,
277) -> Result<(), LuaError> {
278 let func = state.top_idx();
279 // C writes these directly into the EXTRA_STACK reserve area above the
280 // official top. Here push() manages capacity instead; the semantic
281 // result is identical.
282 state.push(f);
283 state.push(p1);
284 state.push(p2);
285 state.push(p3);
286 if state.current_ci().is_lua_code() {
287 state.do_call(func, 0)?;
288 } else {
289 state.do_call_no_yield(func, 0)?;
290 }
291 Ok(())
292}
293
294// ── luaT_callTMres ───────────────────────────────────────────────────────────
295
296/// Call tag method `f` with two arguments, writing the single result into
297/// the stack slot at index `res`.
298///
299/// `res` is a `StackIdx` (index-stable across stack reallocation) that must
300/// refer to a pre-existing or scratch slot. After return, the stack top is
301/// back to what it was before the call (i.e. `top == res`).
302///
303/// C uses `savestack`/`restorestack` (byte-offset from base) to preserve `res`
304/// across potential stack reallocations inside `luaD_call`. In Rust, `StackIdx`
305/// is already an index and needs no save/restore.
306pub(crate) fn call_tm_res(
307 state: &mut LuaState,
308 f: LuaValue,
309 p1: LuaValue,
310 p2: LuaValue,
311 res: StackIdx,
312) -> Result<(), LuaError> {
313 let func = state.top_idx();
314 state.push(f);
315 state.push(p1);
316 state.push(p2);
317
318 if state.current_ci().is_lua_code() {
319 state.do_call(func, 1)?;
320 } else {
321 state.do_call_no_yield(func, 1)?;
322 }
323
324 // Pre-decrement top, then copy that slot to res.
325 let result_val = state.pop();
326 state.set_at(res, result_val);
327 Ok(())
328}
329
330// ── callbinTM (static) ────────────────────────────────────────────────────────
331
332// StkId res, TMS event)
333/// Try to find and call a binary tag method for `event`.
334///
335/// Checks `p1` first, then `p2`, for a metamethod. If neither has one,
336/// returns `false` and leaves `res` unmodified. If found, calls it with
337/// `(p1, p2)` as arguments, writes the result to slot `res`, and returns `true`.
338fn call_bin_tm(
339 state: &mut LuaState,
340 p1: &LuaValue,
341 p2: &LuaValue,
342 res: StackIdx,
343 event: TagMethod,
344) -> Result<bool, LuaError> {
345 let tm = get_tm_by_obj(state, p1, event);
346 // tm = luaT_gettmbyobj(L, p2, event); /* try second operand */
347 let tm = if tm.is_nil() {
348 get_tm_by_obj(state, p2, event)
349 } else {
350 tm
351 };
352 if tm.is_nil() {
353 return Ok(false);
354 }
355 // Clone p1/p2 before the mutable borrow of `state` via call_tm_res.
356 call_tm_res(state, tm, p1.clone(), p2.clone(), res)?;
357 Ok(true)
358}
359
360/// Lua 5.3 core string→integer coercion for bitwise ops.
361///
362/// Returns:
363/// - `Some(Ok(()))` — both operands coerced to integers; the op was computed
364/// and written to `res`.
365/// - `Some(Err(..))` — an operand is a numeric-but-non-integral string
366/// (e.g. `"3.5"`, `"0xff..ff.0"`); raises "number has no integer
367/// representation" (`luaG_tointerror`), matching lua5.3.6.
368/// - `None` — coercion is impossible (an operand is a non-numeric string such
369/// as `"abc"`); the caller falls through to its normal error path, which
370/// raises "perform bitwise operation on".
371///
372/// The 5.3-only gate and the bitwise-event filter are applied by the caller.
373fn try_bitwise_strconv_53(
374 state: &mut LuaState,
375 p1: &LuaValue,
376 p1_idx: Option<StackIdx>,
377 p2: &LuaValue,
378 p2_idx: Option<StackIdx>,
379 res: StackIdx,
380 event: TagMethod,
381) -> Option<Result<(), LuaError>> {
382 // Both operands must be number-ish (integer, float, or a string that
383 // parses as a number). If either is genuinely non-numeric, bail to the
384 // caller's "perform bitwise operation on" path.
385 let n1 = p1.to_number_with_strconv();
386 let n2 = p2.to_number_with_strconv();
387 if n1.is_none() || n2.is_none() {
388 return None;
389 }
390 // Both are number-ish. Now require integer representations. If a number-ish
391 // operand has no integer representation (non-integral numeric string or
392 // float), 5.3 raises "number has no integer representation".
393 let i1 = p1.to_integer_with_strconv();
394 let i2 = p2.to_integer_with_strconv();
395 let (i1, i2) = match (i1, i2) {
396 (Some(a), Some(b)) => (a, b),
397 _ => {
398 let p1_idx = p1_idx.unwrap_or(StackIdx(0));
399 let p2_idx = p2_idx.unwrap_or(StackIdx(0));
400 return Some(Err(crate::debug::to_int_error(
401 state,
402 p1,
403 Some(p1_idx),
404 p2,
405 Some(p2_idx),
406 )));
407 }
408 };
409 let result = match event {
410 TagMethod::BAnd => i1 & i2,
411 TagMethod::BOr => i1 | i2,
412 TagMethod::BXor => i1 ^ i2,
413 TagMethod::Shl => crate::vm::shiftl(i1, i2),
414 TagMethod::Shr => crate::vm::shiftl(i1, i2.wrapping_neg()),
415 // Unary `~x` arrives here as a binary event with p1 == p2; `~i1`.
416 TagMethod::BNot => !i1,
417 _ => return None,
418 };
419 state.set_at(res, LuaValue::Int(result));
420 Some(Ok(()))
421}
422
423// ── luaT_trybinTM ────────────────────────────────────────────────────────────
424
425// StkId res, TMS event)
426/// Attempt a binary metamethod call; raise a type error if no metamethod exists.
427///
428/// For bitwise operations, further distinguishes between:
429/// - Both operands are numbers but not integers → `LuaError::int_overflow`
430/// (`luaG_tointerror` — "number has no integer representation")
431/// - At least one operand is not a number → `LuaError::arith_error` with
432/// "perform bitwise operation on"
433///
434/// All other missing metamethods raise `LuaError::arith_error` with
435/// "perform arithmetic on".
436pub(crate) fn try_bin_tm(
437 state: &mut LuaState,
438 p1: &LuaValue,
439 p1_idx: Option<StackIdx>,
440 p2: &LuaValue,
441 p2_idx: Option<StackIdx>,
442 res: StackIdx,
443 event: TagMethod,
444) -> Result<(), LuaError> {
445 // Lua 5.3 arith error wording with string operands. In the shared (5.4)
446 // model arithmetic metamethods (`__add`/`__sub`/…) are installed on the
447 // string metatable, so a failed arith fast path that has a string operand
448 // dispatches to the string-library `trymt`, which raises `attempt to <op> a
449 // '<t>' with a '<t>'`, adds a spurious `[C]: in metamethod '<op>'` frame, and
450 // cannot produce operand varinfo (it runs as a C function with no access to
451 // the calling bytecode registers). 5.3 instead owns arithmetic string
452 // coercion in the core: when the operation cannot succeed it raises `attempt
453 // to perform arithmetic on a <type> value (<varinfo>)`, blaming the operand
454 // that does not coerce to a number.
455 //
456 // The intercept is narrow: it fires ONLY when a string operand cannot be
457 // coerced to a number AND the other operand carries no genuine arith
458 // metamethod of its own. So `t + "5"` (t has `__add`) still dispatches to
459 // t's metamethod via `call_bin_tm`, and the coercible success path
460 // (`"3" + 2`) still flows through the string metamethod below, preserving
461 // 5.3 float-promotion semantics. See specs/followup/5.3-coerce-err.md.
462 //
463 // 5.1/5.2 own arithmetic string coercion in the core the same way: a
464 // non-coercible string operand raises `attempt to perform arithmetic on a
465 // <type> value` (`luaG_aritherror` → `luaG_typeerror`, no metamethod-name
466 // attribution and no spurious `[C]: in metamethod` frame). They share this
467 // intercept; the per-version message ordering is applied downstream in
468 // `debug::type_error`.
469 if matches!(
470 state.global().lua_version,
471 lua_types::LuaVersion::V51 | lua_types::LuaVersion::V52 | lua_types::LuaVersion::V53
472 )
473 && matches!(
474 event,
475 TagMethod::Add
476 | TagMethod::Sub
477 | TagMethod::Mul
478 | TagMethod::Mod
479 | TagMethod::Pow
480 | TagMethod::Div
481 | TagMethod::IDiv
482 | TagMethod::Unm
483 )
484 && (matches!(p1, LuaValue::Str(_)) || matches!(p2, LuaValue::Str(_)))
485 {
486 use crate::state::LuaValueExt;
487 let p1_num = p1.to_number_with_strconv().is_some();
488 let p2_num = p2.to_number_with_strconv().is_some();
489 if !(p1_num && p2_num) {
490 // A string operand did not coerce. Only raise the core error here if
491 // the non-string operand has no genuine arith metamethod; otherwise
492 // fall through so `call_bin_tm` dispatches to that real metamethod
493 // (the string metatable's synthetic arith mm is ignored for this
494 // decision — it never produces a useful result for a non-coercible
495 // pairing, it only raises the wrong-version wording).
496 //
497 // Unary minus arrives as a binary event with `p1 == p2`, so there is
498 // no genuine "other operand" — the only metamethod available is the
499 // synthetic string one. Treat it as absent so `-"x"` takes the core
500 // path.
501 let unary = matches!(event, TagMethod::Unm);
502 let other_has_mm = !unary
503 && if matches!(p1, LuaValue::Str(_)) {
504 !get_tm_by_obj(state, p2, event).is_nil()
505 } else {
506 !get_tm_by_obj(state, p1, event).is_nil()
507 };
508 if !other_has_mm {
509 // Point varinfo at the operand that does not coerce, matching C
510 // `luaG_opinterror`. A coercible numeric string counts as a
511 // number, so `'2' * nil` blames `nil`, not `'2'`.
512 let (bad, bad_idx) = if !p1_num {
513 (p1, p1_idx.unwrap_or(StackIdx(0)))
514 } else {
515 (p2, p2_idx.unwrap_or(StackIdx(0)))
516 };
517 return Err(crate::debug::arith_type_error(
518 state,
519 bad,
520 bad_idx,
521 b"perform arithmetic on",
522 !unary,
523 ));
524 }
525 }
526 }
527 if !call_bin_tm(state, p1, p2, res, event)? {
528 // Lua 5.3 coerces numeric strings to integers in the *core* bitwise
529 // ops (`& | ~ << >>` and unary `~`), where 5.4/5.5 require a real
530 // number operand and delegate string handling to a (non-existent)
531 // string metamethod. On the 5.3 path, after the metamethod lookup
532 // fails, retry the operation with string→integer coercion before
533 // raising. The boundary semantics (non-integral numeric string →
534 // "no integer representation"; non-numeric string → "perform bitwise
535 // operation") fall out of `to_integer_with_strconv` /
536 // `to_number_with_strconv`. See specs/followup/5.3-coerce-err.md.
537 if matches!(state.global().lua_version, lua_types::LuaVersion::V53)
538 && matches!(
539 event,
540 TagMethod::BAnd
541 | TagMethod::BOr
542 | TagMethod::BXor
543 | TagMethod::Shl
544 | TagMethod::Shr
545 | TagMethod::BNot
546 )
547 && (matches!(p1, LuaValue::Str(_)) || matches!(p2, LuaValue::Str(_)))
548 {
549 if let Some(result) = try_bitwise_strconv_53(state, p1, p1_idx, p2, p2_idx, res, event)
550 {
551 return result;
552 }
553 }
554 // C's switch has a dead "FALLTHROUGH" for bitwise cases because both
555 // branches of the inner if/else call noreturn functions. `match` has
556 // no implicit fallthrough; each arm here is self-contained and
557 // explicitly returns `Err(...)`.
558 match event {
559 TagMethod::BAnd
560 | TagMethod::BOr
561 | TagMethod::BXor
562 | TagMethod::Shl
563 | TagMethod::Shr
564 | TagMethod::BNot => {
565 if matches!(p1, LuaValue::Int(_) | LuaValue::Float(_))
566 && matches!(p2, LuaValue::Int(_) | LuaValue::Float(_))
567 {
568 // "(field 'huge')" / "(local 'x')" etc. based on the bytecode that
569 // produced the bad operand.
570 return Err(crate::debug::to_int_error(state, p1, p1_idx, p2, p2_idx));
571 } else {
572 // varinfo on the non-number operand.
573 let p1_idx = p1_idx.unwrap_or(StackIdx(0));
574 let p2_idx = p2_idx.unwrap_or(StackIdx(0));
575 return Err(crate::debug::op_int_error(
576 state,
577 p1,
578 p1_idx,
579 p2,
580 p2_idx,
581 b"perform bitwise operation on",
582 ));
583 }
584 }
585 _ => {
586 // varinfo enriches with "(global 'aaa')" etc.
587 let p1_idx = p1_idx.unwrap_or(StackIdx(0));
588 let p2_idx = p2_idx.unwrap_or(StackIdx(0));
589 return Err(crate::debug::op_int_error(
590 state,
591 p1,
592 p1_idx,
593 p2,
594 p2_idx,
595 b"perform arithmetic on",
596 ));
597 }
598 }
599 }
600 Ok(())
601}
602
603// ── luaT_tryconcatTM ─────────────────────────────────────────────────────────
604
605/// Attempt the `__concat` metamethod on the two values at the top of the stack.
606///
607/// Reads `stack[top-2]` and `stack[top-1]`, searches both for `__concat`,
608/// calls it with `(stack[top-2], stack[top-1])` writing the result back to
609/// `stack[top-2]`, or raises `LuaError::concat_error` if no metamethod exists.
610pub(crate) fn try_concat_tm(state: &mut LuaState) -> Result<(), LuaError> {
611 let top = state.top_idx();
612 // luaG_concaterror(L, s2v(top - 2), s2v(top - 1));
613 //
614 // Clone the operands before any call that might mutate the stack.
615 let p1 = state.get_at(top - 2).clone();
616 let p2 = state.get_at(top - 1).clone();
617 if !call_bin_tm(state, &p1, &p2, top - 2, TagMethod::Concat)? {
618 let p1_ok = matches!(p1, LuaValue::Str(_) | LuaValue::Int(_) | LuaValue::Float(_));
619 let (bad, bad_idx) = if p1_ok {
620 (&p2, top - 1)
621 } else {
622 (&p1, top - 2)
623 };
624 return Err(crate::debug::type_error(
625 state,
626 bad,
627 bad_idx,
628 b"concatenate",
629 ));
630 }
631 Ok(())
632}
633
634// ── luaT_trybinassocTM ───────────────────────────────────────────────────────
635
636// int flip, StkId res, TMS event)
637/// Try a binary associative metamethod, optionally swapping the operands.
638///
639/// When `flip` is `true`, operands are exchanged before dispatch. This
640/// implements Lua's symmetry rule: if `a OP b` finds no metamethod on `a`,
641/// the VM retries with `b OP a` (setting flip=true to restore the original
642/// argument order for the call).
643pub(crate) fn try_bin_assoc_tm(
644 state: &mut LuaState,
645 p1: &LuaValue,
646 p1_idx: Option<StackIdx>,
647 p2: &LuaValue,
648 p2_idx: Option<StackIdx>,
649 flip: bool,
650 res: StackIdx,
651 event: TagMethod,
652) -> Result<(), LuaError> {
653 // luaT_trybinTM(L, p2, p1, res, event);
654 // else
655 // luaT_trybinTM(L, p1, p2, res, event);
656 if flip {
657 try_bin_tm(state, p2, p2_idx, p1, p1_idx, res, event)
658 } else {
659 try_bin_tm(state, p1, p1_idx, p2, p2_idx, res, event)
660 }
661}
662
663// ── luaT_trybiniTM ───────────────────────────────────────────────────────────
664
665// int flip, StkId res, TMS event)
666/// Try a binary metamethod where the second operand is an integer constant.
667///
668/// Boxes `i2` as `LuaValue::Int` and delegates to `try_bin_assoc_tm`.
669pub(crate) fn try_bini_tm(
670 state: &mut LuaState,
671 p1: &LuaValue,
672 p1_idx: Option<StackIdx>,
673 i2: i64,
674 flip: bool,
675 res: StackIdx,
676 event: TagMethod,
677) -> Result<(), LuaError> {
678 let aux = LuaValue::Int(i2);
679 // The immediate operand has no stack location, so it gets `None`.
680 try_bin_assoc_tm(state, p1, p1_idx, &aux, None, flip, res, event)
681}
682
683// ── get_compTM / get_equalTM (Lua 5.1 same-reference rule) ───────────────────
684
685/// Resolve the metatable governing an operand, mirroring `get_tm_by_obj`'s
686/// metatable dispatch: tables and full userdata carry per-object metatables;
687/// every other type uses the per-type metatable on `GlobalState`.
688fn operand_metatable(
689 state: &LuaState,
690 o: &LuaValue,
691) -> Option<GcRef<lua_types::value::LuaTable>> {
692 match o {
693 LuaValue::Table(t) => t.metatable(),
694 LuaValue::UserData(u) => u.metatable(),
695 _ => {
696 let type_idx = o.base_type() as usize;
697 state.global().mt[type_idx].clone()
698 }
699 }
700}
701
702/// Lua 5.1's `get_compTM`/`get_equalTM`: the comparison/equality metamethod is
703/// honoured only when BOTH operands resolve to the SAME handler function.
704///
705/// Returns the chosen metamethod, or `LuaValue::Nil` when the handlers differ,
706/// when one operand lacks the metamethod, or when neither has a metatable.
707/// 5.1 raised an error for ordered comparisons in this `Nil` case (the caller
708/// does so) and returned "not equal" for equality.
709///
710/// (#events51): 5.1 picks the left metatable's handler, returns it
711/// directly when both metatables are the same object, and otherwise keeps it
712/// only if the right metatable's handler is raw-equal (same function reference).
713/// 5.2+ consult left-then-right unconditionally, so this is gated to V51 by the
714/// caller.
715pub(crate) fn get_comp_tm_51(
716 state: &mut LuaState,
717 p1: &LuaValue,
718 p2: &LuaValue,
719 event: TagMethod,
720) -> LuaValue {
721 let tm1 = get_tm_by_obj(state, p1, event);
722 if tm1.is_nil() {
723 return LuaValue::Nil;
724 }
725 let mt1 = operand_metatable(state, p1);
726 let mt2 = operand_metatable(state, p2);
727 if let (Some(a), Some(b)) = (&mt1, &mt2) {
728 if GcRef::ptr_eq(a, b) {
729 return tm1;
730 }
731 }
732 let tm2 = get_tm_by_obj(state, p2, event);
733 if tm2.is_nil() {
734 return LuaValue::Nil;
735 }
736 if crate::vm::raw_equal_values(&tm1, &tm2) {
737 tm1
738 } else {
739 LuaValue::Nil
740 }
741}
742
743// ── luaT_callorderTM ─────────────────────────────────────────────────────────
744
745/// Call an order metamethod (`__lt` or `__le`) and return its boolean result.
746///
747/// Returns `true` if the metamethod returned a truthy value.
748/// Raises `LuaError::order_error` if neither operand has the metamethod.
749///
750/// `LUA_COMPAT_LT_LE` (deriving `__le` from `__lt`) is ON by default
751/// in the reference `make macosx` builds of 5.1–5.4 and removed in 5.5. We match
752/// the default-built reference (the pinned oracle, per specs/oracle/CONTRACT.md),
753/// so the fallback is implemented and version-gated: derive for 5.1–5.4, raise
754/// for 5.5.
755pub(crate) fn call_order_tm(
756 state: &mut LuaState,
757 p1: &LuaValue,
758 p2: &LuaValue,
759 event: TagMethod,
760) -> Result<bool, LuaError> {
761 // (#139): In 5.1, `luaV_lessthan`/`luaV_lessequal` check
762 // `ttype(l) != ttype(r)` FIRST and raise `luaG_ordererror` before any TM
763 // lookup, so the `__lt`/`__le` metamethod is consulted ONLY for same-Lua-type
764 // operands. 5.2+ removed that guard and consults the TM for mixed types too.
765 // Both-number and both-string operands are resolved by fast paths and never
766 // reach this choke point, so a single gate here on the Lua type tag (Int and
767 // Float share the `Number` tag via `base_type`, matching C's `ttype`)
768 // reproduces 5.1's check order. This is a cold path: a direct `lua_version`
769 // read is correct, mirroring the `__le`-from-`__lt` derivation gate below.
770 {
771 use crate::state::LuaValueExt;
772 if state.global().lua_version == lua_types::LuaVersion::V51
773 && p1.base_type() != p2.base_type()
774 {
775 return Err(crate::debug::order_error(state, p1, p2));
776 }
777 }
778
779 // (#events51): 5.1's `call_orderTM` requires both operands to
780 // carry the SAME `__lt`/`__le` handler (`luaO_rawequalObj(tm1, tm2)`), and
781 // raises `luaG_ordererror` otherwise — including when only one operand has
782 // the metamethod. 5.2+ consult left-then-right unconditionally, so this is
783 // gated to V51. The `__le`→`__lt` (swapped) derivation below uses the same
784 // same-reference rule.
785 if state.global().lua_version == lua_types::LuaVersion::V51 {
786 let res_idx = state.top_idx();
787 let tm = get_comp_tm_51(state, p1, p2, event);
788 if !tm.is_nil() {
789 call_tm_res(state, tm, p1.clone(), p2.clone(), res_idx)?;
790 let result = state.get_at(res_idx).clone();
791 return Ok(!matches!(result, LuaValue::Nil | LuaValue::Bool(false)));
792 }
793 if event == TagMethod::Le {
794 let tm = get_comp_tm_51(state, p2, p1, TagMethod::Lt);
795 if !tm.is_nil() {
796 state.current_call_info_mut().callstatus |= crate::state::CIST_LEQ;
797 call_tm_res(state, tm, p2.clone(), p1.clone(), res_idx)?;
798 state.current_call_info_mut().callstatus &= !crate::state::CIST_LEQ;
799 let result = state.get_at(res_idx).clone();
800 return Ok(matches!(result, LuaValue::Nil | LuaValue::Bool(false)));
801 }
802 }
803 return Err(crate::debug::order_error(state, p1, p2));
804 }
805
806 // C uses `L->top.p` as a scratch slot (written by callTMres then
807 // immediately read) in the EXTRA_STACK reserved area above the official
808 // stack top — the stack top is NOT officially advanced. Here
809 // `state.top_idx()` is passed as `res`; call_bin_tm -> call_tm_res pushes
810 // 3 values, calls, pops the result back to res, and leaves top == res
811 // (i.e. top unchanged relative to entry). Reading get_at(res_idx) after
812 // this is safe because the slot was just written and top == res_idx —
813 // an invariant that depends on do_call with 1 result always leaving
814 // exactly one value on the stack above func, and on no call path
815 // between call_bin_tm returning and this read disturbing the scratch
816 // slot or resetting top below res_idx.
817 let res_idx = state.top_idx();
818 if call_bin_tm(state, p1, p2, res_idx, event)? {
819 let result = state.get_at(res_idx).clone();
820 return Ok(!matches!(result, LuaValue::Nil | LuaValue::Bool(false)));
821 }
822
823 // LUA_COMPAT_LT_LE: in 5.1–5.4 a missing `__le` falls back to `not (b < a)`
824 // via `__lt` with the operands swapped; 5.5 removed this and raises.
825 if event == TagMethod::Le
826 && matches!(
827 state.global().lua_version,
828 lua_types::LuaVersion::V51
829 | lua_types::LuaVersion::V52
830 | lua_types::LuaVersion::V53
831 | lua_types::LuaVersion::V54
832 )
833 {
834 let res_idx = state.top_idx();
835 // Mark the CallInfo: a `__lt` standing in for `__le`. If the `__lt`
836 // metamethod yields, `call_bin_tm` returns Err and the clear below is
837 // skipped, so the mark survives the yield and `vm::finish_op` negates
838 // the result on resume. C: `L->ci->callstatus |= CIST_LEQ`.
839 state.current_call_info_mut().callstatus |= crate::state::CIST_LEQ;
840 let called = call_bin_tm(state, p2, p1, res_idx, TagMethod::Lt)?;
841 // Synchronous return: clear the mark (C: `callstatus ^= CIST_LEQ`).
842 state.current_call_info_mut().callstatus &= !crate::state::CIST_LEQ;
843 if called {
844 // l_isfalse(result): a <= b == not (b < a)
845 let result = state.get_at(res_idx).clone();
846 return Ok(matches!(result, LuaValue::Nil | LuaValue::Bool(false)));
847 }
848 }
849
850 Err(crate::debug::order_error(state, p1, p2))
851}
852
853// ── luaT_callorderiTM ────────────────────────────────────────────────────────
854
855// int flip, int isfloat, TMS event)
856/// Call an order metamethod where the second operand is a primitive int or float.
857///
858/// `v2` is a C `int`; `isfloat` selects whether it is coerced to
859/// `LuaValue::Float` (via `cast_num`) or kept as `LuaValue::Int`.
860/// When `flip` is true the operands are swapped so that `p1` was originally
861/// on the right-hand side.
862pub(crate) fn call_orderi_tm(
863 state: &mut LuaState,
864 p1: &LuaValue,
865 v2: i32,
866 flip: bool,
867 isfloat: bool,
868 event: TagMethod,
869) -> Result<bool, LuaError> {
870 // setfltvalue(&aux, cast_num(v2));
871 // }
872 // else
873 // setivalue(&aux, v2);
874 let aux = if isfloat {
875 LuaValue::Float(v2 as f64)
876 } else {
877 LuaValue::Int(v2 as i64)
878 };
879
880 // p2 = p1; p1 = &aux; /* correct them */
881 // }
882 // else
883 // p2 = &aux;
884 if flip {
885 call_order_tm(state, &aux, p1, event)
886 } else {
887 call_order_tm(state, p1, &aux, event)
888 }
889}
890
891// ── luaT_adjustvarargs ───────────────────────────────────────────────────────
892
893// const Proto *p)
894/// Adjust the stack layout for a vararg function at call entry.
895///
896/// Moves the fixed parameters above the extra (vararg) arguments and copies
897/// the function object alongside them so the function body sees its registers
898/// at the expected offsets. Records the extra-argument count in the CallInfo
899/// for `OP_VARARG` use.
900///
901/// Before call: `[func | fixed... | extra...]`
902/// After call: `[func | nil... | extra... | func′ | fixed...]`
903/// (`ci.func` and `ci.top` are advanced by `actual + 1`.)
904pub(crate) fn adjust_varargs(
905 state: &mut LuaState,
906 nfixparams: i32,
907 ci_idx: CallInfoIdx,
908 proto: &GcRef<lua_types::LuaProto>,
909) -> Result<(), LuaError> {
910 let ci_func: StackIdx = state.call_info[ci_idx.as_usize()].func;
911 let actual = (state.top_idx().0 as i32) - (ci_func.0 as i32) - 1;
912 let nextra = actual - nfixparams;
913 state.call_info[ci_idx.as_usize()].set_nextra_args(nextra);
914
915 let maxstacksize = proto.maxstacksize as i32;
916 state.check_stack(maxstacksize + 1)?;
917
918 // Re-read ci_func after check_stack (stack may have reallocated, but
919 // StackIdx is index-based so the value is still correct).
920 let ci_func: StackIdx = state.call_info[ci_idx.as_usize()].func;
921
922 let func_val = state.get_at(ci_func).clone();
923 state.push(func_val);
924
925 for i in 1..=nfixparams {
926 let src: StackIdx = ci_func + i as i32;
927 let param_val = state.get_at(src).clone();
928 state.push(param_val);
929 state.set_at(src, LuaValue::Nil);
930 }
931
932 let offset = (actual + 1) as i32;
933 state.call_info[ci_idx.as_usize()].func = state.call_info[ci_idx.as_usize()].func + offset;
934 state.call_info[ci_idx.as_usize()].top = state.call_info[ci_idx.as_usize()].top + offset;
935
936 if state.global().lua_version == lua_types::LuaVersion::V55 {
937 let base = state.call_info[ci_idx.as_usize()].func + 1;
938 state.set_at(base + nfixparams, LuaValue::Nil);
939 }
940
941 build_legacy_arg_table(state, ci_idx, proto, nextra)?;
942
943 debug_assert!(state.top_idx().0 <= state.call_info[ci_idx.as_usize()].top.0);
944 Ok(())
945}
946
947/// Build the Lua 5.1 implicit `arg` table at call entry, mirroring C 5.1's
948/// `luaD_precall`, which fills `arg` inside `adjustvarargs` *before* any call or
949/// line hook fires.
950///
951/// Our architecture defers the `arg` materialization to a `VARARGPACK` body
952/// opcode, which runs after `OP_VARARGPREP` and therefore after the call hook.
953/// A hook (or a `debug.getlocal` from a frame above) would then observe `arg`
954/// as nil. Building it here restores the C ordering: by the time the call hook
955/// runs, the frame already holds a populated `arg`. The body `VARARGPACK` later
956/// rebuilds it idempotently for the non-hook path.
957///
958/// Only applies to Lua 5.1, only when the proto's first `VARARGPACK` carries the
959/// K bit set. When the body uses `...` directly the parser rewrites that entry
960/// `VARARGPACK` into a `LOADNIL` (see `clear_arg_table_needed`), so no K-bit
961/// `VARARGPACK` survives, `legacy_arg_table_reg` returns `None`, and we build
962/// nothing here — mirroring stock 5.1, which leaves `arg` declared but nil.
963fn build_legacy_arg_table(
964 state: &mut LuaState,
965 ci_idx: CallInfoIdx,
966 proto: &GcRef<lua_types::LuaProto>,
967 nextra: i32,
968) -> Result<(), LuaError> {
969 if state.global().lua_version != lua_types::LuaVersion::V51 {
970 return Ok(());
971 }
972 let Some(arg_reg) = legacy_arg_table_reg(proto) else {
973 return Ok(());
974 };
975
976 let base = state.call_info[ci_idx.as_usize()].func + 1;
977 let ra = base + arg_reg as i32;
978 let ci_func = base - 1;
979
980 let t = if nextra > 0 {
981 state.new_table_with_sizes(nextra as u32, 1)?
982 } else {
983 state.new_table()
984 };
985 for k in 0..nextra {
986 let src: StackIdx = ci_func - nextra + k;
987 let val = state.get_at(src);
988 t.raw_set_int(state, (k + 1) as i64, val)?;
989 }
990 let n_key = state.intern_str(b"n")?;
991 t.raw_set(state, LuaValue::Str(n_key), LuaValue::Int(nextra as i64))?;
992 state.set_at(ra, LuaValue::Table(t));
993 Ok(())
994}
995
996/// Return the register of the Lua 5.1 implicit `arg` table, or `None` when the
997/// proto should not build one at entry. The signal is the first `VARARGPACK`
998/// instruction with the K bit set (see `build_legacy_arg_table`).
999fn legacy_arg_table_reg(proto: &GcRef<lua_types::LuaProto>) -> Option<u8> {
1000 for inst in proto.code.iter() {
1001 if inst.opcode() == OpCode::VarArgPack {
1002 if inst.test_k() {
1003 return Some(inst.arg_a() as u8);
1004 }
1005 return None;
1006 }
1007 }
1008 None
1009}
1010
1011// ── luaT_getvarargs ──────────────────────────────────────────────────────────
1012
1013/// Copy vararg values into the stack starting at `where_idx`.
1014///
1015/// `wanted` specifies how many values to copy. Pass `wanted < 0` (the
1016/// `LUA_MULTRET` convention) to request all available extra arguments; the
1017/// stack top is then set to `where_idx + nextra`.
1018///
1019/// Slots beyond `nextra` but within `wanted` are filled with `LuaValue::Nil`.
1020pub(crate) fn get_varargs(
1021 state: &mut LuaState,
1022 ci_idx: CallInfoIdx,
1023 where_idx: StackIdx,
1024 wanted: i32,
1025) -> Result<(), LuaError> {
1026 // Lua 5.5 named varargs (`function f(...t)`): `...` unpacks live from the
1027 // shared table `t` (its `n` field is the count), so mutating `t` is
1028 // observable through a later `...`. See `LuaProto.vararg_table_reg`.
1029 let vatab_reg = state
1030 .ci_lua_closure(ci_idx)
1031 .and_then(|cl| cl.proto.vararg_table_reg);
1032 if let Some(reg) = vatab_reg {
1033 let base = state.ci_base(ci_idx);
1034 if let LuaValue::Table(t) = state.get_at(base + reg as i32) {
1035 let n_key = state.intern_str(b"n")?;
1036 let nextra: i32 = match t.get(&LuaValue::Str(n_key)) {
1037 LuaValue::Int(i) if i >= 0 && i <= (i32::MAX / 2) as i64 => i as i32,
1038 _ => {
1039 return Err(LuaError::runtime(format_args!(
1040 "vararg table has no proper 'n'"
1041 )));
1042 }
1043 };
1044 let wanted: i32 = if wanted < 0 {
1045 state.set_top(state.call_info[ci_idx.as_usize()].top);
1046 state.check_stack(nextra)?;
1047 state.gc_pre_collect_clear();
1048 state.gc().check_step();
1049 state.set_top(where_idx + nextra);
1050 nextra
1051 } else {
1052 wanted
1053 };
1054 let copy_count = wanted.min(nextra);
1055 for i in 0..copy_count {
1056 let val = t.get(&LuaValue::Int((i + 1) as i64));
1057 state.set_at(where_idx + i as i32, val);
1058 }
1059 for i in copy_count..wanted {
1060 state.set_at(where_idx + i as i32, LuaValue::Nil);
1061 }
1062 return Ok(());
1063 }
1064 }
1065 let nextra: i32 = state.call_info[ci_idx.as_usize()].nextra_args();
1066
1067 let wanted: i32 = if wanted < 0 {
1068 state.set_top(state.call_info[ci_idx.as_usize()].top);
1069 state.check_stack(nextra)?;
1070 state.gc_pre_collect_clear();
1071 state.gc().check_step();
1072 state.set_top(where_idx + nextra as i32);
1073 nextra
1074 } else {
1075 wanted
1076 };
1077
1078 // After adjustvarargs, the extra args live at positions
1079 // ci->func - nextra .. ci->func - 1.
1080 let ci_func: StackIdx = state.call_info[ci_idx.as_usize()].func;
1081 let copy_count = wanted.min(nextra);
1082 for i in 0..copy_count {
1083 // Invariant: ci_func >= nextra, enforced by adjustvarargs.
1084 let src: StackIdx = ci_func - nextra as i32 + i as i32;
1085 let val = state.get_at(src).clone();
1086 state.set_at(where_idx + i as i32, val);
1087 }
1088
1089 for i in copy_count..wanted {
1090 state.set_at(where_idx + i as i32, LuaValue::Nil);
1091 }
1092 Ok(())
1093}