tvix_eval/value/string/
mod.rs

1//! This module implements Nix language strings.
2//!
3//! See [`NixString`] for more information about the internals of string values
4
5use bstr::{BStr, BString, ByteSlice, Chars};
6use nohash_hasher::BuildNoHashHasher;
7use rnix::ast;
8#[cfg(feature = "no_leak")]
9use rustc_hash::FxHashSet;
10use rustc_hash::FxHasher;
11use std::alloc::dealloc;
12use std::alloc::{alloc, handle_alloc_error, Layout};
13use std::borrow::{Borrow, Cow};
14use std::cell::RefCell;
15use std::ffi::c_void;
16use std::fmt::{self, Debug, Display};
17use std::hash::{Hash, Hasher};
18use std::ops::Deref;
19use std::ptr::{self, NonNull};
20use std::slice;
21
22use serde::de::{Deserializer, Visitor};
23use serde::Deserialize;
24
25mod context;
26
27pub use context::{NixContext, NixContextElement};
28
29/// This type is never instantiated, but serves to document the memory layout of the actual heap
30/// allocation for Nix strings.
31#[allow(dead_code)]
32struct NixStringInner {
33    /// The string context, if any.  Note that this is boxed to take advantage of the null pointer
34    /// niche, otherwise this field ends up being very large:
35    ///
36    /// ```notrust
37    /// >> std::mem::size_of::<Option<HashSet<String>>>()
38    /// 48
39    ///
40    /// >> std::mem::size_of::<Option<Box<HashSet<String>>>>()
41    /// 8
42    /// ```
43    context: Option<Box<NixContext>>,
44    /// The length of the data, stored *inline in the allocation*
45    length: usize,
46    /// The actual data for the string itself. Will always be `length` bytes long
47    data: [u8],
48}
49
50#[allow(clippy::zst_offset)]
51impl NixStringInner {
52    /// Construct a [`Layout`] for a nix string allocation with the given length.
53    ///
54    /// Returns a tuple of:
55    /// 1. The layout itself.
56    /// 2. The offset of [`Self::length`] within the allocation, assuming the allocation starts at 0
57    /// 3. The offset of the data array within the allocation, assuming the allocation starts at 0
58    fn layout(len: usize) -> (Layout, usize, usize) {
59        let layout = Layout::new::<Option<Box<NixContext>>>();
60        let (layout, len_offset) = layout.extend(Layout::new::<usize>()).unwrap();
61        let (layout, data_offset) = layout.extend(Layout::array::<u8>(len).unwrap()).unwrap();
62        (layout, len_offset, data_offset)
63    }
64
65    /// Returns the [`Layout`] for an *already-allocated* nix string, loading the length from the
66    /// pointer.
67    ///
68    /// Returns a tuple of:
69    /// 1. The layout itself.
70    /// 2. The offset of [`Self::length`] within the allocation, assuming the allocation starts at 0
71    /// 3. The offset of the data array within the allocation, assuming the allocation starts at 0
72    ///
73    /// # Safety
74    ///
75    /// This function must only be called on a pointer that has been properly initialized with
76    /// [`Self::alloc`]. The data buffer may not necessarily be initialized
77    unsafe fn layout_of(this: NonNull<c_void>) -> (Layout, usize, usize) {
78        let layout = Layout::new::<Option<Box<NixContext>>>();
79        let (_, len_offset) = layout.extend(Layout::new::<usize>()).unwrap();
80        // SAFETY: Layouts are linear, so even though we haven't involved data at all yet, we know
81        // the len_offset is a valid offset into the second field of the allocation
82        let len = *(this.as_ptr().add(len_offset) as *const usize);
83        Self::layout(len)
84    }
85
86    /// Allocate an *uninitialized* nix string with the given length. Writes the length to the
87    /// length value in the pointer, but leaves both context and data uninitialized
88    ///
89    /// This function is safe to call (as constructing pointers of any sort of validity is always
90    /// safe in Rust) but it is unsafe to use the resulting pointer to do anything other than
91    ///
92    /// 1. Read the length
93    /// 2. Write the context
94    /// 3. Write the data
95    ///
96    /// until the string is fully initialized
97    fn alloc(len: usize) -> NonNull<c_void> {
98        let (layout, len_offset, _data_offset) = Self::layout(len);
99        debug_assert_ne!(layout.size(), 0);
100        unsafe {
101            // SAFETY: Layout has non-zero size, since the layout of the context and the
102            // layout of the len both have non-zero size
103            let ptr = alloc(layout);
104
105            if let Some(this) = NonNull::new(ptr as *mut _) {
106                // SAFETY: We've allocated with a layout that causes the len_offset to be in-bounds
107                // and writeable, and if the allocation succeeded it won't wrap
108                ((this.as_ptr() as *mut u8).add(len_offset) as *mut usize).write(len);
109                debug_assert_eq!(Self::len(this), len);
110                this
111            } else {
112                handle_alloc_error(layout);
113            }
114        }
115    }
116
117    /// Deallocate the Nix string at the given pointer
118    ///
119    /// # Safety
120    ///
121    /// This function must only be called with a pointer that has been properly initialized with
122    /// [`Self::alloc`]
123    unsafe fn dealloc(this: NonNull<c_void>) {
124        let (layout, _, _) = Self::layout_of(this);
125        // SAFETY: okay because of the safety guarantees of this method
126        dealloc(this.as_ptr() as *mut u8, layout)
127    }
128
129    /// Return the length of the Nix string at the given pointer
130    ///
131    /// # Safety
132    ///
133    /// This function must only be called with a pointer that has been properly initialized with
134    /// [`Self::alloc`]
135    unsafe fn len(this: NonNull<c_void>) -> usize {
136        let (_, len_offset, _) = Self::layout_of(this);
137        // SAFETY: As long as the safety guarantees of this method are upheld, we've allocated with
138        // a layout that causes the len_offset to be in-bounds and writeable, and if the allocation
139        // succeeded it won't wrap
140        *(this.as_ptr().add(len_offset) as *const usize)
141    }
142
143    /// Return a pointer to the context value within the given Nix string pointer
144    ///
145    /// # Safety
146    ///
147    /// This function must only be called with a pointer that has been properly initialized with
148    /// [`Self::alloc`]
149    unsafe fn context_ptr(this: NonNull<c_void>) -> *mut Option<Box<NixContext>> {
150        // SAFETY: The context is the first field in the layout of the allocation
151        this.as_ptr() as *mut Option<Box<NixContext>>
152    }
153
154    /// Construct a shared reference to the context value within the given Nix string pointer
155    ///
156    /// # Safety
157    ///
158    /// This function must only be called with a pointer that has been properly initialized with
159    /// [`Self::alloc`], and where the context has been properly initialized (by writing to the
160    /// pointer returned from [`Self::context_ptr`]).
161    ///
162    /// Also, all the normal Rust rules about pointer-to-reference conversion apply. See
163    /// [`NonNull::as_ref`] for more.
164    unsafe fn context_ref<'a>(this: NonNull<c_void>) -> &'a Option<Box<NixContext>> {
165        Self::context_ptr(this).as_ref().unwrap()
166    }
167
168    /// Construct a mutable reference to the context value within the given Nix string pointer
169    ///
170    /// # Safety
171    ///
172    /// This function must only be called with a pointer that has been properly initialized with
173    /// [`Self::alloc`], and where the context has been properly initialized (by writing to the
174    /// pointer returned from [`Self::context_ptr`]).
175    ///
176    /// Also, all the normal Rust rules about pointer-to-reference conversion apply. See
177    /// [`NonNull::as_mut`] for more.
178    unsafe fn context_mut<'a>(this: NonNull<c_void>) -> &'a mut Option<Box<NixContext>> {
179        Self::context_ptr(this).as_mut().unwrap()
180    }
181
182    /// Return a pointer to the data array within the given Nix string pointer
183    ///
184    /// # Safety
185    ///
186    /// This function must only be called with a pointer that has been properly initialized with
187    /// [`Self::alloc`]
188    unsafe fn data_ptr(this: NonNull<c_void>) -> *mut u8 {
189        let (_, _, data_offset) = Self::layout_of(this);
190        // SAFETY: data is the third field in the layout of the allocation
191        this.as_ptr().add(data_offset) as *mut u8
192    }
193
194    /// Construct a shared reference to the data slice within the given Nix string pointer
195    ///
196    /// # Safety
197    ///
198    /// This function must only be called with a pointer that has been properly initialized with
199    /// [`Self::alloc`], and where the data array has been properly initialized (by writing to the
200    /// pointer returned from [`Self::data_ptr`]).
201    ///
202    /// Also, all the normal Rust rules about pointer-to-reference conversion apply. See
203    /// [`slice::from_raw_parts`] for more.
204    unsafe fn data_slice<'a>(this: NonNull<c_void>) -> &'a [u8] {
205        let len = Self::len(this);
206        let data = Self::data_ptr(this);
207        slice::from_raw_parts(data, len)
208    }
209
210    /// Construct a mutable reference to the data slice within the given Nix string pointer
211    ///
212    /// # Safety
213    ///
214    /// This function must only be called with a pointer that has been properly initialized with
215    /// [`Self::alloc`], and where the data array has been properly initialized (by writing to the
216    /// pointer returned from [`Self::data_ptr`]).
217    ///
218    /// Also, all the normal Rust rules about pointer-to-reference conversion apply. See
219    /// [`slice::from_raw_parts_mut`] for more.
220    #[allow(dead_code)]
221    unsafe fn data_slice_mut<'a>(this: NonNull<c_void>) -> &'a mut [u8] {
222        let len = Self::len(this);
223        let data = Self::data_ptr(this);
224        slice::from_raw_parts_mut(data, len)
225    }
226
227    /// Clone the Nix string pointed to by this pointer, and return a pointer to a new Nix string
228    /// containing the same data and context.
229    ///
230    /// # Safety
231    ///
232    /// This function must only be called with a pointer that has been properly initialized with
233    /// [`Self::alloc`], and where the context has been properly initialized (by writing to the
234    /// pointer returned from [`Self::context_ptr`]), and the data array has been properly
235    /// initialized (by writing to the pointer returned from [`Self::data_ptr`]).
236    unsafe fn clone(this: NonNull<c_void>) -> NonNull<c_void> {
237        let (layout, _, _) = Self::layout_of(this);
238        let ptr = alloc(layout);
239        if let Some(new) = NonNull::new(ptr as *mut _) {
240            ptr::copy_nonoverlapping(this.as_ptr(), new.as_ptr(), layout.size());
241            Self::context_ptr(new).write(Self::context_ref(this).clone());
242            new
243        } else {
244            handle_alloc_error(layout);
245        }
246    }
247}
248
249#[derive(Default)]
250struct InternerInner {
251    #[allow(clippy::disallowed_types)] // Not using the default hasher
252    map: std::collections::HashMap<u64, NonNull<c_void>, BuildNoHashHasher<u64>>,
253    #[cfg(feature = "no_leak")]
254    #[allow(clippy::disallowed_types)] // Not using the default hasher
255    interned_strings: FxHashSet<NonNull<c_void>>,
256}
257
258unsafe impl Send for InternerInner {}
259
260fn hash<T>(s: T) -> u64
261where
262    T: Hash,
263{
264    let mut hasher = FxHasher::default();
265    s.hash(&mut hasher);
266    hasher.finish()
267}
268
269impl InternerInner {
270    pub fn intern(&mut self, s: &[u8]) -> NixString {
271        let hash = hash(s);
272        if let Some(s) = self.map.get(&hash) {
273            return NixString(*s);
274        }
275
276        let string = NixString::new_inner(s, None);
277        self.map.insert(hash, string.0);
278        #[cfg(feature = "no_leak")]
279        self.interned_strings.insert(string.0);
280        string
281    }
282}
283
284#[derive(Default)]
285struct Interner(RefCell<InternerInner>);
286
287impl Interner {
288    pub fn intern(&self, s: &[u8]) -> NixString {
289        self.0.borrow_mut().intern(s)
290    }
291
292    #[cfg(feature = "no_leak")]
293    pub fn is_interned_string(&self, string: &NixString) -> bool {
294        self.0.borrow().interned_strings.contains(&string.0)
295    }
296}
297
298thread_local! {
299    static INTERNER: Interner = Interner::default();
300}
301
302/// Nix string values
303///
304/// # Internals
305///
306/// For performance reasons (to keep allocations small, and to avoid indirections), [`NixString`] is
307/// represented as a single *thin* pointer to a packed data structure containing the
308/// [context][NixContext] and the string data itself (which is a raw byte array, to match the Nix
309/// string semantics that allow any array of bytes to be represented by a string).
310///
311/// This memory representation is documented in [`NixStringInner`], but since Rust prefers to deal
312/// with slices via *fat pointers* (pointers that include the length in the *pointer*, not in the
313/// heap allocation), we have to do mostly manual layout management and allocation for this
314/// representation. See the documentation for the methods of [`NixStringInner`] for more information
315pub struct NixString(NonNull<c_void>);
316
317unsafe impl Send for NixString {}
318unsafe impl Sync for NixString {}
319
320impl Drop for NixString {
321    #[cfg(not(feature = "no_leak"))]
322    fn drop(&mut self) {
323        if self.context().is_some() {
324            // SAFETY: There's no way to construct a NixString that doesn't leave the allocation correct
325            // according to the rules of dealloc
326            unsafe {
327                NixStringInner::dealloc(self.0);
328            }
329        }
330    }
331
332    #[cfg(feature = "no_leak")]
333    fn drop(&mut self) {
334        if INTERNER.with(|i| i.is_interned_string(self)) {
335            return;
336        }
337
338        // SAFETY: There's no way to construct a NixString that doesn't leave the allocation correct
339        // according to the rules of dealloc
340        unsafe {
341            NixStringInner::dealloc(self.0);
342        }
343    }
344}
345
346impl Clone for NixString {
347    fn clone(&self) -> Self {
348        if cfg!(feature = "no_leak") || self.context().is_some() {
349            // SAFETY: There's no way to construct a NixString that doesn't leave the allocation correct
350            // according to the rules of clone
351            unsafe { Self(NixStringInner::clone(self.0)) }
352        } else {
353            // SAFETY:
354            //
355            // - NixStrings are never mutated
356            // - NixStrings are never freed
357            Self(self.0)
358        }
359    }
360}
361
362impl Debug for NixString {
363    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364        if let Some(ctx) = self.context() {
365            f.debug_struct("NixString")
366                .field("context", ctx)
367                .field("data", &self.as_bstr())
368                .finish()
369        } else {
370            write!(f, "{:?}", self.as_bstr())
371        }
372    }
373}
374
375impl PartialEq for NixString {
376    fn eq(&self, other: &Self) -> bool {
377        self.0 == other.0 || self.as_bstr() == other.as_bstr()
378    }
379}
380
381impl Eq for NixString {}
382
383impl PartialEq<&[u8]> for NixString {
384    fn eq(&self, other: &&[u8]) -> bool {
385        **self == **other
386    }
387}
388
389impl PartialEq<&str> for NixString {
390    fn eq(&self, other: &&str) -> bool {
391        **self == other.as_bytes()
392    }
393}
394
395impl PartialOrd for NixString {
396    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
397        Some(self.cmp(other))
398    }
399}
400
401impl Ord for NixString {
402    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
403        if self.0 == other.0 {
404            return std::cmp::Ordering::Equal;
405        }
406        self.as_bstr().cmp(other.as_bstr())
407    }
408}
409
410impl From<Box<BStr>> for NixString {
411    fn from(value: Box<BStr>) -> Self {
412        Self::new(&value, None)
413    }
414}
415
416impl From<BString> for NixString {
417    fn from(value: BString) -> Self {
418        Self::new(&value, None)
419    }
420}
421
422impl From<&BStr> for NixString {
423    fn from(value: &BStr) -> Self {
424        value.to_owned().into()
425    }
426}
427
428impl From<&[u8]> for NixString {
429    fn from(value: &[u8]) -> Self {
430        Self::from(value.to_owned())
431    }
432}
433
434impl From<Vec<u8>> for NixString {
435    fn from(value: Vec<u8>) -> Self {
436        value.into_boxed_slice().into()
437    }
438}
439
440impl From<Box<[u8]>> for NixString {
441    fn from(value: Box<[u8]>) -> Self {
442        Self::new(&value, None)
443    }
444}
445
446impl From<&str> for NixString {
447    fn from(s: &str) -> Self {
448        s.as_bytes().into()
449    }
450}
451
452impl From<String> for NixString {
453    fn from(s: String) -> Self {
454        s.into_bytes().into()
455    }
456}
457
458impl From<Box<str>> for NixString {
459    fn from(s: Box<str>) -> Self {
460        s.into_boxed_bytes().into()
461    }
462}
463
464impl From<ast::Ident> for NixString {
465    fn from(ident: ast::Ident) -> Self {
466        ident.ident_token().unwrap().text().into()
467    }
468}
469
470impl<'a> From<&'a NixString> for &'a BStr {
471    fn from(s: &'a NixString) -> Self {
472        s.as_bstr()
473    }
474}
475
476// No impl From<NixString> for String, that one quotes.
477
478impl From<NixString> for BString {
479    fn from(s: NixString) -> Self {
480        s.as_bstr().to_owned()
481    }
482}
483
484impl AsRef<[u8]> for NixString {
485    fn as_ref(&self) -> &[u8] {
486        self.as_bytes()
487    }
488}
489
490impl Borrow<BStr> for NixString {
491    fn borrow(&self) -> &BStr {
492        self.as_bstr()
493    }
494}
495
496impl Borrow<[u8]> for NixString {
497    fn borrow(&self) -> &[u8] {
498        self.as_bytes()
499    }
500}
501
502impl Hash for NixString {
503    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
504        self.as_bstr().hash(state)
505    }
506}
507
508impl<'de> Deserialize<'de> for NixString {
509    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
510    where
511        D: Deserializer<'de>,
512    {
513        struct StringVisitor;
514
515        impl Visitor<'_> for StringVisitor {
516            type Value = NixString;
517
518            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
519                formatter.write_str("a valid Nix string")
520            }
521
522            fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
523            where
524                E: serde::de::Error,
525            {
526                Ok(v.into())
527            }
528
529            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
530            where
531                E: serde::de::Error,
532            {
533                Ok(v.into())
534            }
535        }
536
537        deserializer.deserialize_string(StringVisitor)
538    }
539}
540
541impl Deref for NixString {
542    type Target = BStr;
543
544    fn deref(&self) -> &Self::Target {
545        self.as_bstr()
546    }
547}
548
549#[cfg(feature = "arbitrary")]
550mod arbitrary {
551    use super::*;
552    use proptest::prelude::{any_with, Arbitrary};
553    use proptest::strategy::{BoxedStrategy, Strategy};
554
555    impl Arbitrary for NixString {
556        type Parameters = <String as Arbitrary>::Parameters;
557
558        type Strategy = BoxedStrategy<Self>;
559
560        fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
561            any_with::<String>(args).prop_map(Self::from).boxed()
562        }
563    }
564}
565
566/// Set non-scientifically. TODO(aspen): think more about what this should be
567const INTERN_THRESHOLD: usize = 32;
568
569impl NixString {
570    fn new(contents: &[u8], context: Option<Box<NixContext>>) -> Self {
571        debug_assert!(
572            !context.as_deref().is_some_and(NixContext::is_empty),
573            "BUG: initialized with empty context"
574        );
575
576        if !cfg!(feature = "no_leak") /* It's only safe to intern if we leak strings, since there's
577                                       * nothing yet preventing interned strings from getting freed
578                                       * (and then used by other copies) otherwise
579                                       */
580            && contents.len() <= INTERN_THRESHOLD
581            && context.is_none()
582        {
583            return INTERNER.with(|i| i.intern(contents));
584        }
585
586        Self::new_inner(contents, context)
587    }
588
589    fn new_inner(contents: &[u8], context: Option<Box<NixContext>>) -> Self {
590        // SAFETY: We're always fully initializing a NixString here:
591        //
592        // 1. NixStringInner::alloc sets up the len for us
593        // 2. We set the context, using ptr::write to make sure that the uninitialized memory isn't
594        //    read or dropped
595        // 3. We set the data, using copy_from_nonoverlapping to make sure that the uninitialized
596        //    memory isn't read or dropped
597        //
598        // Only *then* can we construct a NixString
599        unsafe {
600            let inner = NixStringInner::alloc(contents.len());
601            NixStringInner::context_ptr(inner).write(context);
602            NixStringInner::data_ptr(inner)
603                .copy_from_nonoverlapping(contents.as_ptr(), contents.len());
604            Self(inner)
605        }
606    }
607
608    pub fn new_inherit_context_from<T>(other: &NixString, new_contents: T) -> Self
609    where
610        NixString: From<T>,
611    {
612        Self::new(
613            Self::from(new_contents).as_ref(),
614            other.context().map(|c| Box::new(c.clone())),
615        )
616    }
617
618    pub fn new_context_from<T>(context: NixContext, contents: T) -> Self
619    where
620        NixString: From<T>,
621    {
622        Self::new(
623            Self::from(contents).as_ref(),
624            if context.is_empty() {
625                None
626            } else {
627                Some(Box::new(context))
628            },
629        )
630    }
631
632    pub fn as_bstr(&self) -> &BStr {
633        BStr::new(self.as_bytes())
634    }
635
636    pub fn as_bytes(&self) -> &[u8] {
637        // SAFETY: There's no way to construct an uninitialized NixString (see the SAFETY comment in
638        // `new`)
639        unsafe { NixStringInner::data_slice(self.0) }
640    }
641
642    pub fn into_bstring(self) -> BString {
643        self.as_bstr().to_owned()
644    }
645
646    /// Return a displayable representation of the string as an
647    /// identifier.
648    ///
649    /// This is used when printing out strings used as e.g. attribute
650    /// set keys, as those are only escaped in the presence of special
651    /// characters.
652    pub fn ident_str(&self) -> Cow<str> {
653        let escaped = match self.to_str_lossy() {
654            Cow::Borrowed(s) => nix_escape_string(s),
655            Cow::Owned(s) => nix_escape_string(&s).into_owned().into(),
656        };
657        match escaped {
658            // A borrowed string is unchanged and can be returned as
659            // is.
660            Cow::Borrowed(_) => {
661                if is_valid_nix_identifier(&escaped) && !is_keyword(&escaped) {
662                    escaped
663                } else {
664                    Cow::Owned(format!("\"{}\"", escaped))
665                }
666            }
667
668            // An owned string has escapes, and needs the outer quotes
669            // for display.
670            Cow::Owned(s) => Cow::Owned(format!("\"{}\"", s)),
671        }
672    }
673
674    pub fn concat(&self, other: &Self) -> Self {
675        let mut s = self.to_vec();
676        s.extend(&(***other));
677
678        let context = [self.context(), other.context()]
679            .into_iter()
680            .flatten()
681            .fold(NixContext::new(), |mut acc_ctx, new_ctx| {
682                // TODO: consume new_ctx?
683                acc_ctx.extend(new_ctx.iter().cloned());
684                acc_ctx
685            });
686        Self::new_context_from(context, s)
687    }
688
689    pub(crate) fn context(&self) -> Option<&NixContext> {
690        // SAFETY: There's no way to construct an uninitialized or invalid NixString (see the SAFETY
691        // comment in `new`).
692        //
693        // Also, we're using the same lifetime and mutability as self, to fit the
694        // pointer-to-reference conversion rules
695        let context = unsafe { NixStringInner::context_ref(self.0).as_deref() };
696
697        debug_assert!(
698            !context.is_some_and(NixContext::is_empty),
699            "BUG: empty context"
700        );
701
702        context
703    }
704
705    pub(crate) fn context_mut(&mut self) -> &mut Option<Box<NixContext>> {
706        // SAFETY: There's no way to construct an uninitialized or invalid NixString (see the SAFETY
707        // comment in `new`).
708        //
709        // Also, we're using the same lifetime and mutability as self, to fit the
710        // pointer-to-reference conversion rules
711        let context = unsafe { NixStringInner::context_mut(self.0) };
712
713        debug_assert!(
714            !context.as_deref().is_some_and(NixContext::is_empty),
715            "BUG: empty context"
716        );
717
718        context
719    }
720
721    /// Iterates over all context elements.
722    /// See [iter_plain], [iter_derivation], [iter_single_outputs].
723    pub fn iter_context(&self) -> impl Iterator<Item = &NixContext> {
724        self.context().into_iter()
725    }
726
727    /// Iterates over "plain" context elements, e.g. sources imported
728    /// in the store without more information, i.e. `toFile` or coerced imported paths.
729    /// It yields paths to the store.
730    pub fn iter_ctx_plain(&self) -> impl Iterator<Item = &str> {
731        self.iter_context().flat_map(|context| context.iter_plain())
732    }
733
734    /// Iterates over "full derivations" context elements, e.g. something
735    /// referring to their `drvPath`, i.e. their full sources and binary closure.
736    /// It yields derivation paths.
737    pub fn iter_ctx_derivation(&self) -> impl Iterator<Item = &str> {
738        self.iter_context()
739            .flat_map(|context| context.iter_derivation())
740    }
741
742    /// Iterates over "single" context elements, e.g. single derived paths,
743    /// or also known as the single output of a given derivation.
744    /// The first element of the tuple is the output name
745    /// and the second element is the derivation path.
746    pub fn iter_ctx_single_outputs(&self) -> impl Iterator<Item = (&str, &str)> {
747        self.iter_context()
748            .flat_map(|context| context.iter_single_outputs())
749    }
750
751    /// Returns whether this Nix string possess a context or not.
752    pub fn has_context(&self) -> bool {
753        self.context().is_some()
754    }
755
756    /// This clears the context of the string, returning
757    /// the removed dependency tracking information.
758    pub fn take_context(&mut self) -> Option<Box<NixContext>> {
759        self.context_mut().take()
760    }
761
762    /// This clears the context of that string, losing
763    /// all dependency tracking information.
764    pub fn clear_context(&mut self) {
765        let _ = self.take_context();
766    }
767
768    pub fn chars(&self) -> Chars<'_> {
769        self.as_bstr().chars()
770    }
771}
772
773fn nix_escape_char(ch: char, next: Option<&char>) -> Option<&'static str> {
774    match (ch, next) {
775        ('\\', _) => Some("\\\\"),
776        ('"', _) => Some("\\\""),
777        ('\n', _) => Some("\\n"),
778        ('\t', _) => Some("\\t"),
779        ('\r', _) => Some("\\r"),
780        ('$', Some('{')) => Some("\\$"),
781        _ => None,
782    }
783}
784
785/// Return true if this string is a keyword -- character strings
786/// which lexically match the "identifier" production but are not
787/// parsed as identifiers.  See also cppnix commit
788/// b72bc4a972fe568744d98b89d63adcd504cb586c.
789fn is_keyword(s: &str) -> bool {
790    matches!(
791        s,
792        "if" | "then" | "else" | "assert" | "with" | "let" | "in" | "rec" | "inherit"
793    )
794}
795
796/// Return true if this string can be used as an identifier in Nix.
797fn is_valid_nix_identifier(s: &str) -> bool {
798    // adapted from rnix-parser's tokenizer.rs
799    let mut chars = s.chars();
800    match chars.next() {
801        Some('a'..='z' | 'A'..='Z' | '_') => (),
802        _ => return false,
803    }
804    for c in chars {
805        match c {
806            'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | '\'' => (),
807            _ => return false,
808        }
809    }
810    true
811}
812
813/// Escape a Nix string for display, as most user-visible representation
814/// are escaped strings.
815///
816/// Note that this does not add the outer pair of surrounding quotes.
817fn nix_escape_string(input: &str) -> Cow<str> {
818    let mut iter = input.char_indices().peekable();
819
820    while let Some((i, c)) = iter.next() {
821        if let Some(esc) = nix_escape_char(c, iter.peek().map(|(_, c)| c)) {
822            let mut escaped = String::with_capacity(input.len());
823            escaped.push_str(&input[..i]);
824            escaped.push_str(esc);
825
826            // In theory we calculate how many bytes it takes to represent `esc`
827            // in UTF-8 and use that for the offset. It is, however, safe to
828            // assume that to be 1, as all characters that can be escaped in a
829            // Nix string are ASCII.
830            let mut inner_iter = input[i + 1..].chars().peekable();
831            while let Some(c) = inner_iter.next() {
832                match nix_escape_char(c, inner_iter.peek()) {
833                    Some(esc) => escaped.push_str(esc),
834                    None => escaped.push(c),
835                }
836            }
837
838            return Cow::Owned(escaped);
839        }
840    }
841
842    Cow::Borrowed(input)
843}
844
845impl Display for NixString {
846    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
847        f.write_str("\"")?;
848        f.write_str(&nix_escape_string(&self.to_str_lossy()))?;
849        f.write_str("\"")
850    }
851}
852
853#[cfg(all(test, feature = "arbitrary"))]
854mod tests {
855    use test_strategy::proptest;
856
857    use super::*;
858
859    use crate::properties::{eq_laws, hash_laws, ord_laws};
860
861    #[test]
862    fn size() {
863        assert_eq!(std::mem::size_of::<NixString>(), 8);
864    }
865
866    #[proptest]
867    fn clone_strings(s: NixString) {
868        drop(s.clone())
869    }
870
871    eq_laws!(NixString);
872    hash_laws!(NixString);
873    ord_laws!(NixString);
874}