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

facet-rs / facet / 19803020611

30 Nov 2025 06:23PM UTC coverage: 57.923% (-3.0%) from 60.927%
19803020611

push

github

fasterthanlime
Convert facet-kdl to use define_attr_grammar!

- Replace ~140 lines of hand-written macros with concise grammar DSL
- Enhance make_parse_attr.rs grammar compiler:
  - Add `ns "kdl";` syntax for namespace declaration
  - Add to_snake_case() helper (NodeName β†’ node_name)
  - Generate __attr! macro with proper ExtensionAttr return
  - Use () data for unit variants, full dispatch for complex ones
  - Add #[repr(u8)] to generated enums for Facet derive
  - Use HashMap for O(1) struct lookup
- Update design diagrams with namespace fixes

50 of 53 new or added lines in 1 file covered. (94.34%)

3583 existing lines in 40 files now uncovered.

18788 of 32436 relevant lines covered (57.92%)

179.8 hits per line

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

68.85
/facet-value/src/string.rs
1
//! String value type.
2

3
#[cfg(feature = "alloc")]
4
use alloc::alloc::{Layout, alloc, dealloc};
5
#[cfg(feature = "alloc")]
6
use alloc::string::String;
7
use core::borrow::Borrow;
8
use core::cmp::Ordering;
9
use core::fmt::{self, Debug, Formatter};
10
use core::hash::{Hash, Hasher};
11
use core::ops::Deref;
12
use core::ptr;
13

14
use crate::value::{TypeTag, Value};
15

16
/// Header for heap-allocated strings.
17
#[repr(C, align(8))]
18
struct StringHeader {
19
    /// Length of the string in bytes
20
    len: usize,
21
    // String data follows immediately after
22
}
23

24
/// A string value.
25
///
26
/// `VString` stores UTF-8 string data. Unlike some implementations, strings are
27
/// not interned - each `VString` owns its own copy of the data.
28
#[repr(transparent)]
29
#[derive(Clone)]
30
pub struct VString(pub(crate) Value);
31

32
impl VString {
33
    fn layout(len: usize) -> Layout {
742✔
34
        Layout::new::<StringHeader>()
742✔
35
            .extend(Layout::array::<u8>(len).unwrap())
742✔
36
            .unwrap()
742✔
37
            .0
742✔
38
            .pad_to_align()
742✔
39
    }
742✔
40

41
    #[cfg(feature = "alloc")]
42
    fn alloc(s: &str) -> *mut StringHeader {
369✔
43
        unsafe {
44
            let layout = Self::layout(s.len());
369✔
45
            let ptr = alloc(layout).cast::<StringHeader>();
369✔
46
            (*ptr).len = s.len();
369✔
47

48
            // Copy string data
49
            let data_ptr = ptr.add(1).cast::<u8>();
369✔
50
            ptr::copy_nonoverlapping(s.as_ptr(), data_ptr, s.len());
369✔
51

52
            ptr
369✔
53
        }
54
    }
369✔
55

56
    #[cfg(feature = "alloc")]
57
    fn dealloc_ptr(ptr: *mut StringHeader) {
371✔
58
        unsafe {
371✔
59
            let len = (*ptr).len;
371✔
60
            let layout = Self::layout(len);
371✔
61
            dealloc(ptr.cast::<u8>(), layout);
371✔
62
        }
371✔
63
    }
371✔
64

65
    fn header(&self) -> &StringHeader {
1,472✔
66
        unsafe { &*(self.0.heap_ptr() as *const StringHeader) }
1,472✔
67
    }
1,472✔
68

69
    fn data_ptr(&self) -> *const u8 {
1,468✔
70
        // Go through heap_ptr directly to avoid creating intermediate reference
71
        // that would limit provenance to just the header
72
        unsafe { (self.0.heap_ptr() as *const StringHeader).add(1).cast() }
1,468✔
73
    }
1,468✔
74

75
    /// Creates a new string from a `&str`.
76
    #[cfg(feature = "alloc")]
77
    #[must_use]
78
    pub fn new(s: &str) -> Self {
370✔
79
        if s.is_empty() {
370✔
80
            return Self::empty();
1✔
81
        }
369✔
82
        unsafe {
83
            let ptr = Self::alloc(s);
369✔
84
            VString(Value::new_ptr(ptr.cast(), TypeTag::StringOrNull))
369✔
85
        }
86
    }
370✔
87

88
    /// Creates an empty string.
89
    #[cfg(feature = "alloc")]
90
    #[must_use]
91
    pub fn empty() -> Self {
2✔
92
        // For empty strings, we still allocate a header with len=0
93
        // This keeps the code simpler
94
        unsafe {
95
            let layout = Self::layout(0);
2✔
96
            let ptr = alloc(layout).cast::<StringHeader>();
2✔
97
            (*ptr).len = 0;
2✔
98
            VString(Value::new_ptr(ptr.cast(), TypeTag::StringOrNull))
2✔
99
        }
100
    }
2✔
101

102
    /// Returns the length of the string in bytes.
103
    #[must_use]
104
    pub fn len(&self) -> usize {
1,472✔
105
        self.header().len
1,472✔
106
    }
1,472✔
107

108
    /// Returns `true` if the string is empty.
109
    #[must_use]
110
    pub fn is_empty(&self) -> bool {
2✔
111
        self.len() == 0
2✔
112
    }
2✔
113

114
    /// Returns the string as a `&str`.
115
    #[must_use]
116
    pub fn as_str(&self) -> &str {
1,468✔
117
        unsafe {
118
            let bytes = core::slice::from_raw_parts(self.data_ptr(), self.len());
1,468✔
119
            core::str::from_utf8_unchecked(bytes)
1,468✔
120
        }
121
    }
1,468✔
122

123
    /// Returns the string as a byte slice.
124
    #[must_use]
125
    pub fn as_bytes(&self) -> &[u8] {
×
UNCOV
126
        unsafe { core::slice::from_raw_parts(self.data_ptr(), self.len()) }
×
UNCOV
127
    }
×
128

129
    pub(crate) fn clone_impl(&self) -> Value {
8✔
130
        VString::new(self.as_str()).0
8✔
131
    }
8✔
132

133
    pub(crate) fn drop_impl(&mut self) {
371✔
134
        unsafe {
371✔
135
            Self::dealloc_ptr(self.0.heap_ptr_mut().cast());
371✔
136
        }
371✔
137
    }
371✔
138
}
139

