• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

facet-rs / facet / 19925267064

04 Dec 2025 10:10AM UTC coverage: 57.159% (-0.1%) from 57.255%
19925267064

Pull #1040

github

web-flow
Merge 0e7b8bc43 into 78fc8a235
Pull Request #1040: perf: implement dual-mode storage for VObject

85 of 204 new or added lines in 1 file covered. (41.67%)

7 existing lines in 1 file now uncovered.

21725 of 38008 relevant lines covered (57.16%)

511.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

61.58
/facet-value/src/object.rs
1
//! Object (map) value type.
2

3
#[cfg(feature = "alloc")]
4
use alloc::alloc::{Layout, alloc, dealloc, realloc};
5
#[cfg(feature = "alloc")]
6
use alloc::borrow::ToOwned;
7
#[cfg(feature = "std")]
8
use alloc::boxed::Box;
9
#[cfg(feature = "alloc")]
10
use alloc::collections::BTreeMap;
11
use core::fmt::{self, Debug, Formatter};
12
use core::hash::{Hash, Hasher};
13
use core::iter::FromIterator;
14
use core::ops::{Index, IndexMut};
15
use core::{cmp, mem, ptr};
16

17
#[cfg(feature = "std")]
18
use indexmap::IndexMap;
19
#[cfg(feature = "std")]
20
use std::collections::HashMap;
21

22
use crate::string::VString;
23
use crate::value::{TypeTag, Value};
24

25
/// Threshold at which we switch from inline array to IndexMap storage.
26
/// Below this size, linear search is competitive with hash lookups due to cache locality.
27
#[cfg(feature = "std")]
28
const LARGE_MODE_THRESHOLD: usize = 32;
29

30
/// Sentinel value for capacity indicating large mode (IndexMap storage).
31
#[cfg(feature = "std")]
32
const LARGE_MODE_CAP_SENTINEL: usize = usize::MAX;
33

34
/// A key-value pair.
35
#[repr(C)]
36
struct KeyValuePair {
37
    key: VString,
38
    value: Value,
39
}
40

41
/// Header for heap-allocated objects in small mode.
42
#[repr(C, align(8))]
43
struct ObjectHeader {
44
    /// Number of key-value pairs
45
    len: usize,
46
    /// Capacity (usize::MAX indicates large mode with IndexMap storage)
47
    cap: usize,
48
    // Array of KeyValuePair follows immediately after (only in small mode)
49
}
50

51
/// Wrapper for IndexMap storage in large mode.
52
/// Uses the same layout prefix as ObjectHeader so we can detect the mode.
53
#[cfg(feature = "std")]
54
#[repr(C, align(8))]
55
struct LargeModeStorage {
56
    /// Unused in large mode, but must be at same offset as ObjectHeader.len
57
    _len_unused: usize,
58
    /// Sentinel value (usize::MAX) to indicate large mode
59
    cap_sentinel: usize,
60
    /// The actual IndexMap
61
    map: IndexMap<VString, Value>,
62
}
63

64
/// An object (map) value.
65
///
66
/// `VObject` is an ordered map of string keys to `Value`s.
67
/// It preserves insertion order.
68
///
69
/// Storage modes:
70
/// - Small mode (default): inline array of KeyValuePair with linear search
71
/// - Large mode (std feature, >= 32 entries): IndexMap for O(1) lookups
72
#[repr(transparent)]
73
#[derive(Clone)]
74
pub struct VObject(pub(crate) Value);
75

