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