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