76
impl VObject {
77
    fn layout(cap: usize) -> Layout {
642✔
78
        Layout::new::<ObjectHeader>()
642✔
79
            .extend(Layout::array::<KeyValuePair>(cap).unwrap())
642✔
80
            .unwrap()
642✔
81
            .0
642✔
82
            .pad_to_align()
642✔
83
    }
642✔
84

85
    #[cfg(feature = "alloc")]
86
    fn alloc(cap: usize) -> *mut ObjectHeader {
167✔
87
        unsafe {
88
            let layout = Self::layout(cap);
167✔
89
            let ptr = alloc(layout).cast::<ObjectHeader>();
167✔
90
            (*ptr).len = 0;
167✔
91
            (*ptr).cap = cap;
167✔
92
            ptr
167✔
93
        }
94
    }
167✔
95

96
    #[cfg(feature = "alloc")]
97
    fn realloc_ptr(ptr: *mut ObjectHeader, new_cap: usize) -> *mut ObjectHeader {
154✔
98
        unsafe {
99
            let old_cap = (*ptr).cap;
154✔
100
            let old_layout = Self::layout(old_cap);
154✔
101
            let new_layout = Self::layout(new_cap);
154✔
102
            let new_ptr =
154✔
103
                realloc(ptr.cast::<u8>(), old_layout, new_layout.size()).cast::<ObjectHeader>();
154✔
104
            (*new_ptr).cap = new_cap;
154✔
105
            new_ptr
154✔
106
        }
107
    }
154✔
108

109
    #[cfg(feature = "alloc")]
110
    fn dealloc_ptr(ptr: *mut ObjectHeader) {
167✔
111
        unsafe {
167✔
112
            let cap = (*ptr).cap;
167✔
113
            let layout = Self::layout(cap);
167✔
114
            dealloc(ptr.cast::<u8>(), layout);
167✔
115
        }
167✔
116
    }
167✔
117

118
    /// Returns true if this object is in large mode (IndexMap storage).
119
    #[cfg(feature = "std")]
120
    #[inline]
121
    fn is_large_mode(&self) -> bool {
10,247✔
122
        // In large mode, the cap_sentinel field (at same offset as ObjectHeader.cap)
123
        // is set to LARGE_MODE_CAP_SENTINEL
124
        unsafe {
125
            let header = self.0.heap_ptr() as *const ObjectHeader;
10,247✔
126
            (*header).cap == LARGE_MODE_CAP_SENTINEL
10,247✔
127
        }
128
    }
10,247✔
129

130
    /// Returns true if this object is in large mode (IndexMap storage).
131
    #[cfg(not(feature = "std"))]
132
    #[inline]
133
    fn is_large_mode(&self) -> bool {
134
        // Without std, we never use large mode
135
        false
136
    }
137

138
    /// Returns a reference to the IndexMap (large mode only).
139
    #[cfg(feature = "std")]
140
    #[inline]
NEW
141
    fn as_indexmap(&self) -> &IndexMap<VString, Value> {
×
NEW
142
        debug_assert!(self.is_large_mode());
×
143
        unsafe {
NEW
144
            let storage = self.0.heap_ptr() as *const LargeModeStorage;
×
NEW
145
            &(*storage).map
×
146
        }
NEW
147
    }
×
148

149
    /// Returns a mutable reference to the IndexMap (large mode only).
150
    #[cfg(feature = "std")]
151
    #[inline]
NEW
152
    fn as_indexmap_mut(&mut self) -> &mut IndexMap<VString, Value> {
×
NEW
153
        debug_assert!(self.is_large_mode());
×
NEW
154
        unsafe {
×
NEW
155
            let storage = self.0.heap_ptr_mut() as *mut LargeModeStorage;
×
NEW
156
            &mut (*storage).map
×
NEW
157
        }
×
NEW
158
    }
×
159

160
    fn header(&self) -> &ObjectHeader {
3,122✔
161
        debug_assert!(!self.is_large_mode());
3,122✔
162
        unsafe { &*(self.0.heap_ptr() as *const ObjectHeader) }
3,122✔
163
    }
3,122✔
164

165
    fn header_mut(&mut self) -> &mut ObjectHeader {
538✔
166
        debug_assert!(!self.is_large_mode());
538✔
167
        unsafe { &mut *(self.0.heap_ptr_mut() as *mut ObjectHeader) }
538✔
168
    }
538✔
169

170
    fn items_ptr(&self) -> *const KeyValuePair {
927✔
171
        debug_assert!(!self.is_large_mode());
927✔
172
        // Go through heap_ptr directly to avoid creating intermediate reference
173
        // that would limit provenance to just the header
174
        unsafe { (self.0.heap_ptr() as *const ObjectHeader).add(1).cast() }
927✔
175
    }
927✔
176

177
    fn items_ptr_mut(&mut self) -> *mut KeyValuePair {
565✔
178
        debug_assert!(!self.is_large_mode());
565✔
179
        // Use heap_ptr_mut directly to preserve mutable provenance
180
        unsafe { (self.0.heap_ptr_mut() as *mut ObjectHeader).add(1).cast() }
565✔
181
    }
565✔
182

183
    fn items(&self) -> &[KeyValuePair] {
927✔
184
        debug_assert!(!self.is_large_mode());
927✔
185
        unsafe { core::slice::from_raw_parts(self.items_ptr(), self.small_len()) }
927✔
186
    }
927✔
187

188
    fn items_mut(&mut self) -> &mut [KeyValuePair] {
27✔
189
        debug_assert!(!self.is_large_mode());
27✔
190
        unsafe { core::slice::from_raw_parts_mut(self.items_ptr_mut(), self.small_len()) }
27✔
191
    }
27✔
192

193
    /// Returns the length when in small mode.
194
    #[inline]
195
    fn small_len(&self) -> usize {
1,225✔
196
        debug_assert!(!self.is_large_mode());
1,225✔
197
        self.header().len
1,225✔
198
    }
1,225✔
199

200
    /// Converts from small mode to large mode (IndexMap).
201
    #[cfg(feature = "std")]
NEW
202
    fn convert_to_large_mode(&mut self) {
×
NEW
203
        debug_assert!(!self.is_large_mode());
×
204

205
        // Build IndexMap from existing items
NEW
206
        let mut map = IndexMap::with_capacity(self.small_len() + 1);
×
207
        unsafe {
NEW
208
            let len = self.small_len();
×
NEW
209
            let items_ptr = self.items_ptr_mut();
×
210

211
            // Move items into the IndexMap (taking ownership)
NEW
212
            for i in 0..len {
×
NEW
213
                let kv = items_ptr.add(i).read();
×
NEW
214
                map.insert(kv.key, kv.value);
×
NEW
215
            }
×
216

217
            // Free the old small-mode allocation
NEW
218
            Self::dealloc_ptr(self.0.heap_ptr_mut().cast());
×
219

220
            // Allocate and store the LargeModeStorage
NEW
221
            let storage = LargeModeStorage {
×
NEW
222
                _len_unused: 0,
×
NEW
223
                cap_sentinel: LARGE_MODE_CAP_SENTINEL,
×
NEW
224
                map,
×
NEW
225
            };
×
NEW
226
            let boxed = Box::new(storage);
×
NEW
227
            let ptr = Box::into_raw(boxed);
×
NEW
228
            self.0.set_ptr(ptr.cast());
×
229
        }
UNCOV
230
    }
×
231

232
    /// Creates a new empty object.
233
    #[cfg(feature = "alloc")]
234
    #[inline]
235
    #[must_use]
236
    pub fn new() -> Self {
155✔
237
        Self::with_capacity(0)
155✔
238
    }
155✔
239

240
    /// Creates a new object with the specified capacity.
241
    #[cfg(feature = "alloc")]
242
    #[must_use]
243
    pub fn with_capacity(cap: usize) -> Self {
167✔
244
        // For large initial capacity with std feature, start directly in large mode
245
        #[cfg(feature = "std")]
246
        if cap >= LARGE_MODE_THRESHOLD {
167✔
NEW
247
            let storage = LargeModeStorage {
×
NEW
248
                _len_unused: 0,
×
NEW
249
                cap_sentinel: LARGE_MODE_CAP_SENTINEL,
×
NEW
250
                map: IndexMap::with_capacity(cap),
×
NEW
251
            };
×
NEW
252
            let boxed = Box::new(storage);
×
NEW
253
            let ptr = Box::into_raw(boxed);
×
NEW
254
            return VObject(unsafe { Value::new_ptr(ptr.cast(), TypeTag::Object) });
×
255
        }
167✔
256

257
        unsafe {
258
            let ptr = Self::alloc(cap);
167✔
259
            VObject(Value::new_ptr(ptr.cast(), TypeTag::Object))
167✔
260
        }
261
    }
167✔
262

263
    /// Returns the number of entries.
264
    #[inline]
265
    #[must_use]
266
    pub fn len(&self) -> usize {
1,092✔
267
        #[cfg(feature = "std")]
268
        if self.is_large_mode() {
1,092✔
NEW
269
            return self.as_indexmap().len();
×
270
        }
1,092✔
271
        self.header().len
1,092✔
272
    }
1,092✔
273

274
    /// Returns `true` if the object is empty.
275
    #[inline]
276
    #[must_use]
277
    pub fn is_empty(&self) -> bool {
442✔
278
        self.len() == 0
442✔
279
    }
442✔
280

281
    /// Returns the capacity.
282
    #[inline]
283
    #[must_use]
284
    pub fn capacity(&self) -> usize {
269✔
285
        #[cfg(feature = "std")]
286
        if self.is_large_mode() {
269✔
NEW
287
            return self.as_indexmap().capacity();
×
288
        }
269✔
289
        self.header().cap
269✔
290
    }
269✔
291

292
    /// Reserves capacity for at least `additional` more entries.
293
    #[cfg(feature = "alloc")]
294
    pub fn reserve(&mut self, additional: usize) {
269✔
295
        #[cfg(feature = "std")]
296
        if self.is_large_mode() {
269✔
NEW
297
            self.as_indexmap_mut().reserve(additional);
×
NEW
298
            return;
×
299
        }
269✔
300

301
        let current_cap = self.capacity();
269✔
302
        let desired_cap = self
269✔
303
            .len()
269✔
304
            .checked_add(additional)
269✔
305
            .expect("capacity overflow");
269✔
306

307
        if current_cap >= desired_cap {
269✔
308
            return;
115✔
309
        }
154✔
310

311
        let new_cap = cmp::max(current_cap * 2, desired_cap.max(4));
154✔
312

313
        unsafe {
154✔
314
            let new_ptr = Self::realloc_ptr(self.0.heap_ptr_mut().cast(), new_cap);
154✔
315
            self.0.set_ptr(new_ptr.cast());
154✔
316
        }
154✔
317
    }
269✔
318

319
    /// Gets a value by key.
320
    #[inline]
321
    #[must_use]
322
    pub fn get(&self, key: &str) -> Option<&Value> {
146✔
323
        #[cfg(feature = "std")]
324
        if self.is_large_mode() {
146✔
NEW
325
            return self.as_indexmap().get(key);
×
326
        }
146✔
327
        self.items()
146✔
328
            .iter()
146✔
329
            .find(|kv| kv.key.as_str() == key)
196✔
330
            .map(|kv| &kv.value)
146✔
331
    }
146✔
332

333
    /// Gets a mutable value by key.
334
    #[inline]
335
    pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
26✔
336
        #[cfg(feature = "std")]
337
        if self.is_large_mode() {
26✔
NEW
338
            return self.as_indexmap_mut().get_mut(key);
×
339
        }
26✔
340
        self.items_mut()
26✔
341
            .iter_mut()
26✔
342
            .find(|kv| kv.key.as_str() == key)
26✔
343
            .map(|kv| &mut kv.value)
26✔
344
    }
