Skip to main content

toml_spanner/
item.rs

1#![allow(clippy::manual_map)]
2#[cfg(test)]
3#[path = "./value_tests.rs"]
4mod tests;
5
6pub(crate) mod array;
7pub(crate) mod owned;
8pub(crate) mod table;
9#[cfg(feature = "to-toml")]
10mod to_toml;
11use crate::arena::Arena;
12use crate::error::{Error, ErrorKind};
13use crate::item::table::TableIndex;
14use crate::{DateTime, Span, Table};
15use std::fmt;
16use std::mem::ManuallyDrop;
17
18pub use array::Array;
19pub(crate) use array::InternalArray;
20use table::InnerTable;
21
22pub(crate) const TAG_MASK: u32 = 0x7;
23pub(crate) const TAG_SHIFT: u32 = 3;
24
25pub(crate) const TAG_STRING: u32 = 0;
26pub(crate) const TAG_INTEGER: u32 = 1;
27pub(crate) const TAG_FLOAT: u32 = 2;
28pub(crate) const TAG_BOOLEAN: u32 = 3;
29pub(crate) const TAG_DATETIME: u32 = 4;
30pub(crate) const TAG_TABLE: u32 = 5;
31pub(crate) const TAG_ARRAY: u32 = 6;
32
33// Only set in maybe item
34pub(crate) const TAG_NONE: u32 = 7;
35
36/// 3-bit state field in `end_and_flag` encoding container kind and sub-state.
37/// Bit 2 set → table, bits 1:0 == 01 → array. Allows dispatch without
38/// reading `start_and_tag`.
39pub(crate) const FLAG_MASK: u32 = 0x7;
40pub(crate) const FLAG_SHIFT: u32 = 3;
41
42pub(crate) const FLAG_NONE: u32 = 0;
43pub(crate) const FLAG_ARRAY: u32 = 2;
44pub(crate) const FLAG_AOT: u32 = 3;
45pub(crate) const FLAG_TABLE: u32 = 4;
46pub(crate) const FLAG_DOTTED: u32 = 5;
47pub(crate) const FLAG_HEADER: u32 = 6;
48pub(crate) const FLAG_FROZEN: u32 = 7;
49
50/// Bit 31 of `end_and_flag`: when set, the metadata is in format-hints mode
51/// (constructed programmatically); when clear, it is in span mode (from parser).
52pub(crate) const HINTS_BIT: u32 = 1 << 31;
53/// Bit 26 of `end_and_flag`: when set in hints mode, defers style decisions
54/// to normalization time. Resolved based on content heuristics.
55pub(crate) const AUTO_STYLE_BIT: u32 = 1 << 26;
56/// Value bits (above TAG_SHIFT) all set = "not projected".
57const NOT_PROJECTED: u32 = !(TAG_MASK); // 0xFFFF_FFF8
58
59/// Packed 8-byte metadata for `Item`, `Table`, and `Array`.
60///
61/// Two variants discriminated by bit 31 of `end_and_flag`:
62///
63/// **Span variant** (bit 31 = 0): items produced by the parser.
64/// - `start_and_tag`: bits 0-2 = tag, bits 3-30 = span start (28 bits, max 256 MiB)
65/// - `end_and_flag`: bits 0-2 = flag, bits 3-30 = span end (28 bits), bit 31 = 0
66///
67/// **Format hints variant** (bit 31 = 1): items constructed programmatically.
68/// - `start_and_tag`: bits 0-2 = tag, bits 3-31 = projected index (all 1's = not projected)
69/// - `end_and_flag`: bit 31 = 1, bits 0-2 = flag, bits 3-30 = format hint bits
70#[derive(Copy, Clone)]
71#[repr(C)]
72pub struct ItemMetadata {
73    pub(crate) start_and_tag: u32,
74    pub(crate) end_and_flag: u32,
75}
76
77impl ItemMetadata {
78    /// Creates metadata in span mode (parser-produced items).
79    #[inline]
80    pub(crate) fn spanned(tag: u32, flag: u32, start: u32, end: u32) -> Self {
81        Self {
82            start_and_tag: (start << TAG_SHIFT) | tag,
83            end_and_flag: (end << FLAG_SHIFT) | flag,
84        }
85    }
86
87    /// Creates metadata in format-hints mode (programmatically constructed items).
88    #[inline]
89    pub(crate) fn hints(tag: u32, flag: u32) -> Self {
90        Self {
91            start_and_tag: NOT_PROJECTED | tag,
92            end_and_flag: HINTS_BIT | flag,
93        }
94    }
95
96    #[inline]
97    pub(crate) fn tag(&self) -> u32 {
98        self.start_and_tag & TAG_MASK
99    }
100
101    #[inline]
102    pub(crate) fn flag(&self) -> u32 {
103        self.end_and_flag & FLAG_MASK
104    }
105
106    #[inline]
107    pub(crate) fn set_flag(&mut self, flag: u32) {
108        self.end_and_flag = (self.end_and_flag & !FLAG_MASK) | flag;
109    }
110
111    #[inline]
112    pub(crate) fn set_auto_style(&mut self) {
113        self.end_and_flag |= AUTO_STYLE_BIT;
114    }
115
116    #[inline]
117    #[allow(dead_code)]
118    pub(crate) fn is_auto_style(&self) -> bool {
119        self.end_and_flag & (HINTS_BIT | AUTO_STYLE_BIT) == (HINTS_BIT | AUTO_STYLE_BIT)
120    }
121
122    #[inline]
123    pub(crate) fn clear_auto_style(&mut self) {
124        self.end_and_flag &= !AUTO_STYLE_BIT;
125    }
126
127    /// Returns `true` if this metadata carries a source span (parser-produced).
128    #[inline]
129    pub(crate) fn is_span_mode(&self) -> bool {
130        (self.end_and_flag as i32) >= 0
131    }
132
133    /// Returns the source span, or `0..0` if in format-hints mode.
134    #[inline]
135    pub fn span(&self) -> Span {
136        if (self.end_and_flag as i32) >= 0 {
137            self.span_unchecked()
138        } else {
139            Span { start: 0, end: 0 }
140        }
141    }
142
143    /// Returns the source span without checking the variant.
144    /// Valid only during deserialization on parser-produced items.
145    /// In span mode, bit 31 is always 0, so all bits above FLAG_SHIFT are span data.
146    #[inline]
147    pub(crate) fn span_unchecked(&self) -> Span {
148        debug_assert!(self.is_span_mode());
149        Span::new(
150            self.start_and_tag >> TAG_SHIFT,
151            self.end_and_flag >> FLAG_SHIFT,
152        )
153    }
154
155    #[inline]
156    pub(crate) fn span_start(&self) -> u32 {
157        debug_assert!(self.is_span_mode());
158        self.start_and_tag >> TAG_SHIFT
159    }
160
161    #[inline]
162    pub(crate) fn set_span_start(&mut self, v: u32) {
163        debug_assert!(self.is_span_mode());
164        self.start_and_tag = (v << TAG_SHIFT) | (self.start_and_tag & TAG_MASK);
165    }
166
167    #[inline]
168    pub(crate) fn set_span_end(&mut self, v: u32) {
169        debug_assert!(self.is_span_mode());
170        self.end_and_flag = (v << FLAG_SHIFT) | (self.end_and_flag & FLAG_MASK);
171    }
172
173    #[inline]
174    pub(crate) fn extend_span_end(&mut self, new_end: u32) {
175        debug_assert!(self.is_span_mode());
176        let old = self.end_and_flag;
177        let current = old >> FLAG_SHIFT;
178        self.end_and_flag = (current.max(new_end) << FLAG_SHIFT) | (old & FLAG_MASK);
179    }
180}
181
182/// How a TOML table is spelled in source text.
183///
184/// The same logical table can be written in four surface forms. Parsing
185/// records which form produced each table, and emit consults it when
186/// rendering. Read the current style with [`Table::style`] and pin a new
187/// choice with [`Table::set_style`].
188///
189/// ```toml
190/// [section]            # Header
191/// key = 1
192///
193/// inline = { x = 1 }   # Inline
194///
195/// a.b.c = 1            # `a.b` is Dotted, `a` is Implicit
196/// ```
197///
198/// [`Table::style`]: crate::Table::style
199/// [`Table::set_style`]: crate::Table::set_style
200#[derive(Clone, Copy, Debug, PartialEq, Eq)]
201pub enum TableStyle {
202    /// An intermediate parent the user never spelled out.
203    ///
204    /// Writing `[a.b]` or `a.b = 1` brings `a` into existence even though
205    /// the path only names `b`. Such parents are `Implicit`.
206    Implicit,
207    /// A table defined by a dotted key path, for example `a.b = 1`.
208    Dotted,
209    /// A table defined by an explicit `[section]` header.
210    Header,
211    /// A sealed inline table written with braces, such as `{ x = 1, y = 2 }`.
212    Inline,
213}
214
215/// How a TOML array is spelled in source text.
216///
217/// TOML distinguishes a literal array from an array of tables built with
218/// repeated `[[section]]` headers. Read the current style with
219/// [`Array::style`] and pin a new choice with [`Array::set_style`].
220///
221/// ```toml
222/// inline = [1, 2, 3]   # Inline
223///
224/// [[items]]            # Header (array of tables)
225/// name = "first"
226/// [[items]]
227/// name = "second"
228/// ```
229///
230/// [`Array::style`]: crate::Array::style
231/// [`Array::set_style`]: crate::Array::set_style
232#[derive(Clone, Copy, Debug, PartialEq, Eq)]
233pub enum ArrayStyle {
234    /// A literal array written with brackets, such as `[1, 2, 3]`.
235    Inline,
236    /// An array of tables written with repeated `[[section]]` headers.
237    Header,
238}
239
240#[repr(C, packed)]
241#[derive(Clone, Copy)]
242struct PackedI128 {
243    value: i128,
244}
245
246/// A TOML integer value.
247///
248/// This is a storage type that supports the full range of `i128`.
249/// Convert to a primitive with [`as_i128`](Self::as_i128),
250/// [`as_i64`](Self::as_i64), or [`as_u64`](Self::as_u64), perform
251/// your arithmetic there, then convert back with `From`.
252#[repr(align(8))]
253#[derive(Clone, Copy)]
254pub struct Integer {
255    value: PackedI128,
256}
257
258impl Integer {
259    /// Returns the value as an `i128`.
260    #[inline]
261    pub fn as_i128(&self) -> i128 {
262        let copy = *self;
263        copy.value.value
264    }
265
266    /// Returns the value as an `f64`, which may be lossy for large integers.
267    #[inline]
268    pub fn as_f64(&self) -> f64 {
269        let copy = *self;
270        copy.value.value as f64
271    }
272
273    /// Returns the value as an `i64`, or [`None`] if it does not fit.
274    #[inline]
275    pub fn as_i64(&self) -> Option<i64> {
276        i64::try_from(self.as_i128()).ok()
277    }
278
279    /// Returns the value as a `u64`, or [`None`] if it does not fit.
280    #[inline]
281    pub fn as_u64(&self) -> Option<u64> {
282        u64::try_from(self.as_i128()).ok()
283    }
284}
285
286impl From<i128> for Integer {
287    #[inline]
288    fn from(v: i128) -> Self {
289        Self {
290            value: PackedI128 { value: v },
291        }
292    }
293}
294
295impl From<i64> for Integer {
296    #[inline]
297    fn from(v: i64) -> Self {
298        Self::from(v as i128)
299    }
300}
301
302impl From<u64> for Integer {
303    #[inline]
304    fn from(v: u64) -> Self {
305        Self::from(v as i128)
306    }
307}
308
309impl From<i32> for Integer {
310    #[inline]
311    fn from(v: i32) -> Self {
312        Self::from(v as i128)
313    }
314}
315
316impl From<u32> for Integer {
317    #[inline]
318    fn from(v: u32) -> Self {
319        Self::from(v as i128)
320    }
321}
322
323impl PartialEq for Integer {
324    #[inline]
325    fn eq(&self, other: &Self) -> bool {
326        self.as_i128() == other.as_i128()
327    }
328}
329
330impl Eq for Integer {}
331
332impl fmt::Debug for Integer {
333    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334        self.as_i128().fmt(f)
335    }
336}
337
338impl fmt::Display for Integer {
339    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340        self.as_i128().fmt(f)
341    }
342}
343
344#[repr(C, align(8))]
345union Payload<'de> {
346    string: &'de str,
347    integer: Integer,
348    float: f64,
349    boolean: bool,
350    array: ManuallyDrop<InternalArray<'de>>,
351    table: ManuallyDrop<InnerTable<'de>>,
352    datetime: DateTime,
353}
354
355/// A parsed TOML value with span information.
356///
357/// Extract values with the `as_*` methods ([`as_str`](Self::as_str),
358/// [`as_i64`](Self::as_i64), [`as_table`](Self::as_table), etc.) or
359/// pattern match via [`value`](Self::value) and [`value_mut`](Self::value_mut).
360///
361/// Items support indexing with `&str` (table lookup) and `usize` (array
362/// access). These operators return [`MaybeItem`] and never panic. Missing
363/// keys or out-of-bounds indices produce a `None` variant instead.
364///
365/// # Lookup performance
366///
367/// String-key lookups (`item["key"]`, [`as_table`](Self::as_table) +
368/// [`Table::get`]) perform a linear scan over the table entries, O(n) in
369/// the number of keys. For small tables or a handful of lookups, as is
370/// typical in TOML, this is fast enough.
371///
372/// For structured conversion of larger tables, use
373/// [`TableHelper`](crate::de::TableHelper) with the [`Context`](crate::de::Context)
374/// from [`parse`](crate::parse), which internally uses an index for O(1) lookups.
375///
376/// # Examples
377///
378/// ```
379/// let arena = toml_spanner::Arena::new();
380/// let table = toml_spanner::parse("x = 42", &arena).unwrap();
381/// assert_eq!(table["x"].as_i64(), Some(42));
382/// assert_eq!(table["missing"].as_i64(), None);
383/// ```
384#[repr(C)]
385pub struct Item<'de> {
386    payload: Payload<'de>,
387    pub(crate) meta: ItemMetadata,
388}
389
390const _: () = assert!(std::mem::size_of::<Item<'_>>() == 24);
391const _: () = assert!(std::mem::align_of::<Item<'_>>() == 8);
392
393// SAFETY: `Item` is an owned 24-byte value. Its payload union holds only
394// plain data (integers, floats, bools, `DateTime`) or arena-backed handles
395// (`&'de str`, `InnerTable`, `InternalArray`) whose backing memory is itself
396// arena-allocated. The crate never applies interior mutability to parsed
397// items: every mutation goes through `&mut Item`, `&mut Table`, or
398// `&mut Array`, so concurrent readers behind `&Item` cannot observe a write
399// in progress. The `NonNull` fields inside `InnerTable`/`InternalArray` that
400// block auto-derivation are really just pointers into arena storage that
401// follows the same discipline; they carry no synchronization semantics of
402// their own. Sending an `Item<'de>` across threads is sound because the
403// borrow checker still requires the backing `'de` data (the source string
404// and the `Arena`) to remain valid on the receiving thread, and `Arena` is
405// already `Send`.
406unsafe impl Send for Item<'_> {}
407// SAFETY: See the `Send` impl above. Because `Item` exposes no interior
408// mutability through `&Item`, sharing `&Item` across threads only lets each
409// thread read bits that never change, which is data-race-free.
410unsafe impl Sync for Item<'_> {}
411
412impl<'de> From<i64> for Item<'de> {
413    fn from(value: i64) -> Self {
414        Self::raw_hints(
415            TAG_INTEGER,
416            FLAG_NONE,
417            Payload {
418                integer: Integer::from(value),
419            },
420        )
421    }
422}
423impl<'de> From<i128> for Item<'de> {
424    fn from(value: i128) -> Self {
425        Self::raw_hints(
426            TAG_INTEGER,
427            FLAG_NONE,
428            Payload {
429                integer: Integer::from(value),
430            },
431        )
432    }
433}
434impl<'de> From<i32> for Item<'de> {
435    fn from(value: i32) -> Self {
436        Self::from(value as i64)
437    }
438}
439impl<'de> From<&'de str> for Item<'de> {
440    fn from(value: &'de str) -> Self {
441        Self::raw_hints(TAG_STRING, FLAG_NONE, Payload { string: value })
442    }
443}
444
445impl<'de> From<f64> for Item<'de> {
446    fn from(value: f64) -> Self {
447        Self::raw_hints(TAG_FLOAT, FLAG_NONE, Payload { float: value })
448    }
449}
450
451impl<'de> From<bool> for Item<'de> {
452    fn from(value: bool) -> Self {
453        Self::raw_hints(TAG_BOOLEAN, FLAG_NONE, Payload { boolean: value })
454    }
455}
456
457impl<'de> From<DateTime> for Item<'de> {
458    fn from(value: DateTime) -> Self {
459        Self::raw_hints(TAG_DATETIME, FLAG_NONE, Payload { datetime: value })
460    }
461}
462
463impl<'de> Item<'de> {
464    #[inline]
465    fn raw(tag: u32, flag: u32, start: u32, end: u32, payload: Payload<'de>) -> Self {
466        Self {
467            meta: ItemMetadata::spanned(tag, flag, start, end),
468            payload,
469        }
470    }
471
472    #[inline]
473    fn raw_hints(tag: u32, flag: u32, payload: Payload<'de>) -> Self {
474        Self {
475            meta: ItemMetadata::hints(tag, flag),
476            payload,
477        }
478    }
479
480    /// Creates a string [`Item`] in format-hints mode (no source span).
481    #[inline]
482    pub fn string(s: &'de str) -> Self {
483        Self::raw_hints(TAG_STRING, FLAG_NONE, Payload { string: s })
484    }
485
486    #[inline]
487    pub(crate) fn string_spanned(s: &'de str, span: Span) -> Self {
488        Self::raw(
489            TAG_STRING,
490            FLAG_NONE,
491            span.start,
492            span.end,
493            Payload { string: s },
494        )
495    }
496
497    #[inline]
498    pub(crate) fn integer_spanned(i: i128, span: Span) -> Self {
499        Self::raw(
500            TAG_INTEGER,
501            FLAG_NONE,
502            span.start,
503            span.end,
504            Payload {
505                integer: Integer::from(i),
506            },
507        )
508    }
509
510    #[inline]
511    pub(crate) fn float_spanned(f: f64, span: Span) -> Self {
512        Self::raw(
513            TAG_FLOAT,
514            FLAG_NONE,
515            span.start,
516            span.end,
517            Payload { float: f },
518        )
519    }
520
521    #[inline]
522    pub(crate) fn boolean(b: bool, span: Span) -> Self {
523        Self::raw(
524            TAG_BOOLEAN,
525            FLAG_NONE,
526            span.start,
527            span.end,
528            Payload { boolean: b },
529        )
530    }
531
532    #[inline]
533    pub(crate) fn array(a: InternalArray<'de>, span: Span) -> Self {
534        Self::raw(
535            TAG_ARRAY,
536            FLAG_ARRAY,
537            span.start,
538            span.end,
539            Payload {
540                array: ManuallyDrop::new(a),
541            },
542        )
543    }
544
545    #[inline]
546    pub(crate) fn table(t: InnerTable<'de>, span: Span) -> Self {
547        Self::raw(
548            TAG_TABLE,
549            FLAG_TABLE,
550            span.start,
551            span.end,
552            Payload {
553                table: ManuallyDrop::new(t),
554            },
555        )
556    }
557
558    /// Creates an array-of-tables value.
559    #[inline]
560    pub(crate) fn array_aot(a: InternalArray<'de>, span: Span) -> Self {
561        Self::raw(
562            TAG_ARRAY,
563            FLAG_AOT,
564            span.start,
565            span.end,
566            Payload {
567                array: ManuallyDrop::new(a),
568            },
569        )
570    }
571
572    /// Creates a frozen (inline) table value.
573    #[inline]
574    pub(crate) fn table_frozen(t: InnerTable<'de>, span: Span) -> Self {
575        Self::raw(
576            TAG_TABLE,
577            FLAG_FROZEN,
578            span.start,
579            span.end,
580            Payload {
581                table: ManuallyDrop::new(t),
582            },
583        )
584    }
585
586    /// Creates a table with HEADER state (explicitly opened by `[header]`).
587    #[inline]
588    pub(crate) fn table_header(t: InnerTable<'de>, span: Span) -> Self {
589        Self::raw(
590            TAG_TABLE,
591            FLAG_HEADER,
592            span.start,
593            span.end,
594            Payload {
595                table: ManuallyDrop::new(t),
596            },
597        )
598    }
599
600    /// Creates a table with DOTTED state (created by dotted-key navigation).
601    #[inline]
602    pub(crate) fn table_dotted(t: InnerTable<'de>, span: Span) -> Self {
603        Self::raw(
604            TAG_TABLE,
605            FLAG_DOTTED,
606            span.start,
607            span.end,
608            Payload {
609                table: ManuallyDrop::new(t),
610            },
611        )
612    }
613
614    #[inline]
615    pub(crate) fn moment(m: DateTime, span: Span) -> Self {
616        Self::raw(
617            TAG_DATETIME,
618            FLAG_NONE,
619            span.start,
620            span.end,
621            Payload { datetime: m },
622        )
623    }
624}
625/// Discriminant for the TOML value types stored in an [`Item`].
626///
627/// Obtained via [`Item::kind`].
628#[derive(Clone, Copy, PartialEq, Eq)]
629#[repr(u8)]
630#[allow(unused)]
631pub enum Kind {
632    String = 0,
633    Integer = 1,
634    Float = 2,
635    Boolean = 3,
636    DateTime = 4,
637    Table = 5,
638    Array = 6,
639}
640
641impl std::fmt::Debug for Kind {
642    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
643        self.as_str().fmt(f)
644    }
645}
646
647impl std::fmt::Display for Kind {
648    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
649        self.as_str().fmt(f)
650    }
651}
652
653impl Kind {
654    /// Returns the TOML type name as a lowercase string (e.g. `"string"`, `"table"`).
655    pub fn as_str(&self) -> &'static str {
656        match self {
657            Kind::String => "string",
658            Kind::Integer => "integer",
659            Kind::Float => "float",
660            Kind::Boolean => "boolean",
661            Kind::Array => "array",
662            Kind::Table => "table",
663            Kind::DateTime => "datetime",
664        }
665    }
666}
667
668impl<'de> Item<'de> {
669    /// Returns the type discriminant of this value.
670    #[inline]
671    pub fn kind(&self) -> Kind {
672        debug_assert!((self.meta.start_and_tag & TAG_MASK) as u8 <= Kind::Array as u8);
673        // SAFETY: Kind is #[repr(u8)] with discriminants 0..=6. The tag bits
674        // (bits 0 to 2 of start_and_tag) are set exclusively by pub(crate)
675        // constructors which only use TAG_STRING(0)..TAG_ARRAY(6). TAG_NONE(7)
676        // is only used for MaybeItem, which has its own tag() method returning
677        // u32. kind() is never called on a NONE tagged value. Therefore the
678        // masked value is always a valid Kind discriminant.
679        unsafe { std::mem::transmute::<u8, Kind>(self.meta.start_and_tag as u8 & 0x7) }
680    }
681    #[inline]
682    pub(crate) fn tag(&self) -> u32 {
683        self.meta.tag()
684    }
685
686    /// Returns `true` for leaf values (string, integer, float, boolean,
687    /// datetime) that contain no arena-allocated children.
688    #[inline]
689    pub(crate) fn is_scalar(&self) -> bool {
690        self.tag() < TAG_TABLE
691    }
692
693    /// Returns the raw 3-bit flag encoding the container sub-kind.
694    ///
695    /// Prefer [`Table::style`] or [`Array::style`] for a typed alternative.
696    #[inline]
697    pub fn flag(&self) -> u32 {
698        self.meta.flag()
699    }
700
701    /// Returns the byte-offset span of this value in the source document.
702    /// Only valid on parser-produced items (span mode).
703    #[inline]
704    pub(crate) fn span_unchecked(&self) -> Span {
705        self.meta.span_unchecked()
706    }
707
708    /// Returns the source span, or `0..0` if this item was constructed
709    /// programmatically (format-hints mode).
710    #[inline]
711    pub fn span(&self) -> Span {
712        self.meta.span()
713    }
714
715    /// Returns the TOML type name (e.g. `"string"`, `"integer"`, `"table"`).
716    #[inline]
717    pub fn type_str(&self) -> &'static &'static str {
718        match self.kind() {
719            Kind::String => &"string",
720            Kind::Integer => &"integer",
721            Kind::Float => &"float",
722            Kind::Boolean => &"boolean",
723            Kind::Array => &"array",
724            Kind::Table => &"table",
725            Kind::DateTime => &"datetime",
726        }
727    }
728
729    #[inline]
730    pub(crate) fn is_table(&self) -> bool {
731        self.flag() >= FLAG_TABLE
732    }
733
734    #[inline]
735    pub(crate) fn is_array(&self) -> bool {
736        self.flag() & 6 == 2
737    }
738
739    #[inline]
740    pub(crate) fn is_frozen(&self) -> bool {
741        self.flag() == FLAG_FROZEN
742    }
743
744    #[inline]
745    pub(crate) fn is_aot(&self) -> bool {
746        self.flag() == FLAG_AOT
747    }
748
749    #[inline]
750    pub(crate) fn has_header_bit(&self) -> bool {
751        self.flag() == FLAG_HEADER
752    }
753
754    #[inline]
755    pub(crate) fn has_dotted_bit(&self) -> bool {
756        self.flag() == FLAG_DOTTED
757    }
758
759    /// Returns `true` if this is an implicit intermediate table, a plain
760    /// table that is neither a `[header]` section, a dotted-key intermediate,
761    /// nor a frozen inline `{ }` table. These entries act as structural
762    /// parents for header sections and have no text of their own.
763    #[inline]
764    pub(crate) fn is_implicit_table(&self) -> bool {
765        self.flag() == FLAG_TABLE
766    }
767
768    /// Splits this array item into disjoint borrows of the span field and array payload.
769    ///
770    /// # Safety
771    ///
772    /// - `self.is_array()` must be true (i.e. the payload union holds `array`).
773    #[inline]
774    pub(crate) unsafe fn split_array_end_flag(&mut self) -> (&mut u32, &mut InternalArray<'de>) {
775        debug_assert!(self.is_array());
776        let ptr = self as *mut Item<'de>;
777        // SAFETY:
778        // - Caller guarantees this is an array item, so `payload.array` is the
779        //   active union field.
780        // - `payload.array` occupies bytes 0..16 (ManuallyDrop<InternalArray>).
781        //   `meta.end_and_flag` occupies bytes 20..24. These do not overlap.
782        // - `addr_of_mut!` derives raw pointers without creating intermediate
783        //   references, avoiding aliasing violations.
784        // - The `.cast::<InternalArray>()` strips ManuallyDrop, which is
785        //   #[repr(transparent)] and therefore has identical layout.
786        unsafe {
787            let end_flag = &mut *std::ptr::addr_of_mut!((*ptr).meta.end_and_flag);
788            let array =
789                &mut *std::ptr::addr_of_mut!((*ptr).payload.array).cast::<InternalArray<'de>>();
790            (end_flag, array)
791        }
792    }
793}
794
795/// Borrowed view into an [`Item`] for pattern matching.
796///
797/// Obtained via [`Item::value`].
798///
799/// # Examples
800///
801/// ```
802/// use toml_spanner::{Arena, Value};
803///
804/// let arena = Arena::new();
805/// let table = toml_spanner::parse("n = 10", &arena).unwrap();
806/// match table["n"].item().unwrap().value() {
807///     Value::Integer(i) => assert_eq!(i.as_i128(), 10),
808///     _ => panic!("expected integer"),
809/// }
810/// ```
811#[derive(Debug)]
812pub enum Value<'a, 'de> {
813    /// A string value.
814    String(&'a &'de str),
815    /// An integer value.
816    Integer(&'a Integer),
817    /// A floating-point value.
818    Float(&'a f64),
819    /// A boolean value.
820    Boolean(&'a bool),
821    /// A datetime value.
822    DateTime(&'a DateTime),
823    /// A table value.
824    Table(&'a Table<'de>),
825    /// An array value.
826    Array(&'a Array<'de>),
827}
828
829/// Mutable view into an [`Item`] for pattern matching.
830///
831/// Obtained via [`Item::value_mut`].
832pub enum ValueMut<'a, 'de> {
833    /// A string value.
834    String(&'a mut &'de str),
835    /// An integer value.
836    Integer(&'a mut Integer),
837    /// A floating-point value.
838    Float(&'a mut f64),
839    /// A boolean value.
840    Boolean(&'a mut bool),
841    /// A datetime value (read-only, datetime fields are not mutable).
842    DateTime(&'a DateTime),
843    /// A table value.
844    Table(&'a mut Table<'de>),
845    /// An array value.
846    Array(&'a mut Array<'de>),
847}
848
849impl<'de> Item<'de> {
850    /// Returns a borrowed view for pattern matching.
851    pub fn value(&self) -> Value<'_, 'de> {
852        // SAFETY: kind() returns the discriminant set at construction. Each
853        // match arm reads the union field that was written for that discriminant.
854        unsafe {
855            match self.kind() {
856                Kind::String => Value::String(&self.payload.string),
857                Kind::Integer => Value::Integer(&self.payload.integer),
858                Kind::Float => Value::Float(&self.payload.float),
859                Kind::Boolean => Value::Boolean(&self.payload.boolean),
860                Kind::Array => Value::Array(self.as_array_unchecked()),
861                Kind::Table => Value::Table(self.as_table_unchecked()),
862                Kind::DateTime => Value::DateTime(&self.payload.datetime),
863            }
864        }
865    }
866
867    /// Returns a mutable view for pattern matching.
868    pub fn value_mut(&mut self) -> ValueMut<'_, 'de> {
869        // SAFETY: kind() returns the discriminant set at construction. Each
870        // match arm accesses the union field that was written for that discriminant.
871        unsafe {
872            match self.kind() {
873                Kind::String => ValueMut::String(&mut self.payload.string),
874                Kind::Integer => ValueMut::Integer(&mut self.payload.integer),
875                Kind::Float => ValueMut::Float(&mut self.payload.float),
876                Kind::Boolean => ValueMut::Boolean(&mut self.payload.boolean),
877                Kind::Array => ValueMut::Array(self.as_array_mut_unchecked()),
878                Kind::Table => ValueMut::Table(self.as_table_mut_unchecked()),
879                Kind::DateTime => ValueMut::DateTime(&self.payload.datetime),
880            }
881        }
882    }
883}
884
885impl<'de> Item<'de> {
886    /// Returns a borrowed string if this is a string value.
887    #[inline]
888    pub fn as_str(&self) -> Option<&str> {
889        if self.tag() == TAG_STRING {
890            // SAFETY: tag check guarantees the payload is a string.
891            Some(unsafe { self.payload.string })
892        } else {
893            None
894        }
895    }
896
897    #[doc(hidden)]
898    /// Used in derive macro for style attributes
899    pub fn with_style_of_array_or_table(mut self, style: TableStyle) -> Item<'de> {
900        match self.value_mut() {
901            ValueMut::Table(table) => table.set_style(style),
902            ValueMut::Array(array) => match style {
903                TableStyle::Header => array.set_style(ArrayStyle::Header),
904                TableStyle::Inline => array.set_style(ArrayStyle::Inline),
905                _ => (),
906            },
907            _ => (),
908        }
909        self
910    }
911
912    /// Returns an `i128` if this is an integer value.
913    #[inline]
914    pub fn as_i128(&self) -> Option<i128> {
915        if self.tag() == TAG_INTEGER {
916            // SAFETY: tag check guarantees the payload is an integer.
917            Some(unsafe { self.payload.integer.as_i128() })
918        } else {
919            None
920        }
921    }
922
923    /// Returns an `i64` if this is an integer value that fits in the `i64` range.
924    #[inline]
925    pub fn as_i64(&self) -> Option<i64> {
926        if self.tag() == TAG_INTEGER {
927            // SAFETY: tag check guarantees the payload is an integer.
928            unsafe { self.payload.integer.as_i64() }
929        } else {
930            None
931        }
932    }
933
934    /// Returns a `u64` if this is an integer value that fits in the `u64` range.
935    #[inline]
936    pub fn as_u64(&self) -> Option<u64> {
937        if self.tag() == TAG_INTEGER {
938            // SAFETY: tag check guarantees the payload is an integer.
939            unsafe { self.payload.integer.as_u64() }
940        } else {
941            None
942        }
943    }
944
945    /// Returns an `f64` if this is a float or integer value.
946    ///
947    /// Integer values are converted to `f64` via `as` cast (lossy for large
948    /// values outside the 2^53 exact-integer range).
949    #[inline]
950    pub fn as_f64(&self) -> Option<f64> {
951        match self.value() {
952            Value::Float(f) => Some(*f),
953            Value::Integer(i) => Some(i.as_i128() as f64),
954            _ => None,
955        }
956    }
957
958    /// Returns a `bool` if this is a boolean value.
959    #[inline]
960    pub fn as_bool(&self) -> Option<bool> {
961        if self.tag() == TAG_BOOLEAN {
962            // SAFETY: tag check guarantees the payload is a boolean.
963            Some(unsafe { self.payload.boolean })
964        } else {
965            None
966        }
967    }
968
969    /// Returns a borrowed array if this is an array value.
970    #[inline]
971    pub fn as_array(&self) -> Option<&Array<'de>> {
972        if self.tag() == TAG_ARRAY {
973            // SAFETY: tag check guarantees this item is an array variant.
974            Some(unsafe { self.as_array_unchecked() })
975        } else {
976            None
977        }
978    }
979
980    /// Returns a borrowed table if this is a table value.
981    #[inline]
982    pub fn as_table(&self) -> Option<&Table<'de>> {
983        if self.is_table() {
984            // SAFETY: is_table() check guarantees this item is a table variant.
985            Some(unsafe { self.as_table_unchecked() })
986        } else {
987            None
988        }
989    }
990
991    /// Returns a borrowed [`DateTime`] if this is a datetime value.
992    #[inline]
993    pub fn as_datetime(&self) -> Option<&DateTime> {
994        if self.tag() == TAG_DATETIME {
995            // SAFETY: tag check guarantees the payload is a moment.
996            Some(unsafe { &self.payload.datetime })
997        } else {
998            None
999        }
1000    }
1001
1002    /// Returns a mutable array reference.
1003    #[inline]
1004    pub fn as_array_mut(&mut self) -> Option<&mut Array<'de>> {
1005        if self.tag() == TAG_ARRAY {
1006            // SAFETY: tag check guarantees this item is an array variant.
1007            Some(unsafe { self.as_array_mut_unchecked() })
1008        } else {
1009            None
1010        }
1011    }
1012
1013    /// Consumes this item, returning the table if it is one.
1014    #[inline]
1015    pub fn into_table(self) -> Option<Table<'de>> {
1016        if self.is_table() {
1017            // SAFETY: is_table() guarantees the active union field is `table`.
1018            // Item and Table have identical size, alignment, and repr(C) layout
1019            // (verified by const assertions on Table). Item has no Drop impl.
1020            Some(unsafe { std::mem::transmute::<Item<'de>, Table<'de>>(self) })
1021        } else {
1022            None
1023        }
1024    }
1025
1026    /// Returns a mutable table reference.
1027    #[inline]
1028    pub fn as_table_mut(&mut self) -> Option<&mut Table<'de>> {
1029        if self.is_table() {
1030            // SAFETY: is_table() check guarantees this item is a table variant.
1031            Some(unsafe { self.as_table_mut_unchecked() })
1032        } else {
1033            None
1034        }
1035    }
1036
1037    /// Reinterprets this [`Item`] as an [`Array`] (shared reference).
1038    ///
1039    /// # Safety
1040    ///
1041    /// - `self.tag()` must be `TAG_ARRAY`.
1042    #[inline]
1043    pub(crate) unsafe fn as_array_unchecked(&self) -> &Array<'de> {
1044        debug_assert!(self.tag() == TAG_ARRAY);
1045        // SAFETY: Item is #[repr(C)] { payload: Payload, meta: ItemMetadata }.
1046        // Array is #[repr(C)] { value: InternalArray, meta: ItemMetadata }.
1047        // Payload is a union whose `array` field is ManuallyDrop<InternalArray>
1048        // (#[repr(transparent)]). Both types are 24 bytes, align 8 (verified by
1049        // const assertions). Field offsets match: data at 0..16, metadata at 16..24.
1050        // Caller guarantees the active union field is `array`.
1051        unsafe { &*(self as *const Item<'de>).cast::<Array<'de>>() }
1052    }
1053
1054    /// Reinterprets this [`Item`] as an [`Array`] (mutable reference).
1055    ///
1056    /// # Safety
1057    ///
1058    /// - `self.tag()` must be `TAG_ARRAY`.
1059    #[inline]
1060    pub(crate) unsafe fn as_array_mut_unchecked(&mut self) -> &mut Array<'de> {
1061        debug_assert!(self.tag() == TAG_ARRAY);
1062        // SAFETY: Same layout argument as as_array_unchecked.
1063        unsafe { &mut *(self as *mut Item<'de>).cast::<Array<'de>>() }
1064    }
1065
1066    /// Returns a mutable reference to the inner table payload (parser-internal).
1067    ///
1068    /// # Safety
1069    ///
1070    /// - `self.is_table()` must be true.
1071    #[inline]
1072    pub(crate) unsafe fn as_inner_table_mut_unchecked(&mut self) -> &mut InnerTable<'de> {
1073        debug_assert!(self.is_table());
1074        // SAFETY: Caller guarantees the active union field is `table`.
1075        // ManuallyDrop<InnerTable> dereferences to &mut InnerTable.
1076        unsafe { &mut self.payload.table }
1077    }
1078
1079    /// Reinterprets this [`Item`] as a [`Table`] (mutable reference).
1080    ///
1081    /// # Safety
1082    ///
1083    /// - `self.is_table()` must be true.
1084    #[inline]
1085    pub(crate) unsafe fn as_table_mut_unchecked(&mut self) -> &mut Table<'de> {
1086        debug_assert!(self.is_table());
1087        // SAFETY: Item is #[repr(C)] { payload: Payload, meta: ItemMetadata }.
1088        // Table is #[repr(C)] { value: InnerTable, meta: ItemMetadata }.
1089        // Payload's `table` field is ManuallyDrop<InnerTable> (#[repr(transparent)]).
1090        // Both are 24 bytes, align 8 (const assertions). Field offsets match.
1091        // Caller guarantees the active union field is `table`.
1092        unsafe { &mut *(self as *mut Item<'de>).cast::<Table<'de>>() }
1093    }
1094
1095    /// Reinterprets this [`Item`] as a [`Table`] (shared reference).
1096    ///
1097    /// # Safety
1098    ///
1099    /// - `self.is_table()` must be true.
1100    #[inline]
1101    pub(crate) unsafe fn as_table_unchecked(&self) -> &Table<'de> {
1102        debug_assert!(self.is_table());
1103        // SAFETY: Same layout argument as as_table_mut_unchecked.
1104        unsafe { &*(self as *const Item<'de>).cast::<Table<'de>>() }
1105    }
1106
1107    /// Returns `true` if the value is a non-empty table.
1108    #[inline]
1109    pub fn has_keys(&self) -> bool {
1110        self.as_table().is_some_and(|t| !t.is_empty())
1111    }
1112
1113    /// Returns `true` if the value is a table containing `key`.
1114    #[inline]
1115    pub fn has_key(&self, key: &str) -> bool {
1116        self.as_table().is_some_and(|t| t.contains_key(key))
1117    }
1118    /// Clones this item into `arena`, sharing existing strings.
1119    ///
1120    /// Scalar values are copied directly. Tables and arrays are
1121    /// recursively cloned with new arena-allocated storage. String
1122    /// values and table key names continue to reference their original
1123    /// memory, so the source arena (or input string) must remain alive.
1124    pub fn clone_in(&self, arena: &'de Arena) -> Item<'de> {
1125        if self.is_scalar() {
1126            // SAFETY: Scalar items have tags 0..=4 (STRING, INTEGER, FLOAT,
1127            // BOOLEAN, DATETIME). None of these own arena-allocated children.
1128            // STRING contains a &'de str (shared reference to input/arena data)
1129            // which is safe to duplicate. Item has no Drop impl, so ptr::read
1130            // is a plain bitwise copy with no double-free risk.
1131            unsafe { std::ptr::read(self) }
1132        } else if self.tag() == TAG_ARRAY {
1133            // SAFETY: tag == TAG_ARRAY guarantees payload.array is the active
1134            // union field.
1135            let cloned = unsafe { self.payload.array.clone_in(arena) };
1136            Item {
1137                payload: Payload {
1138                    array: ManuallyDrop::new(cloned),
1139                },
1140                meta: self.meta,
1141            }
1142        } else {
1143            // SAFETY: Tags are 0..=6. is_scalar() is false (tag >= 5) and
1144            // tag != TAG_ARRAY (6), so tag must be TAG_TABLE (5). Therefore
1145            // payload.table is the active union field.
1146            let cloned = unsafe { self.payload.table.clone_in(arena) };
1147            Item {
1148                payload: Payload {
1149                    table: ManuallyDrop::new(cloned),
1150                },
1151                meta: self.meta,
1152            }
1153        }
1154    }
1155
1156    /// Copies this item into `target`, returning a copy with `'static` lifetime.
1157    ///
1158    /// # Safety
1159    ///
1160    /// `target` must have sufficient space as computed by
1161    /// [`compute_size`](crate::item::owned).
1162    pub(crate) unsafe fn emplace_in(
1163        &self,
1164        target: &mut crate::item::owned::ItemCopyTarget,
1165    ) -> Item<'static> {
1166        match self.tag() {
1167            TAG_STRING => {
1168                // SAFETY: tag == TAG_STRING guarantees payload.string is active.
1169                let s = unsafe { self.payload.string };
1170                // SAFETY: Caller guarantees sufficient string space.
1171                let new_s = unsafe { target.copy_str(s) };
1172                Item {
1173                    payload: Payload { string: new_s },
1174                    meta: self.meta,
1175                }
1176            }
1177            TAG_TABLE => {
1178                // SAFETY: tag == TAG_TABLE guarantees payload.table is active.
1179                // Caller guarantees sufficient space for recursive emplace.
1180                let new_table = unsafe { self.payload.table.emplace_in(target) };
1181                Item {
1182                    payload: Payload {
1183                        table: ManuallyDrop::new(new_table),
1184                    },
1185                    meta: self.meta,
1186                }
1187            }
1188            TAG_ARRAY => {
1189                // SAFETY: tag == TAG_ARRAY guarantees payload.array is active.
1190                // Caller guarantees sufficient space for recursive emplace.
1191                let new_array = unsafe { self.payload.array.emplace_in(target) };
1192                Item {
1193                    payload: Payload {
1194                        array: ManuallyDrop::new(new_array),
1195                    },
1196                    meta: self.meta,
1197                }
1198            }
1199            _ => {
1200                // SAFETY: Non-string scalars (INTEGER, FLOAT, BOOLEAN, DATETIME)
1201                // contain no borrowed data. Item<'de> and Item<'static> have
1202                // identical layout. Item has no Drop impl.
1203                unsafe { std::mem::transmute_copy(self) }
1204            }
1205        }
1206    }
1207}
1208
1209impl<'de> Item<'de> {
1210    /// Creates an "expected X, found Y" error using this value's type and span.
1211    #[inline]
1212    pub fn expected(&self, expected: &'static &'static str) -> Error {
1213        Error::new(
1214            ErrorKind::Wanted {
1215                expected,
1216                found: self.type_str(),
1217            },
1218            self.span_unchecked(),
1219        )
1220    }
1221
1222    /// Takes a string value and parses it via [`std::str::FromStr`].
1223    ///
1224    /// Returns an error if the value is not a string or parsing fails.
1225    #[inline]
1226    pub fn parse<T>(&self) -> Result<T, Error>
1227    where
1228        T: std::str::FromStr,
1229        <T as std::str::FromStr>::Err: std::fmt::Display,
1230    {
1231        let Some(s) = self.as_str() else {
1232            return Err(self.expected(&"a string"));
1233        };
1234        match s.parse() {
1235            Ok(v) => Ok(v),
1236            Err(err) => Err(Error::custom(
1237                format!("failed to parse string: {err}"),
1238                self.span_unchecked(),
1239            )),
1240        }
1241    }
1242}
1243
1244impl fmt::Debug for Item<'_> {
1245    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1246        match self.value() {
1247            Value::String(s) => s.fmt(f),
1248            Value::Integer(i) => i.fmt(f),
1249            Value::Float(v) => v.fmt(f),
1250            Value::Boolean(b) => b.fmt(f),
1251            Value::Array(a) => a.fmt(f),
1252            Value::Table(t) => t.fmt(f),
1253            Value::DateTime(m) => {
1254                let mut buf = std::mem::MaybeUninit::uninit();
1255                f.write_str(m.format(&mut buf))
1256            }
1257        }
1258    }
1259}
1260
1261/// A TOML table key with its source span.
1262///
1263/// Keys appear as the first element in `(`[`Key`]`, `[`Item`]`)` entry pairs
1264/// when iterating over a [`Table`].
1265#[derive(Copy, Clone)]
1266pub struct Key<'de> {
1267    /// The key name.
1268    pub name: &'de str,
1269    /// The byte-offset span of the key in the source document.
1270    pub span: Span,
1271}
1272
1273impl<'de> Key<'de> {
1274    /// Creates a key with no source span.
1275    ///
1276    /// Use this when constructing tables programmatically via
1277    /// [`Table::insert`].
1278    pub fn new(value: &'de str) -> Self {
1279        Self {
1280            name: value,
1281            span: Span::default(),
1282        }
1283    }
1284    /// Returns the key name as a string slice.
1285    pub fn as_str(&self) -> &'de str {
1286        self.name
1287    }
1288}
1289
1290impl<'de> From<&'de str> for Key<'de> {
1291    fn from(value: &'de str) -> Self {
1292        Self::new(value)
1293    }
1294}
1295
1296#[cfg(target_pointer_width = "64")]
1297const _: () = assert!(std::mem::size_of::<Key<'_>>() == 24);
1298
1299impl std::borrow::Borrow<str> for Key<'_> {
1300    fn borrow(&self) -> &str {
1301        self.name
1302    }
1303}
1304
1305impl fmt::Debug for Key<'_> {
1306    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1307        f.write_str(self.name)
1308    }
1309}
1310
1311impl fmt::Display for Key<'_> {
1312    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1313        f.write_str(self.name)
1314    }
1315}
1316
1317impl Ord for Key<'_> {
1318    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1319        self.name.cmp(other.name)
1320    }
1321}
1322
1323impl PartialOrd for Key<'_> {
1324    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1325        Some(self.cmp(other))
1326    }
1327}
1328
1329impl PartialEq for Key<'_> {
1330    fn eq(&self, other: &Self) -> bool {
1331        self.name.eq(other.name)
1332    }
1333}
1334
1335impl Eq for Key<'_> {}
1336
1337impl std::hash::Hash for Key<'_> {
1338    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1339        self.name.hash(state);
1340    }
1341}
1342
1343pub(crate) fn equal_items(a: &Item<'_>, b: &Item<'_>, index: Option<&TableIndex<'_>>) -> bool {
1344    if a.kind() != b.kind() {
1345        return false;
1346    }
1347    // SAFETY: The kind() equality check above guarantees both items hold the
1348    // same union variant. Each match arm reads only the union field that
1349    // corresponds to the matched Kind discriminant. Since both a and b have
1350    // the same kind, both payload accesses read the active field.
1351    unsafe {
1352        match a.kind() {
1353            Kind::String => a.payload.string == b.payload.string,
1354            Kind::Integer => a.payload.integer == b.payload.integer,
1355            Kind::Float => {
1356                let af = a.payload.float;
1357                let bf = b.payload.float;
1358                if af.is_nan() && bf.is_nan() {
1359                    af.is_sign_negative() == bf.is_sign_negative()
1360                } else {
1361                    af.to_bits() == bf.to_bits()
1362                }
1363            }
1364            Kind::Boolean => a.payload.boolean == b.payload.boolean,
1365            Kind::DateTime => a.payload.datetime == b.payload.datetime,
1366            Kind::Array => {
1367                let a = a.payload.array.as_slice();
1368                let b = b.payload.array.as_slice();
1369                if a.len() != b.len() {
1370                    return false;
1371                }
1372                for i in 0..a.len() {
1373                    if !equal_items(&*a.as_ptr().add(i), &*b.as_ptr().add(i), index) {
1374                        return false;
1375                    }
1376                }
1377                true
1378            }
1379            Kind::Table => {
1380                let tab_a = a.as_table_unchecked();
1381                let tab_b = b.as_table_unchecked();
1382                equal_tables(tab_a, tab_b, index)
1383            }
1384        }
1385    }
1386}
1387
1388pub(crate) fn equal_tables(a: &Table<'_>, b: &Table<'_>, index: Option<&TableIndex<'_>>) -> bool {
1389    if a.len() != b.len() {
1390        return false;
1391    }
1392    for (key, val_a) in a {
1393        let Some((_, val_b)) = b.value.get_entry_with_maybe_index(key.name, index) else {
1394            return false;
1395        };
1396        if !equal_items(val_a, val_b, index) {
1397            return false;
1398        }
1399    }
1400    true
1401}
1402
1403impl<'de> PartialEq for Item<'de> {
1404    fn eq(&self, other: &Self) -> bool {
1405        equal_items(self, other, None)
1406    }
1407}
1408
1409impl<'de> std::ops::Index<&str> for Item<'de> {
1410    type Output = MaybeItem<'de>;
1411
1412    #[inline]
1413    fn index(&self, index: &str) -> &Self::Output {
1414        if let Some(table) = self.as_table()
1415            && let Some(item) = table.get(index)
1416        {
1417            return MaybeItem::from_ref(item);
1418        }
1419        &NONE
1420    }
1421}
1422
1423impl<'de> std::ops::Index<usize> for Item<'de> {
1424    type Output = MaybeItem<'de>;
1425
1426    #[inline]
1427    fn index(&self, index: usize) -> &Self::Output {
1428        if let Some(arr) = self.as_array()
1429            && let Some(item) = arr.get(index)
1430        {
1431            return MaybeItem::from_ref(item);
1432        }
1433        &NONE
1434    }
1435}
1436
1437impl<'de> std::ops::Index<&str> for MaybeItem<'de> {
1438    type Output = MaybeItem<'de>;
1439
1440    #[inline]
1441    fn index(&self, index: &str) -> &Self::Output {
1442        if let Some(table) = self.as_table()
1443            && let Some(item) = table.get(index)
1444        {
1445            return MaybeItem::from_ref(item);
1446        }
1447        &NONE
1448    }
1449}
1450
1451impl<'de> std::ops::Index<usize> for MaybeItem<'de> {
1452    type Output = MaybeItem<'de>;
1453
1454    #[inline]
1455    fn index(&self, index: usize) -> &Self::Output {
1456        if let Some(arr) = self.as_array()
1457            && let Some(item) = arr.get(index)
1458        {
1459            return MaybeItem::from_ref(item);
1460        }
1461        &NONE
1462    }
1463}
1464
1465impl fmt::Debug for MaybeItem<'_> {
1466    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1467        match self.item() {
1468            Some(item) => item.fmt(f),
1469            None => f.write_str("None"),
1470        }
1471    }
1472}
1473
1474/// A nullable reference to a parsed TOML value.
1475///
1476/// `MaybeItem` is returned by the index operators (`[]`) on [`Item`],
1477/// [`Table`], [`Array`], and `MaybeItem` itself. It acts like an
1478/// [`Option<&Item>`] that can be further indexed without panicking. Chained
1479/// lookups on missing keys simply propagate the `None` state.
1480///
1481/// Use the `as_*` accessors to extract a value, or call [`item`](Self::item)
1482/// to get back an `Option<&Item>`.
1483///
1484/// # Examples
1485///
1486/// ```
1487/// use toml_spanner::Arena;
1488///
1489/// let arena = Arena::new();
1490/// let table = toml_spanner::parse(r#"
1491/// [server]
1492/// host = "localhost"
1493/// port = 8080
1494/// "#, &arena).unwrap();
1495///
1496/// // Successful nested lookup.
1497/// assert_eq!(table["server"]["host"].as_str(), Some("localhost"));
1498/// assert_eq!(table["server"]["port"].as_i64(), Some(8080));
1499///
1500/// // Missing keys propagate through chained indexing without panicking.
1501/// assert_eq!(table["server"]["missing"].as_str(), None);
1502/// assert_eq!(table["nonexistent"]["deep"]["path"].as_str(), None);
1503///
1504/// // Convert back to an Option<&Item> when needed.
1505/// assert!(table["server"]["host"].item().is_some());
1506/// assert!(table["nope"].item().is_none());
1507/// ```
1508#[repr(C)]
1509pub struct MaybeItem<'de> {
1510    payload: Payload<'de>,
1511    meta: ItemMetadata,
1512}
1513
1514// SAFETY: `MaybeItem` is only constructed as either (a) the static `NONE`
1515// sentinel, whose payload is zeroed and tag is `TAG_NONE` so no pointers are
1516// dereferenced, or (b) a reinterpretation of `&Item` via `from_ref`. In case
1517// (b) it is layout-compatible with `Item` (verified by the const assertions
1518// on sizes and the `#[repr(C)]` field order), so any thread that can access
1519// an `Item` can access the equivalent `MaybeItem` with the same safety. The
1520// same "no interior mutability" argument used for `Item` applies here.
1521//
1522// `Send` is sound because the only owned form is the `'static` `NONE`
1523// sentinel; any transfer of a `MaybeItem` across threads ultimately moves a
1524// value layout-equivalent to an `Item`, which is itself `Send`.
1525unsafe impl Send for MaybeItem<'_> {}
1526unsafe impl Sync for MaybeItem<'_> {}
1527
1528pub(crate) static NONE: MaybeItem<'static> = MaybeItem {
1529    payload: Payload {
1530        integer: Integer {
1531            value: PackedI128 { value: 0 },
1532        },
1533    },
1534    meta: ItemMetadata {
1535        start_and_tag: TAG_NONE,
1536        end_and_flag: FLAG_NONE,
1537    },
1538};
1539
1540impl<'de> MaybeItem<'de> {
1541    /// Views an [`Item`] reference as a `MaybeItem`.
1542    pub fn from_ref<'a>(item: &'a Item<'de>) -> &'a Self {
1543        // SAFETY: Item and MaybeItem are both #[repr(C)] with identical field
1544        // layout (Payload, ItemMetadata). Size and alignment equality is verified
1545        // by const assertions.
1546        unsafe { &*(item as *const Item<'de>).cast::<MaybeItem<'de>>() }
1547    }
1548    #[inline]
1549    pub(crate) fn tag(&self) -> u32 {
1550        self.meta.tag()
1551    }
1552    /// Returns the underlying [`Item`], or [`None`] if this is a missing value.
1553    pub fn item(&self) -> Option<&Item<'de>> {
1554        if self.tag() != TAG_NONE {
1555            // SAFETY: tag != TAG_NONE means this was created via from_ref from
1556            // a valid Item. Item and MaybeItem have identical repr(C) layout.
1557            Some(unsafe { &*(self as *const MaybeItem<'de>).cast::<Item<'de>>() })
1558        } else {
1559            None
1560        }
1561    }
1562    /// Returns a borrowed string if this is a string value.
1563    #[inline]
1564    pub fn as_str(&self) -> Option<&str> {
1565        if self.tag() == TAG_STRING {
1566            // SAFETY: tag check guarantees the payload is a string.
1567            Some(unsafe { self.payload.string })
1568        } else {
1569            None
1570        }
1571    }
1572
1573    /// Returns an `i128` if this is an integer value.
1574    #[inline]
1575    pub fn as_i128(&self) -> Option<i128> {
1576        if self.tag() == TAG_INTEGER {
1577            // SAFETY: tag check guarantees the payload is an integer.
1578            Some(unsafe { self.payload.integer.as_i128() })
1579        } else {
1580            None
1581        }
1582    }
1583
1584    /// Returns an `i64` if this is an integer value that fits in the `i64` range.
1585    #[inline]
1586    pub fn as_i64(&self) -> Option<i64> {
1587        if self.tag() == TAG_INTEGER {
1588            // SAFETY: tag check guarantees the payload is an integer.
1589            unsafe { self.payload.integer.as_i64() }
1590        } else {
1591            None
1592        }
1593    }
1594
1595    /// Returns a `u64` if this is an integer value that fits in the `u64` range.
1596    #[inline]
1597    pub fn as_u64(&self) -> Option<u64> {
1598        if self.tag() == TAG_INTEGER {
1599            // SAFETY: tag check guarantees the payload is an integer.
1600            unsafe { self.payload.integer.as_u64() }
1601        } else {
1602            None
1603        }
1604    }
1605
1606    /// Returns an `f64` if this is a float or integer value.
1607    ///
1608    /// Integer values are converted to `f64` via `as` cast (lossy for large
1609    /// values outside the 2^53 exact-integer range).
1610    #[inline]
1611    pub fn as_f64(&self) -> Option<f64> {
1612        self.item()?.as_f64()
1613    }
1614
1615    /// Returns a `bool` if this is a boolean value.
1616    #[inline]
1617    pub fn as_bool(&self) -> Option<bool> {
1618        if self.tag() == TAG_BOOLEAN {
1619            // SAFETY: tag check guarantees the payload is a boolean.
1620            Some(unsafe { self.payload.boolean })
1621        } else {
1622            None
1623        }
1624    }
1625
1626    /// Returns a borrowed array if this is an array value.
1627    #[inline]
1628    pub fn as_array(&self) -> Option<&Array<'de>> {
1629        if self.tag() == TAG_ARRAY {
1630            // SAFETY: tag == TAG_ARRAY guarantees the payload is an array.
1631            // MaybeItem and Array have identical repr(C) layout (verified by
1632            // const size/align assertions on Item and Array).
1633            Some(unsafe { &*(self as *const Self).cast::<Array<'de>>() })
1634        } else {
1635            None
1636        }
1637    }
1638
1639    /// Returns a borrowed table if this is a table value.
1640    #[inline]
1641    pub fn as_table(&self) -> Option<&Table<'de>> {
1642        if self.tag() == TAG_TABLE {
1643            // SAFETY: tag == TAG_TABLE guarantees the payload is a table.
1644            // MaybeItem and Table have identical repr(C) layout (verified by
1645            // const size/align assertions on Item and Table).
1646            Some(unsafe { &*(self as *const Self).cast::<Table<'de>>() })
1647        } else {
1648            None
1649        }
1650    }
1651
1652    /// Returns a borrowed [`DateTime`] if this is a datetime value.
1653    #[inline]
1654    pub fn as_datetime(&self) -> Option<&DateTime> {
1655        if self.tag() == TAG_DATETIME {
1656            // SAFETY: tag check guarantees the payload is a moment.
1657            Some(unsafe { &self.payload.datetime })
1658        } else {
1659            None
1660        }
1661    }
1662
1663    /// Returns the source span, or `0..0` if this is a missing value.
1664    pub fn span(&self) -> Span {
1665        if let Some(item) = self.item() {
1666            item.span_unchecked()
1667        } else {
1668            Span::default()
1669        }
1670    }
1671
1672    /// Returns a borrowed [`Value`] for pattern matching, or [`None`] if missing.
1673    pub fn value(&self) -> Option<Value<'_, 'de>> {
1674        if let Some(item) = self.item() {
1675            Some(item.value())
1676        } else {
1677            None
1678        }
1679    }
1680}