Skip to main content

toml_spanner/
value.rs

1#![allow(clippy::manual_map)]
2#[cfg(test)]
3#[path = "./value_tests.rs"]
4mod tests;
5use crate::{DateTime, Error, ErrorKind, Span, Table};
6use std::fmt;
7use std::mem::ManuallyDrop;
8
9/// A toml array
10pub use crate::array::Array;
11/// A toml table: flat list of key-value pairs in insertion order
12use crate::table::InnerTable;
13
14pub(crate) const TAG_MASK: u32 = 0x7;
15pub(crate) const TAG_SHIFT: u32 = 3;
16
17pub(crate) const TAG_STRING: u32 = 0;
18pub(crate) const TAG_INTEGER: u32 = 1;
19pub(crate) const TAG_FLOAT: u32 = 2;
20pub(crate) const TAG_BOOLEAN: u32 = 3;
21pub(crate) const TAG_ARRAY: u32 = 4;
22pub(crate) const TAG_TABLE: u32 = 5;
23pub(crate) const TAG_DATETIME: u32 = 6;
24
25// Only set in maybe item
26pub(crate) const TAG_NONE: u32 = 7;
27
28/// 3-bit state field in `end_and_flag` encoding container kind and sub-state.
29/// Bit 2 set → table, bits 1:0 == 01 → array. Allows dispatch without
30/// reading `start_and_tag`.
31pub(crate) const FLAG_MASK: u32 = 0x7;
32pub(crate) const FLAG_SHIFT: u32 = 3;
33
34pub(crate) const FLAG_NONE: u32 = 0;
35pub(crate) const FLAG_ARRAY: u32 = 2;
36pub(crate) const FLAG_AOT: u32 = 3;
37pub(crate) const FLAG_TABLE: u32 = 4;
38pub(crate) const FLAG_DOTTED: u32 = 5;
39pub(crate) const FLAG_HEADER: u32 = 6;
40pub(crate) const FLAG_FROZEN: u32 = 7;
41
42#[repr(C, align(8))]
43union Payload<'de> {
44    string: &'de str,
45    integer: i64,
46    float: f64,
47    boolean: bool,
48    array: ManuallyDrop<Array<'de>>,
49    table: ManuallyDrop<InnerTable<'de>>,
50    moment: DateTime,
51}
52
53/// A parsed TOML value with span information.
54///
55/// Use the `as_*` methods ([`as_str`](Self::as_str),
56/// [`as_i64`](Self::as_i64), [`as_table`](Self::as_table), etc.) to
57/// extract the value, or call [`value`](Self::value) /
58/// [`value_mut`](Self::value_mut) to pattern match via the [`Value`] /
59/// [`ValueMut`] enums.
60///
61/// Items support indexing with `&str` (table lookup) and `usize` (array
62/// access). These operators return [`MaybeItem`] and never panic — missing
63/// keys or out-of-bounds indices produce a `None` variant instead.
64///
65/// # Lookup performance
66///
67/// String-key lookups (`item["key"]`, [`as_table`](Self::as_table) +
68/// [`Table::get`]) perform a linear scan over the table entries — O(n) in
69/// the number of keys. For small tables or a handful of lookups, as is
70/// typical in TOML, this is well fast enough.
71///
72/// For structured deserialization of larger tables, use
73/// [`TableHelper`](crate::de::TableHelper) with the [`Context`](crate::de::Context)
74/// returned by [`parse`](crate::parse).
75///
76/// # Examples
77///
78/// ```
79/// let arena = toml_spanner::Arena::new();
80/// let table = toml_spanner::parse("x = 42", &arena)?;
81/// assert_eq!(table["x"].as_i64(), Some(42));
82/// assert_eq!(table["missing"].as_i64(), None);
83/// # Ok::<(), toml_spanner::Error>(())
84/// ```
85#[repr(C)]
86pub struct Item<'de> {
87    payload: Payload<'de>,
88    start_and_tag: u32,
89    end_and_flag: u32,
90}
91
92const _: () = assert!(std::mem::size_of::<Item<'_>>() == 24);
93const _: () = assert!(std::mem::align_of::<Item<'_>>() == 8);
94
95impl<'de> Item<'de> {
96    #[inline]
97    fn raw(tag: u32, flag: u32, start: u32, end: u32, payload: Payload<'de>) -> Self {
98        Self {
99            start_and_tag: (start << TAG_SHIFT) | tag,
100            end_and_flag: (end << FLAG_SHIFT) | flag,
101            payload,
102        }
103    }
104
105    #[inline]
106    pub(crate) fn string(s: &'de str, span: Span) -> Self {
107        Self::raw(
108            TAG_STRING,
109            FLAG_NONE,
110            span.start,
111            span.end,
112            Payload { string: s },
113        )
114    }
115
116    #[inline]
117    pub(crate) fn integer(i: i64, span: Span) -> Self {
118        Self::raw(
119            TAG_INTEGER,
120            FLAG_NONE,
121            span.start,
122            span.end,
123            Payload { integer: i },
124        )
125    }
126
127    #[inline]
128    pub(crate) fn float(f: f64, span: Span) -> Self {
129        Self::raw(
130            TAG_FLOAT,
131            FLAG_NONE,
132            span.start,
133            span.end,
134            Payload { float: f },
135        )
136    }
137
138    #[inline]
139    pub(crate) fn boolean(b: bool, span: Span) -> Self {
140        Self::raw(
141            TAG_BOOLEAN,
142            FLAG_NONE,
143            span.start,
144            span.end,
145            Payload { boolean: b },
146        )
147    }
148
149    #[inline]
150    pub(crate) fn array(a: Array<'de>, span: Span) -> Self {
151        Self::raw(
152            TAG_ARRAY,
153            FLAG_ARRAY,
154            span.start,
155            span.end,
156            Payload {
157                array: ManuallyDrop::new(a),
158            },
159        )
160    }
161
162    #[inline]
163    pub(crate) fn table(t: InnerTable<'de>, span: Span) -> Self {
164        Self::raw(
165            TAG_TABLE,
166            FLAG_TABLE,
167            span.start,
168            span.end,
169            Payload {
170                table: ManuallyDrop::new(t),
171            },
172        )
173    }
174
175    /// Creates an array-of-tables value.
176    #[inline]
177    pub(crate) fn array_aot(a: Array<'de>, span: Span) -> Self {
178        Self::raw(
179            TAG_ARRAY,
180            FLAG_AOT,
181            span.start,
182            span.end,
183            Payload {
184                array: ManuallyDrop::new(a),
185            },
186        )
187    }
188
189    /// Creates a frozen (inline) table value.
190    #[inline]
191    pub(crate) fn table_frozen(t: InnerTable<'de>, span: Span) -> Self {
192        Self::raw(
193            TAG_TABLE,
194            FLAG_FROZEN,
195            span.start,
196            span.end,
197            Payload {
198                table: ManuallyDrop::new(t),
199            },
200        )
201    }
202
203    /// Creates a table with HEADER state (explicitly opened by `[header]`).
204    #[inline]
205    pub(crate) fn table_header(t: InnerTable<'de>, span: Span) -> Self {
206        Self::raw(
207            TAG_TABLE,
208            FLAG_HEADER,
209            span.start,
210            span.end,
211            Payload {
212                table: ManuallyDrop::new(t),
213            },
214        )
215    }
216
217    /// Creates a table with DOTTED state (created by dotted-key navigation).
218    #[inline]
219    pub(crate) fn table_dotted(t: InnerTable<'de>, span: Span) -> Self {
220        Self::raw(
221            TAG_TABLE,
222            FLAG_DOTTED,
223            span.start,
224            span.end,
225            Payload {
226                table: ManuallyDrop::new(t),
227            },
228        )
229    }
230
231    #[inline]
232    pub(crate) fn moment(m: DateTime, span: Span) -> Self {
233        Self::raw(
234            TAG_DATETIME,
235            FLAG_NONE,
236            span.start,
237            span.end,
238            Payload { moment: m },
239        )
240    }
241}
242/// Discriminant for the TOML value types stored in an [`Item`].
243///
244/// Obtained via [`Item::kind`].
245#[derive(Clone, Copy)]
246#[repr(u8)]
247#[allow(unused)]
248pub enum Kind {
249    String = 0,
250    Integer = 1,
251    Float = 2,
252    Boolean = 3,
253    Array = 4,
254    Table = 5,
255    DateTime = 6,
256}
257
258impl std::fmt::Debug for Kind {
259    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
260        self.as_str().fmt(f)
261    }
262}
263
264impl std::fmt::Display for Kind {
265    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266        self.as_str().fmt(f)
267    }
268}
269
270impl Kind {
271    /// Returns the TOML type name as a lowercase string (e.g. `"string"`, `"table"`).
272    pub fn as_str(self) -> &'static str {
273        match self {
274            Kind::String => "string",
275            Kind::Integer => "integer",
276            Kind::Float => "float",
277            Kind::Boolean => "boolean",
278            Kind::Array => "array",
279            Kind::Table => "table",
280            Kind::DateTime => "datetime",
281        }
282    }
283}
284
285impl<'de> Item<'de> {
286    #[inline]
287    pub fn kind(&self) -> Kind {
288        debug_assert!((self.start_and_tag & TAG_MASK) as u8 <= Kind::DateTime as u8);
289        // SAFETY: tag bits 0-2 are always in 0..=6 (set only by pub(crate)
290        // constructors). All values are valid Kind discriminants.
291        unsafe { std::mem::transmute::<u8, Kind>(self.start_and_tag as u8 & 0x7) }
292    }
293    #[inline]
294    pub(crate) fn tag(&self) -> u32 {
295        self.start_and_tag & TAG_MASK
296    }
297
298    #[inline]
299    pub(crate) fn flag(&self) -> u32 {
300        self.end_and_flag & FLAG_MASK
301    }
302
303    /// Returns the byte-offset span of this value in the source document.
304    #[inline]
305    pub fn span(&self) -> Span {
306        Span::new(
307            self.start_and_tag >> TAG_SHIFT,
308            self.end_and_flag >> FLAG_SHIFT,
309        )
310    }
311
312    /// Returns the TOML type name (e.g. `"string"`, `"integer"`, `"table"`).
313    #[inline]
314    pub fn type_str(&self) -> &'static str {
315        match self.kind() {
316            Kind::String => "string",
317            Kind::Integer => "integer",
318            Kind::Float => "float",
319            Kind::Boolean => "boolean",
320            Kind::Array => "array",
321            Kind::Table => "table",
322            Kind::DateTime => "datetime",
323        }
324    }
325
326    #[inline]
327    pub(crate) fn is_table(&self) -> bool {
328        self.flag() >= FLAG_TABLE
329    }
330
331    #[inline]
332    pub(crate) fn is_array(&self) -> bool {
333        self.flag() & 6 == 2
334    }
335
336    #[inline]
337    pub(crate) fn is_frozen(&self) -> bool {
338        self.flag() == FLAG_FROZEN
339    }
340
341    #[inline]
342    pub(crate) fn is_aot(&self) -> bool {
343        self.flag() == FLAG_AOT
344    }
345
346    #[inline]
347    pub(crate) fn has_header_bit(&self) -> bool {
348        self.flag() == FLAG_HEADER
349    }
350
351    #[inline]
352    pub(crate) fn has_dotted_bit(&self) -> bool {
353        self.flag() == FLAG_DOTTED
354    }
355
356    /// Splits this array item into disjoint borrows of the span field and array payload.
357    ///
358    /// # Safety
359    ///
360    /// The caller must ensure `self.is_array()` is true.
361    #[inline]
362    pub(crate) unsafe fn split_array_end_flag(&mut self) -> (&mut u32, &mut Array<'de>) {
363        debug_assert!(self.is_array());
364        let ptr = self as *mut Item<'de>;
365        // SAFETY: end_and_flag and payload.array are at disjoint offsets within
366        // the repr(C) layout. addr_of_mut! creates derived pointers without
367        // intermediate references, avoiding aliasing violations.
368        unsafe {
369            let end_flag = &mut *std::ptr::addr_of_mut!((*ptr).end_and_flag);
370            let array = &mut *std::ptr::addr_of_mut!((*ptr).payload.array).cast::<Array<'de>>();
371            (end_flag, array)
372        }
373    }
374}
375
376/// Borrowed view into an [`Item`] for pattern matching.
377///
378/// Obtained via [`Item::value`].
379///
380/// # Examples
381///
382/// ```
383/// use toml_spanner::{Arena, Value};
384///
385/// let arena = Arena::new();
386/// let table = toml_spanner::parse("n = 10", &arena)?;
387/// match table["n"].item().unwrap().value() {
388///     Value::Integer(i) => assert_eq!(*i, 10),
389///     _ => panic!("expected integer"),
390/// }
391/// # Ok::<(), toml_spanner::Error>(())
392/// ```
393#[derive(Debug)]
394pub enum Value<'a, 'de> {
395    /// A string value.
396    String(&'a &'de str),
397    /// An integer value.
398    Integer(&'a i64),
399    /// A floating-point value.
400    Float(&'a f64),
401    /// A boolean value.
402    Boolean(&'a bool),
403    /// An array value.
404    Array(&'a Array<'de>),
405    /// A table value.
406    Table(&'a Table<'de>),
407    /// A datetime value.
408    DateTime(&'a DateTime),
409}
410
411/// Mutable view into an [`Item`] for pattern matching.
412///
413/// Obtained via [`Item::value_mut`].
414pub enum ValueMut<'a, 'de> {
415    /// A string value.
416    String(&'a mut &'de str),
417    /// An integer value.
418    Integer(&'a mut i64),
419    /// A floating-point value.
420    Float(&'a mut f64),
421    /// A boolean value.
422    Boolean(&'a mut bool),
423    /// An array value.
424    Array(&'a mut Array<'de>),
425    /// A table value.
426    Table(&'a mut Table<'de>),
427    /// A datetime value (read-only; datetime fields are not mutable).
428    DateTime(&'a DateTime),
429}
430
431impl<'de> Item<'de> {
432    /// Returns a borrowed view for pattern matching.
433    #[inline(never)]
434    pub fn value(&self) -> Value<'_, 'de> {
435        // SAFETY: kind() returns the discriminant set at construction. Each
436        // match arm reads the union field that was written for that discriminant.
437        unsafe {
438            match self.kind() {
439                Kind::String => Value::String(&self.payload.string),
440                Kind::Integer => Value::Integer(&self.payload.integer),
441                Kind::Float => Value::Float(&self.payload.float),
442                Kind::Boolean => Value::Boolean(&self.payload.boolean),
443                Kind::Array => Value::Array(&self.payload.array),
444                Kind::Table => Value::Table(self.as_table_unchecked()),
445                Kind::DateTime => Value::DateTime(&self.payload.moment),
446            }
447        }
448    }
449
450    /// Returns a mutable view for pattern matching.
451    #[inline(never)]
452    pub fn value_mut(&mut self) -> ValueMut<'_, 'de> {
453        // SAFETY: kind() returns the discriminant set at construction. Each
454        // match arm accesses the union field that was written for that discriminant.
455        unsafe {
456            match self.kind() {
457                Kind::String => ValueMut::String(&mut self.payload.string),
458                Kind::Integer => ValueMut::Integer(&mut self.payload.integer),
459                Kind::Float => ValueMut::Float(&mut self.payload.float),
460                Kind::Boolean => ValueMut::Boolean(&mut self.payload.boolean),
461                Kind::Array => ValueMut::Array(&mut self.payload.array),
462                Kind::Table => ValueMut::Table(self.as_table_mut_unchecked()),
463                Kind::DateTime => ValueMut::DateTime(&self.payload.moment),
464            }
465        }
466    }
467}
468
469impl<'de> Item<'de> {
470    /// Returns a borrowed string if this is a string value.
471    #[inline]
472    pub fn as_str(&self) -> Option<&str> {
473        if self.tag() == TAG_STRING {
474            // SAFETY: tag check guarantees the payload is a string.
475            Some(unsafe { self.payload.string })
476        } else {
477            None
478        }
479    }
480
481    /// Returns an `i64` if this is an integer value.
482    #[inline]
483    pub fn as_i64(&self) -> Option<i64> {
484        if self.tag() == TAG_INTEGER {
485            // SAFETY: tag check guarantees the payload is an integer.
486            Some(unsafe { self.payload.integer })
487        } else {
488            None
489        }
490    }
491
492    /// Returns an `f64` if this is a float or integer value.
493    ///
494    /// Integer values are converted to `f64` via `as` cast (lossy for large
495    /// values outside the 2^53 exact-integer range).
496    #[inline]
497    pub fn as_f64(&self) -> Option<f64> {
498        match self.value() {
499            Value::Float(f) => Some(*f),
500            Value::Integer(i) => Some(*i as f64),
501            _ => None,
502        }
503    }
504
505    /// Returns a `bool` if this is a boolean value.
506    #[inline]
507    pub fn as_bool(&self) -> Option<bool> {
508        if self.tag() == TAG_BOOLEAN {
509            // SAFETY: tag check guarantees the payload is a boolean.
510            Some(unsafe { self.payload.boolean })
511        } else {
512            None
513        }
514    }
515
516    /// Returns a borrowed array if this is an array value.
517    #[inline]
518    pub fn as_array(&self) -> Option<&Array<'de>> {
519        if self.tag() == TAG_ARRAY {
520            // SAFETY: tag check guarantees the payload is an array.
521            Some(unsafe { &self.payload.array })
522        } else {
523            None
524        }
525    }
526
527    /// Returns a borrowed table if this is a table value.
528    #[inline]
529    pub fn as_table(&self) -> Option<&Table<'de>> {
530        if self.is_table() {
531            // SAFETY: is_table() check guarantees this item is a table variant.
532            Some(unsafe { self.as_table_unchecked() })
533        } else {
534            None
535        }
536    }
537
538    /// Returns a borrowed [`DateTime`] if this is a datetime value.
539    #[inline]
540    pub fn as_datetime(&self) -> Option<&DateTime> {
541        if self.tag() == TAG_DATETIME {
542            // SAFETY: tag check guarantees the payload is a moment.
543            Some(unsafe { &self.payload.moment })
544        } else {
545            None
546        }
547    }
548
549    /// Returns a mutable array reference.
550    #[inline]
551    pub fn as_array_mut(&mut self) -> Option<&mut Array<'de>> {
552        if self.tag() == TAG_ARRAY {
553            // SAFETY: tag check guarantees the payload is an array.
554            Some(unsafe { &mut self.payload.array })
555        } else {
556            None
557        }
558    }
559
560    /// Returns a mutable table reference.
561    #[inline]
562    pub fn as_table_mut(&mut self) -> Option<&mut Table<'de>> {
563        if self.is_table() {
564            // SAFETY: is_table() check guarantees this item is a table variant.
565            Some(unsafe { self.as_table_mut_unchecked() })
566        } else {
567            None
568        }
569    }
570
571    /// Returns a mutable table pointer (parser-internal).
572    #[inline]
573    pub(crate) unsafe fn as_inner_table_mut_unchecked(&mut self) -> &mut InnerTable<'de> {
574        debug_assert!(self.is_table());
575        unsafe { &mut self.payload.table }
576    }
577
578    /// Reinterprets this [`Item`] as a [`Table`].
579    ///
580    /// SAFETY: The caller must ensure `self.is_table()` is true. Both types
581    /// are `#[repr(C)]` with identical layout when the payload is a table.
582    #[inline]
583    pub(crate) unsafe fn as_table_mut_unchecked(&mut self) -> &mut Table<'de> {
584        debug_assert!(self.is_table());
585        unsafe { &mut *(self as *mut Item<'de>).cast::<Table<'de>>() }
586    }
587
588    /// Reinterprets this [`Item`] as a [`Table`] (shared reference).
589    ///
590    /// # Safety
591    ///
592    /// The caller must ensure `self.is_table()` is true.
593    #[inline]
594    pub(crate) unsafe fn as_table_unchecked(&self) -> &Table<'de> {
595        debug_assert!(self.is_table());
596        // SAFETY: Both types are `#[repr(C)]` with identical layout when the payload is a table.
597        unsafe { &*(self as *const Item<'de>).cast::<Table<'de>>() }
598    }
599
600    /// Returns true if the value is a table and is non-empty.
601    #[inline]
602    pub fn has_keys(&self) -> bool {
603        self.as_table().is_some_and(|t| !t.is_empty())
604    }
605
606    /// Returns true if the value is a table and has the specified key.
607    #[inline]
608    pub fn has_key(&self, key: &str) -> bool {
609        self.as_table().is_some_and(|t| t.contains_key(key))
610    }
611}
612
613impl<'de> Item<'de> {
614    /// Creates an "expected X, found Y" error using this value's type and span.
615    #[inline]
616    pub fn expected(&self, expected: &'static str) -> Error {
617        Error {
618            kind: ErrorKind::Wanted {
619                expected,
620                found: self.type_str(),
621            },
622            span: self.span(),
623        }
624    }
625
626    /// Takes a string value and parses it via [`std::str::FromStr`].
627    ///
628    /// Returns an error if the value is not a string or parsing fails.
629    #[inline]
630    pub fn parse<T>(&self) -> Result<T, Error>
631    where
632        T: std::str::FromStr,
633        <T as std::str::FromStr>::Err: std::fmt::Display,
634    {
635        let Some(s) = self.as_str() else {
636            return Err(self.expected("a string"));
637        };
638        match s.parse() {
639            Ok(v) => Ok(v),
640            Err(err) => Err(Error {
641                kind: ErrorKind::Custom(format!("failed to parse string: {err}").into()),
642                span: self.span(),
643            }),
644        }
645    }
646}
647
648impl fmt::Debug for Item<'_> {
649    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
650        match self.value() {
651            Value::String(s) => s.fmt(f),
652            Value::Integer(i) => i.fmt(f),
653            Value::Float(v) => v.fmt(f),
654            Value::Boolean(b) => b.fmt(f),
655            Value::Array(a) => a.fmt(f),
656            Value::Table(t) => t.fmt(f),
657            Value::DateTime(m) => {
658                let mut buf = std::mem::MaybeUninit::uninit();
659                f.write_str(m.format(&mut buf))
660            }
661        }
662    }
663}
664
665#[cfg(feature = "serde")]
666impl serde::Serialize for Item<'_> {
667    fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
668    where
669        S: serde::Serializer,
670    {
671        match self.value() {
672            Value::String(s) => ser.serialize_str(s),
673            Value::Integer(i) => ser.serialize_i64(*i),
674            Value::Float(f) => ser.serialize_f64(*f),
675            Value::Boolean(b) => ser.serialize_bool(*b),
676            Value::Array(arr) => {
677                use serde::ser::SerializeSeq;
678                let mut seq = ser.serialize_seq(Some(arr.len()))?;
679                for ele in arr {
680                    seq.serialize_element(ele)?;
681                }
682                seq.end()
683            }
684            Value::Table(tab) => {
685                use serde::ser::SerializeMap;
686                let mut map = ser.serialize_map(Some(tab.len()))?;
687                for (k, v) in tab {
688                    map.serialize_entry(&*k.name, v)?;
689                }
690                map.end()
691            }
692            Value::DateTime(m) => {
693                let mut buf = std::mem::MaybeUninit::uninit();
694                ser.serialize_str(m.format(&mut buf))
695            }
696        }
697    }
698}
699
700#[cfg(feature = "serde")]
701impl serde::Serialize for InnerTable<'_> {
702    fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
703    where
704        S: serde::Serializer,
705    {
706        use serde::ser::SerializeMap;
707        let mut map = ser.serialize_map(Some(self.len()))?;
708        for (k, v) in self.entries() {
709            map.serialize_entry(&*k.name, v)?;
710        }
711        map.end()
712    }
713}
714
715#[cfg(feature = "serde")]
716impl serde::Serialize for Table<'_> {
717    fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
718    where
719        S: serde::Serializer,
720    {
721        self.value.serialize(ser)
722    }
723}
724
725/// A TOML table key with its source span.
726///
727/// Keys appear as the first element in `(`[`Key`]`, `[`Item`]`)` entry pairs
728/// when iterating over a [`Table`].
729#[derive(Copy, Clone)]
730pub struct Key<'de> {
731    /// The key name.
732    pub name: &'de str,
733    /// The byte-offset span of the key in the source document.
734    pub span: Span,
735}
736impl<'de> Key<'de> {
737    /// Returns the key name as a string slice.
738    pub fn as_str(&self) -> &'de str {
739        self.name
740    }
741}
742
743#[cfg(target_pointer_width = "64")]
744const _: () = assert!(std::mem::size_of::<Key<'_>>() == 24);
745
746impl std::borrow::Borrow<str> for Key<'_> {
747    fn borrow(&self) -> &str {
748        self.name
749    }
750}
751
752impl fmt::Debug for Key<'_> {
753    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
754        f.write_str(self.name)
755    }
756}
757
758impl fmt::Display for Key<'_> {
759    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
760        f.write_str(self.name)
761    }
762}
763
764impl Ord for Key<'_> {
765    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
766        self.name.cmp(other.name)
767    }
768}
769
770impl PartialOrd for Key<'_> {
771    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
772        Some(self.cmp(other))
773    }
774}
775
776impl PartialEq for Key<'_> {
777    fn eq(&self, other: &Self) -> bool {
778        self.name.eq(other.name)
779    }
780}
781
782impl Eq for Key<'_> {}
783
784impl<'de> std::ops::Index<&str> for Item<'de> {
785    type Output = MaybeItem<'de>;
786
787    #[inline]
788    fn index(&self, index: &str) -> &Self::Output {
789        if let Some(table) = self.as_table()
790            && let Some(item) = table.get(index)
791        {
792            return MaybeItem::from_ref(item);
793        }
794        &NONE
795    }
796}
797
798impl<'de> std::ops::Index<usize> for Item<'de> {
799    type Output = MaybeItem<'de>;
800
801    #[inline]
802    fn index(&self, index: usize) -> &Self::Output {
803        if let Some(arr) = self.as_array()
804            && let Some(item) = arr.get(index)
805        {
806            return MaybeItem::from_ref(item);
807        }
808        &NONE
809    }
810}
811
812impl<'de> std::ops::Index<&str> for MaybeItem<'de> {
813    type Output = MaybeItem<'de>;
814
815    #[inline]
816    fn index(&self, index: &str) -> &Self::Output {
817        if let Some(table) = self.as_table()
818            && let Some(item) = table.get(index)
819        {
820            return MaybeItem::from_ref(item);
821        }
822        &NONE
823    }
824}
825
826impl<'de> std::ops::Index<usize> for MaybeItem<'de> {
827    type Output = MaybeItem<'de>;
828
829    #[inline]
830    fn index(&self, index: usize) -> &Self::Output {
831        if let Some(arr) = self.as_array()
832            && let Some(item) = arr.get(index)
833        {
834            return MaybeItem::from_ref(item);
835        }
836        &NONE
837    }
838}
839
840/// A nullable reference to a parsed TOML value.
841///
842/// `MaybeItem` is returned by the index operators (`[]`) on [`Item`],
843/// [`Table`], [`Array`], and `MaybeItem` itself. It acts like an
844/// [`Option<&Item>`] that can be further indexed without panicking — chained
845/// lookups on missing keys simply propagate the `None` state.
846///
847/// Use the `as_*` accessors to extract a value, or call [`item`](Self::item)
848/// to get back an `Option<&Item>`.
849///
850/// # Examples
851///
852/// ```
853/// use toml_spanner::Arena;
854///
855/// let arena = Arena::new();
856/// let table = toml_spanner::parse(r#"
857/// [server]
858/// host = "localhost"
859/// port = 8080
860/// "#, &arena)?;
861///
862/// // Successful nested lookup.
863/// assert_eq!(table["server"]["host"].as_str(), Some("localhost"));
864/// assert_eq!(table["server"]["port"].as_i64(), Some(8080));
865///
866/// // Missing keys propagate through chained indexing without panicking.
867/// assert_eq!(table["server"]["missing"].as_str(), None);
868/// assert_eq!(table["nonexistent"]["deep"]["path"].as_str(), None);
869///
870/// // Convert back to an Option<&Item> when needed.
871/// assert!(table["server"]["host"].item().is_some());
872/// assert!(table["nope"].item().is_none());
873/// # Ok::<(), toml_spanner::Error>(())
874/// ```
875#[repr(C)]
876pub struct MaybeItem<'de> {
877    payload: Payload<'de>,
878    start_and_tag: u32,
879    end_and_flag: u32,
880}
881
882// SAFETY: MaybeItem is only constructed as either (a) the static NONE sentinel
883// (payload is zeroed, tag is TAG_NONE — no pointers are dereferenced), or
884// (b) a reinterpretation of &Item via from_ref. In both cases the data is
885// accessed only through shared references. All payload variants (integers,
886// floats, bools, &str, Array, InnerTable) are safe to share across threads
887// when behind a shared reference.
888unsafe impl Sync for MaybeItem<'_> {}
889
890pub(crate) static NONE: MaybeItem<'static> = MaybeItem {
891    payload: Payload { integer: 0 },
892    start_and_tag: TAG_NONE,
893    end_and_flag: FLAG_NONE,
894};
895
896impl<'de> MaybeItem<'de> {
897    /// Views an [`Item`] reference as a `MaybeItem`.
898    pub fn from_ref<'a>(item: &'a Item<'de>) -> &'a Self {
899        // SAFETY: Item and MaybeItem are both #[repr(C)] with identical field
900        // layout (Payload, u32, u32). Size and alignment equality is verified
901        // by const assertions.
902        unsafe { &*(item as *const Item<'de>).cast::<MaybeItem<'de>>() }
903    }
904    #[inline]
905    pub(crate) fn tag(&self) -> u32 {
906        self.start_and_tag & TAG_MASK
907    }
908    /// Returns the underlying [`Item`], or [`None`] if this is a missing value.
909    pub fn item(&self) -> Option<&Item<'de>> {
910        if self.tag() != TAG_NONE {
911            // SAFETY: tag != TAG_NONE means this was created via from_ref from
912            // a valid Item. Item and MaybeItem have identical repr(C) layout.
913            Some(unsafe { &*(self as *const MaybeItem<'de>).cast::<Item<'de>>() })
914        } else {
915            None
916        }
917    }
918    /// Returns a borrowed string if this is a string value.
919    #[inline]
920    pub fn as_str(&self) -> Option<&str> {
921        if self.tag() == TAG_STRING {
922            // SAFETY: tag check guarantees the payload is a string.
923            Some(unsafe { self.payload.string })
924        } else {
925            None
926        }
927    }
928
929    /// Returns an `i64` if this is an integer value.
930    #[inline]
931    pub fn as_i64(&self) -> Option<i64> {
932        if self.tag() == TAG_INTEGER {
933            // SAFETY: tag check guarantees the payload is an integer.
934            Some(unsafe { self.payload.integer })
935        } else {
936            None
937        }
938    }
939
940    /// Returns an `f64` if this is a float or integer value.
941    ///
942    /// Integer values are converted to `f64` via `as` cast (lossy for large
943    /// values outside the 2^53 exact-integer range).
944    #[inline]
945    pub fn as_f64(&self) -> Option<f64> {
946        self.item()?.as_f64()
947    }
948
949    /// Returns a `bool` if this is a boolean value.
950    #[inline]
951    pub fn as_bool(&self) -> Option<bool> {
952        if self.tag() == TAG_BOOLEAN {
953            // SAFETY: tag check guarantees the payload is a boolean.
954            Some(unsafe { self.payload.boolean })
955        } else {
956            None
957        }
958    }
959
960    /// Returns a borrowed array if this is an array value.
961    #[inline]
962    pub fn as_array(&self) -> Option<&Array<'de>> {
963        if self.tag() == TAG_ARRAY {
964            // SAFETY: tag check guarantees the payload is an array.
965            Some(unsafe { &self.payload.array })
966        } else {
967            None
968        }
969    }
970
971    /// Returns a borrowed table if this is a table value.
972    #[inline]
973    pub fn as_table(&self) -> Option<&Table<'de>> {
974        if self.tag() == TAG_TABLE {
975            // SAFETY: tag == TAG_TABLE guarantees the payload is a table.
976            // MaybeItem and Table have identical repr(C) layout (verified by
977            // const size/align assertions on Item and Table).
978            Some(unsafe { &*(self as *const Self).cast::<Table<'de>>() })
979        } else {
980            None
981        }
982    }
983
984    /// Returns a borrowed [`DateTime`] if this is a datetime value.
985    #[inline]
986    pub fn as_datetime(&self) -> Option<&DateTime> {
987        if self.tag() == TAG_DATETIME {
988            // SAFETY: tag check guarantees the payload is a moment.
989            Some(unsafe { &self.payload.moment })
990        } else {
991            None
992        }
993    }
994
995    /// Returns the source span, or [`None`] if this is a missing value.
996    pub fn span(&self) -> Option<Span> {
997        if let Some(item) = self.item() {
998            Some(item.span())
999        } else {
1000            None
1001        }
1002    }
1003
1004    /// Returns a borrowed [`Value`] for pattern matching, or [`None`] if missing.
1005    pub fn value(&self) -> Option<Value<'_, 'de>> {
1006        if let Some(item) = self.item() {
1007            Some(item.value())
1008        } else {
1009            None
1010        }
1011    }
1012}