26✔
345

346
    /// Gets a key-value pair by key.
347
    #[inline]
348
    #[must_use]
349
    pub fn get_key_value(&self, key: &str) -> Option<(&VString, &Value)> {
×
350
        #[cfg(feature = "std")]
NEW
351
        if self.is_large_mode() {
×
NEW
352
            return self.as_indexmap().get_key_value(key);
×
NEW
353
        }
×
NEW
354
        self.items()
×
NEW
355
            .iter()
×
NEW
356
            .find(|kv| kv.key.as_str() == key)
×
NEW
357
            .map(|kv| (&kv.key, &kv.value))
×
UNCOV
358
    }
×
359

360
    /// Returns `true` if the object contains the key.
361
    #[inline]
362
    #[must_use]
363
    pub fn contains_key(&self, key: &str) -> bool {
4✔
364
        #[cfg(feature = "std")]
365
        if self.is_large_mode() {
4✔
NEW
366
            return self.as_indexmap().contains_key(key);
×
367
        }
4✔
368
        self.items().iter().any(|kv| kv.key.as_str() == key)
7✔
369
    }
4✔
370

371
    /// Inserts a key-value pair. Returns the old value if the key existed.
372
    #[cfg(feature = "alloc")]
373
    pub fn insert(&mut self, key: impl Into<VString>, value: impl Into<Value>) -> Option<Value> {
270✔
374
        let key = key.into();
270✔
375
        let value = value.into();
270✔
376

377
        #[cfg(feature = "std")]
378
        if self.is_large_mode() {
270✔
NEW
379
            return self.as_indexmap_mut().insert(key, value);
×
380
        }
270✔
381

382
        // Check if key exists (linear search in small mode)
383
        if let Some(idx) = self
270✔
384
            .items()
270✔
385
            .iter()
270✔
386
            .position(|kv| kv.key.as_str() == key.as_str())
270✔
387
        {
388
            // Key exists, replace value
389
            return Some(mem::replace(&mut self.items_mut()[idx].value, value));
1✔
390
        }
269✔
391

392
        // Check if we should convert to large mode
393
        #[cfg(feature = "std")]
394
        if self.small_len() >= LARGE_MODE_THRESHOLD {
269✔
NEW
395
            self.convert_to_large_mode();
×
NEW
396
            return self.as_indexmap_mut().insert(key, value);
×
397
        }
269✔
398

399
        // New key in small mode
400
        self.reserve(1);
269✔
401
        let new_idx = self.header().len;
269✔
402

403
        unsafe {
269✔
404
            let ptr = self.items_ptr_mut().add(new_idx);
269✔
405
            ptr.write(KeyValuePair { key, value });
269✔
406
            self.header_mut().len = new_idx + 1;
269✔
407
        }
269✔
408

409
        None
269✔
410
    }
