mlua/
table.rs

1use std::collections::HashSet;
2use std::fmt;
3use std::marker::PhantomData;
4use std::os::raw::{c_int, c_void};
5use std::string::String as StdString;
6
7use crate::error::{Error, Result};
8use crate::function::Function;
9use crate::state::{LuaGuard, RawLua};
10use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
11use crate::types::{Integer, LuaType, ValueRef};
12use crate::util::{assert_stack, check_stack, get_metatable_ptr, StackGuard};
13use crate::value::{Nil, Value};
14
15#[cfg(feature = "async")]
16use crate::function::AsyncCallFuture;
17
18#[cfg(feature = "serde")]
19use {
20    rustc_hash::FxHashSet,
21    serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer},
22    std::{cell::RefCell, rc::Rc, result::Result as StdResult},
23};
24
25/// Handle to an internal Lua table.
26#[derive(Clone, PartialEq)]
27pub struct Table(pub(crate) ValueRef);
28
29impl Table {
30    /// Sets a key-value pair in the table.
31    ///
32    /// If the value is `nil`, this will effectively remove the pair.
33    ///
34    /// This might invoke the `__newindex` metamethod. Use the [`raw_set`] method if that is not
35    /// desired.
36    ///
37    /// # Examples
38    ///
39    /// Export a value as a global to make it usable from Lua:
40    ///
41    /// ```
42    /// # use mlua::{Lua, Result};
43    /// # fn main() -> Result<()> {
44    /// # let lua = Lua::new();
45    /// let globals = lua.globals();
46    ///
47    /// globals.set("assertions", cfg!(debug_assertions))?;
48    ///
49    /// lua.load(r#"
50    ///     if assertions == true then
51    ///         -- ...
52    ///     elseif assertions == false then
53    ///         -- ...
54    ///     else
55    ///         error("assertions neither on nor off?")
56    ///     end
57    /// "#).exec()?;
58    /// # Ok(())
59    /// # }
60    /// ```
61    ///
62    /// [`raw_set`]: Table::raw_set
63    pub fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
64        // Fast track (skip protected call)
65        if !self.has_metatable() {
66            return self.raw_set(key, value);
67        }
68
69        self.set_protected(key, value)
70    }
71
72    pub(crate) fn set_protected(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
73        let lua = self.0.lua.lock();
74        let state = lua.state();
75        unsafe {
76            let _sg = StackGuard::new(state);
77            check_stack(state, 5)?;
78
79            lua.push_ref(&self.0);
80            key.push_into_stack(&lua)?;
81            value.push_into_stack(&lua)?;
82            protect_lua!(state, 3, 0, fn(state) ffi::lua_settable(state, -3))
83        }
84    }
85
86    /// Gets the value associated to `key` from the table.
87    ///
88    /// If no value is associated to `key`, returns the `nil` value.
89    ///
90    /// This might invoke the `__index` metamethod. Use the [`raw_get`] method if that is not
91    /// desired.
92    ///
93    /// # Examples
94    ///
95    /// Query the version of the Lua interpreter:
96    ///
97    /// ```
98    /// # use mlua::{Lua, Result};
99    /// # fn main() -> Result<()> {
100    /// # let lua = Lua::new();
101    /// let globals = lua.globals();
102    ///
103    /// let version: String = globals.get("_VERSION")?;
104    /// println!("Lua version: {}", version);
105    /// # Ok(())
106    /// # }
107    /// ```
108    ///
109    /// [`raw_get`]: Table::raw_get
110    pub fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
111        // Fast track (skip protected call)
112        if !self.has_metatable() {
113            return self.raw_get(key);
114        }
115
116        self.get_protected(key)
117    }
118
119    pub(crate) fn get_protected<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
120        let lua = self.0.lua.lock();
121        let state = lua.state();
122        unsafe {
123            let _sg = StackGuard::new(state);
124            check_stack(state, 4)?;
125
126            lua.push_ref(&self.0);
127            key.push_into_stack(&lua)?;
128            protect_lua!(state, 2, 1, fn(state) ffi::lua_gettable(state, -2))?;
129
130            V::from_stack(-1, &lua)
131        }
132    }
133
134    /// Checks whether the table contains a non-nil value for `key`.
135    ///
136    /// This might invoke the `__index` metamethod.
137    pub fn contains_key(&self, key: impl IntoLua) -> Result<bool> {
138        Ok(self.get::<Value>(key)? != Value::Nil)
139    }
140
141    /// Appends a value to the back of the table.
142    ///
143    /// This might invoke the `__len` and `__newindex` metamethods.
144    pub fn push(&self, value: impl IntoLua) -> Result<()> {
145        // Fast track (skip protected call)
146        if !self.has_metatable() {
147            return self.raw_push(value);
148        }
149
150        let lua = self.0.lua.lock();
151        let state = lua.state();
152        unsafe {
153            let _sg = StackGuard::new(state);
154            check_stack(state, 4)?;
155
156            lua.push_ref(&self.0);
157            value.push_into_stack(&lua)?;
158            protect_lua!(state, 2, 0, fn(state) {
159                let len = ffi::luaL_len(state, -2) as Integer;
160                ffi::lua_seti(state, -2, len + 1);
161            })?
162        }
163        Ok(())
164    }
165
166    /// Removes the last element from the table and returns it.
167    ///
168    /// This might invoke the `__len` and `__newindex` metamethods.
169    pub fn pop<V: FromLua>(&self) -> Result<V> {
170        // Fast track (skip protected call)
171        if !self.has_metatable() {
172            return self.raw_pop();
173        }
174
175        let lua = self.0.lua.lock();
176        let state = lua.state();
177        unsafe {
178            let _sg = StackGuard::new(state);
179            check_stack(state, 4)?;
180
181            lua.push_ref(&self.0);
182            protect_lua!(state, 1, 1, fn(state) {
183                let len = ffi::luaL_len(state, -1) as Integer;
184                ffi::lua_geti(state, -1, len);
185                ffi::lua_pushnil(state);
186                ffi::lua_seti(state, -3, len);
187            })?;
188            V::from_stack(-1, &lua)
189        }
190    }
191
192    /// Compares two tables for equality.
193    ///
194    /// Tables are compared by reference first.
195    /// If they are not primitively equals, then mlua will try to invoke the `__eq` metamethod.
196    /// mlua will check `self` first for the metamethod, then `other` if not found.
197    ///
198    /// # Examples
199    ///
200    /// Compare two tables using `__eq` metamethod:
201    ///
202    /// ```
203    /// # use mlua::{Lua, Result, Table};
204    /// # fn main() -> Result<()> {
205    /// # let lua = Lua::new();
206    /// let table1 = lua.create_table()?;
207    /// table1.set(1, "value")?;
208    ///
209    /// let table2 = lua.create_table()?;
210    /// table2.set(2, "value")?;
211    ///
212    /// let always_equals_mt = lua.create_table()?;
213    /// always_equals_mt.set("__eq", lua.create_function(|_, (_t1, _t2): (Table, Table)| Ok(true))?)?;
214    /// table2.set_metatable(Some(always_equals_mt))?;
215    ///
216    /// assert!(table1.equals(&table1.clone())?);
217    /// assert!(table1.equals(&table2)?);
218    /// # Ok(())
219    /// # }
220    /// ```
221    pub fn equals(&self, other: &Self) -> Result<bool> {
222        if self == other {
223            return Ok(true);
224        }
225
226        // Compare using `__eq` metamethod if exists
227        // First, check the self for the metamethod.
228        // If self does not define it, then check the other table.
229        if let Some(mt) = self.metatable() {
230            if mt.contains_key("__eq")? {
231                return mt.get::<Function>("__eq")?.call((self, other));
232            }
233        }
234        if let Some(mt) = other.metatable() {
235            if mt.contains_key("__eq")? {
236                return mt.get::<Function>("__eq")?.call((self, other));
237            }
238        }
239
240        Ok(false)
241    }
242
243    /// Sets a key-value pair without invoking metamethods.
244    pub fn raw_set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
245        let lua = self.0.lua.lock();
246        let state = lua.state();
247        unsafe {
248            #[cfg(feature = "luau")]
249            self.check_readonly_write(&lua)?;
250
251            let _sg = StackGuard::new(state);
252            check_stack(state, 5)?;
253
254            lua.push_ref(&self.0);
255            key.push_into_stack(&lua)?;
256            value.push_into_stack(&lua)?;
257
258            if lua.unlikely_memory_error() {
259                ffi::lua_rawset(state, -3);
260                ffi::lua_pop(state, 1);
261                Ok(())
262            } else {
263                protect_lua!(state, 3, 0, fn(state) ffi::lua_rawset(state, -3))
264            }
265        }
266    }
267
268    /// Gets the value associated to `key` without invoking metamethods.
269    pub fn raw_get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
270        let lua = self.0.lua.lock();
271        let state = lua.state();
272        unsafe {
273            let _sg = StackGuard::new(state);
274            check_stack(state, 3)?;
275
276            lua.push_ref(&self.0);
277            key.push_into_stack(&lua)?;
278            ffi::lua_rawget(state, -2);
279
280            V::from_stack(-1, &lua)
281        }
282    }
283
284    /// Inserts element value at position `idx` to the table, shifting up the elements from
285    /// `table[idx]`.
286    ///
287    /// The worst case complexity is O(n), where n is the table length.
288    pub fn raw_insert(&self, idx: Integer, value: impl IntoLua) -> Result<()> {
289        let size = self.raw_len() as Integer;
290        if idx < 1 || idx > size + 1 {
291            return Err(Error::runtime("index out of bounds"));
292        }
293
294        let lua = self.0.lua.lock();
295        let state = lua.state();
296        unsafe {
297            let _sg = StackGuard::new(state);
298            check_stack(state, 5)?;
299
300            lua.push_ref(&self.0);
301            value.push_into_stack(&lua)?;
302            protect_lua!(state, 2, 0, |state| {
303                for i in (idx..=size).rev() {
304                    // table[i+1] = table[i]
305                    ffi::lua_rawgeti(state, -2, i);
306                    ffi::lua_rawseti(state, -3, i + 1);
307                }
308                ffi::lua_rawseti(state, -2, idx)
309            })
310        }
311    }
312
313    /// Appends a value to the back of the table without invoking metamethods.
314    pub fn raw_push(&self, value: impl IntoLua) -> Result<()> {
315        let lua = self.0.lua.lock();
316        let state = lua.state();
317        unsafe {
318            #[cfg(feature = "luau")]
319            self.check_readonly_write(&lua)?;
320
321            let _sg = StackGuard::new(state);
322            check_stack(state, 4)?;
323
324            lua.push_ref(&self.0);
325            value.push_into_stack(&lua)?;
326
327            unsafe fn callback(state: *mut ffi::lua_State) {
328                let len = ffi::lua_rawlen(state, -2) as Integer;
329                ffi::lua_rawseti(state, -2, len + 1);
330            }
331
332            if lua.unlikely_memory_error() {
333                callback(state);
334            } else {
335                protect_lua!(state, 2, 0, fn(state) callback(state))?;
336            }
337        }
338        Ok(())
339    }
340
341    /// Removes the last element from the table and returns it, without invoking metamethods.
342    pub fn raw_pop<V: FromLua>(&self) -> Result<V> {
343        let lua = self.0.lua.lock();
344        let state = lua.state();
345        unsafe {
346            #[cfg(feature = "luau")]
347            self.check_readonly_write(&lua)?;
348
349            let _sg = StackGuard::new(state);
350            check_stack(state, 3)?;
351
352            lua.push_ref(&self.0);
353            let len = ffi::lua_rawlen(state, -1) as Integer;
354            ffi::lua_rawgeti(state, -1, len);
355            // Set slot to nil (it must be safe to do)
356            ffi::lua_pushnil(state);
357            ffi::lua_rawseti(state, -3, len);
358
359            V::from_stack(-1, &lua)
360        }
361    }
362
363    /// Removes a key from the table.
364    ///
365    /// If `key` is an integer, mlua shifts down the elements from `table[key+1]`,
366    /// and erases element `table[key]`. The complexity is `O(n)` in the worst case,
367    /// where `n` is the table length.
368    ///
369    /// For other key types this is equivalent to setting `table[key] = nil`.
370    pub fn raw_remove(&self, key: impl IntoLua) -> Result<()> {
371        let lua = self.0.lua.lock();
372        let state = lua.state();
373        let key = key.into_lua(lua.lua())?;
374        match key {
375            Value::Integer(idx) => {
376                let size = self.raw_len() as Integer;
377                if idx < 1 || idx > size {
378                    return Err(Error::runtime("index out of bounds"));
379                }
380                unsafe {
381                    let _sg = StackGuard::new(state);
382                    check_stack(state, 4)?;
383
384                    lua.push_ref(&self.0);
385                    protect_lua!(state, 1, 0, |state| {
386                        for i in idx..size {
387                            ffi::lua_rawgeti(state, -1, i + 1);
388                            ffi::lua_rawseti(state, -2, i);
389                        }
390                        ffi::lua_pushnil(state);
391                        ffi::lua_rawseti(state, -2, size);
392                    })
393                }
394            }
395            _ => self.raw_set(key, Nil),
396        }
397    }
398
399    /// Clears the table, removing all keys and values from array and hash parts,
400    /// without invoking metamethods.
401    ///
402    /// This method is useful to clear the table while keeping its capacity.
403    pub fn clear(&self) -> Result<()> {
404        let lua = self.0.lua.lock();
405        unsafe {
406            #[cfg(feature = "luau")]
407            {
408                self.check_readonly_write(&lua)?;
409                ffi::lua_cleartable(lua.ref_thread(), self.0.index);
410            }
411
412            #[cfg(not(feature = "luau"))]
413            {
414                let state = lua.state();
415                check_stack(state, 4)?;
416
417                lua.push_ref(&self.0);
418
419                // Clear array part
420                for i in 1..=ffi::lua_rawlen(state, -1) {
421                    ffi::lua_pushnil(state);
422                    ffi::lua_rawseti(state, -2, i as Integer);
423                }
424
425                // Clear hash part
426                // It must be safe as long as we don't use invalid keys
427                ffi::lua_pushnil(state);
428                while ffi::lua_next(state, -2) != 0 {
429                    ffi::lua_pop(state, 1); // pop value
430                    ffi::lua_pushvalue(state, -1); // copy key
431                    ffi::lua_pushnil(state);
432                    ffi::lua_rawset(state, -4);
433                }
434            }
435        }
436
437        Ok(())
438    }
439
440    /// Returns the result of the Lua `#` operator.
441    ///
442    /// This might invoke the `__len` metamethod. Use the [`Table::raw_len`] method if that is not
443    /// desired.
444    pub fn len(&self) -> Result<Integer> {
445        // Fast track (skip protected call)
446        if !self.has_metatable() {
447            return Ok(self.raw_len() as Integer);
448        }
449
450        let lua = self.0.lua.lock();
451        let state = lua.state();
452        unsafe {
453            let _sg = StackGuard::new(state);
454            check_stack(state, 4)?;
455
456            lua.push_ref(&self.0);
457            protect_lua!(state, 1, 0, |state| ffi::luaL_len(state, -1))
458        }
459    }
460
461    /// Returns the result of the Lua `#` operator, without invoking the `__len` metamethod.
462    pub fn raw_len(&self) -> usize {
463        let lua = self.0.lua.lock();
464        unsafe { ffi::lua_rawlen(lua.ref_thread(), self.0.index) }
465    }
466
467    /// Returns `true` if the table is empty, without invoking metamethods.
468    ///
469    /// It checks both the array part and the hash part.
470    pub fn is_empty(&self) -> bool {
471        let lua = self.0.lua.lock();
472        let ref_thread = lua.ref_thread();
473        unsafe {
474            ffi::lua_pushnil(ref_thread);
475            if ffi::lua_next(ref_thread, self.0.index) == 0 {
476                return true;
477            }
478            ffi::lua_pop(ref_thread, 2);
479        }
480        false
481    }
482
483    /// Returns a reference to the metatable of this table, or `None` if no metatable is set.
484    ///
485    /// Unlike the [`getmetatable`] Lua function, this method ignores the `__metatable` field.
486    ///
487    /// [`getmetatable`]: https://www.lua.org/manual/5.4/manual.html#pdf-getmetatable
488    pub fn metatable(&self) -> Option<Table> {
489        let lua = self.0.lua.lock();
490        let ref_thread = lua.ref_thread();
491        unsafe {
492            if ffi::lua_getmetatable(ref_thread, self.0.index) == 0 {
493                None
494            } else {
495                Some(Table(lua.pop_ref_thread()))
496            }
497        }
498    }
499
500    /// Sets or removes the metatable of this table.
501    ///
502    /// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does
503    /// nothing).
504    pub fn set_metatable(&self, metatable: Option<Table>) -> Result<()> {
505        #[cfg(feature = "luau")]
506        if self.is_readonly() {
507            return Err(Error::runtime("attempt to modify a readonly table"));
508        }
509
510        let lua = self.0.lua.lock();
511        let ref_thread = lua.ref_thread();
512        unsafe {
513            if let Some(metatable) = &metatable {
514                ffi::lua_pushvalue(ref_thread, metatable.0.index);
515            } else {
516                ffi::lua_pushnil(ref_thread);
517            }
518            ffi::lua_setmetatable(ref_thread, self.0.index);
519        }
520        Ok(())
521    }
522
523    /// Returns true if the table has metatable attached.
524    #[doc(hidden)]
525    #[inline]
526    pub fn has_metatable(&self) -> bool {
527        let lua = self.0.lua.lock();
528        unsafe { !get_metatable_ptr(lua.ref_thread(), self.0.index).is_null() }
529    }
530
531    /// Sets `readonly` attribute on the table.
532    #[cfg(any(feature = "luau", doc))]
533    #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
534    pub fn set_readonly(&self, enabled: bool) {
535        let lua = self.0.lua.lock();
536        let ref_thread = lua.ref_thread();
537        unsafe {
538            ffi::lua_setreadonly(ref_thread, self.0.index, enabled as _);
539            if !enabled {
540                // Reset "safeenv" flag
541                ffi::lua_setsafeenv(ref_thread, self.0.index, 0);
542            }
543        }
544    }
545
546    /// Returns `readonly` attribute of the table.
547    #[cfg(any(feature = "luau", doc))]
548    #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
549    pub fn is_readonly(&self) -> bool {
550        let lua = self.0.lua.lock();
551        let ref_thread = lua.ref_thread();
552        unsafe { ffi::lua_getreadonly(ref_thread, self.0.index) != 0 }
553    }
554
555    /// Controls `safeenv` attribute on the table.
556    ///
557    /// This a special flag that activates some performance optimizations for environment tables.
558    /// In particular, it controls:
559    /// - Optimization of import resolution (cache values of constant keys).
560    /// - Fast-path for built-in iteration with pairs/ipairs.
561    /// - Fast-path for some built-in functions (fastcall).
562    ///
563    /// For `safeenv` environments, monkey patching or modifying values may not work as expected.
564    #[cfg(any(feature = "luau", doc))]
565    #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
566    pub fn set_safeenv(&self, enabled: bool) {
567        let lua = self.0.lua.lock();
568        unsafe { ffi::lua_setsafeenv(lua.ref_thread(), self.0.index, enabled as _) };
569    }
570
571    /// Converts this table to a generic C pointer.
572    ///
573    /// Different tables will give different pointers.
574    /// There is no way to convert the pointer back to its original value.
575    ///
576    /// Typically this function is used only for hashing and debug information.
577    #[inline]
578    pub fn to_pointer(&self) -> *const c_void {
579        self.0.to_pointer()
580    }
581
582    /// Returns an iterator over the pairs of the table.
583    ///
584    /// This works like the Lua `pairs` function, but does not invoke the `__pairs` metamethod.
585    ///
586    /// The pairs are wrapped in a [`Result`], since they are lazily converted to `K` and `V` types.
587    ///
588    /// # Examples
589    ///
590    /// Iterate over all globals:
591    ///
592    /// ```
593    /// # use mlua::{Lua, Result, Value};
594    /// # fn main() -> Result<()> {
595    /// # let lua = Lua::new();
596    /// let globals = lua.globals();
597    ///
598    /// for pair in globals.pairs::<Value, Value>() {
599    ///     let (key, value) = pair?;
600    /// #   let _ = (key, value);   // used
601    ///     // ...
602    /// }
603    /// # Ok(())
604    /// # }
605    /// ```
606    ///
607    /// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next
608    pub fn pairs<K: FromLua, V: FromLua>(&self) -> TablePairs<'_, K, V> {
609        TablePairs {
610            guard: self.0.lua.lock(),
611            table: self,
612            key: Some(Nil),
613            _phantom: PhantomData,
614        }
615    }
616
617    /// Iterates over the pairs of the table, invoking the given closure on each pair.
618    ///
619    /// This method is similar to [`Table::pairs`], but optimized for performance.
620    /// It does not invoke the `__pairs` metamethod.
621    pub fn for_each<K, V>(&self, mut f: impl FnMut(K, V) -> Result<()>) -> Result<()>
622    where
623        K: FromLua,
624        V: FromLua,
625    {
626        let lua = self.0.lua.lock();
627        let state = lua.state();
628        unsafe {
629            let _sg = StackGuard::new(state);
630            check_stack(state, 5)?;
631
632            lua.push_ref(&self.0);
633            ffi::lua_pushnil(state);
634            while ffi::lua_next(state, -2) != 0 {
635                let k = K::from_stack(-2, &lua)?;
636                let v = V::from_stack(-1, &lua)?;
637                f(k, v)?;
638                // Keep key for next iteration
639                ffi::lua_pop(state, 1);
640            }
641        }
642        Ok(())
643    }
644
645    /// Returns an iterator over all values in the sequence part of the table.
646    ///
647    /// The iterator will yield all values `t[1]`, `t[2]` and so on, until a `nil` value is
648    /// encountered. This mirrors the behavior of Lua's `ipairs` function but does not invoke
649    /// any metamethods.
650    ///
651    /// # Examples
652    ///
653    /// ```
654    /// # use mlua::{Lua, Result, Table};
655    /// # fn main() -> Result<()> {
656    /// # let lua = Lua::new();
657    /// let my_table: Table = lua.load(r#"
658    ///     {
659    ///         [1] = 4,
660    ///         [2] = 5,
661    ///         [4] = 7,
662    ///         key = 2
663    ///     }
664    /// "#).eval()?;
665    ///
666    /// let expected = [4, 5];
667    /// for (&expected, got) in expected.iter().zip(my_table.sequence_values::<u32>()) {
668    ///     assert_eq!(expected, got?);
669    /// }
670    /// # Ok(())
671    /// # }
672    /// ```
673    pub fn sequence_values<V: FromLua>(&self) -> TableSequence<'_, V> {
674        TableSequence {
675            guard: self.0.lua.lock(),
676            table: self,
677            index: 1,
678            _phantom: PhantomData,
679        }
680    }
681
682    /// Iterates over the sequence part of the table, invoking the given closure on each value.
683    #[doc(hidden)]
684    pub fn for_each_value<V>(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()>
685    where
686        V: FromLua,
687    {
688        let lua = self.0.lua.lock();
689        let state = lua.state();
690        unsafe {
691            let _sg = StackGuard::new(state);
692            check_stack(state, 4)?;
693
694            lua.push_ref(&self.0);
695            let len = ffi::lua_rawlen(state, -1);
696            for i in 1..=len {
697                ffi::lua_rawgeti(state, -1, i as _);
698                f(V::from_stack(-1, &lua)?)?;
699                ffi::lua_pop(state, 1);
700            }
701        }
702        Ok(())
703    }
704
705    /// Sets element value at position `idx` without invoking metamethods.
706    #[doc(hidden)]
707    pub fn raw_seti(&self, idx: usize, value: impl IntoLua) -> Result<()> {
708        let lua = self.0.lua.lock();
709        let state = lua.state();
710        unsafe {
711            #[cfg(feature = "luau")]
712            self.check_readonly_write(&lua)?;
713
714            let _sg = StackGuard::new(state);
715            check_stack(state, 5)?;
716
717            lua.push_ref(&self.0);
718            value.push_into_stack(&lua)?;
719
720            let idx = idx.try_into().unwrap();
721            if lua.unlikely_memory_error() {
722                ffi::lua_rawseti(state, -2, idx);
723            } else {
724                protect_lua!(state, 2, 0, |state| ffi::lua_rawseti(state, -2, idx))?;
725            }
726        }
727        Ok(())
728    }
729
730    #[cfg(feature = "serde")]
731    pub(crate) fn is_array(&self) -> bool {
732        let lua = self.0.lua.lock();
733        let state = lua.state();
734        unsafe {
735            let _sg = StackGuard::new(state);
736            assert_stack(state, 3);
737
738            lua.push_ref(&self.0);
739            if ffi::lua_getmetatable(state, -1) == 0 {
740                return false;
741            }
742            crate::serde::push_array_metatable(state);
743            ffi::lua_rawequal(state, -1, -2) != 0
744        }
745    }
746
747    #[cfg(feature = "luau")]
748    #[inline(always)]
749    fn check_readonly_write(&self, lua: &RawLua) -> Result<()> {
750        if unsafe { ffi::lua_getreadonly(lua.ref_thread(), self.0.index) != 0 } {
751            return Err(Error::runtime("attempt to modify a readonly table"));
752        }
753        Ok(())
754    }
755
756    pub(crate) fn fmt_pretty(
757        &self,
758        fmt: &mut fmt::Formatter,
759        ident: usize,
760        visited: &mut HashSet<*const c_void>,
761    ) -> fmt::Result {
762        visited.insert(self.to_pointer());
763
764        // Collect key/value pairs into a vector so we can sort them
765        let mut pairs = self.pairs::<Value, Value>().flatten().collect::<Vec<_>>();
766        // Sort keys
767        pairs.sort_by(|(a, _), (b, _)| a.sort_cmp(b));
768        let is_sequence = (pairs.iter().enumerate())
769            .all(|(i, (k, _))| matches!(k, Value::Integer(n) if *n == (i + 1) as Integer));
770        if pairs.is_empty() {
771            return write!(fmt, "{{}}");
772        }
773        writeln!(fmt, "{{")?;
774        if is_sequence {
775            // Format as list
776            for (_, value) in pairs {
777                write!(fmt, "{}", " ".repeat(ident + 2))?;
778                value.fmt_pretty(fmt, true, ident + 2, visited)?;
779                writeln!(fmt, ",")?;
780            }
781        } else {
782            fn is_simple_key(key: &[u8]) -> bool {
783                key.iter().take(1).all(|c| c.is_ascii_alphabetic() || *c == b'_')
784                    && key.iter().all(|c| c.is_ascii_alphanumeric() || *c == b'_')
785            }
786
787            for (key, value) in pairs {
788                match key {
789                    Value::String(key) if is_simple_key(&key.as_bytes()) => {
790                        write!(fmt, "{}{}", " ".repeat(ident + 2), key.display())?;
791                        write!(fmt, " = ")?;
792                    }
793                    _ => {
794                        write!(fmt, "{}[", " ".repeat(ident + 2))?;
795                        key.fmt_pretty(fmt, false, ident + 2, visited)?;
796                        write!(fmt, "] = ")?;
797                    }
798                }
799                value.fmt_pretty(fmt, true, ident + 2, visited)?;
800                writeln!(fmt, ",")?;
801            }
802        }
803        write!(fmt, "{}}}", " ".repeat(ident))
804    }
805}
806
807impl fmt::Debug for Table {
808    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
809        if fmt.alternate() {
810            return self.fmt_pretty(fmt, 0, &mut HashSet::new());
811        }
812        fmt.debug_tuple("Table").field(&self.0).finish()
813    }
814}
815
816impl<T> PartialEq<[T]> for Table
817where
818    T: IntoLua + Clone,
819{
820    fn eq(&self, other: &[T]) -> bool {
821        let lua = self.0.lua.lock();
822        let state = lua.state();
823        unsafe {
824            let _sg = StackGuard::new(state);
825            assert_stack(state, 4);
826
827            lua.push_ref(&self.0);
828
829            let len = ffi::lua_rawlen(state, -1);
830            for i in 0..len {
831                ffi::lua_rawgeti(state, -1, (i + 1) as _);
832                let val = lua.pop_value();
833                if val == Nil {
834                    return i == other.len();
835                }
836                match other.get(i).map(|v| v.clone().into_lua(lua.lua())) {
837                    Some(Ok(other_val)) if val == other_val => continue,
838                    _ => return false,
839                }
840            }
841        }
842        true
843    }
844}
845
846impl<T> PartialEq<&[T]> for Table
847where
848    T: IntoLua + Clone,
849{
850    #[inline]
851    fn eq(&self, other: &&[T]) -> bool {
852        self == *other
853    }
854}
855
856impl<T, const N: usize> PartialEq<[T; N]> for Table
857where
858    T: IntoLua + Clone,
859{
860    #[inline]
861    fn eq(&self, other: &[T; N]) -> bool {
862        self == &other[..]
863    }
864}
865
866impl LuaType for Table {
867    const TYPE_ID: c_int = ffi::LUA_TTABLE;
868}
869
870impl ObjectLike for Table {
871    #[inline]
872    fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
873        self.get(key)
874    }
875
876    #[inline]
877    fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
878        self.set(key, value)
879    }
880
881    #[inline]
882    fn call<R>(&self, args: impl IntoLuaMulti) -> Result<R>
883    where
884        R: FromLuaMulti,
885    {
886        // Convert table to a function and call via pcall that respects the `__call` metamethod.
887        Function(self.0.copy()).call(args)
888    }
889
890    #[cfg(feature = "async")]
891    #[inline]
892    fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
893    where
894        R: FromLuaMulti,
895    {
896        Function(self.0.copy()).call_async(args)
897    }
898
899    #[inline]
900    fn call_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> Result<R>
901    where
902        R: FromLuaMulti,
903    {
904        self.call_function(name, (self, args))
905    }
906
907    #[cfg(feature = "async")]
908    fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
909    where
910        R: FromLuaMulti,
911    {
912        self.call_async_function(name, (self, args))
913    }
914
915    #[inline]
916    fn call_function<R: FromLuaMulti>(&self, name: &str, args: impl IntoLuaMulti) -> Result<R> {
917        match self.get(name)? {
918            Value::Function(func) => func.call(args),
919            val => {
920                let msg = format!("attempt to call a {} value (function '{name}')", val.type_name());
921                Err(Error::runtime(msg))
922            }
923        }
924    }
925
926    #[cfg(feature = "async")]
927    #[inline]
928    fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
929    where
930        R: FromLuaMulti,
931    {
932        match self.get(name) {
933            Ok(Value::Function(func)) => func.call_async(args),
934            Ok(val) => {
935                let msg = format!("attempt to call a {} value (function '{name}')", val.type_name());
936                AsyncCallFuture::error(Error::RuntimeError(msg))
937            }
938            Err(err) => AsyncCallFuture::error(err),
939        }
940    }
941
942    #[inline]
943    fn to_string(&self) -> Result<StdString> {
944        Value::Table(Table(self.0.copy())).to_string()
945    }
946}
947
948/// A wrapped [`Table`] with customized serialization behavior.
949#[cfg(feature = "serde")]
950pub(crate) struct SerializableTable<'a> {
951    table: &'a Table,
952    options: crate::serde::de::Options,
953    visited: Rc<RefCell<FxHashSet<*const c_void>>>,
954}
955
956#[cfg(feature = "serde")]
957impl Serialize for Table {
958    #[inline]
959    fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
960        SerializableTable::new(self, Default::default(), Default::default()).serialize(serializer)
961    }
962}
963
964#[cfg(feature = "serde")]
965impl<'a> SerializableTable<'a> {
966    #[inline]
967    pub(crate) fn new(
968        table: &'a Table,
969        options: crate::serde::de::Options,
970        visited: Rc<RefCell<FxHashSet<*const c_void>>>,
971    ) -> Self {
972        Self {
973            table,
974            options,
975            visited,
976        }
977    }
978}
979
980#[cfg(feature = "serde")]
981impl Serialize for SerializableTable<'_> {
982    fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
983    where
984        S: Serializer,
985    {
986        use crate::serde::de::{check_value_for_skip, MapPairs, RecursionGuard};
987        use crate::value::SerializableValue;
988
989        let convert_result = |res: Result<()>, serialize_err: Option<S::Error>| match res {
990            Ok(v) => Ok(v),
991            Err(Error::SerializeError(_)) if serialize_err.is_some() => Err(serialize_err.unwrap()),
992            Err(Error::SerializeError(msg)) => Err(serde::ser::Error::custom(msg)),
993            Err(err) => Err(serde::ser::Error::custom(err.to_string())),
994        };
995
996        let options = self.options;
997        let visited = &self.visited;
998        let _guard = RecursionGuard::new(self.table, visited);
999
1000        // Array
1001        let len = self.table.raw_len();
1002        if len > 0
1003            || self.table.is_array()
1004            || (self.options.encode_empty_tables_as_array && self.table.is_empty())
1005        {
1006            let mut seq = serializer.serialize_seq(Some(len))?;
1007            let mut serialize_err = None;
1008            let res = self.table.for_each_value::<Value>(|value| {
1009                let skip = check_value_for_skip(&value, self.options, visited)
1010                    .map_err(|err| Error::SerializeError(err.to_string()))?;
1011                if skip {
1012                    // continue iteration
1013                    return Ok(());
1014                }
1015                seq.serialize_element(&SerializableValue::new(&value, options, Some(visited)))
1016                    .map_err(|err| {
1017                        serialize_err = Some(err);
1018                        Error::SerializeError(StdString::new())
1019                    })
1020            });
1021            convert_result(res, serialize_err)?;
1022            return seq.end();
1023        }
1024
1025        // HashMap
1026        let mut map = serializer.serialize_map(None)?;
1027        let mut serialize_err = None;
1028        let mut process_pair = |key, value| {
1029            let skip_key = check_value_for_skip(&key, self.options, visited)
1030                .map_err(|err| Error::SerializeError(err.to_string()))?;
1031            let skip_value = check_value_for_skip(&value, self.options, visited)
1032                .map_err(|err| Error::SerializeError(err.to_string()))?;
1033            if skip_key || skip_value {
1034                // continue iteration
1035                return Ok(());
1036            }
1037            map.serialize_entry(
1038                &SerializableValue::new(&key, options, Some(visited)),
1039                &SerializableValue::new(&value, options, Some(visited)),
1040            )
1041            .map_err(|err| {
1042                serialize_err = Some(err);
1043                Error::SerializeError(StdString::new())
1044            })
1045        };
1046
1047        let res = if !self.options.sort_keys {
1048            // Fast track
1049            self.table.for_each(process_pair)
1050        } else {
1051            MapPairs::new(self.table, self.options.sort_keys)
1052                .map_err(serde::ser::Error::custom)?
1053                .try_for_each(|kv| {
1054                    let (key, value) = kv?;
1055                    process_pair(key, value)
1056                })
1057        };
1058        convert_result(res, serialize_err)?;
1059        map.end()
1060    }
1061}
1062
1063/// An iterator over the pairs of a Lua table.
1064///
1065/// This struct is created by the [`Table::pairs`] method.
1066///
1067/// [`Table::pairs`]: crate::Table::pairs
1068pub struct TablePairs<'a, K, V> {
1069    guard: LuaGuard,
1070    table: &'a Table,
1071    key: Option<Value>,
1072    _phantom: PhantomData<(K, V)>,
1073}
1074
1075impl<K, V> Iterator for TablePairs<'_, K, V>
1076where
1077    K: FromLua,
1078    V: FromLua,
1079{
1080    type Item = Result<(K, V)>;
1081
1082    fn next(&mut self) -> Option<Self::Item> {
1083        if let Some(prev_key) = self.key.take() {
1084            let lua: &RawLua = &self.guard;
1085            let state = lua.state();
1086
1087            let res = (|| unsafe {
1088                let _sg = StackGuard::new(state);
1089                check_stack(state, 5)?;
1090
1091                lua.push_ref(&self.table.0);
1092                lua.push_value(&prev_key)?;
1093
1094                // It must be safe to call `lua_next` unprotected as deleting a key from a table is
1095                // a permitted operation.
1096                // It fails only if the key is not found (never existed) which seems impossible scenario.
1097                if ffi::lua_next(state, -2) != 0 {
1098                    let key = lua.stack_value(-2, None);
1099                    Ok(Some((
1100                        key.clone(),
1101                        K::from_lua(key, lua.lua())?,
1102                        V::from_stack(-1, lua)?,
1103                    )))
1104                } else {
1105                    Ok(None)
1106                }
1107            })();
1108
1109            match res {
1110                Ok(Some((key, ret_key, value))) => {
1111                    self.key = Some(key);
1112                    Some(Ok((ret_key, value)))
1113                }
1114                Ok(None) => None,
1115                Err(e) => Some(Err(e)),
1116            }
1117        } else {
1118            None
1119        }
1120    }
1121}
1122
1123/// An iterator over the sequence part of a Lua table.
1124///
1125/// This struct is created by the [`Table::sequence_values`] method.
1126///
1127/// [`Table::sequence_values`]: crate::Table::sequence_values
1128pub struct TableSequence<'a, V> {
1129    guard: LuaGuard,
1130    table: &'a Table,
1131    index: Integer,
1132    _phantom: PhantomData<V>,
1133}
1134
1135impl<V> Iterator for TableSequence<'_, V>
1136where
1137    V: FromLua,
1138{
1139    type Item = Result<V>;
1140
1141    fn next(&mut self) -> Option<Self::Item> {
1142        let lua: &RawLua = &self.guard;
1143        let state = lua.state();
1144        unsafe {
1145            let _sg = StackGuard::new(state);
1146            if let Err(err) = check_stack(state, 1) {
1147                return Some(Err(err));
1148            }
1149
1150            lua.push_ref(&self.table.0);
1151            match ffi::lua_rawgeti(state, -1, self.index) {
1152                ffi::LUA_TNIL => None,
1153                _ => {
1154                    self.index += 1;
1155                    Some(V::from_stack(-1, lua))
1156                }
1157            }
1158        }
1159    }
1160}
1161
1162#[cfg(test)]
1163mod assertions {
1164    use super::*;
1165
1166    #[cfg(not(feature = "send"))]
1167    static_assertions::assert_not_impl_any!(Table: Send);
1168    #[cfg(feature = "send")]
1169    static_assertions::assert_impl_all!(Table: Send, Sync);
1170}