140
impl Deref for VString {
141
    type Target = str;
142

143
    fn deref(&self) -> &str {
2✔
144
        self.as_str()
2✔
145
    }
2✔
146
}
147

148
impl Borrow<str> for VString {
149
    fn borrow(&self) -> &str {
×
UNCOV
150
        self.as_str()
×
UNCOV
151
    }
×
152
}
153

154
impl AsRef<str> for VString {
155
    fn as_ref(&self) -> &str {
×
UNCOV
156
        self.as_str()
×
UNCOV
157
    }
×
158
}
159

160
impl AsRef<[u8]> for VString {
161
    fn as_ref(&self) -> &[u8] {
×
UNCOV
162
        self.as_bytes()
×
UNCOV
163
    }
×
164
}
165

166
impl PartialEq for VString {
167
    fn eq(&self, other: &Self) -> bool {
75✔
168
        self.as_str() == other.as_str()
75✔
169
    }
75✔
170
}
171

172
impl Eq for VString {}
173

174
impl PartialOrd for VString {
175
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1✔
176
        Some(self.cmp(other))
1✔
177
    }
1✔
178
}
179

180
impl Ord for VString {
181
    fn cmp(&self, other: &Self) -> Ordering {
1✔
182
        self.as_str().cmp(other.as_str())
1✔
183
    }
1✔
184
}
185

186
impl Hash for VString {
187
    fn hash<H: Hasher>(&self, state: &mut H) {
×
UNCOV
188
        self.as_str().hash(state);
×
UNCOV
189
    }
×
190
}
191

192
impl Debug for VString {
193
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
×
UNCOV
194
        Debug::fmt(self.as_str(), f)
×
UNCOV
195
    }
×
196
}
197

198
impl fmt::Display for VString {
199
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
×
UNCOV
200
        fmt::Display::fmt(self.as_str(), f)
×
UNCOV
201
    }
×
202
}
203

204
impl Default for VString {
205
    fn default() -> Self {
×
UNCOV
206
        Self::empty()
×
UNCOV
207
    }
×
208
}
209

210
// === PartialEq with str ===
211

212
impl PartialEq<str> for VString {
213
    fn eq(&self, other: &str) -> bool {
×
UNCOV
214
        self.as_str() == other
×
UNCOV
215
    }
×
216
}
217

218
impl PartialEq<VString> for str {
219
    fn eq(&self, other: &VString) -> bool {
×
UNCOV
220
        self == other.as_str()
×
UNCOV
221
    }
×
222
}
223

224
impl PartialEq<&str> for VString {
225
    fn eq(&self, other: &&str) -> bool {
1✔
226
        self.as_str() == *other
1✔
227
    }
1✔
228
}
229

230
#[cfg(feature = "alloc")]
231
impl PartialEq<String> for VString {
232
    fn eq(&self, other: &String) -> bool {
×
UNCOV
233
        self.as_str() == other.as_str()
×
UNCOV
234
    }
×
235
}
236