270✔
411

412
    /// Removes a key-value pair. Returns the value if the key existed.
413
    pub fn remove(&mut self, key: &str) -> Option<Value> {
1✔
414
        self.remove_entry(key).map(|(_, v)| v)
1✔
415
    }
1✔
416

417
    /// Removes and returns a key-value pair.
418
    pub fn remove_entry(&mut self, key: &str) -> Option<(VString, Value)> {
2✔
419
        #[cfg(feature = "std")]
420
        if self.is_large_mode() {
2✔
NEW
421
            return self.as_indexmap_mut().shift_remove_entry(key);
×
422
        }
2✔
423

424
        let idx = self.items().iter().position(|kv| kv.key.as_str() == key)?;
4✔
425
        let len = self.small_len();
2✔
426

427
        unsafe {
428
            let ptr = self.items_ptr_mut().add(idx);
2✔
429
            let kv = ptr.read();
2✔
430

431
            // Shift remaining elements
432
            if idx < len - 1 {
2✔
433
                ptr::copy(ptr.add(1), ptr, len - idx - 1);
2✔
434
            }
2✔
435

436
            self.header_mut().len = len - 1;
2✔
437

438
            Some((kv.key, kv.value))
2✔
439
        }
440
    }
2✔
441

442
    /// Clears the object.
443
    pub fn clear(&mut self) {
167✔
444
        #[cfg(feature = "std")]
445
        if self.is_large_mode() {
167✔
NEW
446
            self.as_indexmap_mut().clear();
×
NEW
447
            return;
×
448
        }
167✔
449

450
        while !self.is_empty() {
434✔
451
            unsafe {
267✔
452
                let len = self.header().len;
267✔
453
                self.header_mut().len = len - 1;
267✔
454
                let ptr = self.items_ptr_mut().add(len - 1);
267✔
455
                ptr::drop_in_place(ptr);
267✔
456
            }
267✔
457
        }
458
    }
167✔
459

460
    /// Returns an iterator over keys.
461
    #[inline]
462
    pub fn keys(&self) -> Keys<'_> {
1✔
463
        #[cfg(feature = "std")]
464
        if self.is_large_mode() {
1✔
NEW
465
            return Keys(KeysInner::Large(self.as_indexmap().keys()));
×
466
        }
1✔
467
        Keys(KeysInner::Small(self.items().iter()))
1✔
468
    }
1✔
469

470
    /// Returns an iterator over values.
471
    #[inline]
NEW
472
    pub fn values(&self) -> Values<'_> {
×
473
        #[cfg(feature = "std")]
NEW
474
        if self.is_large_mode() {
×
NEW
475
            return Values(ValuesInner::Large(self.as_indexmap().values()));
×
NEW
476
        }
×
NEW
477
        Values(ValuesInner::Small(self.items().iter()))
×
UNCOV
478
    }
×
479

480
    /// Returns an iterator over mutable values.
481
    #[inline]
NEW
482
    pub fn values_mut(&mut self) -> ValuesMut<'_> {
×
483
        #[cfg(feature = "std")]
NEW
484
        if self.is_large_mode() {
×
NEW
485
            return ValuesMut(ValuesMutInner::Large(self.as_indexmap_mut().values_mut()));
×
NEW
486
        }
×
NEW
487
        ValuesMut(ValuesMutInner::Small(self.items_mut().iter_mut()))
×
UNCOV
488
    }
×
489

