Skip to main content

toml_spanner/item/
table.rs

1#[cfg(test)]
2#[path = "./table_tests.rs"]
3mod tests;
4
5use crate::Span;
6use crate::item::{
7    FLAG_DOTTED, FLAG_FROZEN, FLAG_HEADER, FLAG_TABLE, Item, ItemMetadata, Key, MaybeItem, NONE,
8    TAG_TABLE, TableStyle,
9};
10use crate::parser::KeyRef;
11use std::mem::size_of;
12use std::ptr::NonNull;
13
14use crate::arena::Arena;
15
16pub(crate) type TableIndex<'de> = foldhash::HashMap<KeyRef<'de>, usize>;
17
18type TableEntry<'de> = (Key<'de>, Item<'de>);
19
20const MIN_CAP: u32 = 2;
21
22/// A TOML table: a flat list of key-value pairs with linear lookup.
23#[repr(C, align(8))]
24pub(crate) struct InnerTable<'de> {
25    pub(super) len: u32,
26    pub(super) cap: u32,
27    pub(super) ptr: NonNull<TableEntry<'de>>,
28}
29
30impl<'de> InnerTable<'de> {
31    /// Creates an empty table.
32    #[inline]
33    pub fn new() -> Self {
34        Self {
35            len: 0,
36            cap: 0,
37            ptr: NonNull::dangling(),
38        }
39    }
40
41    pub(crate) fn with_capacity(cap: u32, arena: &'de Arena) -> Self {
42        let mut table = Self::new();
43        if cap > 0 {
44            table.grow_to(cap, arena);
45        }
46        table
47    }
48
49    /// Inserts a key-value pair. Does **not** check for duplicates.
50    pub fn insert_unique(
51        &mut self,
52        key: Key<'de>,
53        item: Item<'de>,
54        arena: &'de Arena,
55    ) -> &mut TableEntry<'de> {
56        let len = self.len;
57        if self.len == self.cap {
58            self.grow(arena);
59        }
60        // SAFETY: grow() ensures len < cap, so ptr.add(len) is within the
61        // allocation. The write targets uninitialized memory past the current length.
62        unsafe {
63            let ptr = self.ptr.as_ptr().add(len as usize);
64            ptr.write((key, item));
65            self.len = len + 1;
66            &mut (*ptr)
67        }
68    }
69
70    /// Returns the number of entries.
71    #[inline]
72    pub fn len(&self) -> usize {
73        self.len as usize
74    }
75
76    /// Returns `true` if the table has no entries.
77    #[inline]
78    pub fn is_empty(&self) -> bool {
79        self.len == 0
80    }
81    /// Looks up a key using the parser's hash index when the table is large
82    /// enough, falling back to a linear scan for small tables.
83    ///
84    /// Both the table and the index must originate from the same `parse` call
85    /// and neither must have been mutated since parsing.
86    #[cfg(feature = "to-toml")]
87    pub(crate) fn get_entry_with_index(
88        &self,
89        key: &str,
90        index: &TableIndex<'_>,
91    ) -> Option<&TableEntry<'de>> {
92        if self.len() > crate::parser::INDEXED_TABLE_THRESHOLD {
93            // SAFETY: len > INDEXED_TABLE_THRESHOLD (> 6), so the table is non-empty.
94            let first_key_span = unsafe { self.first_key_span_start_unchecked() };
95            let i = *index.get(&KeyRef::new(key, first_key_span))?;
96            self.entries().get(i)
97        } else {
98            for entry in self.entries() {
99                if entry.0.name == key {
100                    return Some(entry);
101                }
102            }
103            None
104        }
105    }
106
107    pub(crate) fn get_entry_with_maybe_index(
108        &self,
109        key: &str,
110        index: Option<&TableIndex<'_>>,
111    ) -> Option<&TableEntry<'de>> {
112        if self.len() > crate::parser::INDEXED_TABLE_THRESHOLD {
113            if let Some(index) = index {
114                // SAFETY: len > INDEXED_TABLE_THRESHOLD (> 6), so the table is non-empty.
115                let first_key_span = unsafe { self.first_key_span_start_unchecked() };
116                let i = *index.get(&KeyRef::new(key, first_key_span))?;
117                return self.entries().get(i);
118            }
119        }
120        for entry in self.entries() {
121            if entry.0.name == key {
122                return Some(entry);
123            }
124        }
125        None
126    }
127    /// Linear scan for a key, returning both key and value references.
128    pub fn get_entry(&self, name: &str) -> Option<(&Key<'de>, &Item<'de>)> {
129        for (key, item) in self.entries() {
130            if key.name == name {
131                return Some((key, item));
132            }
133        }
134        None
135    }
136
137    /// Returns a reference to the value for `name`.
138    pub fn get(&self, name: &str) -> Option<&Item<'de>> {
139        for (key, item) in self.entries() {
140            if key.name == name {
141                return Some(item);
142            }
143        }
144        None
145    }
146
147    /// Returns a mutable reference to the value for `name`.
148    pub fn get_mut(&mut self, name: &str) -> Option<&mut Item<'de>> {
149        for (key, item) in self.entries_mut() {
150            if key.name == name {
151                return Some(item);
152            }
153        }
154        None
155    }
156
157    /// Returns `true` if the table contains the key.
158    #[inline]
159    pub fn contains_key(&self, name: &str) -> bool {
160        self.get(name).is_some()
161    }
162
163    /// Removes the first entry matching `name`, returning the key-value pair.
164    /// Uses swap-remove, so the ordering of remaining entries may change.
165    pub fn remove_entry(&mut self, name: &str) -> Option<(Key<'de>, Item<'de>)> {
166        let idx = self.find_index(name)?;
167        Some(self.remove_at(idx))
168    }
169
170    /// Returns the span start of the first key. Used as a table discriminator
171    /// in the parser's hash index.
172    ///
173    /// # Safety
174    ///
175    /// The table must be non-empty (`self.len > 0`).
176    #[inline]
177    pub(crate) unsafe fn first_key_span_start_unchecked(&self) -> u32 {
178        debug_assert!(self.len > 0);
179        // SAFETY: caller guarantees len > 0, so the first entry is initialized.
180        unsafe { (*self.ptr.as_ptr()).0.span.start }
181    }
182
183    /// Returns a slice of all entries.
184    #[inline]
185    pub fn entries(&self) -> &[TableEntry<'de>] {
186        // SAFETY: ptr points to arena-allocated memory with at least len
187        // initialized entries. When len == 0, ptr is NonNull::dangling() which
188        // satisfies from_raw_parts' alignment requirement for zero-length slices.
189        unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len as usize) }
190    }
191
192    #[inline]
193    pub fn entries_mut(&mut self) -> &mut [TableEntry<'de>] {
194        // SAFETY: same as entries() — ptr is valid for len initialized entries.
195        unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len as usize) }
196    }
197
198    pub(crate) fn find_index(&self, name: &str) -> Option<usize> {
199        for (i, (key, _)) in self.entries().iter().enumerate() {
200            if key.name == name {
201                return Some(i);
202            }
203        }
204        None
205    }
206
207    /// Remove entry at `idx` by swapping it with the last entry.
208    fn remove_at(&mut self, idx: usize) -> (Key<'de>, Item<'de>) {
209        let last = self.len as usize - 1;
210        // SAFETY: idx was returned by find_index, so idx < len and the
211        // pointer is within initialized entries. read() moves the value out.
212        let ptr = unsafe { self.ptr.as_ptr().add(idx) };
213        let entry = unsafe { ptr.read() };
214        if idx != last {
215            // Safety: `last` is a valid, initialized index distinct from `idx`.
216            unsafe {
217                ptr.write(self.ptr.as_ptr().add(last).read());
218            }
219        }
220        self.len -= 1;
221        entry
222    }
223
224    #[cold]
225    fn grow(&mut self, arena: &'de Arena) {
226        let new_cap = if self.cap == 0 {
227            MIN_CAP
228        } else {
229            self.cap.checked_mul(2).expect("capacity overflow")
230        };
231        self.grow_to(new_cap, arena);
232    }
233
234    fn grow_to(&mut self, new_cap: u32, arena: &'de Arena) {
235        // On 64-bit, u32 * size_of::<TableEntry>() cannot overflow usize.
236        #[cfg(target_pointer_width = "32")]
237        let new_size = (new_cap as usize)
238            .checked_mul(size_of::<TableEntry<'_>>())
239            .expect("capacity overflow");
240        #[cfg(not(target_pointer_width = "32"))]
241        let new_size = new_cap as usize * size_of::<TableEntry<'_>>();
242        if self.cap > 0 {
243            let old_size = self.cap as usize * size_of::<TableEntry<'_>>();
244            // Safety: ptr was returned by a prior arena alloc of old_size bytes.
245            self.ptr = unsafe { arena.realloc(self.ptr.cast(), old_size, new_size).cast() };
246        } else {
247            self.ptr = arena.alloc(new_size).cast();
248        }
249        self.cap = new_cap;
250    }
251
252    /// Deep-clones this table into `arena`. Keys and strings are shared
253    /// with the source.
254    pub(crate) fn clone_in(&self, arena: &'de Arena) -> Self {
255        let len = self.len as usize;
256        if len == 0 {
257            return Self::new();
258        }
259        let size = len * size_of::<TableEntry<'de>>();
260        let dst: NonNull<TableEntry<'de>> = arena.alloc(size).cast();
261        let src = self.ptr.as_ptr();
262        let dst_ptr = dst.as_ptr();
263
264        let mut run_start = 0;
265        for i in 0..len {
266            // SAFETY: i < len, so src.add(i) is within initialized entries.
267            if unsafe { !(*src.add(i)).1.is_scalar() } {
268                if run_start < i {
269                    // SAFETY: entries[run_start..i] have scalar values — Key is
270                    // Copy, scalar Items are bitwise-copyable. Source and
271                    // destination are disjoint arena regions.
272                    unsafe {
273                        std::ptr::copy_nonoverlapping(
274                            src.add(run_start),
275                            dst_ptr.add(run_start),
276                            i - run_start,
277                        );
278                    }
279                }
280                // SAFETY: the entry's value is an aggregate; deep-clone it.
281                // Key is Copy.
282                unsafe {
283                    let (key, item) = &*src.add(i);
284                    dst_ptr.add(i).write((*key, item.clone_in(arena)));
285                }
286                run_start = i + 1;
287            }
288        }
289        if run_start < len {
290            unsafe {
291                std::ptr::copy_nonoverlapping(
292                    src.add(run_start),
293                    dst_ptr.add(run_start),
294                    len - run_start,
295                );
296            }
297        }
298
299        Self {
300            len: self.len,
301            cap: self.len,
302            ptr: dst,
303        }
304    }
305}
306
307impl std::fmt::Debug for InnerTable<'_> {
308    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309        let mut map = f.debug_map();
310        for (k, v) in self.entries() {
311            map.entry(k, v);
312        }
313        map.finish()
314    }
315}
316
317/// Consuming iterator over a [`Table`], yielding `(`[`Key`]`, `[`Item`]`)` pairs.
318pub struct IntoIter<'de> {
319    table: InnerTable<'de>,
320    index: u32,
321}
322
323impl<'de> Iterator for IntoIter<'de> {
324    type Item = (Key<'de>, Item<'de>);
325
326    fn next(&mut self) -> Option<Self::Item> {
327        if self.index < self.table.len {
328            // SAFETY: index < len is checked above, so the read is within
329            // bounds of initialized entries.
330            let entry = unsafe { self.table.ptr.as_ptr().add(self.index as usize).read() };
331            self.index += 1;
332            Some(entry)
333        } else {
334            None
335        }
336    }
337
338    fn size_hint(&self) -> (usize, Option<usize>) {
339        let remaining = (self.table.len - self.index) as usize;
340        (remaining, Some(remaining))
341    }
342}
343
344impl ExactSizeIterator for IntoIter<'_> {}
345
346/// A TOML table, a transient collection of key/value pairs inside an [`Item`].
347///
348/// `Table` is an intermediate structure for converting between Rust types and
349/// TOML through [`FromToml`] and [`ToToml`]. It is the top level value
350/// returned by [`parse`](crate::parse) and the value inside any `[section]`
351/// or inline `{ ... }` table.
352///
353/// <div class="warning">
354///
355/// Entries are stored in insertion order, but the TOML specification defines
356/// table keys as unordered. Avoid relying on iteration order for semantic
357/// purposes.
358///
359/// </div>
360///
361/// # Accessing values
362///
363/// Use `table["key"]` for index access, which returns a [`MaybeItem`] that
364/// never panics on missing keys and supports chained indexing. Use
365/// [`get`](Self::get) or [`get_mut`](Self::get_mut) for `Option` based access.
366///
367/// For structured extraction, use [`Item::table_helper`](crate::Item::table_helper)
368/// to create a [`TableHelper`](crate::de::TableHelper).
369///
370/// # Lookup performance
371///
372/// Direct lookups ([`get`](Self::get), `table["key"]`) perform a linear scan
373/// over entries, O(n) in the number of keys. For small tables or a handful
374/// of lookups, as is typical in TOML, this is fast enough.
375///
376/// For structured conversion of larger tables, use
377/// [`TableHelper`](crate::de::TableHelper) via
378/// [`Document::table_helper`](crate::Document::table_helper) or
379/// [`Item::table_helper`](crate::Item::table_helper), which use the
380/// parser's hash index for O(1) lookups.
381///
382/// # Constructing tables
383///
384/// To build a table programmatically, create one with [`Table::new`] and
385/// insert entries with [`Table::insert`]. An [`Arena`](crate::Arena) is
386/// required because entries are arena allocated.
387///
388/// # Iteration
389///
390/// `Table` implements [`IntoIterator`] (both by reference and by value),
391/// yielding `(`[`Key`]`, `[`Item`]`)` pairs.
392///
393/// [`FromToml`]: crate::FromToml
394/// [`ToToml`]: crate::ToToml
395#[repr(C)]
396pub struct Table<'de> {
397    pub(crate) value: InnerTable<'de>,
398    pub(crate) meta: ItemMetadata,
399}
400
401impl<'de> Table<'de> {
402    /// Creates an empty table in format-hints mode (no source span).
403    ///
404    /// The table starts with automatic style: normalization will choose
405    /// between inline and header based on content. Call [`set_style`](Self::set_style)
406    /// to override.
407    pub fn new() -> Table<'de> {
408        let mut meta = ItemMetadata::hints(TAG_TABLE, FLAG_TABLE);
409        meta.set_auto_style();
410        Table {
411            meta,
412            value: InnerTable::new(),
413        }
414    }
415
416    /// Creates an empty table with pre-allocated capacity.
417    ///
418    /// Returns `None` if `cap` exceeds `u32::MAX`.
419    pub fn try_with_capacity(cap: usize, arena: &'de Arena) -> Option<Table<'de>> {
420        let cap: u32 = cap.try_into().ok()?;
421        let mut meta = ItemMetadata::hints(TAG_TABLE, FLAG_TABLE);
422        meta.set_auto_style();
423        Some(Table {
424            meta,
425            value: InnerTable::with_capacity(cap, arena),
426        })
427    }
428
429    /// Creates an empty table in span mode (parser-produced).
430    pub(crate) fn new_spanned(span: Span) -> Table<'de> {
431        Table {
432            meta: ItemMetadata::spanned(TAG_TABLE, FLAG_TABLE, span.start, span.end),
433            value: InnerTable::new(),
434        }
435    }
436
437    /// Returns the source span, or `0..0` if this table was constructed
438    /// programmatically (format-hints mode).
439    pub fn span(&self) -> Span {
440        self.meta.span()
441    }
442}
443
444impl<'de> Default for Table<'de> {
445    fn default() -> Self {
446        Self::new()
447    }
448}
449
450impl std::fmt::Debug for Table<'_> {
451    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
452        self.value.fmt(f)
453    }
454}
455
456impl<'de> Table<'de> {
457    /// Inserts a key-value pair, replacing any existing entry with the same key.
458    ///
459    /// Performs an O(n) linear scan for duplicates. Prefer [`Table::insert_unique`]
460    /// when the key is known to be absent.
461    pub fn insert(&mut self, key: Key<'de>, value: Item<'de>, arena: &'de Arena) {
462        if let Some(existing) = self.get_mut(key.name) {
463            *existing = value;
464            return;
465        }
466        self.value.insert_unique(key, value, arena);
467    }
468
469    /// Inserts a key-value pair without checking for duplicates.
470    ///
471    /// Inserting a duplicate key is sound but the behavior is unspecified. It may
472    /// panic, produce invalid TOML on emit, or cause deserialization errors.
473    pub fn insert_unique(&mut self, key: Key<'de>, value: Item<'de>, arena: &'de Arena) {
474        self.value.insert_unique(key, value, arena);
475    }
476    /// Returns the number of entries.
477    #[inline]
478    pub fn len(&self) -> usize {
479        self.value.len()
480    }
481
482    /// Returns `true` if the table has no entries.
483    #[inline]
484    pub fn is_empty(&self) -> bool {
485        self.value.is_empty()
486    }
487
488    /// Linear scan for a key, returning both key and value references.
489    pub fn get_key_value(&self, name: &str) -> Option<(&Key<'de>, &Item<'de>)> {
490        self.value.get_entry(name)
491    }
492
493    /// Returns a reference to the value for `name`.
494    pub fn get(&self, name: &str) -> Option<&Item<'de>> {
495        self.value.get(name)
496    }
497
498    /// Returns a mutable reference to the value for `name`.
499    pub fn get_mut(&mut self, name: &str) -> Option<&mut Item<'de>> {
500        self.value.get_mut(name)
501    }
502
503    /// Returns `true` if the table contains the key.
504    #[inline]
505    pub fn contains_key(&self, name: &str) -> bool {
506        self.value.contains_key(name)
507    }
508
509    /// Removes the first entry matching `name`, returning the key-value pair.
510    /// Uses swap-remove, so the ordering of remaining entries may change.
511    pub fn remove_entry(&mut self, name: &str) -> Option<(Key<'de>, Item<'de>)> {
512        self.value.remove_entry(name)
513    }
514
515    /// Returns a slice of all entries.
516    #[inline]
517    pub fn entries(&self) -> &[TableEntry<'de>] {
518        self.value.entries()
519    }
520    /// Returns a slice of all entries.
521    #[inline]
522    pub fn entries_mut(&mut self) -> &mut [TableEntry<'de>] {
523        self.value.entries_mut()
524    }
525
526    /// Returns an iterator over all entries (key-value pairs).
527    #[inline]
528    pub fn iter(&self) -> std::slice::Iter<'_, TableEntry<'de>> {
529        self.entries().iter()
530    }
531
532    /// Converts this `Table` into an [`Item`] with the same span and payload.
533    pub fn as_item(&self) -> &Item<'de> {
534        // SAFETY: Table is #[repr(C)] { InnerTable, ItemMetadata }.
535        // Item  is #[repr(C)] { Payload,    ItemMetadata }.
536        // Payload is a union whose `table` field is ManuallyDrop<InnerTable>
537        // (#[repr(transparent)]). Both types are 24 bytes, align 8 (verified
538        // by const assertions). The field offsets match (data at 0..16,
539        // metadata at 16..24). Only a shared reference is returned, so no
540        // mutation can change the tag.
541        unsafe { &*(self as *const Table<'de>).cast::<Item<'de>>() }
542    }
543
544    /// Converts this `Table` into an [`Item`] with the same span and payload.
545    pub fn into_item(self) -> Item<'de> {
546        // SAFETY: Same layout argument as as_item(). Size and alignment
547        // equality verified by const assertions below. The tag in
548        // ItemMetadata is preserved unchanged through the transmute.
549        unsafe { std::mem::transmute(self) }
550    }
551}
552
553impl<'a, 'de> IntoIterator for &'a mut Table<'de> {
554    type Item = &'a mut (Key<'de>, Item<'de>);
555    type IntoIter = std::slice::IterMut<'a, TableEntry<'de>>;
556
557    fn into_iter(self) -> Self::IntoIter {
558        self.value.entries_mut().iter_mut()
559    }
560}
561impl<'a, 'de> IntoIterator for &'a Table<'de> {
562    type Item = &'a (Key<'de>, Item<'de>);
563    type IntoIter = std::slice::Iter<'a, TableEntry<'de>>;
564
565    fn into_iter(self) -> Self::IntoIter {
566        self.value.entries().iter()
567    }
568}
569
570impl<'de> IntoIterator for Table<'de> {
571    type Item = (Key<'de>, Item<'de>);
572    type IntoIter = IntoIter<'de>;
573
574    fn into_iter(self) -> Self::IntoIter {
575        IntoIter {
576            table: self.value,
577            index: 0,
578        }
579    }
580}
581
582const _: () = assert!(std::mem::size_of::<Table<'_>>() == std::mem::size_of::<Item<'_>>());
583const _: () = assert!(std::mem::align_of::<Table<'_>>() == std::mem::align_of::<Item<'_>>());
584
585impl<'de> Table<'de> {
586    #[inline]
587    pub(crate) fn span_start(&self) -> u32 {
588        self.meta.span_start()
589    }
590
591    #[inline]
592    pub(crate) fn set_span_start(&mut self, v: u32) {
593        self.meta.set_span_start(v);
594    }
595
596    #[inline]
597    pub(crate) fn set_span_end(&mut self, v: u32) {
598        self.meta.set_span_end(v);
599    }
600
601    #[inline]
602    pub(crate) fn extend_span_end(&mut self, new_end: u32) {
603        self.meta.extend_span_end(new_end);
604    }
605
606    #[inline]
607    pub(crate) fn set_header_flag(&mut self) {
608        self.meta.set_flag(FLAG_HEADER);
609    }
610
611    #[inline]
612    pub(crate) fn set_dotted_flag(&mut self) {
613        self.meta.set_flag(FLAG_DOTTED);
614    }
615
616    /// Returns the kind of this table (implicit, dotted, header, or inline).
617    #[inline]
618    pub fn style(&self) -> TableStyle {
619        match self.meta.flag() {
620            FLAG_DOTTED => TableStyle::Dotted,
621            FLAG_HEADER => TableStyle::Header,
622            FLAG_FROZEN => TableStyle::Inline,
623            _ => TableStyle::Implicit,
624        }
625    }
626
627    /// Sets the kind of this table, clearing automatic style resolution.
628    #[inline]
629    pub fn set_style(&mut self, kind: TableStyle) {
630        let flag = match kind {
631            TableStyle::Implicit => FLAG_TABLE,
632            TableStyle::Dotted => FLAG_DOTTED,
633            TableStyle::Header => FLAG_HEADER,
634            TableStyle::Inline => FLAG_FROZEN,
635        };
636        self.meta.set_flag(flag);
637        self.meta.clear_auto_style();
638    }
639
640    /// Deep-clones this table into `arena`. Keys and strings are shared
641    /// with the source.
642    pub fn clone_in(&self, arena: &'de Arena) -> Table<'de> {
643        Table {
644            value: self.value.clone_in(arena),
645            meta: self.meta,
646        }
647    }
648}
649
650impl<'de> std::ops::Index<&str> for Table<'de> {
651    type Output = MaybeItem<'de>;
652
653    #[inline]
654    fn index(&self, index: &str) -> &Self::Output {
655        if let Some(item) = self.get(index) {
656            return MaybeItem::from_ref(item);
657        }
658        &NONE
659    }
660}