lua_vm/func.rs
1//! Auxiliary functions to manipulate prototypes and closures.
2//!
3//! Port of `reference/lua-5.4.7/src/lfunc.c` (295 lines, 16 functions).
4//! The companion header `lfunc.h` is merged here per PORTING.md §1.
5//!
6//! # Design notes
7//!
8//! The C implementation uses two intrusive linked lists managed through pointer
9//! fields embedded in stack slots and upvalue objects:
10//!
11//! - **`openupval`**: a singly-linked list of `UpVal`s sorted by stack level
12//! (highest first), threaded through `UpVal.u.open.next / .previous`.
13//! - **`tbclist`**: a to-be-closed variable list encoded as `unsigned short` delta
14//! offsets stored inside `StackValue.tbclist.delta`.
15//!
16//! Both are replaced in the Rust port:
17//! - `openupval` → `LuaState.openupval: Vec<GcRef<UpVal>>` (descending by StackIdx).
18//! - `tbclist` → `LuaState.tbclist: Vec<StackIdx>` (back = most recent entry).
19//!
20//! The delta-encoding machinery (MAXDELTA, dummy nodes) is an artifact of the u16
21//! delta field and is entirely superseded by the `Vec<StackIdx>` model.
22
23// PORT NOTE: `LuaProto` is currently a stub in crate::state (from lstate.c's
24// partial port in state.rs). The full `LuaProto` definition belongs in
25// crate::object (lobject.c → object.rs). Fields referenced below will compile
26// once object.rs is written; see TODO(port) at each field site.
27
28// PORT NOTE: `GcRef<T> = Rc<T>` in Phase A–C provides no interior mutability.
29// `close_upval` and `init_upvals` must mutate `UpVal` and `LuaClosure` values
30// that are shared through `GcRef`. In Phase B, the design options are:
31// (a) `GcRef<T> = Rc<RefCell<T>>` for mutable GC objects, or
32// (b) a custom `GcCell<T>` wrapper with conditional interior mutability.
33// Both `close_upval` and `init_upvals` carry `TODO(port)` at the mutation sites.
34
35#[allow(unused_imports)] use crate::prelude::*;
36
37use crate::state::{
38 GcRef, LuaState, LuaValue, UpVal,
39};
40use lua_types::error::LuaError;
41pub use lua_types::{CallInfoIdx, StackIdx};
42
43// ── lfunc.h constants ─────────────────────────────────────────────────────────
44
45// macros.tsv: CLOSEKTOP → const CLOSE_K_TOP: i32 = -1
46/// Sentinel status meaning "close upvalues but preserve the stack top."
47/// Passed as `status` to `close` / `prep_call_close_mth`.
48pub(crate) const CLOSE_K_TOP: i32 = -1;
49
50// ── Closure allocation ────────────────────────────────────────────────────────
51
52/// Fills a Lua closure's upvalue slots with freshly-allocated closed upvalues,
53/// each holding `LuaValue::Nil`. Used when compiling closures that capture no
54/// live stack variables.
55///
56pub(crate) fn init_upvals(state: &mut LuaState, cl: &GcRef<lua_types::LuaLClosure>) -> Result<(), LuaError> {
57 // GCObject *o = luaC_newobj(L, LUA_VUPVAL, sizeof(UpVal));
58 // UpVal *uv = gco2upv(o);
59 // uv->v.p = &uv->u.value; /* make it closed */
60 // setnilvalue(uv->v.p); /* *o = LuaValue::Nil */
61 // cl->upvals[i] = uv;
62 // luaC_objbarrier(L, cl, uv);
63 // }
64 //
65 // In Rust: create UpVal::Closed(Nil) for each slot; GC barrier is no-op Phase A–C.
66
67 // TODO(port): GcRef<T> = Rc<T> has no interior mutability. Mutating
68 // `cl.upvals[i]` here requires either Rc<RefCell<LuaClosure>> or Rc::get_mut.
69 // The code below captures the intended logic; it will not compile until
70 // GcRef provides a borrow_mut() path (Phase B design decision).
71 let n = cl.upvals.len();
72 for i in 0..n {
73 let uv: GcRef<UpVal> = state.new_upval_closed(LuaValue::Nil);
74 // TODO(port): cl.borrow_mut().as_lua_mut().upvals[i] = Some(uv.clone());
75 // Requires interior mutability; see PORT NOTE at top of file.
76 let _ = (i, uv);
77 }
78 Ok(())
79}
80
81// ── Open-upvalue management ───────────────────────────────────────────────────
82
83/// Creates a new open upvalue for stack slot `level`, inserts it into
84/// `state.openupval` at `insert_pos`, and registers the thread in the
85/// global `twups` list if necessary.
86///
87fn new_open_upval(
88 state: &mut LuaState,
89 level: StackIdx,
90 insert_pos: usize,
91) -> GcRef<UpVal> {
92 // UpVal *uv = gco2upv(o);
93 // UpVal *next = *prev;
94 // uv->v.p = s2v(level); /* current value lives in the stack */
95 // uv->u.open.next = next;
96 // uv->u.open.previous = prev;
97 // if (next) next->u.open.previous = &uv->u.open.next;
98 // *prev = uv;
99 //
100 // In Rust: intrusive next/previous fields are gone; Vec insertion replaces
101 // the pointer-threading. The `prev` parameter (UpVal **) becomes `insert_pos`.
102 //
103 // The home thread of the upvalue is whichever thread is currently
104 // executing `find_upval` — it captures one of that thread's stack
105 // slots. Phase E-3 makes this id real so `upvalue_get`/`upvalue_set`
106 // can dispatch through `GlobalState::cross_thread_upvals` when a
107 // coroutine reads or writes an upvalue belonging to its parent.
108 let owner_tid = state.global().current_thread_id as usize;
109 let uv: GcRef<UpVal> = state.new_upval_open(owner_tid, level);
110 // PORT NOTE: Vec insert maintains descending StackIdx order (highest first),
111 // mirroring the C intrusive list where the head is always the topmost slot.
112 state.openupval.insert(insert_pos, uv.clone());
113 // macros.tsv: isintwups → state.in_twups()
114 // TODO(port): implement state.in_twups() and the twups insertion. The method needs to
115 // check whether this LuaState is already in global.twups. Requires either a flag on
116 // LuaState or a scan of global.twups. See also lstate.h discussion in state.rs.
117 if !state_in_twups(state) {
118 // TODO(port): state.global_mut().twups.push(gc_ref_to_this_thread(state));
119 // Deferred: obtaining a GcRef<LuaState> to self requires Arc/Rc self-reference
120 // which is an unsolved design problem for Phase E coroutines.
121 }
122 uv
123}
124
125/// Finds or creates an open upvalue for stack slot `level`.
126///
127/// Searches `state.openupval` (sorted descending by StackIdx) for an existing
128/// open upvalue at exactly `level`. If found, returns it. Otherwise, inserts a
129/// new one at the correct sorted position and returns it.
130///
131pub(crate) fn find_upval(state: &mut LuaState, level: StackIdx) -> GcRef<UpVal> {
132 debug_assert!(
133 state_in_twups(state) || state.openupval.is_empty(),
134 "thread must be in twups if it has open upvalues"
135 );
136 // while ((p = *pp) != NULL && uplevel(p) >= level) {
137 // lua_assert(!isdead(G(L), p));
138 // if (uplevel(p) == level) return p; /* found */
139 // pp = &p->u.open.next;
140 // }
141 // return newupval(L, level, pp);
142 //
143 // The list is sorted descending. We scan from index 0 (highest) downward.
144 // When we find an entry with idx < level we've passed the insertion point.
145 let mut insert_pos = state.openupval.len(); // default: append at end
146 for (i, uv_ref) in state.openupval.iter().enumerate() {
147 // macros.tsv: uplevel → extract thread_stack_idx from UpVal::Open
148 let uv_idx = match &*uv_ref.slot() {
149 lua_types::UpValState::Open { thread_id: _, idx: thread_stack_idx } => *thread_stack_idx,
150 lua_types::UpValState::Closed(_) => {
151 debug_assert!(false, "closed upvalue found in openupval list");
152 continue;
153 }
154 };
155 if uv_idx.0 >= level.0 {
156 if uv_idx == level {
157 return uv_ref.clone();
158 }
159 // uv_idx.0 > level.0: this entry is higher on the stack; keep searching.
160 } else {
161 // uv_idx.0 < level.0: correct insertion point reached.
162 insert_pos = i;
163 break;
164 }
165 }
166 new_open_upval(state, level, insert_pos)
167}
168
169// ── Close-method call helpers ─────────────────────────────────────────────────
170
171/// Calls the `__close` metamethod on `obj` with error argument `err`.
172/// `yy` controls whether the call is yieldable (true) or non-yieldable (false).
173///
174/// This function assumes EXTRA_STACK free slots are available.
175///
176fn call_close_method(
177 state: &mut LuaState,
178 obj: LuaValue,
179 err: LuaValue,
180 yy: bool,
181) -> Result<(), LuaError> {
182 // const TValue *tm = luaT_gettmbyobj(L, obj, TM_CLOSE);
183 // setobj2s(L, top, tm); /* push metamethod */
184 // setobj2s(L, top + 1, obj); /* 1st arg: self */
185 // setobj2s(L, top + 2, err); /* 2nd arg: error message */
186 // L->top.p = top + 3;
187 // if (yy) luaD_call(L, top, 0);
188 // else luaD_callnoyield(L, top, 0);
189 //
190 // In Rust: state.push() manages the top pointer; no pointer arithmetic needed.
191 // setobj2s → state.push(value.clone())
192 // macros.tsv: luaT_gettmbyobj → state.get_tm_by_obj(&obj, TagMethod::Close)
193 let tm = state.get_tm_by_obj(&obj, lua_types::tagmethod::TagMethod::Close);
194 let top = state.top;
195 state.push(tm);
196 state.push(obj);
197 state.push(err);
198 // TODO(port): state.call(top, 0) / state.call_noyield(top, 0) —
199 // these methods live in do_.rs (ldo.c); cross-module call.
200 if yy {
201 state.lua_call(top, 0)?;
202 } else {
203 state.lua_callnoyield(top, 0)?;
204 }
205 Ok(())
206}
207
208/// Checks that the value at `level` has a `__close` metamethod, raising a
209/// runtime error if it does not.
210///
211fn check_close_mth(state: &mut LuaState, level: StackIdx) -> Result<(), LuaError> {
212 // if (ttisnil(tm)) {
213 // int idx = cast_int(level - L->ci->func.p);
214 // const char *vname = luaG_findlocal(L, L->ci, idx, NULL);
215 // if (vname == NULL) vname = "?";
216 // luaG_runerror(L, "variable '%s' got a non-closable value", vname);
217 // }
218 //
219 // macros.tsv: s2v(level) → state.stack_at(level) — returns &LuaValue
220 // macros.tsv: ttisnil(tm) → matches!(tm, LuaValue::Nil)
221 let val = state.get_stack_value(level).clone();
222 let tm = state.get_tm_by_obj(&val, lua_types::tagmethod::TagMethod::Close);
223 if matches!(tm, LuaValue::Nil) {
224 // macros.tsv: cast_int → x as i32
225 // CallInfo.func is the StackIdx of the function on the stack.
226 let func_idx = state.current_ci().func;
227 let idx = (level.0 as i32) - (func_idx.0 as i32);
228 let vname_owned: Vec<u8> = state.debug_find_local(state.ci, idx).unwrap_or_else(|| b"?".to_vec());
229 // PORT NOTE: Lua variable names are ASCII identifiers; `escape_ascii`
230 // produces a Display-compatible wrapper for the byte slice.
231 return Err(LuaError::runtime(format_args!(
232 "variable '{}' got a non-closable value",
233 vname_owned.escape_ascii()
234 )));
235 }
236 Ok(())
237}
238
239/// Prepares and calls the closing method for the variable at `level`.
240///
241/// If `status == CLOSE_K_TOP`, the error argument passed to `__close` is nil.
242/// Otherwise, `set_error_obj` is called to materialise the error at `level + 1`
243/// before the close method is invoked.
244///
245fn prep_call_close_mth(
246 state: &mut LuaState,
247 level: StackIdx,
248 status: i32,
249 yy: bool,
250) -> Result<(), LuaError> {
251 // TValue *errobj;
252 // if (status == CLOSEKTOP)
253 // errobj = &G(L)->nilvalue; /* error object is nil */
254 // else { /* luaD_seterrorobj will set top to level+2 */
255 // errobj = s2v(level + 1);
256 // luaD_seterrorobj(L, status, level + 1);
257 // }
258 // callclosemethod(L, uv, errobj, yy);
259 //
260 // macros.tsv: s2v(level) → state.stack_at(level), returning &LuaValue
261 // Clone before any mutable operations to avoid borrow conflicts.
262 let uv = state.get_stack_value(level).clone();
263 let err = if status == CLOSE_K_TOP {
264 LuaValue::Nil
265 } else {
266 // TODO(port): state.set_error_obj(status, ...) lives in do_.rs (ldo.c).
267 state.set_error_obj(status, StackIdx(level.0 + 1))?;
268 state.get_stack_value(StackIdx(level.0 + 1)).clone()
269 };
270 call_close_method(state, uv, err, yy)
271}
272
273// ── To-be-closed variable management ─────────────────────────────────────────
274
275/// Inserts the variable at `level` into the to-be-closed (`tbc`) list.
276///
277/// If the value is falsy (nil or false) it does not need closing and the
278/// function returns immediately. Otherwise it verifies that the value has a
279/// `__close` metamethod, then records it in `state.tbclist`.
280///
281pub(crate) fn new_tbc_upval(state: &mut LuaState, level: StackIdx) -> Result<(), LuaError> {
282 // In Rust: tbclist is Vec<StackIdx>, "current head" = last element.
283 debug_assert!(
284 state.tbclist.last().map_or(true, |&top| level.0 > top.0),
285 "new tbc entry must be above current tbclist head"
286 );
287 // macros.tsv: l_isfalse → matches!(o, LuaValue::Nil | LuaValue::Bool(false))
288 // Clone before borrow to avoid aliasing with later mutable calls.
289 let val = state.get_stack_value(level).clone();
290 if matches!(val, LuaValue::Nil | LuaValue::Bool(false)) {
291 return Ok(());
292 }
293 check_close_mth(state, level)?;
294 // while (cast_uint(level - L->tbclist.p) > MAXDELTA) {
295 // L->tbclist.p += MAXDELTA;
296 // L->tbclist.p->tbclist.delta = 0; /* dummy node */
297 // }
298 // level->tbclist.delta = cast(unsigned short, level - L->tbclist.p);
299 // L->tbclist.p = level;
300 //
301 // PORT NOTE: The MAXDELTA / dummy-node mechanism is a C-only optimisation
302 // required because `StackValue.tbclist.delta` is a `u16` (max 65535). With
303 // `Vec<StackIdx>` the index fits a u32 and no dummy nodes are ever needed.
304 state.tbclist.push(level);
305 Ok(())
306}
307
308/// Closes all open upvalues whose stack index is ≥ `level`, transitioning each
309/// from `UpVal::Open { thread_id: _, idx: thread_stack_idx }` to `UpVal::Closed(value)` by copying
310/// the current stack value into the upvalue's own storage.
311///
312pub(crate) fn close_upval(state: &mut LuaState, level: StackIdx) {
313 // TValue *slot = &uv->u.value;
314 // lua_assert(uplevel(uv) < L->top.p);
315 // luaF_unlinkupval(uv);
316 // setobj(L, slot, uv->v.p); /* copy stack value into upvalue */
317 // uv->v.p = slot; /* now the value lives here */
318 // if (!iswhite(uv)) { nw2black(uv); luaC_barrier(L, uv, slot); }
319 // }
320 //
321 // openupval is sorted descending; front element is the topmost open upvalue.
322 loop {
323 let uv = match state.openupval.first() {
324 Some(uv) => uv.clone(),
325 None => break,
326 };
327 let uv_idx = match &*uv.slot() {
328 lua_types::UpValState::Open { thread_id: _, idx: thread_stack_idx } => *thread_stack_idx,
329 lua_types::UpValState::Closed(_) => {
330 // Cross-thread close/reset paths can leave a stale closed
331 // upvalue in this Vec-backed open list. The C intrusive list
332 // cannot represent that state; in Rust, unlink it and keep
333 // closing the remaining open entries.
334 state.openupval.remove(0);
335 continue;
336 }
337 };
338 if uv_idx.0 < level.0 {
339 break;
340 }
341 // PORT NOTE: C asserts `uplevel(uv) < L->top.p` because the C stack is a
342 // contiguous block where slots above top are undefined. The Rust stack is
343 // a `Vec<StackValue>` whose backing storage outlives any top movement, so
344 // reading `stack[uv_idx]` is always valid here even when `state.top` has
345 // been rolled back below the upvalue (which is exactly what happens on
346 // pcall error unwind, e.g. when `assert_fn` calls `set_top(L, 1)` before
347 // raising). Dropping the C-style assertion lets close_upval correctly
348 // close upvalues during error unwind regardless of top position.
349 state.openupval.remove(0);
350 let stack_val = state.get_stack_value(uv_idx).clone();
351 uv.close_with(stack_val);
352 // macros.tsv: iswhite → obj.is_white(); nw2black → obj.set_black()
353 // luaC_barrier → state.gc().barrier(p, v) — no-op Phase A–C
354 // TODO(port): GC color methods (is_white, set_black) on GcRef<UpVal>;
355 // Phase D only. Omitted in Phase A–C.
356 }
357}
358
359/// Removes the most-recent entry from `state.tbclist`.
360///
361/// The C version must also skip over any delta==0 "dummy" nodes inserted to
362/// bridge gaps larger than MAXDELTA. In Rust no dummy nodes are ever inserted,
363/// so this is a straight `Vec::pop`.
364///
365fn pop_tbc_list(state: &mut LuaState) {
366 // lua_assert(tbc->tbclist.delta > 0); /* first element cannot be dummy */
367 // tbc -= tbc->tbclist.delta;
368 // while (tbc > L->stack.p && tbc->tbclist.delta == 0)
369 // tbc -= MAXDELTA; /* skip dummy nodes */
370 // L->tbclist.p = tbc;
371 //
372 // PORT NOTE: Delta-encoding dropped (see new_tbc_upval). Just pop.
373 state.tbclist.pop();
374}
375
376/// Closes all upvalues and to-be-closed variables down to `level`, invoking
377/// `__close` metamethods as needed. Returns the (stable) `level` index.
378///
379/// `status` is passed to `prep_call_close_mth` to determine the error argument:
380/// `CLOSE_K_TOP` means nil; other statuses produce the appropriate error object.
381/// `yy` controls yieldability of the close-method calls.
382///
383pub(crate) fn close(
384 state: &mut LuaState,
385 level: StackIdx,
386 status: i32,
387 yy: bool,
388) -> Result<StackIdx, LuaError> {
389 // macros.tsv: savestack → idx (StackIdx is already stable across reallocs in Rust)
390 // PORT NOTE: savestack / restorestack are no-ops here. In C they save/restore a
391 // pointer as a byte-offset because the stack may reallocate during close-method
392 // calls. In Rust, StackIdx is an index into Vec and remains valid after any resize.
393
394 close_upval(state, level);
395 // StkId tbc = L->tbclist.p;
396 // poptbclist(L);
397 // prepcallclosemth(L, tbc, status, yy);
398 // level = restorestack(L, levelrel);
399 // }
400 while state.tbclist.last().copied().map_or(false, |tbc| tbc.0 >= level.0) {
401 let tbc = state
402 .tbclist
403 .last()
404 .copied()
405 .expect("tbclist non-empty (just checked)");
406 pop_tbc_list(state);
407 prep_call_close_mth(state, tbc, status, yy)?;
408 }
409 Ok(level)
410}
411
412// ── Debug helpers ─────────────────────────────────────────────────────────────
413
414/// Returns the byte-string name of the `local_number`-th local variable that is
415/// active at bytecode position `pc` in prototype `f`, or `None` if no such
416/// variable exists.
417///
418/// Variables are scanned in order. A variable is active when
419/// `startpc <= pc < endpc`. The first active variable is numbered 1.
420///
421pub(crate) fn get_local_name(
422 f: &crate::state::LuaProto,
423 local_number: i32,
424 pc: i32,
425) -> Option<&[u8]> {
426 // for (i = 0; i < f->sizelocvars && f->locvars[i].startpc <= pc; i++) {
427 // if (pc < f->locvars[i].endpc) { /* is variable active? */
428 // local_number--;
429 // if (local_number == 0)
430 // return getstr(f->locvars[i].varname);
431 // }
432 // }
433 // return NULL;
434 //
435 // macros.tsv: getstr(ts) → ts.as_bytes() returning &[u8]
436 //
437 // TODO(port): `f.locvars` does not exist on the current LuaProto stub in state.rs.
438 // This will compile once LuaProto gains its full set of fields from object.rs.
439 // The logic below faithfully translates the C loop.
440 let mut remaining = local_number;
441 // We break early once startpc > pc (variables are ordered by startpc).
442 for lv in f.locvars.iter() {
443 if lv.startpc > pc {
444 break;
445 }
446 if pc < lv.endpc {
447 remaining -= 1;
448 if remaining == 0 {
449 // macros.tsv: getstr → ts.as_bytes()
450 return Some(lv.varname.as_bytes());
451 }
452 }
453 }
454 None
455}
456
457// ── Private helpers (Rust-only) ───────────────────────────────────────────────
458
459/// Returns `true` if this thread is already registered in `global.twups`.
460///
461/// iff its twups pointer doesn't point back to itself).
462///
463/// PORT NOTE: In Phase A–D with coroutines stubbed there is effectively a
464/// single thread. The actual `GlobalState.twups` Vec management (insertion in
465/// `new_open_upval`) is deferred to Phase D/E and would require a GcRef-to-self.
466/// Until then we treat every thread as conceptually present in twups, which
467/// satisfies the invariant `state_in_twups || openupval.is_empty()` asserted by
468/// `find_upval`. The actual twups list does not yet drive any behaviour.
469fn state_in_twups(state: &LuaState) -> bool {
470 let _ = state;
471 true
472}
473
474// ── Trait stubs needed for compilation ───────────────────────────────────────
475
476/// Stub methods on `LuaState` assumed by this module.
477///
478/// These will be implemented in their home modules (do_.rs, debug.rs, tagmethods.rs)
479/// and removed from this file in Phase B.
480impl LuaState {
481 /// Returns the `LuaValue` at stack index `idx`.
482 ///
483 /// macros.tsv: `s2v → state.stack_at(idx)`.
484 pub(crate) fn get_stack_value(&self, idx: StackIdx) -> &LuaValue {
485 // TODO(port): bounds-check and return &self.stack[idx.0 as usize].val
486 &self.stack[idx.0 as usize].val
487 }
488
489 /// Returns the current CallInfo (active call frame).
490 ///
491 pub(crate) fn current_ci(&self) -> &crate::state::CallInfo {
492 // TODO(port): return &self.call_info[self.ci.0 as usize]
493 &self.call_info[self.ci.0 as usize]
494 }
495
496 /// Looks up the `__close` (or other) metamethod for a value.
497 ///
498 /// macros.tsv: `fasttm → state.fast_tm(et, e)`.
499 pub(crate) fn get_tm_by_obj(
500 &mut self,
501 val: &LuaValue,
502 tm: lua_types::tagmethod::TagMethod,
503 ) -> LuaValue {
504 let mt: Option<GcRef<lua_types::value::LuaTable>> = match val {
505 LuaValue::Table(t) => t.metatable(),
506 LuaValue::UserData(u) => u.metatable(),
507 other => {
508 let type_idx = other.base_type() as usize;
509 self.global().mt[type_idx].clone()
510 }
511 };
512 match mt {
513 Some(mt_ref) => {
514 let ename = self.global().tmname[tm as usize].clone();
515 mt_ref.get_short_str(&ename)
516 }
517 None => LuaValue::Nil,
518 }
519 }
520
521 /// Calls a Lua or C function (yieldable).
522 ///
523 pub(crate) fn lua_call(&mut self, top: StackIdx, nresults: i32) -> Result<(), LuaError> {
524 crate::do_::call(self, top, nresults)
525 }
526
527 /// Calls a Lua or C function (non-yieldable).
528 ///
529 pub(crate) fn lua_callnoyield(
530 &mut self,
531 top: StackIdx,
532 nresults: i32,
533 ) -> Result<(), LuaError> {
534 crate::do_::callnoyield(self, top, nresults)
535 }
536
537 /// Sets the error object at a given stack index for a given status code.
538 ///
539 pub(crate) fn set_error_obj(
540 &mut self,
541 status: i32,
542 idx: StackIdx,
543 ) -> Result<(), LuaError> {
544 let s = lua_types::status::LuaStatus::from_raw(status);
545 crate::do_::set_error_obj(self, s, idx);
546 Ok(())
547 }
548
549 /// Returns the local-variable name at frame position `n` for CallInfo `ci`.
550 ///
551 pub(crate) fn debug_find_local(
552 &self,
553 ci: CallInfoIdx,
554 n: i32,
555 ) -> Option<Vec<u8>> {
556 crate::debug::find_local(self, ci, n, None)
557 }
558}
559
560// ──────────────────────────────────────────────────────────────────────────
561// PORT STATUS
562// source: src/lfunc.c (295 lines, 16 functions)
563// target_crate: lua-vm
564// confidence: medium
565// todos: 36
566// port_notes: 7
567// unsafe_blocks: 0
568// notes: Logic is faithful. Two blockers for Phase B:
569// (1) GcRef<UpVal> needs interior mutability (Rc<RefCell<UpVal>>)
570// so close_upval and init_upvals can mutate in-place.
571// (2) LuaProto stub in state.rs must gain full field list from
572// object.rs before new_proto / get_local_name compile.
573// LuaClosureLua.proto needs Option<> wrapper for NULL init in
574// new_lua_closure. Stub methods on LuaState (get_tm_by_obj,
575// lua_call, set_error_obj, debug_find_local) must be removed
576// once their home modules are written (do_.rs, debug.rs,
577// tagmethods.rs). The 36 TODO(port) markers include both the
578// core design blockers and the stub-method placeholders; the
579// stub-method TODOs will auto-resolve as other modules land.
580// ──────────────────────────────────────────────────────────────────────────