490
    /// Returns an iterator over key-value pairs.
491
    #[inline]
492
    pub fn iter(&self) -> Iter<'_> {
497✔
493
        #[cfg(feature = "std")]
494
        if self.is_large_mode() {
497✔
NEW
495
            return Iter(IterInner::Large(self.as_indexmap().iter()));
×
496
        }
497✔
497
        Iter(IterInner::Small(self.items().iter()))
497✔
498
    }
497✔
499

500
    /// Returns an iterator over mutable key-value pairs.
501
    #[inline]
502
    pub fn iter_mut(&mut self) -> IterMut<'_> {
×
503
        #[cfg(feature = "std")]
NEW
504
        if self.is_large_mode() {
×
NEW
505
            return IterMut(IterMutInner::Large(self.as_indexmap_mut().iter_mut()));
×
506
        }
×
NEW
507
        IterMut(IterMutInner::Small(self.items_mut().iter_mut()))
×
UNCOV
508
    }
×
509

510
    /// Shrinks the capacity to match the length.
511
    #[cfg(feature = "alloc")]
512
    pub fn shrink_to_fit(&mut self) {
×
513
        #[cfg(feature = "std")]
NEW
514
        if self.is_large_mode() {
×
NEW
515
            self.as_indexmap_mut().shrink_to_fit();
×
NEW
516
            return;
×
NEW
517
        }
×
518

519
        let len = self.len();
×
520
        let cap = self.capacity();
×
521

522
        if len < cap {
×
523
            unsafe {
×
524
                let new_ptr = Self::realloc_ptr(self.0.heap_ptr_mut().cast(), len);
×
525
                self.0.set_ptr(new_ptr.cast());
×
526
            }
×
527
        }
×
528
    }
×
529

530
    pub(crate) fn clone_impl(&self) -> Value {
6✔
531
        #[cfg(feature = "std")]
532
        if self.is_large_mode() {
6✔
NEW
533
            let storage = LargeModeStorage {
×
NEW
534
                _len_unused: 0,
×
NEW
535
                cap_sentinel: LARGE_MODE_CAP_SENTINEL,
×
NEW
536
                map: self.as_indexmap().clone(),
×
NEW
537
            };
×
NEW
538
            let boxed = Box::new(storage);
×
NEW
539
            let ptr = Box::into_raw(boxed);
×
NEW
540
            return unsafe { Value::new_ptr(ptr.cast(), TypeTag::Object) };
×
541
        }
6✔
542

543
        let mut new = VObject::with_capacity(self.len());
6✔
544
        for kv in self.items() {
11✔
545
            new.insert(kv.key.clone(), kv.value.clone());
11✔
546
        }
11✔
547
        new.0
6✔
548
    }
6✔
549

550
    pub(crate) fn drop_impl(&mut self) {
167✔
551
        #[cfg(feature = "std")]
552
        if self.is_large_mode() {
167✔
NEW
553
            unsafe {
×
NEW
554
                drop(Box::from_raw(self.0.heap_ptr_mut() as *mut LargeModeStorage));
×
NEW
555
            }
×
NEW
556
            return;
×
557
        }
167✔
558

559
        self.clear();
167✔
560
        unsafe {
167✔
561
            Self::dealloc_ptr(self.0.heap_ptr_mut().cast());
167✔
562
        }
167✔
563
    }
167✔
564
}
565

566
// === Iterators ===
567

568
enum KeysInner<'a> {
569
    Small(core::slice::Iter<'a, KeyValuePair>),
570
    #[cfg(feature = "std")]
571
    Large(indexmap::map::Keys<'a, VString, Value>),
572
}
573

574
/// Iterator over keys.
575
pub struct Keys<'a>(KeysInner<'a>);
576

577
impl<'a> Iterator for Keys<'a> {
578
    type Item = &'a VString;
579

580
    fn next(&mut self) -> Option<Self::Item> {
3✔
581
        match &mut self.0 {
3✔
582
            KeysInner::Small(iter) => iter.next().map(|kv| &kv.key),
3✔
583
            #[cfg(feature = "std")]
NEW
584
            KeysInner::Large(iter) => iter.next(),
×
585
        }
586
    }
3✔
587

588
    fn size_hint(&self) -> (usize, Option<usize>) {
1✔
589
        match &self.0 {
1✔
590
            KeysInner::Small(iter) => iter.size_hint(),
1✔
591
            #[cfg(feature = "std")]
NEW
592
            KeysInner::Large(iter) => iter.size_hint(),
×
593
        }
594
    }
1✔
595
}
596

597
impl ExactSizeIterator for Keys<'_> {}
598

599
enum ValuesInner<'a> {
600
    Small(core::slice::Iter<'a, KeyValuePair>),
601
    #[cfg(feature = "std")]
602
    Large(indexmap::map::Values<'a, VString, Value>),
603
}
604

605
/// Iterator over values.
606
pub struct Values<'a>(ValuesInner<'a>);
607

608
impl<'a> Iterator for Values<'a> {
609
    type Item = &'a Value;
610

NEW
611
    fn next(&mut self) -> Option<Self::Item> {
×
NEW
612
        match &mut self.0 {
×
NEW
613
            ValuesInner::Small(iter) => iter.next().map(|kv| &kv.value),
×
614
            #[cfg(feature = "std")]
NEW
615
            ValuesInner::Large(iter) => iter.next(),
×
616
        }
NEW
617
    }
×
618

NEW
619
    fn size_hint(&self) -> (usize, Option<usize>) {
×
NEW
620
        match &self.0 {
×
NEW
621
            ValuesInner::Small(iter) => iter.size_hint(),
×
622
            #[cfg(feature = "std")]
NEW
623
            ValuesInner::Large(iter) => iter.size_hint(),
×
624
        }
NEW
625
    }
×
626
}
627

