Skip to main content

luaur_rt/
table.rs

1//! The [`Table`] handle. Mirrors `mlua::Table`.
2
3use crate::error::Result;
4use crate::state::{Lua, LuaRef};
5use crate::sync::{NotSync, XRc, NOT_SYNC};
6use crate::sys::*;
7use crate::traits::{FromLua, IntoLua};
8use crate::value::Value;
9
10/// A handle to a Lua table.
11///
12/// Mirrors `mlua::Table`. Holds a registry reference keeping the table alive.
13///
14/// Under the `send` feature this handle is `Send` (the VM can be moved across
15/// threads) but never `Sync` — see [`crate::sync::NotSync`].
16#[derive(Clone)]
17pub struct Table {
18    pub(crate) reference: XRc<LuaRef>,
19    pub(crate) _not_sync: NotSync,
20}
21
22impl Table {
23    pub(crate) fn from_ref(reference: LuaRef) -> Table {
24        Table {
25            reference: XRc::new(reference),
26            _not_sync: NOT_SYNC,
27        }
28    }
29
30    pub(crate) unsafe fn push_to_stack(&self) {
31        self.reference.push();
32    }
33
34    /// The owning [`Lua`].
35    pub fn lua(&self) -> Lua {
36        self.reference.lua()
37    }
38
39    /// Set `table[key] = value`, honoring metamethods (`__newindex`).
40    ///
41    /// Mirrors `mlua::Table::set`. Errors (`RuntimeError`) propagate from a
42    /// `__newindex` metamethod that raises.
43    pub fn set<K: IntoLua, V: IntoLua>(&self, key: K, value: V) -> Result<()> {
44        let lua = self.lua();
45        let state = lua.state();
46        let k = key.into_lua(&lua)?;
47        let v = value.into_lua(&lua)?;
48        // Drive the (possibly metamethod-invoking) settable under pcall so a
49        // raising `__newindex` (or readonly table) surfaces as `Err`.
50        unsafe {
51            self.reference.push(); // table
52            lua.push_value(&k)?; // key
53            lua.push_value(&v)?; // value
54            let status = protected_settable(state);
55            if status != 0 {
56                return Err(lua.pop_error(status));
57            }
58        }
59        Ok(())
60    }
61
62    /// Get `table[key]`, honoring metamethods (`__index`), converting the
63    /// result to `V`.
64    ///
65    /// Mirrors `mlua::Table::get`. The value type is the sole explicit type
66    /// parameter (key type is inferred), matching mlua's `get::<V>(key)`.
67    pub fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
68        let lua = self.lua();
69        let state = lua.state();
70        let k = key.into_lua(&lua)?;
71        let value = unsafe {
72            self.reference.push(); // table
73            lua.push_value(&k)?; // key
74            let status = protected_gettable(state);
75            if status != 0 {
76                return Err(lua.pop_error(status));
77            }
78            let v = lua.value_from_stack(-1)?;
79            lua_pop(state, 1); // pop the result value
80            v
81        };
82        V::from_lua(value, &lua)
83    }
84
85    /// Whether `table[key]` is non-nil.
86    ///
87    /// Mirrors `mlua::Table::contains_key`.
88    pub fn contains_key<K: IntoLua>(&self, key: K) -> Result<bool> {
89        let v: Value = self.get(key)?;
90        Ok(!v.is_nil())
91    }
92
93    /// The border length (`#table`).
94    ///
95    /// Mirrors `mlua::Table::raw_len` (returns `usize`). luaur's `lua_objlen`
96    /// gives the same border-length semantics as `lua_rawlen`.
97    pub fn raw_len(&self) -> usize {
98        let state = self.reference.state();
99        unsafe {
100            self.reference.push();
101            let n = lua_objlen(state, -1);
102            lua_pop(state, 1);
103            n.max(0) as usize
104        }
105    }
106
107    /// The length (`#table`), honoring a `__len` metamethod.
108    ///
109    /// Mirrors `mlua::Table::len`. Returns `Err` if a `__len` metamethod
110    /// raises. Without a `__len` metamethod this is the raw border length.
111    pub fn len(&self) -> Result<usize> {
112        let lua = self.lua();
113        // Fast path: no metatable -> raw border length (no metamethod possible).
114        if self.metatable().is_none() {
115            return Ok(self.raw_len());
116        }
117        // Evaluate `#self` protected so a raising/returning `__len` is honored.
118        let f = lua.load("local t = ...; return #t").into_function()?;
119        let n: i64 = f.call(self.clone())?;
120        Ok(n.max(0) as usize)
121    }
122
123    /// Whether the table is empty, without invoking metamethods.
124    ///
125    /// Mirrors `mlua::Table::is_empty`: checks **both** the array part and the
126    /// hash part (a table with only string keys is *not* empty). Uses a single
127    /// `lua_next` probe — present iff the table has at least one key.
128    pub fn is_empty(&self) -> bool {
129        let state = self.reference.state();
130        unsafe {
131            self.reference.push(); // table
132            lua_pushnil(state); // first key
133            if lua_next(state, -2) == 0 {
134                // No first key: table is empty. `lua_next` already popped the key.
135                lua_pop(state, 1); // pop table
136                return true;
137            }
138            // stack: table, key, value — pop value+key+table.
139            lua_pop(state, 3);
140            false
141        }
142    }
143
144    /// Iterate over `(key, value)` pairs.
145    ///
146    /// Mirrors `mlua::Table::pairs`. Returns an iterator yielding `Result<(K,
147    /// V)>` items. Uses `lua_next` under the hood.
148    pub fn pairs<K: FromLua, V: FromLua>(&self) -> TablePairs<K, V> {
149        TablePairs {
150            table: self.clone(),
151            next_key: Some(Value::Nil),
152            _phantom: std::marker::PhantomData,
153        }
154    }
155
156    /// Collect all `(key, value)` pairs into a `Vec`. Convenience over
157    /// [`Table::pairs`].
158    pub fn pairs_vec<K: FromLua, V: FromLua>(&self) -> Result<Vec<(K, V)>> {
159        self.pairs().collect()
160    }
161
162    /// Iterate over the sequence part `[1..]`, stopping at the first `nil`
163    /// (raw access — ignores `__index`). Mirrors `mlua::Table::sequence_values`.
164    pub fn sequence_values<V: FromLua>(&self) -> TableSequence<V> {
165        TableSequence {
166            table: self.clone(),
167            index: 1,
168            _phantom: std::marker::PhantomData,
169        }
170    }
171
172    /// Call `f` for each `(key, value)` pair (raw `lua_next` traversal).
173    /// Stops early on the first `Err`. Mirrors `mlua::Table::for_each`.
174    ///
175    /// The key/value types are the only explicit type parameters (the closure
176    /// type is inferred), matching mlua's `for_each::<K, V>(f)`.
177    pub fn for_each<K: FromLua, V: FromLua>(
178        &self,
179        mut f: impl FnMut(K, V) -> Result<()>,
180    ) -> Result<()> {
181        for pair in self.pairs::<K, V>() {
182            let (k, v) = pair?;
183            f(k, v)?;
184        }
185        Ok(())
186    }
187
188    /// Call `f` for each value in the sequence part. Mirrors
189    /// `mlua::Table::for_each_value`.
190    pub fn for_each_value<V: FromLua>(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()> {
191        for v in self.sequence_values::<V>() {
192            f(v?)?;
193        }
194        Ok(())
195    }
196
197    // --- raw (metamethod-bypassing) access ---------------------------------
198
199    /// Set `table[key] = value` without invoking `__newindex`.
200    ///
201    /// Mirrors `mlua::Table::raw_set`. Errors if the table is readonly.
202    pub fn raw_set<K: IntoLua, V: IntoLua>(&self, key: K, value: V) -> Result<()> {
203        let lua = self.lua();
204        let state = lua.state();
205        let k = key.into_lua(&lua)?;
206        let v = value.into_lua(&lua)?;
207        if self.is_readonly() {
208            return Err(crate::error::Error::RuntimeError(
209                "attempt to modify a readonly table".to_string(),
210            ));
211        }
212        unsafe {
213            self.reference.push(); // table
214            lua.push_value(&k)?; // key
215            lua.push_value(&v)?; // value
216            lua_rawset(state, -3);
217            lua_pop(state, 1); // pop table
218        }
219        Ok(())
220    }
221
222    /// Get `table[key]` without invoking `__index`.
223    ///
224    /// Mirrors `mlua::Table::raw_get`.
225    pub fn raw_get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
226        let lua = self.lua();
227        let state = lua.state();
228        let k = key.into_lua(&lua)?;
229        let value = unsafe {
230            self.reference.push(); // table
231            lua.push_value(&k)?; // key
232            lua_rawget(state, -2); // replaces key with value
233            let v = lua.value_from_stack(-1)?;
234            lua_pop(state, 2); // pop value + table
235            v
236        };
237        V::from_lua(value, &lua)
238    }
239
240    /// Append `value` at position `#table + 1` using raw access.
241    ///
242    /// Mirrors `mlua::Table::raw_push`. Errors if readonly.
243    pub fn raw_push<V: IntoLua>(&self, value: V) -> Result<()> {
244        let n = self.raw_len();
245        self.raw_set((n + 1) as i64, value)
246    }
247
248    /// Remove and return the last sequence element via raw access.
249    ///
250    /// Mirrors `mlua::Table::raw_pop`. Errors if readonly.
251    pub fn raw_pop<V: FromLua>(&self) -> Result<V> {
252        let lua = self.lua();
253        let n = self.raw_len();
254        if n == 0 {
255            return V::from_lua(Value::Nil, &lua);
256        }
257        if self.is_readonly() {
258            return Err(crate::error::Error::RuntimeError(
259                "attempt to modify a readonly table".to_string(),
260            ));
261        }
262        let v: V = self.raw_get(n as i64)?;
263        self.raw_set(n as i64, Value::Nil)?;
264        Ok(v)
265    }
266
267    /// Insert `value` at 1-based `idx`, shifting later elements up (raw).
268    ///
269    /// Mirrors `mlua::Table::raw_insert`. Errors on bad index or readonly.
270    pub fn raw_insert<V: IntoLua>(&self, idx: i64, value: V) -> Result<()> {
271        let n = self.raw_len() as i64;
272        if idx < 1 || idx > n + 1 {
273            return Err(crate::error::Error::RuntimeError(format!(
274                "bad argument #2 to 'insert' (position out of bounds): {idx}"
275            )));
276        }
277        if self.is_readonly() {
278            return Err(crate::error::Error::RuntimeError(
279                "attempt to modify a readonly table".to_string(),
280            ));
281        }
282        // Shift [idx..=n] up by one, then place the new value.
283        let mut i = n;
284        while i >= idx {
285            let moved: Value = self.raw_get(i)?;
286            self.raw_set(i + 1, moved)?;
287            i -= 1;
288        }
289        self.raw_set(idx, value.into_lua(&self.lua())?)
290    }
291
292    /// Remove and return the element at 1-based `idx`, shifting later
293    /// elements down (raw). Mirrors `mlua::Table::raw_remove`.
294    pub fn raw_remove(&self, idx: i64) -> Result<Value> {
295        let n = self.raw_len() as i64;
296        if n == 0 {
297            return Ok(Value::Nil);
298        }
299        if idx < 1 || idx > n {
300            return Err(crate::error::Error::RuntimeError(format!(
301                "bad argument #1 to 'remove' (position out of bounds): {idx}"
302            )));
303        }
304        if self.is_readonly() {
305            return Err(crate::error::Error::RuntimeError(
306                "attempt to modify a readonly table".to_string(),
307            ));
308        }
309        let removed: Value = self.raw_get(idx)?;
310        let mut i = idx;
311        while i < n {
312            let moved: Value = self.raw_get(i + 1)?;
313            self.raw_set(i, moved)?;
314            i += 1;
315        }
316        self.raw_set(n, Value::Nil)?;
317        Ok(removed)
318    }
319
320    /// Append `value` honoring `__len`/`__newindex` (uses `#self + 1`).
321    /// Mirrors `mlua::Table::push`.
322    pub fn push<V: IntoLua>(&self, value: V) -> Result<()> {
323        let n = self.len()?;
324        self.set((n + 1) as i64, value)
325    }
326
327    /// Pop the last element honoring `__len`/`__index`/`__newindex`.
328    /// Mirrors `mlua::Table::pop`.
329    pub fn pop<V: FromLua>(&self) -> Result<V> {
330        let lua = self.lua();
331        let n = self.len()?;
332        if n == 0 {
333            return V::from_lua(Value::Nil, &lua);
334        }
335        let v: V = self.get(n as i64)?;
336        self.set(n as i64, Value::Nil)?;
337        Ok(v)
338    }
339
340    /// Remove all keys from the table (raw). Errors if readonly.
341    /// Mirrors `mlua::Table::clear`.
342    pub fn clear(&self) -> Result<()> {
343        if self.is_readonly() {
344            return Err(crate::error::Error::RuntimeError(
345                "attempt to modify a readonly table".to_string(),
346            ));
347        }
348        // Collect every key (raw traversal), then nil them out.
349        let lua = self.lua();
350        let state = lua.state();
351        let mut keys: Vec<Value> = Vec::new();
352        unsafe {
353            self.reference.push(); // table
354            lua_pushnil(state); // first key
355            while lua_next(state, -2) != 0 {
356                // stack: table, key, value
357                let k = lua.value_from_stack(-2)?;
358                keys.push(k);
359                lua_pop(state, 1); // pop value, keep key for next iteration
360            }
361            lua_pop(state, 1); // pop table
362        }
363        for k in keys {
364            self.raw_set(k, Value::Nil)?;
365        }
366        Ok(())
367    }
368
369    // --- identity / equality / metatables ----------------------------------
370
371    /// A raw pointer identifying this table (for identity comparison).
372    /// Mirrors `mlua::Table::to_pointer`.
373    pub fn to_pointer(&self) -> *const std::ffi::c_void {
374        let state = self.reference.state();
375        unsafe {
376            self.reference.push();
377            let p = lua_topointer(state, -1);
378            lua_pop(state, 1);
379            p
380        }
381    }
382
383    /// Compare for equality honoring an `__eq` metamethod.
384    /// Mirrors `mlua::Table::equals`.
385    pub fn equals(&self, other: &Table) -> Result<bool> {
386        let lua = self.lua();
387        let state = lua.state();
388        unsafe {
389            self.reference.push();
390            other.reference.push();
391            let eq = lua_equal(state, -2, -1);
392            lua_pop(state, 2);
393            Ok(eq != 0)
394        }
395    }
396
397    /// The table's metatable, if any. Mirrors `mlua::Table::metatable`.
398    pub fn metatable(&self) -> Option<Table> {
399        let lua = self.lua();
400        let state = lua.state();
401        unsafe {
402            self.reference.push();
403            let has = lua_getmetatable(state, -1);
404            if has == 0 {
405                lua_pop(state, 1); // pop table
406                return None;
407            }
408            // stack: table, metatable
409            let mt = Table::from_ref(lua.pop_ref());
410            lua_pop(state, 1); // pop table
411            Some(mt)
412        }
413    }
414
415    /// Set (or clear, with `None`) the table's metatable.
416    /// Mirrors `mlua::Table::set_metatable`. Errors if the table is readonly.
417    pub fn set_metatable(&self, metatable: Option<Table>) -> Result<()> {
418        if self.is_readonly() {
419            return Err(crate::error::Error::RuntimeError(
420                "attempt to modify a readonly table".to_string(),
421            ));
422        }
423        let lua = self.lua();
424        let state = lua.state();
425        unsafe {
426            self.reference.push(); // table
427            match metatable {
428                Some(mt) => mt.push_to_stack(),
429                None => lua_pushnil(state),
430            }
431            lua_setmetatable(state, -2);
432            lua_pop(state, 1); // pop table
433        }
434        Ok(())
435    }
436
437    // --- readonly (Luau extension) -----------------------------------------
438
439    /// Whether the table is marked readonly (Luau). Mirrors
440    /// `mlua::Table::is_readonly`.
441    pub fn is_readonly(&self) -> bool {
442        let state = self.reference.state();
443        unsafe {
444            self.reference.push();
445            let ro = lua_getreadonly(state, -1);
446            lua_pop(state, 1);
447            ro != 0
448        }
449    }
450
451    /// Mark the table readonly or writable (Luau). Mirrors
452    /// `mlua::Table::set_readonly`.
453    pub fn set_readonly(&self, enabled: bool) {
454        let state = self.reference.state();
455        unsafe {
456            self.reference.push();
457            lua_setreadonly(state, -1, enabled as c_int);
458            lua_pop(state, 1);
459        }
460    }
461}
462
463/// Iterator over a table's sequence part (see [`Table::sequence_values`]).
464pub struct TableSequence<V> {
465    table: Table,
466    index: i64,
467    _phantom: std::marker::PhantomData<V>,
468}
469
470impl<V: FromLua> Iterator for TableSequence<V> {
471    type Item = Result<V>;
472
473    fn next(&mut self) -> Option<Self::Item> {
474        let lua = self.table.lua();
475        // Raw get of the next sequence slot; stop at the first nil.
476        let value: Value = match self.table.raw_get(self.index) {
477            Ok(v) => v,
478            Err(e) => return Some(Err(e)),
479        };
480        if value.is_nil() {
481            return None;
482        }
483        self.index += 1;
484        Some(V::from_lua(value, &lua))
485    }
486}
487
488impl std::fmt::Debug for Table {
489    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
490        write!(f, "Table(len={})", self.raw_len())
491    }
492}
493
494impl PartialEq for Table {
495    fn eq(&self, other: &Self) -> bool {
496        // Reference (pointer) identity, matching mlua's `==` on handles: two
497        // handles are equal iff they point at the *same* Lua table object.
498        self.to_pointer() == other.to_pointer()
499    }
500}
501
502/// Compare a [`Table`]'s sequence part to a Rust slice of values.
503///
504/// Mirrors mlua's `impl PartialEq<[T]> for Table`: equal when the table's
505/// `1..=len` sequence (read raw) matches `other` element-wise and has the
506/// same length.
507impl<T> PartialEq<[T]> for Table
508where
509    T: FromLua + PartialEq + Clone,
510{
511    fn eq(&self, other: &[T]) -> bool {
512        // Compare the *sequence* part (stopping at the first nil border),
513        // matching mlua's `sequence_values`-based slice comparison. This is the
514        // robust border semantics for tables with nil holes (e.g.
515        // `{1, 2, nil, 4, 5}` compares equal to `[1, 2]`).
516        let mut iter = self.sequence_values::<T>();
517        for expected in other.iter() {
518            match iter.next() {
519                Some(Ok(got)) if &got == expected => {}
520                _ => return false,
521            }
522        }
523        // The sequence must be exactly the slice length (no extra elements).
524        iter.next().is_none()
525    }
526}
527
528impl<T, const N: usize> PartialEq<[T; N]> for Table
529where
530    T: FromLua + PartialEq + Clone,
531{
532    fn eq(&self, other: &[T; N]) -> bool {
533        self == other.as_slice()
534    }
535}
536
537impl<T> PartialEq<&[T]> for Table
538where
539    T: FromLua + PartialEq + Clone,
540{
541    fn eq(&self, other: &&[T]) -> bool {
542        self == *other
543    }
544}
545
546/// Iterator over a table's key/value pairs (see [`Table::pairs`]).
547pub struct TablePairs<K, V> {
548    table: Table,
549    next_key: Option<Value>,
550    _phantom: std::marker::PhantomData<(K, V)>,
551}
552
553impl<K: FromLua, V: FromLua> Iterator for TablePairs<K, V> {
554    type Item = Result<(K, V)>;
555
556    fn next(&mut self) -> Option<Self::Item> {
557        let key = self.next_key.take()?;
558        let lua = self.table.lua();
559        let state = lua.state();
560        unsafe {
561            self.table.reference.push(); // [.. table]
562            if lua.push_value(&key).is_err() {
563                lua_pop(state, 1);
564                return None;
565            }
566            // stack: [table, key]
567            let has = lua_next(state, -2);
568            if has == 0 {
569                // lua_next popped the key; pop the table.
570                lua_pop(state, 1);
571                self.next_key = None;
572                return None;
573            }
574            // stack: [table, next_key, value]
575            let k_val = match lua.value_from_stack(-2) {
576                Ok(v) => v,
577                Err(e) => {
578                    lua_pop(state, 3);
579                    return Some(Err(e));
580                }
581            };
582            let v_val = match lua.value_from_stack(-1) {
583                Ok(v) => v,
584                Err(e) => {
585                    lua_pop(state, 3);
586                    return Some(Err(e));
587                }
588            };
589            // Remember the key for the next iteration, then clean the stack.
590            self.next_key = Some(k_val.clone());
591            lua_pop(state, 3); // value, next_key, table
592
593            let k = match K::from_lua(k_val, &lua) {
594                Ok(k) => k,
595                Err(e) => return Some(Err(e)),
596            };
597            let v = match V::from_lua(v_val, &lua) {
598                Ok(v) => v,
599                Err(e) => return Some(Err(e)),
600            };
601            Some(Ok((k, v)))
602        }
603    }
604}
605
606/// Create a fresh empty table on `lua` and return a handle.
607pub(crate) fn create_table(lua: &Lua) -> Table {
608    let state = lua.state();
609    unsafe {
610        lua_createtable(state, 0, 0);
611        Table::from_ref(lua.pop_ref())
612    }
613}
614
615// ---------------------------------------------------------------------------
616// Protected indexing
617//
618// `lua_gettable`/`lua_settable` may invoke `__index`/`__newindex` metamethods
619// that *raise* (longjmp). Calling them unprotected across the Rust/VM boundary
620// would unwind past Rust frames. We therefore run them inside `lua_pcall` via a
621// tiny C trampoline, so a raising metamethod (or a readonly-table write) is
622// reported as an ordinary non-zero status with the error object on the stack.
623// ---------------------------------------------------------------------------
624
625/// C trampoline: stack is `[table, key]`; performs `lua_gettable` and leaves
626/// the result on top.
627unsafe fn c_gettable(state: *mut lua_State) -> c_int {
628    unsafe {
629        lua_gettable(state, 1);
630        1
631    }
632}
633
634/// C trampoline: stack is `[table, key, value]`; performs `lua_settable`.
635unsafe fn c_settable(state: *mut lua_State) -> c_int {
636    unsafe {
637        lua_settable(state, 1);
638        0
639    }
640}
641
642/// Run `lua_gettable` protected. Expects `[table, key]` on top; on success
643/// leaves `[result]` where the two inputs were; on failure leaves the error
644/// object on top and returns the non-zero status.
645unsafe fn protected_gettable(state: *mut lua_State) -> c_int {
646    unsafe {
647        // Insert the C function below the two arguments: [t, k] -> [f, t, k].
648        lua_pushcclosurek(
649            state,
650            Some(c_gettable),
651            c"luaur-rt-gettable".as_ptr(),
652            0,
653            None,
654        );
655        lua_insert(state, -3);
656        lua_pcall(state, 2, 1, 0)
657    }
658}
659
660/// Run `lua_settable` protected. Expects `[table, key, value]` on top; pops
661/// them on success; on failure leaves the error object and returns the status.
662unsafe fn protected_settable(state: *mut lua_State) -> c_int {
663    unsafe {
664        // [t, k, v] -> [f, t, k, v].
665        lua_pushcclosurek(
666            state,
667            Some(c_settable),
668            c"luaur-rt-settable".as_ptr(),
669            0,
670            None,
671        );
672        lua_insert(state, -4);
673        lua_pcall(state, 3, 0, 0)
674    }
675}