237
#[cfg(feature = "alloc")]
238
impl PartialEq<VString> for String {
239
    fn eq(&self, other: &VString) -> bool {
×
UNCOV
240
        self.as_str() == other.as_str()
×
UNCOV
241
    }
×
242
}
243

244
// === From implementations ===
245

246
#[cfg(feature = "alloc")]
247
impl From<&str> for VString {
248
    fn from(s: &str) -> Self {
242✔
249
        Self::new(s)
242✔
250
    }
242✔
251
}
252

253
#[cfg(feature = "alloc")]
254
impl From<String> for VString {
255
    fn from(s: String) -> Self {
×
UNCOV
256
        Self::new(&s)
×
UNCOV
257
    }
×
258
}
259

260
#[cfg(feature = "alloc")]
261
impl From<&String> for VString {
262
    fn from(s: &String) -> Self {
×
UNCOV
263
        Self::new(s)
×
UNCOV
264
    }
×
265
}
266

267
#[cfg(feature = "alloc")]
268
impl From<VString> for String {
269
    fn from(s: VString) -> Self {
×
UNCOV
270
        s.as_str().into()
×
UNCOV
271
    }
×
272
}
273

274
// === Value conversions ===
275

276
impl AsRef<Value> for VString {
277
    fn as_ref(&self) -> &Value {
×
UNCOV
278
        &self.0
×
UNCOV
279
    }
×
280
}
281

282
impl AsMut<Value> for VString {
283
    fn as_mut(&mut self) -> &mut Value {
×
UNCOV
284
        &mut self.0
×
UNCOV
285
    }
×
286
}
287

288
impl From<VString> for Value {
289
    fn from(s: VString) -> Self {
6✔
290
        s.0
6✔
291
    }
6✔
292
}
293

294
impl VString {
295
    /// Converts this VString into a Value, consuming self.
296
    #[inline]
297
    pub fn into_value(self) -> Value {
11✔
298
        self.0
11✔
299
    }
11✔
300
}
301

302
#[cfg(feature = "alloc")]
303
impl From<&str> for Value {
304
    fn from(s: &str) -> Self {
94✔
305
        VString::new(s).0
94✔
306
    }
94✔
307
}
308

309
#[cfg(feature = "alloc")]
310
impl From<String> for Value {
311
    fn from(s: String) -> Self {
×
UNCOV
312
        VString::new(&s).0
×
UNCOV
313
    }
×
314
}
315

316
#[cfg(feature = "alloc")]
317
impl From<&String> for Value {
318
    fn from(s: &String) -> Self {
×
UNCOV
319
        VString::new(s).0
×
UNCOV
320
    }
×
321
}
322

323
#[cfg(test)]
324
mod tests {
325
    use super::*;
326

327
    #[test]
328
    fn test_new() {
1✔
329
        let s = VString::new("hello");
1✔
330
        assert_eq!(s.as_str(), "hello");
1✔
331
        assert_eq!(s.len(), 5);
1✔
332
        assert!(!s.is_empty());
1✔
333
    }
1✔
334

335
    #[test]
336
    fn test_empty() {
1✔
337
        let s = VString::empty();
1✔
338
        assert_eq!(s.as_str(), "");
1✔
339
        assert_eq!(s.len(), 0);
1✔
340
        assert!(s.is_empty());
1✔
341
    }
1✔
342

343
    #[test]
344
    fn test_equality() {
1✔
345
        let a = VString::new("hello");
1✔
346
        let b = VString::new("hello");
1✔
347
        let c = VString::new("world");
1✔
348

349
        assert_eq!(a, b);
1✔
350
        assert_ne!(a, c);
1✔
351
        assert_eq!(a, "hello");
1✔
352
        assert_eq!(a.as_str(), "hello");
1✔
353
    }
1✔
354

355
    #[test]
356
    fn test_clone() {
1✔
357
        let a = VString::new("test");
1✔
358
        let b = a.clone();
1✔
359
        assert_eq!(a, b);
1✔
360
    }
1✔
361

362
    #[test]
363
    fn test_unicode() {
1✔
364
        let s = VString::new("hello δΈ–η•Œ 🌍");
1✔
365
        assert_eq!(s.as_str(), "hello δΈ–η•Œ 🌍");
1✔
366
    }
1✔
367

368
    #[test]
369
    fn test_deref() {
1✔
370
        let s = VString::new("hello");
1✔
371
        assert!(s.starts_with("hel"));
1✔
372
        assert!(s.ends_with("llo"));
1✔
373
    }
1✔
374

375
    #[test]
376
    fn test_ordering() {
1✔
377
        let a = VString::new("apple");
1✔
378
        let b = VString::new("banana");
1✔
379
        assert!(a < b);
1✔
380
    }
1✔
381
}
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