628
impl ExactSizeIterator for Values<'_> {}
629

630
enum ValuesMutInner<'a> {
631
    Small(core::slice::IterMut<'a, KeyValuePair>),
632
    #[cfg(feature = "std")]
633
    Large(indexmap::map::ValuesMut<'a, VString, Value>),
634
}
635

636
/// Iterator over mutable values.
637
pub struct ValuesMut<'a>(ValuesMutInner<'a>);
638

639
impl<'a> Iterator for ValuesMut<'a> {
640
    type Item = &'a mut Value;
641

NEW
642
    fn next(&mut self) -> Option<Self::Item> {
×
NEW
643
        match &mut self.0 {
×
NEW
644
            ValuesMutInner::Small(iter) => iter.next().map(|kv| &mut kv.value),
×
645
            #[cfg(feature = "std")]
NEW
646
            ValuesMutInner::Large(iter) => iter.next(),
×
647
        }
NEW
648
    }
×
649

NEW
650
    fn size_hint(&self) -> (usize, Option<usize>) {
×
NEW
651
        match &self.0 {
×
NEW
652
            ValuesMutInner::Small(iter) => iter.size_hint(),
×
653
            #[cfg(feature = "std")]
NEW
654
            ValuesMutInner::Large(iter) => iter.size_hint(),
×
655
        }
NEW
656
    }
×
657
}
658

659
impl ExactSizeIterator for ValuesMut<'_> {}
660

661
enum IterInner<'a> {
662
    Small(core::slice::Iter<'a, KeyValuePair>),
663
    #[cfg(feature = "std")]
664
    Large(indexmap::map::Iter<'a, VString, Value>),
665
}
666

667
/// Iterator over `(&VString, &Value)` pairs.
668
pub struct Iter<'a>(IterInner<'a>);
669

670
impl<'a> Iterator for Iter<'a> {
671
    type Item = (&'a VString, &'a Value);
672

673
    fn next(&mut self) -> Option<Self::Item> {
863✔
674
        match &mut self.0 {
863✔
675
            IterInner::Small(iter) => iter.next().map(|kv| (&kv.key, &kv.value)),
863✔
676
            #[cfg(feature = "std")]
NEW
677
            IterInner::Large(iter) => iter.next(),
×
678
        }
679
    }
863✔
680

681
    fn size_hint(&self) -> (usize, Option<usize>) {
6✔
682
        match &self.0 {
6✔
683
            IterInner::Small(iter) => iter.size_hint(),
6✔
684
            #[cfg(feature = "std")]
NEW
685
            IterInner::Large(iter) => iter.size_hint(),
×
686
        }
687
    }
6✔
688
}
689

690
impl ExactSizeIterator for Iter<'_> {}
691

692
enum IterMutInner<'a> {
693
    Small(core::slice::IterMut<'a, KeyValuePair>),
694
    #[cfg(feature = "std")]
695
    Large(indexmap::map::IterMut<'a, VString, Value>),
696
}
697

698
/// Iterator over `(&VString, &mut Value)` pairs.
699
pub struct IterMut<'a>(IterMutInner<'a>);
700

701
impl<'a> Iterator for IterMut<'a> {
702
    type Item = (&'a VString, &'a mut Value);
703

704
    fn next(&mut self) -> Option<Self::Item> {
×
NEW
705
        match &mut self.0 {
×
NEW
706
            IterMutInner::Small(iter) => iter.next().map(|kv| (&kv.key, &mut kv.value)),
×
707
            #[cfg(feature = "std")]
NEW
708
            IterMutInner::Large(iter) => iter.next(),
×
709
        }
UNCOV
710
    }
×
711

712
    fn size_hint(&self) -> (usize, Option<usize>) {
×
NEW
713
        match &self.0 {
×
NEW
714
            IterMutInner::Small(iter) => iter.size_hint(),
×
715
            #[cfg(feature = "std")]
NEW
716
            IterMutInner::Large(iter) => iter.size_hint(),
×
717
        }
UNCOV
718
    }
×
719
}
720

721
impl ExactSizeIterator for IterMut<'_> {}
722

723
/// Iterator over owned `(VString, Value)` pairs.
724
pub struct ObjectIntoIter {
725
    object: VObject,
726
}
727

728
impl Iterator for ObjectIntoIter {
729
    type Item = (VString, Value);
730

731
    fn next(&mut self) -> Option<Self::Item> {
×
732
        if self.object.is_empty() {
×
733
            None
×
734
        } else {
735
            // Remove from the front to preserve order
736
            let key = self.object.items()[0].key.as_str().to_owned();
×
737
            self.object.remove_entry(&key)
×
738
        }
739
    }
×
740

741
    fn size_hint(&self) -> (usize, Option<usize>) {
×
742
        let len = self.object.len();
×
743
        (len, Some(len))
×
744
    }
×
745
}
746

747
impl ExactSizeIterator for ObjectIntoIter {}
748

