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