Skip to main content

toml_spanner/
value.rs

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