749
impl IntoIterator for VObject {
750
    type Item = (VString, Value);
751
    type IntoIter = ObjectIntoIter;
752

753
    fn into_iter(self) -> Self::IntoIter {
×
754
        ObjectIntoIter { object: self }
×
755
    }
×
756
}
757

758
impl<'a> IntoIterator for &'a VObject {
759
    type Item = (&'a VString, &'a Value);
760
    type IntoIter = Iter<'a>;
761

762
    fn into_iter(self) -> Self::IntoIter {
×
763
        self.iter()
×
764
    }
×
765
}
766

767
impl<'a> IntoIterator for &'a mut VObject {
768
    type Item = (&'a VString, &'a mut Value);
769
    type IntoIter = IterMut<'a>;
770

771
    fn into_iter(self) -> Self::IntoIter {
×
772
        self.iter_mut()
×
773
    }
×
774
}
775

776
// === Index ===
777

778
impl Index<&str> for VObject {
779
    type Output = Value;
780

781
    fn index(&self, key: &str) -> &Value {
17✔
782
        self.get(key).expect("key not found")
17✔
783
    }
17✔
784
}
785

786
impl IndexMut<&str> for VObject {
787
    fn index_mut(&mut self, key: &str) -> &mut Value {
×
788
        self.get_mut(key).expect("key not found")
×
789
    }
×
790
}
791

792
// === Comparison ===
793

794
impl PartialEq for VObject {
795
    fn eq(&self, other: &Self) -> bool {
90✔
796
        if self.len() != other.len() {
90✔
797
            return false;
2✔
798
        }
88✔
799
        for (k, v) in self.iter() {
119✔
800
            if other.get(k.as_str()) != Some(v) {
119✔
801
                return false;
60✔
802
            }
59✔
803
        }
804
        true
28✔
805
    }
90✔
806
}
807

808
impl Eq for VObject {}
809

810
impl Hash for VObject {
811
    fn hash<H: Hasher>(&self, state: &mut H) {
×
812
        // Hash length and then each key-value pair
813
        // Note: This doesn't depend on order, which is correct for map semantics
814
        self.len().hash(state);
×
815

816
        // Sum hashes to make order-independent (XOR is order-independent)
817
        let mut total: u64 = 0;
×
818
        for (k, _v) in self.iter() {
×
819
            // Simple hash combining for each pair
820
            let mut kh: u64 = 0;
×
821
            for byte in k.as_bytes() {
×
822
                kh = kh.wrapping_mul(31).wrapping_add(*byte as u64);
×
823
            }
×
824
            // Just XOR the key hash contribution
825
            total ^= kh;
×
826
        }
827
        total.hash(state);
×
828
    }
×
829
}
830

831
impl Debug for VObject {
832
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
×
833
        f.debug_map().entries(self.iter()).finish()
×
834
    }
×
835
}
836

837
impl Default for VObject {
838
    fn default() -> Self {
×
839
        Self::new()
×
840
    }
×
841
}
842

843
// === FromIterator / Extend ===
844

845
#[cfg(feature = "alloc")]
846
impl<K: Into<VString>, V: Into<Value>> FromIterator<(K, V)> for VObject {
847
    fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
1✔
848
        let iter = iter.into_iter();
1✔
849
        let (lower, _) = iter.size_hint();
1✔
850
        let mut obj = VObject::with_capacity(lower);
1✔
851
        for (k, v) in iter {
2✔
852
            obj.insert(k, v);
2✔
853
        }
2✔
854
        obj
1✔
855
    }
1✔
856
}
857

858
#[cfg(feature = "alloc")]
859
impl<K: Into<VString>, V: Into<Value>> Extend<(K, V)> for VObject {
860
    fn extend<I: IntoIterator<Item = (K, V)>>(&mut self, iter: I) {
×
861
        let iter = iter.into_iter();
×
862
        let (lower, _) = iter.size_hint();
×
863
        self.reserve(lower);
×
864
        for (k, v) in iter {
×
865
            self.insert(k, v);
×
866
        }
×
867
    }
×
868
}
869

870
// === From implementations ===
871

872
#[cfg(feature = "std")]
873
impl<K: Into<VString>, V: Into<Value>> From<HashMap<K, V>> for VObject {
874
    fn from(map: HashMap<K, V>) -> Self {
×
875
        map.into_iter().collect()
×
876
    }
×
877
}
878

879
#[cfg(feature = "alloc")]
880
impl<K: Into<VString>, V: Into<Value>> From<BTreeMap<K, V>> for VObject {
881
    fn from(map: BTreeMap<K, V>) -> Self {
×
882
        map.into_iter().collect()
×
883
    }
×
884
}
885

886
// === Value conversions ===
887

888
impl AsRef<Value> for VObject {
889
    fn as_ref(&self) -> &Value {
×
890
        &self.0
×
891
    }
×
892
}
893

894
impl AsMut<Value> for VObject {
895
    fn as_mut(&mut self) -> &mut Value {
×
896
        &mut self.0
×
897
    }
×
898
}
899

900
impl From<VObject> for Value {
901
    fn from(obj: VObject) -> Self {
130✔
902
        obj.0
130✔
903
    }
130✔
904
}
905

906
impl VObject {
907
    /// Converts this VObject into a Value, consuming self.
908
    #[inline]
909
    pub fn into_value(self) -> Value {
22✔
910
        self.0
22✔
911
    }
22✔
912
}
913

