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