toml_spanner/item/owned.rs
1#[cfg(test)]
2#[path = "./owned_tests.rs"]
3mod tests;
4
5use crate::item::{TAG_ARRAY, TAG_STRING, TAG_TABLE};
6use crate::{Array, DateTime, Item, Key, Kind, Span, Table, TableStyle, Value};
7use std::mem::size_of;
8use std::ptr::NonNull;
9
10/// Write cursor into an [`OwnedItem`] allocation, used by
11/// [`Item::emplace_in`] to copy item trees without an arena.
12///
13/// The allocation is split into two contiguous regions:
14/// - **aligned** (front): table entries and array elements (8-byte aligned)
15/// - **string** (back): key names and string values (packed, no alignment)
16pub(crate) struct ItemCopyTarget {
17 pub(crate) aligned: *mut u8,
18 #[cfg(debug_assertions)]
19 pub(crate) aligned_end: *mut u8,
20 pub(crate) string: *mut u8,
21 #[cfg(debug_assertions)]
22 pub(crate) string_end: *mut u8,
23}
24
25impl ItemCopyTarget {
26 /// Bumps the aligned pointer forward by `size` bytes, returning
27 /// a pointer to the start of the allocated region.
28 ///
29 /// # Safety
30 ///
31 /// `size` bytes must remain in the aligned region.
32 pub(crate) unsafe fn alloc_aligned(&mut self, size: usize) -> NonNull<u8> {
33 #[cfg(debug_assertions)]
34 // SAFETY: Both pointers are within (or one-past-the-end of) the
35 // same allocation, so offset_from is well-defined.
36 unsafe {
37 let remaining = self.aligned_end.offset_from(self.aligned) as usize;
38 assert!(size <= remaining);
39 };
40 let ptr = self.aligned;
41 // SAFETY: Caller guarantees sufficient space in the aligned region.
42 unsafe {
43 self.aligned = self.aligned.add(size);
44 NonNull::new_unchecked(ptr)
45 }
46 }
47
48 /// Copies a string into the string region and returns a reference to it.
49 ///
50 /// # Safety
51 ///
52 /// `s.len()` bytes must remain in the string region. The returned
53 /// reference is `'static` only because OwnedItem manages the backing
54 /// memory; the caller must not let it escape.
55 pub(crate) unsafe fn copy_str(&mut self, s: &str) -> &'static str {
56 if s.is_empty() {
57 return "";
58 }
59 let len = s.len();
60 #[cfg(debug_assertions)]
61 // SAFETY: Both pointers are within (or one-past-the-end of) the
62 // same allocation, so offset_from is well-defined.
63 unsafe {
64 let remaining = self.string_end.offset_from(self.string) as usize;
65 assert!(len <= remaining);
66 };
67 // SAFETY: Caller guarantees sufficient space. Source and destination
68 // do not overlap (source is the parsed input or arena, destination is
69 // the OwnedItem allocation).
70 unsafe {
71 std::ptr::copy_nonoverlapping(s.as_ptr(), self.string, len);
72 let result =
73 std::str::from_utf8_unchecked(std::slice::from_raw_parts(self.string, len));
74 self.string = self.string.add(len);
75 result
76 }
77 }
78}
79
80/// Computes the total aligned and string bytes needed to deep-copy `item`.
81fn compute_size(item: &Item<'_>, aligned: &mut usize, strings: &mut usize) {
82 match item.tag() {
83 TAG_STRING => {
84 if let Some(s) = item.as_str() {
85 *strings += s.len();
86 }
87 }
88 TAG_TABLE => {
89 // SAFETY: tag == TAG_TABLE guarantees this is a table item.
90 let table = unsafe { item.as_table_unchecked() };
91 *aligned += table.len() * size_of::<(Key<'_>, Item<'_>)>();
92 for (key, child) in table {
93 *strings += key.name.len();
94 compute_size(child, aligned, strings);
95 }
96 }
97 TAG_ARRAY => {
98 // SAFETY: tag == TAG_ARRAY guarantees this is an array item.
99 let array = unsafe { item.as_array_unchecked() };
100 *aligned += array.len() * size_of::<Item<'_>>();
101 for child in array {
102 compute_size(child, aligned, strings);
103 }
104 }
105 _ => {}
106 }
107}
108
109/// A self-contained TOML table that owns its backing storage.
110///
111/// The table-specific counterpart of [`OwnedItem`]. The inner value
112/// is always a table, so [`table()`](Self::table) is infallible.
113///
114/// # Flattening
115///
116/// [`FromFlattened`](crate::FromFlattened) is not provided because
117/// [`flatten_any`](crate::helper::flatten_any) is more performant
118/// than direct implementation could be, due to current trait definition.
119///
120/// ```rust,ignore
121/// use toml_spanner::Toml;
122/// use toml_spanner::helper::flatten_any;
123///
124/// #[derive(Toml)]
125/// #[toml(Toml)]
126/// struct Config {
127/// name: String,
128/// #[toml(flatten, with = flatten_any)]
129/// extra: OwnedTable,
130/// }
131/// ```
132///
133/// # Examples
134///
135/// ```
136/// use toml_spanner::OwnedTable;
137///
138/// let owned: OwnedTable = toml_spanner::from_str("
139/// host = 'localhost'
140/// port = 8080
141/// ").unwrap();
142///
143/// assert_eq!(owned.get("host").unwrap().as_str(), Some("localhost"));
144/// assert_eq!(owned.len(), 2);
145/// ```
146pub struct OwnedTable {
147 inner: OwnedItem,
148}
149
150impl OwnedTable {
151 /// Returns a reference to the contained [`Table`].
152 #[inline(always)]
153 pub fn table<'a>(&'a self) -> &'a Table<'a> {
154 // SAFETY: OwnedItem guarantees the item is valid for the lifetime of self.
155 unsafe { self.inner.item().as_table_unchecked() }
156 }
157
158 /// Returns the source span, or `0..0` if this table was constructed
159 /// programmatically (format-hints mode).
160 #[inline]
161 pub fn span(&self) -> Span {
162 self.table().span()
163 }
164
165 /// Returns the number of entries.
166 #[inline]
167 pub fn len(&self) -> usize {
168 self.table().len()
169 }
170
171 /// Returns `true` if the table has no entries.
172 #[inline]
173 pub fn is_empty(&self) -> bool {
174 self.table().is_empty()
175 }
176
177 /// Returns references to both key and value for `name`.
178 pub fn get_key_value<'a>(&'a self, name: &str) -> Option<(&'a Key<'a>, &'a Item<'a>)> {
179 self.table().get_key_value(name)
180 }
181
182 /// Returns a reference to the value for `name`.
183 pub fn get<'a>(&'a self, name: &str) -> Option<&'a Item<'a>> {
184 self.table().get(name)
185 }
186
187 /// Returns `true` if the table contains the key.
188 #[inline]
189 pub fn contains_key(&self, name: &str) -> bool {
190 self.table().contains_key(name)
191 }
192
193 /// Returns a slice of all entries.
194 #[inline]
195 pub fn entries<'a>(&'a self) -> &'a [(Key<'a>, Item<'a>)] {
196 self.table().entries()
197 }
198
199 /// Returns an iterator over all entries (key-value pairs).
200 #[inline]
201 pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, (Key<'a>, Item<'a>)> {
202 self.table().iter()
203 }
204
205 /// Returns the contained table as an [`Item`] reference.
206 #[inline]
207 pub fn as_item<'a>(&'a self) -> &'a Item<'a> {
208 self.inner.item()
209 }
210
211 /// Returns the kind of this table (implicit, dotted, header, or inline).
212 #[inline]
213 pub fn style(&self) -> TableStyle {
214 self.table().style()
215 }
216}
217
218impl std::fmt::Debug for OwnedTable {
219 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220 self.table().fmt(f)
221 }
222}
223
224impl Clone for OwnedTable {
225 fn clone(&self) -> Self {
226 Self {
227 inner: self.inner.clone(),
228 }
229 }
230}
231
232impl PartialEq for OwnedTable {
233 fn eq(&self, other: &Self) -> bool {
234 self.table() == other.table()
235 }
236}
237
238impl<'a> IntoIterator for &'a OwnedTable {
239 type Item = &'a (Key<'a>, Item<'a>);
240 type IntoIter = std::slice::Iter<'a, (Key<'a>, Item<'a>)>;
241
242 fn into_iter(self) -> Self::IntoIter {
243 self.table().iter()
244 }
245}
246
247impl From<&Table<'_>> for OwnedTable {
248 fn from(value: &Table<'_>) -> Self {
249 let owned_item = OwnedItem::from(value.as_item());
250 debug_assert_eq!(owned_item.item().kind(), Kind::Table);
251 Self { inner: owned_item }
252 }
253}
254
255/// A self-contained TOML value that owns its backing storage.
256///
257/// An [`Item`] normally borrows from an [`Arena`](crate::Arena).
258/// `OwnedItem` bundles the value with its own allocation so it can
259/// be stored, returned, or moved independently of any parse context.
260///
261/// Create one by converting from an [`Item`] reference or value, or
262/// deserialize directly via [`FromToml`](crate::FromToml). Access
263/// the underlying [`Item`] through [`item()`](Self::item).
264///
265/// [`FromFlattened`](crate::FromFlattened) is not provided because
266/// [`flatten_any`](crate::helper::flatten_any) is more performant
267/// than direct implementation could be. See [`OwnedTable`] for an example.
268///
269/// # Examples
270///
271/// ```
272/// use toml_spanner::{Arena, OwnedItem, parse};
273///
274/// let arena = Arena::new();
275/// let doc = parse("greeting = 'hello'", &arena).unwrap();
276/// let owned = OwnedItem::from(doc.table()["greeting"].item().unwrap());
277///
278/// drop(arena);
279/// assert_eq!(owned.as_str(), Some("hello"));
280/// ```
281pub struct OwnedItem {
282 // This 'static is a lie! It borrows from ptr.
283 item: Item<'static>,
284 ptr: NonNull<u8>,
285 capacity: usize,
286}
287
288// SAFETY: `OwnedItem` exclusively owns the heap allocation referenced by
289// `ptr` (allocated in `From<&Item>` and freed in `Drop`). The embedded
290// `Item<'static>` has `NonNull`s into that same allocation. No other handle
291// to the allocation exists, and the crate never applies interior mutability
292// to an `OwnedItem` (`Clone` produces a fresh allocation, and there is no
293// method that mutates the buffer through `&self`). Transferring ownership
294// to another thread moves the allocation as one unit; sharing `&OwnedItem`
295// across threads is sound because every accessor returns borrowed data from
296// memory that is only ever read.
297unsafe impl Send for OwnedItem {}
298unsafe impl Sync for OwnedItem {}
299
300// SAFETY: `OwnedTable` is a thin wrapper around `OwnedItem` and inherits
301// the same ownership and immutability invariants.
302unsafe impl Send for OwnedTable {}
303unsafe impl Sync for OwnedTable {}
304
305impl std::fmt::Debug for OwnedItem {
306 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
307 self.item.fmt(f)
308 }
309}
310
311impl Clone for OwnedItem {
312 fn clone(&self) -> Self {
313 OwnedItem::from(self.item())
314 }
315}
316
317impl Drop for OwnedItem {
318 fn drop(&mut self) {
319 if self.capacity > 0 {
320 // Pedantically, remove the allocated Item before deallocating.
321 // probrably not needed, MIRI seams to be happy without it.
322 // self.item = Item::from(false);
323
324 // SAFETY: ptr was allocated with Layout { size: capacity, align: 8 }
325 // in `From<&Item>`. capacity > 0 guarantees a real allocation.
326 unsafe {
327 let layout = std::alloc::Layout::from_size_align_unchecked(self.capacity, 8);
328 std::alloc::dealloc(self.ptr.as_ptr(), layout);
329 }
330 }
331 }
332}
333
334impl<'a> From<&Item<'a>> for OwnedItem {
335 /// Creates an `OwnedItem` by copying `item` into a single managed allocation.
336 ///
337 /// All strings (keys and values) are copied, and all table/array
338 /// backing storage is laid out in one contiguous buffer. The result
339 /// is fully independent of the source arena.
340 fn from(item: &Item<'a>) -> Self {
341 let mut aligned = 0usize;
342 let mut strings = 0usize;
343 compute_size(item, &mut aligned, &mut strings);
344 let total = aligned + strings;
345
346 if total == 0 {
347 // SAFETY: When total is 0 the item is either a non-string scalar
348 // (no borrowed data), an empty string (payload is static ""), or
349 // an empty container (dangling pointer with len 0). Transmuting
350 // the lifetime is safe because nothing actually borrows from an
351 // arena. Item has no Drop impl.
352 return Self {
353 item: unsafe { std::mem::transmute_copy(item) },
354 ptr: NonNull::dangling(),
355 capacity: 0,
356 };
357 }
358
359 let layout = std::alloc::Layout::from_size_align(total, 8).expect("layout overflow");
360 // SAFETY: layout has non-zero size (total > 0).
361 let raw = unsafe { std::alloc::alloc(layout) };
362 let Some(base) = NonNull::new(raw) else {
363 std::alloc::handle_alloc_error(layout);
364 };
365
366 // SAFETY: base.add(aligned) and base.add(total) are within or
367 // one-past-the-end of the allocation.
368 let mut target = unsafe {
369 ItemCopyTarget {
370 aligned: base.as_ptr(),
371 #[cfg(debug_assertions)]
372 aligned_end: base.as_ptr().add(aligned),
373 string: base.as_ptr().add(aligned),
374 #[cfg(debug_assertions)]
375 string_end: base.as_ptr().add(total),
376 }
377 };
378
379 // SAFETY: compute_size computed the exact space needed; emplace_in
380 // consumes exactly that much from target.
381 let new_item = unsafe { item.emplace_in(&mut target) };
382
383 #[cfg(debug_assertions)]
384 {
385 assert_eq!(target.aligned as usize, base.as_ptr() as usize + aligned);
386 assert_eq!(target.string as usize, base.as_ptr() as usize + total);
387 }
388
389 Self {
390 item: new_item,
391 ptr: base,
392 capacity: total,
393 }
394 }
395}
396
397impl<'a> From<Item<'a>> for OwnedItem {
398 /// Creates an `OwnedItem` by copying `item` into a single managed allocation.
399 ///
400 /// This is a convenience wrapper that delegates to `From<&Item>`.
401 fn from(item: Item<'a>) -> Self {
402 OwnedItem::from(&item)
403 }
404}
405
406impl OwnedItem {
407 /// Returns a reference to the contained [`Item`].
408 ///
409 /// The returned item borrows from `self` and provides the same
410 /// accessor methods as any other [`Item`] (`as_str()`, `as_table()`,
411 /// `value()`, etc.).
412 #[inline(always)]
413 pub fn item<'a>(&'a self) -> &'a Item<'a> {
414 &self.item
415 }
416
417 /// Returns the type discriminant of this value.
418 #[inline]
419 pub fn kind(&self) -> Kind {
420 self.item().kind()
421 }
422
423 /// Returns the source span, or `0..0` if this item was constructed
424 /// programmatically (format-hints mode).
425 #[inline]
426 pub fn span(&self) -> Span {
427 self.item().span()
428 }
429
430 /// Returns a borrowed string if this is a string value.
431 #[inline]
432 pub fn as_str(&self) -> Option<&str> {
433 self.item().as_str()
434 }
435
436 /// Returns an `i128` if this is an integer value.
437 #[inline]
438 pub fn as_i128(&self) -> Option<i128> {
439 self.item().as_i128()
440 }
441
442 /// Returns an `i64` if this is an integer value that fits in the `i64` range.
443 #[inline]
444 pub fn as_i64(&self) -> Option<i64> {
445 self.item().as_i64()
446 }
447
448 /// Returns a `u64` if this is an integer value that fits in the `u64` range.
449 #[inline]
450 pub fn as_u64(&self) -> Option<u64> {
451 self.item().as_u64()
452 }
453
454 /// Returns an `f64` if this is a float or integer value.
455 ///
456 /// Integer values are converted to `f64` via `as` cast (lossy for large
457 /// values outside the 2^53 exact-integer range).
458 #[inline]
459 pub fn as_f64(&self) -> Option<f64> {
460 self.item().as_f64()
461 }
462
463 /// Returns a `bool` if this is a boolean value.
464 #[inline]
465 pub fn as_bool(&self) -> Option<bool> {
466 self.item().as_bool()
467 }
468
469 /// Returns a borrowed array if this is an array value.
470 #[inline]
471 pub fn as_array<'a>(&'a self) -> Option<&'a Array<'a>> {
472 self.item().as_array()
473 }
474
475 /// Returns a borrowed table if this is a table value.
476 #[inline]
477 pub fn as_table<'a>(&'a self) -> Option<&'a Table<'a>> {
478 self.item().as_table()
479 }
480
481 /// Returns a borrowed [`DateTime`] if this is a datetime value.
482 #[inline]
483 pub fn as_datetime(&self) -> Option<&DateTime> {
484 self.item().as_datetime()
485 }
486
487 /// Returns a borrowed view for pattern matching.
488 #[inline]
489 pub fn value<'a>(&'a self) -> Value<'a, 'a> {
490 self.item().value()
491 }
492
493 /// Returns `true` if the value is a non-empty table.
494 #[inline]
495 pub fn has_keys(&self) -> bool {
496 self.item().has_keys()
497 }
498
499 /// Returns `true` if the value is a table containing `key`.
500 #[inline]
501 pub fn has_key(&self, key: &str) -> bool {
502 self.item().has_key(key)
503 }
504}
505
506impl PartialEq for OwnedItem {
507 fn eq(&self, other: &Self) -> bool {
508 self.item() == other.item()
509 }
510}
511
512#[cfg(feature = "from-toml")]
513impl<'a> crate::FromToml<'a> for OwnedItem {
514 fn from_toml(_: &mut crate::Context<'a>, item: &Item<'a>) -> Result<Self, crate::Failed> {
515 Ok(OwnedItem::from(item))
516 }
517}
518
519#[cfg(feature = "to-toml")]
520impl crate::ToToml for OwnedItem {
521 fn to_toml<'a>(&'a self, arena: &'a crate::Arena) -> Result<Item<'a>, crate::ToTomlError> {
522 Ok(self.item().clone_in(arena))
523 }
524}
525
526#[cfg(feature = "to-toml")]
527impl crate::ToFlattened for OwnedItem {
528 fn to_flattened<'a>(
529 &'a self,
530 arena: &'a crate::Arena,
531 table: &mut crate::Table<'a>,
532 ) -> Result<(), crate::ToTomlError> {
533 self.item().to_flattened(arena, table)
534 }
535}
536
537#[cfg(feature = "from-toml")]
538impl<'a> crate::FromToml<'a> for OwnedTable {
539 fn from_toml(ctx: &mut crate::Context<'a>, item: &Item<'a>) -> Result<Self, crate::Failed> {
540 let Ok(table) = item.require_table(ctx) else {
541 return Err(crate::Failed);
542 };
543 Ok(OwnedTable::from(table))
544 }
545}
546
547#[cfg(feature = "to-toml")]
548impl crate::ToToml for OwnedTable {
549 fn to_toml<'a>(&'a self, arena: &'a crate::Arena) -> Result<Item<'a>, crate::ToTomlError> {
550 Ok(self.table().as_item().clone_in(arena))
551 }
552}
553
554#[cfg(feature = "to-toml")]
555impl crate::ToFlattened for OwnedTable {
556 fn to_flattened<'a>(
557 &'a self,
558 arena: &'a crate::Arena,
559 table: &mut crate::Table<'a>,
560 ) -> Result<(), crate::ToTomlError> {
561 self.table().to_flattened(arena, table)
562 }
563}