914
#[cfg(test)]
915
mod tests {
916
    use super::*;
917
    use crate::ValueType;
918

919
    #[test]
920
    fn test_new() {
1✔
921
        let obj = VObject::new();
1✔
922
        assert!(obj.is_empty());
1✔
923
        assert_eq!(obj.len(), 0);
1✔
924
    }
1✔
925

926
    #[test]
927
    fn test_insert_get() {
1✔
928
        let mut obj = VObject::new();
1✔
929
        obj.insert("name", Value::from("Alice"));
1✔
930
        obj.insert("age", Value::from(30));
1✔
931

932
        assert_eq!(obj.len(), 2);
1✔
933
        assert!(obj.contains_key("name"));
1✔
934
        assert!(obj.contains_key("age"));
1✔
935
        assert!(!obj.contains_key("email"));
1✔
936

937
        assert_eq!(
1✔
938
            obj.get("name").unwrap().as_string().unwrap().as_str(),
1✔
939
            "Alice"
940
        );
941
        assert_eq!(
1✔
942
            obj.get("age").unwrap().as_number().unwrap().to_i64(),
1✔
943
            Some(30)
944
        );
945
    }
1✔
946

947
    #[test]
948
    fn test_insert_replace() {
1✔
949
        let mut obj = VObject::new();
1✔
950
        assert!(obj.insert("key", Value::from(1)).is_none());
1✔
951
        assert!(obj.insert("key", Value::from(2)).is_some());
1✔
952
        assert_eq!(obj.len(), 1);
1✔
953
        assert_eq!(
1✔
954
            obj.get("key").unwrap().as_number().unwrap().to_i64(),
1✔
955
            Some(2)
956
        );
957
    }
1✔
958

959
    #[test]
960
    fn test_remove() {
1✔
961
        let mut obj = VObject::new();
1✔
962
        obj.insert("a", Value::from(1));
1✔
963
        obj.insert("b", Value::from(2));
1✔
964
        obj.insert("c", Value::from(3));
1✔
965

966
        let removed = obj.remove("b");
1✔
967
        assert!(removed.is_some());
1✔
968
        assert_eq!(obj.len(), 2);
1✔
969
        assert!(!obj.contains_key("b"));
1✔
970
    }
1✔
971

972
    #[test]
973
    fn test_clone() {
1✔
974
        let mut obj = VObject::new();
1✔
975
        obj.insert("key", Value::from("value"));
1✔
976

977
        let obj2 = obj.clone();
1✔
978
        assert_eq!(obj, obj2);
1✔
979
    }
1✔
980

981
    #[test]
982
    fn test_iter() {
1✔
983
        let mut obj = VObject::new();
1✔
984
        obj.insert("a", Value::from(1));
1✔
985
        obj.insert("b", Value::from(2));
1✔
986

987
        let keys: Vec<_> = obj.keys().map(|k| k.as_str()).collect();
2✔
988
        assert_eq!(keys, vec!["a", "b"]);
1✔
989
    }
1✔
990

991
    #[test]
992
    fn test_collect() {
1✔
993
        let obj: VObject = vec![("a", Value::from(1)), ("b", Value::from(2))]
1✔
994
            .into_iter()
1✔
995
            .collect();
1✔
996
        assert_eq!(obj.len(), 2);
1✔
997
    }
1✔
998

999
    #[test]
1000
    fn test_index() {
1✔
1001
        let mut obj = VObject::new();
1✔
1002
        obj.insert("key", Value::from(42));
1✔
1003

1004
        assert_eq!(obj["key"].as_number().unwrap().to_i64(), Some(42));
1✔
1005
    }
1✔
1006

1007
    #[test]
1008
    fn inline_strings_in_objects_remain_inline() {
1✔
1009
        let mut obj = VObject::new();
1✔
1010
        for idx in 0..=crate::string::VString::INLINE_LEN_MAX.min(5) {
6✔
1011
            let key = format!("k{idx}");
6✔
1012
            let val = "v".repeat(idx);
6✔
1013
            obj.insert(key.as_str(), Value::from(val.as_str()));
6✔
1014
        }
6✔
1015

1016
        for (key, value) in obj.iter() {
6✔
1017
            assert!(
6✔
1018
                key.0.is_inline_string(),
6✔
1019
                "object key {:?} expected inline storage",
1020
                key.as_str()
×
1021
            );
1022
            if value.value_type() == ValueType::String {
6✔
1023
                assert!(
6✔
1024
                    value.is_inline_string(),
6✔
1025
                    "object value {value:?} expected inline storage"
1026
                );
1027
            }
×
1028
        }
1029

1030
        let mut cloned = obj.clone();
1✔
1031
        for (key, value) in cloned.iter() {
6✔
1032
            assert!(key.0.is_inline_string(), "cloned key lost inline storage");
6✔
1033
            if value.value_type() == ValueType::String {
6✔
1034
                assert!(value.is_inline_string(), "cloned value lost inline storage");
6✔
1035
            }
×
1036
        }
1037

1038
        let (removed_key, removed_value) = cloned.remove_entry("k1").expect("entry exists");
1✔
1039
        assert!(
1✔
1040
            removed_key.0.is_inline_string(),
1✔
1041
            "removed key should stay inline"
1042
        );
1043
        if removed_value.value_type() == ValueType::String {
1✔
1044
            assert!(
1✔
1045
                removed_value.is_inline_string(),
1✔
1046
                "removed value should stay inline"
1047
            );
1048
        }
×
1049
    }
1✔
1050
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc