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 futures_util::future::{self, Either, Future};
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 state = lua.state();
491        unsafe {
492            let _sg = StackGuard::new(state);
493            assert_stack(state, 2);
494
495            lua.push_ref(&self.0);
496            if ffi::lua_getmetatable(state, -1) == 0 {
497                None
498            } else {
499                Some(Table(lua.pop_ref()))
500            }
501        }
502    }
503
504    /// Sets or removes the metatable of this table.
505    ///
506    /// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does
507    /// nothing).
508    pub fn set_metatable(&self, metatable: Option<Table>) {
509        // Workaround to throw readonly error without returning Result
510        #[cfg(feature = "luau")]
511        if self.is_readonly() {
512            panic!("attempt to modify a readonly table");
513        }
514
515        let lua = self.0.lua.lock();
516        let state = lua.state();
517        unsafe {
518            let _sg = StackGuard::new(state);
519            assert_stack(state, 2);
520
521            lua.push_ref(&self.0);
522            if let Some(metatable) = metatable {
523                lua.push_ref(&metatable.0);
524            } else {
525                ffi::lua_pushnil(state);
526            }
527            ffi::lua_setmetatable(state, -2);
528        }
529    }
530
531    /// Returns true if the table has metatable attached.
532    #[doc(hidden)]
533    #[inline]
534    pub fn has_metatable(&self) -> bool {
535        let lua = self.0.lua.lock();
536        unsafe { !get_metatable_ptr(lua.ref_thread(), self.0.index).is_null() }
537    }
538
539    /// Sets `readonly` attribute on the table.
540    #[cfg(any(feature = "luau", doc))]
541    #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
542    pub fn set_readonly(&self, enabled: bool) {
543        let lua = self.0.lua.lock();
544        let ref_thread = lua.ref_thread();
545        unsafe {
546            ffi::lua_setreadonly(ref_thread, self.0.index, enabled as _);
547            if !enabled {
548                // Reset "safeenv" flag
549                ffi::lua_setsafeenv(ref_thread, self.0.index, 0);
550            }
551        }
552    }
553
554    /// Returns `readonly` attribute of the table.
555    #[cfg(any(feature = "luau", doc))]
556    #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
557    pub fn is_readonly(&self) -> bool {
558        let lua = self.0.lua.lock();
559        let ref_thread = lua.ref_thread();
560        unsafe { ffi::lua_getreadonly(ref_thread, self.0.index) != 0 }
561    }
562
563    /// Controls `safeenv` attribute on the table.
564    ///
565    /// This a special flag that activates some performance optimizations for environment tables.
566    /// In particular, it controls:
567    /// - Optimization of import resolution (cache values of constant keys).
568    /// - Fast-path for built-in iteration with pairs/ipairs.
569    /// - Fast-path for some built-in functions (fastcall).
570    ///
571    /// For `safeenv` environments, monkey patching or modifying values may not work as expected.
572    #[cfg(any(feature = "luau", doc))]
573    #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
574    pub fn set_safeenv(&self, enabled: bool) {
575        let lua = self.0.lua.lock();
576        unsafe { ffi::lua_setsafeenv(lua.ref_thread(), self.0.index, enabled as _) };
577    }
578
579    /// Converts this table to a generic C pointer.
580    ///
581    /// Different tables will give different pointers.
582    /// There is no way to convert the pointer back to its original value.
583    ///
584    /// Typically this function is used only for hashing and debug information.
585    #[inline]
586    pub fn to_pointer(&self) -> *const c_void {
587        self.0.to_pointer()
588    }
589
590    /// Returns an iterator over the pairs of the table.
591    ///
592    /// This works like the Lua `pairs` function, but does not invoke the `__pairs` metamethod.
593    ///
594    /// The pairs are wrapped in a [`Result`], since they are lazily converted to `K` and `V` types.
595    ///
596    /// # Examples
597    ///
598    /// Iterate over all globals:
599    ///
600    /// ```
601    /// # use mlua::{Lua, Result, Value};
602    /// # fn main() -> Result<()> {
603    /// # let lua = Lua::new();
604    /// let globals = lua.globals();
605    ///
606    /// for pair in globals.pairs::<Value, Value>() {
607    ///     let (key, value) = pair?;
608    /// #   let _ = (key, value);   // used
609    ///     // ...
610    /// }
611    /// # Ok(())
612    /// # }
613    /// ```
614    ///
615    /// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next
616    pub fn pairs<K: FromLua, V: FromLua>(&self) -> TablePairs<K, V> {
617        TablePairs {
618            guard: self.0.lua.lock(),
619            table: self,
620            key: Some(Nil),
621            _phantom: PhantomData,
622        }
623    }
624
625    /// Iterates over the pairs of the table, invoking the given closure on each pair.
626    ///
627    /// This method is similar to [`Table::pairs`], but optimized for performance.
628    /// It does not invoke the `__pairs` metamethod.
629    pub fn for_each<K, V>(&self, mut f: impl FnMut(K, V) -> Result<()>) -> Result<()>
630    where
631        K: FromLua,
632        V: FromLua,
633    {
634        let lua = self.0.lua.lock();
635        let state = lua.state();
636        unsafe {
637            let _sg = StackGuard::new(state);
638            check_stack(state, 5)?;
639
640            lua.push_ref(&self.0);
641            ffi::lua_pushnil(state);
642            while ffi::lua_next(state, -2) != 0 {
643                let k = K::from_stack(-2, &lua)?;
644                let v = V::from_stack(-1, &lua)?;
645                f(k, v)?;
646                // Keep key for next iteration
647                ffi::lua_pop(state, 1);
648            }
649        }
650        Ok(())
651    }
652
653    /// Returns an iterator over all values in the sequence part of the table.
654    ///
655    /// The iterator will yield all values `t[1]`, `t[2]` and so on, until a `nil` value is
656    /// encountered. This mirrors the behavior of Lua's `ipairs` function but does not invoke
657    /// any metamethods.
658    ///
659    /// # Examples
660    ///
661    /// ```
662    /// # use mlua::{Lua, Result, Table};
663    /// # fn main() -> Result<()> {
664    /// # let lua = Lua::new();
665    /// let my_table: Table = lua.load(r#"
666    ///     {
667    ///         [1] = 4,
668    ///         [2] = 5,
669    ///         [4] = 7,
670    ///         key = 2
671    ///     }
672    /// "#).eval()?;
673    ///
674    /// let expected = [4, 5];
675    /// for (&expected, got) in expected.iter().zip(my_table.sequence_values::<u32>()) {
676    ///     assert_eq!(expected, got?);
677    /// }
678    /// # Ok(())
679    /// # }
680    /// ```
681    pub fn sequence_values<V: FromLua>(&self) -> TableSequence<V> {
682        TableSequence {
683            guard: self.0.lua.lock(),
684            table: self,
685            index: 1,
686            _phantom: PhantomData,
687        }
688    }
689
690    /// Iterates over the sequence part of the table, invoking the given closure on each value.
691    #[doc(hidden)]
692    pub fn for_each_value<V>(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()>
693    where
694        V: FromLua,
695    {
696        let lua = self.0.lua.lock();
697        let state = lua.state();
698        unsafe {
699            let _sg = StackGuard::new(state);
700            check_stack(state, 4)?;
701
702            lua.push_ref(&self.0);
703            let len = ffi::lua_rawlen(state, -1);
704            for i in 1..=len {
705                ffi::lua_rawgeti(state, -1, i as _);
706                f(V::from_stack(-1, &lua)?)?;
707                ffi::lua_pop(state, 1);
708            }
709        }
710        Ok(())
711    }
712
713    /// Sets element value at position `idx` without invoking metamethods.
714    #[doc(hidden)]
715    pub fn raw_seti(&self, idx: usize, value: impl IntoLua) -> Result<()> {
716        let lua = self.0.lua.lock();
717        let state = lua.state();
718        unsafe {
719            #[cfg(feature = "luau")]
720            self.check_readonly_write(&lua)?;
721
722            let _sg = StackGuard::new(state);
723            check_stack(state, 5)?;
724
725            lua.push_ref(&self.0);
726            value.push_into_stack(&lua)?;
727
728            let idx = idx.try_into().unwrap();
729            if lua.unlikely_memory_error() {
730                ffi::lua_rawseti(state, -2, idx);
731            } else {
732                protect_lua!(state, 2, 0, |state| ffi::lua_rawseti(state, -2, idx))?;
733            }
734        }
735        Ok(())
736    }
737
738    #[cfg(feature = "serde")]
739    pub(crate) fn is_array(&self) -> bool {
740        let lua = self.0.lua.lock();
741        let state = lua.state();
742        unsafe {
743            let _sg = StackGuard::new(state);
744            assert_stack(state, 3);
745
746            lua.push_ref(&self.0);
747            if ffi::lua_getmetatable(state, -1) == 0 {
748                return false;
749            }
750            crate::serde::push_array_metatable(state);
751            ffi::lua_rawequal(state, -1, -2) != 0
752        }
753    }
754
755    #[cfg(feature = "luau")]
756    #[inline(always)]
757    fn check_readonly_write(&self, lua: &RawLua) -> Result<()> {
758        if unsafe { ffi::lua_getreadonly(lua.ref_thread(), self.0.index) != 0 } {
759            return Err(Error::runtime("attempt to modify a readonly table"));
760        }
761        Ok(())
762    }
763
764    pub(crate) fn fmt_pretty(
765        &self,
766        fmt: &mut fmt::Formatter,
767        ident: usize,
768        visited: &mut HashSet<*const c_void>,
769    ) -> fmt::Result {
770        visited.insert(self.to_pointer());
771
772        // Collect key/value pairs into a vector so we can sort them
773        let mut pairs = self.pairs::<Value, Value>().flatten().collect::<Vec<_>>();
774        // Sort keys
775        pairs.sort_by(|(a, _), (b, _)| a.sort_cmp(b));
776        let is_sequence = (pairs.iter().enumerate())
777            .all(|(i, (k, _))| matches!(k, Value::Integer(n) if *n == (i + 1) as Integer));
778        if pairs.is_empty() {
779            return write!(fmt, "{{}}");
780        }
781        writeln!(fmt, "{{")?;
782        if is_sequence {
783            // Format as list
784            for (_, value) in pairs {
785                write!(fmt, "{}", " ".repeat(ident + 2))?;
786                value.fmt_pretty(fmt, true, ident + 2, visited)?;
787                writeln!(fmt, ",")?;
788            }
789        } else {
790            fn is_simple_key(key: &[u8]) -> bool {
791                key.iter().take(1).all(|c| c.is_ascii_alphabetic() || *c == b'_')
792                    && key.iter().all(|c| c.is_ascii_alphanumeric() || *c == b'_')
793            }
794
795            for (key, value) in pairs {
796                match key {
797                    Value::String(key) if is_simple_key(&key.as_bytes()) => {
798                        write!(fmt, "{}{}", " ".repeat(ident + 2), key.display())?;
799                        write!(fmt, " = ")?;
800                    }
801                    _ => {
802                        write!(fmt, "{}[", " ".repeat(ident + 2))?;
803                        key.fmt_pretty(fmt, false, ident + 2, visited)?;
804                        write!(fmt, "] = ")?;
805                    }
806                }
807                value.fmt_pretty(fmt, true, ident + 2, visited)?;
808                writeln!(fmt, ",")?;
809            }
810        }
811        write!(fmt, "{}}}", " ".repeat(ident))
812    }
813}
814
815impl fmt::Debug for Table {
816    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
817        if fmt.alternate() {
818            return self.fmt_pretty(fmt, 0, &mut HashSet::new());
819        }
820        fmt.debug_tuple("Table").field(&self.0).finish()
821    }
822}
823
824impl<T> PartialEq<[T]> for Table
825where
826    T: IntoLua + Clone,
827{
828    fn eq(&self, other: &[T]) -> bool {
829        let lua = self.0.lua.lock();
830        let state = lua.state();
831        unsafe {
832            let _sg = StackGuard::new(state);
833            assert_stack(state, 4);
834
835            lua.push_ref(&self.0);
836
837            let len = ffi::lua_rawlen(state, -1);
838            for i in 0..len {
839                ffi::lua_rawgeti(state, -1, (i + 1) as _);
840                let val = lua.pop_value();
841                if val == Nil {
842                    return i == other.len();
843                }
844                match other.get(i).map(|v| v.clone().into_lua(lua.lua())) {
845                    Some(Ok(other_val)) if val == other_val => continue,
846                    _ => return false,
847                }
848            }
849        }
850        true
851    }
852}
853
854impl<T> PartialEq<&[T]> for Table
855where
856    T: IntoLua + Clone,
857{
858    #[inline]
859    fn eq(&self, other: &&[T]) -> bool {
860        self == *other
861    }
862}
863
864impl<T, const N: usize> PartialEq<[T; N]> for Table
865where
866    T: IntoLua + Clone,
867{
868    #[inline]
869    fn eq(&self, other: &[T; N]) -> bool {
870        self == &other[..]
871    }
872}
873
874impl LuaType for Table {
875    const TYPE_ID: c_int = ffi::LUA_TTABLE;
876}
877
878impl ObjectLike for Table {
879    #[inline]
880    fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
881        self.get(key)
882    }
883
884    #[inline]
885    fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
886        self.set(key, value)
887    }
888
889    #[inline]
890    fn call<R>(&self, args: impl IntoLuaMulti) -> Result<R>
891    where
892        R: FromLuaMulti,
893    {
894        // Convert table to a function and call via pcall that respects the `__call` metamethod.
895        Function(self.0.copy()).call(args)
896    }
897
898    #[cfg(feature = "async")]
899    #[inline]
900    fn call_async<R>(&self, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
901    where
902        R: FromLuaMulti,
903    {
904        Function(self.0.copy()).call_async(args)
905    }
906
907    #[inline]
908    fn call_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> Result<R>
909    where
910        R: FromLuaMulti,
911    {
912        self.call_function(name, (self, args))
913    }
914
915    #[cfg(feature = "async")]
916    fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
917    where
918        R: FromLuaMulti,
919    {
920        self.call_async_function(name, (self, args))
921    }
922
923    #[inline]
924    fn call_function<R: FromLuaMulti>(&self, name: &str, args: impl IntoLuaMulti) -> Result<R> {
925        match self.get(name)? {
926            Value::Function(func) => func.call(args),
927            val => {
928                let msg = format!("attempt to call a {} value (function '{name}')", val.type_name());
929                Err(Error::runtime(msg))
930            }
931        }
932    }
933
934    #[cfg(feature = "async")]
935    #[inline]
936    fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
937    where
938        R: FromLuaMulti,
939    {
940        match self.get(name) {
941            Ok(Value::Function(func)) => Either::Left(func.call_async(args)),
942            Ok(val) => {
943                let msg = format!("attempt to call a {} value (function '{name}')", val.type_name());
944                Either::Right(future::ready(Err(Error::RuntimeError(msg))))
945            }
946            Err(err) => Either::Right(future::ready(Err(err))),
947        }
948    }
949
950    #[inline]
951    fn to_string(&self) -> Result<StdString> {
952        Value::Table(Table(self.0.copy())).to_string()
953    }
954}
955
956/// A wrapped [`Table`] with customized serialization behavior.
957#[cfg(feature = "serde")]
958pub(crate) struct SerializableTable<'a> {
959    table: &'a Table,
960    options: crate::serde::de::Options,
961    visited: Rc<RefCell<FxHashSet<*const c_void>>>,
962}
963
964#[cfg(feature = "serde")]
965impl Serialize for Table {
966    #[inline]
967    fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
968        SerializableTable::new(self, Default::default(), Default::default()).serialize(serializer)
969    }
970}
971
972#[cfg(feature = "serde")]
973impl<'a> SerializableTable<'a> {
974    #[inline]
975    pub(crate) fn new(
976        table: &'a Table,
977        options: crate::serde::de::Options,
978        visited: Rc<RefCell<FxHashSet<*const c_void>>>,
979    ) -> Self {
980        Self {
981            table,
982            options,
983            visited,
984        }
985    }
986}
987
988#[cfg(feature = "serde")]
989impl Serialize for SerializableTable<'_> {
990    fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
991    where
992        S: Serializer,
993    {
994        use crate::serde::de::{check_value_for_skip, MapPairs, RecursionGuard};
995        use crate::value::SerializableValue;
996
997        let convert_result = |res: Result<()>, serialize_err: Option<S::Error>| match res {
998            Ok(v) => Ok(v),
999            Err(Error::SerializeError(_)) if serialize_err.is_some() => Err(serialize_err.unwrap()),
1000            Err(Error::SerializeError(msg)) => Err(serde::ser::Error::custom(msg)),
1001            Err(err) => Err(serde::ser::Error::custom(err.to_string())),
1002        };
1003
1004        let options = self.options;
1005        let visited = &self.visited;
1006        let _guard = RecursionGuard::new(self.table, visited);
1007
1008        // Array
1009        let len = self.table.raw_len();
1010        if len > 0
1011            || self.table.is_array()
1012            || (self.options.encode_empty_tables_as_array && self.table.is_empty())
1013        {
1014            let mut seq = serializer.serialize_seq(Some(len))?;
1015            let mut serialize_err = None;
1016            let res = self.table.for_each_value::<Value>(|value| {
1017                let skip = check_value_for_skip(&value, self.options, visited)
1018                    .map_err(|err| Error::SerializeError(err.to_string()))?;
1019                if skip {
1020                    // continue iteration
1021                    return Ok(());
1022                }
1023                seq.serialize_element(&SerializableValue::new(&value, options, Some(visited)))
1024                    .map_err(|err| {
1025                        serialize_err = Some(err);
1026                        Error::SerializeError(StdString::new())
1027                    })
1028            });
1029            convert_result(res, serialize_err)?;
1030            return seq.end();
1031        }
1032
1033        // HashMap
1034        let mut map = serializer.serialize_map(None)?;
1035        let mut serialize_err = None;
1036        let mut process_pair = |key, value| {
1037            let skip_key = check_value_for_skip(&key, self.options, visited)
1038                .map_err(|err| Error::SerializeError(err.to_string()))?;
1039            let skip_value = check_value_for_skip(&value, self.options, visited)
1040                .map_err(|err| Error::SerializeError(err.to_string()))?;
1041            if skip_key || skip_value {
1042                // continue iteration
1043                return Ok(());
1044            }
1045            map.serialize_entry(
1046                &SerializableValue::new(&key, options, Some(visited)),
1047                &SerializableValue::new(&value, options, Some(visited)),
1048            )
1049            .map_err(|err| {
1050                serialize_err = Some(err);
1051                Error::SerializeError(StdString::new())
1052            })
1053        };
1054
1055        let res = if !self.options.sort_keys {
1056            // Fast track
1057            self.table.for_each(process_pair)
1058        } else {
1059            MapPairs::new(self.table, self.options.sort_keys)
1060                .map_err(serde::ser::Error::custom)?
1061                .try_for_each(|kv| {
1062                    let (key, value) = kv?;
1063                    process_pair(key, value)
1064                })
1065        };
1066        convert_result(res, serialize_err)?;
1067        map.end()
1068    }
1069}
1070
1071/// An iterator over the pairs of a Lua table.
1072///
1073/// This struct is created by the [`Table::pairs`] method.
1074///
1075/// [`Table::pairs`]: crate::Table::pairs
1076pub struct TablePairs<'a, K, V> {
1077    guard: LuaGuard,
1078    table: &'a Table,
1079    key: Option<Value>,
1080    _phantom: PhantomData<(K, V)>,
1081}
1082
1083impl<K, V> Iterator for TablePairs<'_, K, V>
1084where
1085    K: FromLua,
1086    V: FromLua,
1087{
1088    type Item = Result<(K, V)>;
1089
1090    fn next(&mut self) -> Option<Self::Item> {
1091        if let Some(prev_key) = self.key.take() {
1092            let lua: &RawLua = &self.guard;
1093            let state = lua.state();
1094
1095            let res = (|| unsafe {
1096                let _sg = StackGuard::new(state);
1097                check_stack(state, 5)?;
1098
1099                lua.push_ref(&self.table.0);
1100                lua.push_value(&prev_key)?;
1101
1102                // It must be safe to call `lua_next` unprotected as deleting a key from a table is
1103                // a permitted operation.
1104                // It fails only if the key is not found (never existed) which seems impossible scenario.
1105                if ffi::lua_next(state, -2) != 0 {
1106                    let key = lua.stack_value(-2, None);
1107                    Ok(Some((
1108                        key.clone(),
1109                        K::from_lua(key, lua.lua())?,
1110                        V::from_stack(-1, lua)?,
1111                    )))
1112                } else {
1113                    Ok(None)
1114                }
1115            })();
1116
1117            match res {
1118                Ok(Some((key, ret_key, value))) => {
1119                    self.key = Some(key);
1120                    Some(Ok((ret_key, value)))
1121                }
1122                Ok(None) => None,
1123                Err(e) => Some(Err(e)),
1124            }
1125        } else {
1126            None
1127        }
1128    }
1129}
1130
1131/// An iterator over the sequence part of a Lua table.
1132///
1133/// This struct is created by the [`Table::sequence_values`] method.
1134///
1135/// [`Table::sequence_values`]: crate::Table::sequence_values
1136pub struct TableSequence<'a, V> {
1137    guard: LuaGuard,
1138    table: &'a Table,
1139    index: Integer,
1140    _phantom: PhantomData<V>,
1141}
1142
1143impl<V> Iterator for TableSequence<'_, V>
1144where
1145    V: FromLua,
1146{
1147    type Item = Result<V>;
1148
1149    fn next(&mut self) -> Option<Self::Item> {
1150        let lua: &RawLua = &self.guard;
1151        let state = lua.state();
1152        unsafe {
1153            let _sg = StackGuard::new(state);
1154            if let Err(err) = check_stack(state, 1) {
1155                return Some(Err(err));
1156            }
1157
1158            lua.push_ref(&self.table.0);
1159            match ffi::lua_rawgeti(state, -1, self.index) {
1160                ffi::LUA_TNIL => None,
1161                _ => {
1162                    self.index += 1;
1163                    Some(V::from_stack(-1, lua))
1164                }
1165            }
1166        }
1167    }
1168}
1169
1170#[cfg(test)]
1171mod assertions {
1172    use super::*;
1173
1174    #[cfg(not(feature = "send"))]
1175    static_assertions::assert_not_impl_any!(Table: Send);
1176    #[cfg(feature = "send")]
1177    static_assertions::assert_impl_all!(Table: Send, Sync);
1178}