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