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