nix_bindings_expr/
eval_state.rs

1//! # Nix Expression Evaluation
2//!
3//! This module provides the core [`EvalState`] type for evaluating Nix expressions
4//! and extracting typed values from the results.
5//!
6//! ## Overview
7//!
8//! The [`EvalState`] manages the evaluation context for Nix expressions, including:
9//! - Expression parsing and evaluation with [`eval_from_string`](EvalState::eval_from_string)
10//! - Type-safe value extraction with [`require_*`](EvalState#implementations) methods
11//! - Memory management and garbage collection integration
12//! - Store integration for derivations and store paths
13//! - Custom function creation with [`new_value_primop`](EvalState::new_value_primop) and [`new_value_thunk`](EvalState::new_value_thunk)
14//!
15//! ### Construction
16//!
17//! Create an [`EvalState`] using [`EvalState::new`] or [`EvalStateBuilder`] for advanced configuration:
18//!
19//! ```rust
20//! # use nix_bindings_expr::eval_state::{EvalState, EvalStateBuilder, test_init, gc_register_my_thread};
21//! # use nix_bindings_store::store::Store;
22//! # use std::collections::HashMap;
23//! # fn example() -> anyhow::Result<()> {
24//! # test_init(); let guard = gc_register_my_thread()?;
25//! let store = Store::open(None, HashMap::new())?;
26//!
27//! // Simple creation
28//! let mut es = EvalState::new(store.clone(), [])?;
29//!
30//! // With custom lookup paths
31//! let mut es = EvalStateBuilder::new(store)?
32//!     .lookup_path(["nixpkgs=/path/to/nixpkgs"])?
33//!     .build()?;
34//! # drop(guard);
35//! # Ok(())
36//! # }
37//! ```
38//!
39//! ## Value Extraction
40//!
41//! All `require_*` methods perform these steps:
42//! 1. **Evaluation**: Force evaluation of thunks as needed
43//! 2. **Type checking**: Verify the value matches the expected type
44//! 3. **Extraction**: Return the typed Rust value or an error
45//!
46//! Methods with `_strict` in their name also evaluate their return values before returning them.
47//!
48//! ### Evaluation Strictness
49//!
50//! - **Lazy methods** (e.g., [`require_list_size`](EvalState::require_list_size)):
51//!   Evaluate only the structure needed
52//! - **Strict methods** (e.g., [`require_list_strict`](EvalState::require_list_strict)):
53//!   Force full evaluation of all contained values
54//! - **Selective methods** (e.g., [`require_list_select_idx_strict`](EvalState::require_list_select_idx_strict)):
55//!   Evaluate only the accessed elements
56//!
57//! ## Laziness and Strictness
58//!
59//! The terms "lazy" and "strict" in this API refer to Nix's [Weak Head Normal Form (WHNF)](https://nix.dev/manual/nix/latest/language/evaluation.html#values)
60//! evaluation model, not the kind of deep strictness that is exercised by functions such as `builtins.toJSON` or `builtins.deepSeq`.
61//!
62//! - **WHNF evaluation**: Values are evaluated just enough to determine their type and basic structure
63//! - **Deep evaluation**: All nested values are recursively forced (like `builtins.deepSeq`)
64//!
65//! For example, a list in WHNF has its length determined but individual elements may remain unevaluated thunks.
66//! Methods marked as "strict" in this API force WHNF evaluation of their results, but do not perform deep evaluation
67//! of arbitrarily nested structures unless explicitly documented otherwise.
68//!
69//! ### Thread Safety and Memory Management
70//!
71//! Before using [`EvalState`] in a thread, register it with the (process memory) garbage collector:
72//!
73//! ```rust,no_run
74//! # use nix_bindings_expr::eval_state::{init, gc_register_my_thread, test_init};
75//! # fn example() -> anyhow::Result<()> {
76//! # test_init(); // Use test_init() in tests
77//! init()?; // Initialize Nix library
78//! let guard = gc_register_my_thread()?; // Register thread with GC
79//! // Now safe to use EvalState in this thread
80//! drop(guard);
81//! # Ok(())
82//! # }
83//! ```
84//!
85//! ## Error Handling
86//!
87//! Evaluation methods return [`Result`] types. Common error scenarios include:
88//! - **Type mismatches**: Expected type doesn't match actual value type
89//! - **Evaluation errors**: Nix expressions that throw or have undefined behavior
90//! - **Bounds errors**: Out-of-range access for indexed operations
91//!
92//! ## Examples
93//!
94//! ```rust
95//! use nix_bindings_expr::eval_state::{EvalState, test_init, gc_register_my_thread};
96//! use nix_bindings_store::store::Store;
97//! use std::collections::HashMap;
98//!
99//! # fn main() -> anyhow::Result<()> {
100//! test_init(); // init() in non-test code
101//! let guard = gc_register_my_thread()?;
102//!
103//! let store = Store::open(None, HashMap::new())?;
104//! let mut es = EvalState::new(store, [])?;
105//!
106//! // Evaluate a list expression
107//! let list_value = es.eval_from_string("[1 2 3]", "<example>")?;
108//!
109//! // Check the size (lazy - doesn't evaluate elements)
110//! let size = es.require_list_size(&list_value)?;
111//! println!("List has {} elements", size);
112//!
113//! // Access specific elements (evaluates only accessed elements)
114//! if let Some(first) = es.require_list_select_idx_strict(&list_value, 0)? {
115//!     let value = es.require_int(&first)?;
116//!     println!("First element: {}", value);
117//! }
118//!
119//! // Process all elements (evaluates all elements)
120//! let all_elements: Vec<_> = es.require_list_strict(&list_value)?;
121//! for element in all_elements {
122//!     let value = es.require_int(&element)?;
123//!     println!("Element: {}", value);
124//! }
125//!
126//! drop(guard);
127//! # Ok(())
128//! # }
129//! ```
130
131use crate::primop;
132use crate::value::{Int, Value, ValueType};
133use anyhow::Context as _;
134use anyhow::{bail, Result};
135use cstr::cstr;
136use nix_bindings_bdwgc_sys as gc;
137use nix_bindings_expr_sys as raw;
138use nix_bindings_store::path::StorePath;
139use nix_bindings_store::store::{Store, StoreWeak};
140use nix_bindings_store_sys as raw_store;
141use nix_bindings_util::context::Context;
142use nix_bindings_util::string_return::{
143    callback_get_result_string, callback_get_result_string_data,
144};
145use nix_bindings_util::{check_call, check_call_opt_key, result_string_init};
146use std::ffi::{c_char, CString};
147use std::iter::FromIterator;
148use std::os::raw::c_uint;
149use std::ptr::{null, null_mut, NonNull};
150use std::sync::{Arc, LazyLock, Weak};
151
152static INIT: LazyLock<Result<()>> = LazyLock::new(|| unsafe {
153    gc::GC_allow_register_threads();
154    check_call!(raw::libexpr_init(&mut Context::new()))?;
155    Ok(())
156});
157
158pub fn init() -> Result<()> {
159    let x = INIT.as_ref();
160    match x {
161        Ok(_) => Ok(()),
162        Err(e) => {
163            // Couldn't just clone the error, so we have to print it here.
164            Err(anyhow::format_err!("nix_bindings_expr::init error: {}", e))
165        }
166    }
167}
168
169/// A string value with its associated [store paths](https://nix.dev/manual/nix/stable/store/store-path.html).
170///
171/// Represents a Nix string with references to store paths.
172pub struct RealisedString {
173    /// The string content.
174    pub s: String,
175    /// Store paths referenced by the string.
176    pub paths: Vec<StorePath>,
177}
178
179/// A [Weak] reference to an [EvalState].
180pub struct EvalStateWeak {
181    inner: Weak<EvalStateRef>,
182    store: StoreWeak,
183}
184impl EvalStateWeak {
185    /// Upgrade the weak reference to a proper [EvalState].
186    ///
187    /// If no normal reference to the [EvalState] is around anymore elsewhere, this fails by returning `None`.
188    pub fn upgrade(&self) -> Option<EvalState> {
189        self.inner.upgrade().and_then(|eval_state| {
190            self.store.upgrade().map(|store| EvalState {
191                eval_state,
192                store,
193                context: Context::new(),
194            })
195        })
196    }
197}
198
199struct EvalStateRef {
200    eval_state: NonNull<raw::EvalState>,
201}
202impl EvalStateRef {
203    /// Returns a raw pointer to the underlying EvalState.
204    ///
205    /// # Safety
206    ///
207    /// The caller must ensure that the pointer is not used beyond the lifetime of the underlying [raw::EvalState].
208    unsafe fn as_ptr(&self) -> *mut raw::EvalState {
209        self.eval_state.as_ptr()
210    }
211}
212impl Drop for EvalStateRef {
213    fn drop(&mut self) {
214        unsafe {
215            raw::state_free(self.eval_state.as_ptr());
216        }
217    }
218}
219/// Builder for configuring and creating an [`EvalState`].
220///
221/// Provides advanced configuration options for evaluation context setup.
222/// Use [`EvalState::new`] for simple cases or this builder for custom configuration.
223///
224/// Requires Nix 2.26.0 or later.
225///
226/// # Examples
227///
228/// ```rust
229/// # use nix_bindings_expr::eval_state::{EvalState, EvalStateBuilder, test_init, gc_register_my_thread};
230/// # use nix_bindings_store::store::Store;
231/// # use std::collections::HashMap;
232/// # fn example() -> anyhow::Result<()> {
233/// # test_init();
234/// # let guard = gc_register_my_thread()?;
235/// let store = Store::open(None, HashMap::new())?;
236///
237/// let mut es: EvalState = EvalStateBuilder::new(store)?
238///     .lookup_path(["nixpkgs=/path/to/nixpkgs", "home-manager=/path/to/hm"])?
239///     .build()?;
240///
241/// let value = es.eval_from_string("<nixpkgs>", /* path display: */ "in-memory")?;
242/// # drop(guard);
243/// # Ok(())
244/// # }
245/// ```
246#[cfg(nix_at_least = "2.26")]
247pub struct EvalStateBuilder {
248    eval_state_builder: *mut raw::eval_state_builder,
249    lookup_path: Vec<CString>,
250    load_ambient_settings: bool,
251    store: Store,
252}
253#[cfg(nix_at_least = "2.26")]
254impl Drop for EvalStateBuilder {
255    fn drop(&mut self) {
256        unsafe {
257            raw::eval_state_builder_free(self.eval_state_builder);
258        }
259    }
260}
261#[cfg(nix_at_least = "2.26")]
262impl EvalStateBuilder {
263    /// Creates a new [`EvalStateBuilder`].
264    pub fn new(store: Store) -> Result<EvalStateBuilder> {
265        let mut context = Context::new();
266        let eval_state_builder =
267            unsafe { check_call!(raw::eval_state_builder_new(&mut context, store.raw_ptr())) }?;
268        Ok(EvalStateBuilder {
269            store,
270            eval_state_builder,
271            lookup_path: Vec::new(),
272            load_ambient_settings: true,
273        })
274    }
275    /// Sets the [lookup path](https://nix.dev/manual/nix/latest/language/constructs/lookup-path.html) for Nix expression evaluation.
276    pub fn lookup_path<'a>(mut self, path: impl IntoIterator<Item = &'a str>) -> Result<Self> {
277        let lookup_path: Vec<CString> = path
278            .into_iter()
279            .map(|path| {
280                CString::new(path).with_context(|| {
281                    format!("EvalStateBuilder::lookup_path: path `{path}` contains null byte")
282                })
283            })
284            .collect::<Result<_>>()?;
285        self.lookup_path = lookup_path;
286        Ok(self)
287    }
288    /// Sets whether to load settings from the ambient environment.
289    ///
290    /// When enabled (default), calls `nix_eval_state_builder_load` to load settings
291    /// from NIX_CONFIG and other environment variables. When disabled, only the
292    /// explicitly configured settings are used.
293    pub fn load_ambient_settings(mut self, load: bool) -> Self {
294        self.load_ambient_settings = load;
295        self
296    }
297    /// Builds the configured [`EvalState`].
298    pub fn build(&self) -> Result<EvalState> {
299        // Make sure the library is initialized
300        init()?;
301
302        let mut context = Context::new();
303
304        // Load settings from global configuration (including readOnlyMode = false).
305        // This is necessary for path coercion to work (adding files to the store).
306        if self.load_ambient_settings {
307            unsafe {
308                check_call!(raw::eval_state_builder_load(
309                    &mut context,
310                    self.eval_state_builder
311                ))?;
312            }
313        }
314
315        // Note: these raw C string pointers borrow from self.lookup_path
316        let mut lookup_path: Vec<*const c_char> = self
317            .lookup_path
318            .iter()
319            .map(|s| s.as_ptr())
320            .chain(std::iter::once(null())) // signal the end of the array
321            .collect();
322
323        unsafe {
324            check_call!(raw::eval_state_builder_set_lookup_path(
325                &mut context,
326                self.eval_state_builder,
327                lookup_path.as_mut_ptr()
328            ))?;
329        }
330
331        let eval_state =
332            unsafe { check_call!(raw::eval_state_build(&mut context, self.eval_state_builder)) }?;
333        Ok(EvalState {
334            eval_state: Arc::new(EvalStateRef {
335                eval_state: NonNull::new(eval_state).unwrap_or_else(|| {
336                    panic!("nix_state_create returned a null pointer without an error")
337                }),
338            }),
339            store: self.store.clone(),
340            context,
341        })
342    }
343    /// Returns a raw pointer to the underlying eval state builder.
344    ///
345    /// # Safety
346    ///
347    /// The caller must ensure that the pointer is not used beyond the lifetime of this builder.
348    // TODO: This function should be marked `unsafe`.
349    pub fn raw_ptr(&self) -> *mut raw::eval_state_builder {
350        self.eval_state_builder
351    }
352}
353
354pub struct EvalState {
355    eval_state: Arc<EvalStateRef>,
356    store: Store,
357    pub(crate) context: Context,
358}
359impl EvalState {
360    /// Creates a new EvalState with basic configuration.
361    ///
362    /// For more options, use [EvalStateBuilder].
363    pub fn new<'a>(store: Store, lookup_path: impl IntoIterator<Item = &'a str>) -> Result<Self> {
364        EvalStateBuilder::new(store)?
365            .lookup_path(lookup_path)?
366            .build()
367    }
368
369    /// Returns a raw pointer to the raw Nix C API EvalState.
370    ///
371    /// # Safety
372    ///
373    /// The caller must ensure that the pointer is not used beyond the lifetime of this `EvalState`.
374    pub unsafe fn raw_ptr(&self) -> *mut raw::EvalState {
375        self.eval_state.as_ptr()
376    }
377
378    /// Returns a reference to the Store that's used for instantiation, import from derivation, etc.
379    pub fn store(&self) -> &Store {
380        &self.store
381    }
382
383    /// Creates a weak reference to this EvalState.
384    pub fn weak_ref(&self) -> EvalStateWeak {
385        EvalStateWeak {
386            inner: Arc::downgrade(&self.eval_state),
387            store: self.store.weak_ref(),
388        }
389    }
390
391    /// Parses and evaluates a Nix expression `expr`.
392    ///
393    /// Expressions can contain relative paths such as `./.` that are resolved relative to the given `path`.
394    ///
395    /// # Examples
396    ///
397    /// ```
398    /// # use nix_bindings_expr::eval_state::{EvalState, test_init, gc_register_my_thread};
399    /// use nix_bindings_store::store::Store;
400    /// use nix_bindings_expr::value::Value;
401    /// use std::collections::HashMap;
402    ///
403    /// # fn main() -> anyhow::Result<()> {
404    /// # test_init();
405    /// # let guard = gc_register_my_thread()?;
406    /// # let mut es = EvalState::new(Store::open(None, HashMap::new())?, [])?;
407    /// let v: Value = es.eval_from_string("42", ".")?;
408    /// assert_eq!(es.require_int(&v)?, 42);
409    /// # drop(guard);
410    /// # Ok(())
411    /// # }
412    /// ```
413    #[doc(alias = "nix_expr_eval_from_string")]
414    #[doc(alias = "parse")]
415    #[doc(alias = "eval")]
416    #[doc(alias = "evaluate")]
417    pub fn eval_from_string(&mut self, expr: &str, path: &str) -> Result<Value> {
418        let expr_ptr =
419            CString::new(expr).with_context(|| "eval_from_string: expr contains null byte")?;
420        let path_ptr =
421            CString::new(path).with_context(|| "eval_from_string: path contains null byte")?;
422        unsafe {
423            let value = self.new_value_uninitialized()?;
424            check_call!(raw::expr_eval_from_string(
425                &mut self.context,
426                self.eval_state.as_ptr(),
427                expr_ptr.as_ptr(),
428                path_ptr.as_ptr(),
429                value.raw_ptr()
430            ))?;
431            Ok(value)
432        }
433    }
434
435    /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) of a value to [weak head normal form](https://nix.dev/manual/nix/latest/language/evaluation.html?highlight=WHNF#values).
436    ///
437    /// Converts [thunks](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness) to their evaluated form. Does not modify already-evaluated values.
438    ///
439    /// Does not perform deep evaluation of nested structures.
440    ///
441    /// See also: [Shared Evaluation State](Value#shared-evaluation-state)
442    #[doc(alias = "evaluate")]
443    #[doc(alias = "strict")]
444    pub fn force(&mut self, v: &Value) -> Result<()> {
445        unsafe {
446            check_call!(raw::value_force(
447                &mut self.context,
448                self.eval_state.as_ptr(),
449                v.raw_ptr()
450            ))
451        }?;
452        Ok(())
453    }
454
455    /// Returns the type of a value without forcing [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html).
456    ///
457    /// Returns [`None`] if the value is an unevaluated [thunk](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness).
458    ///
459    /// Returns [`Some`] if the value is already evaluated.
460    ///
461    /// See also: [Shared Evaluation State](Value#shared-evaluation-state)
462    #[doc(alias = "type_of")]
463    #[doc(alias = "value_type_lazy")]
464    #[doc(alias = "nix_get_type")]
465    #[doc(alias = "get_type")]
466    #[doc(alias = "nix_value_type")]
467    pub fn value_type_unforced(&mut self, value: &Value) -> Option<ValueType> {
468        let r = unsafe { check_call!(raw::get_type(&mut self.context, value.raw_ptr())) };
469        // .unwrap(): no reason for this to fail, as it does not evaluate
470        ValueType::from_raw(r.unwrap())
471    }
472    /// Returns the [type][`ValueType`] of a value, [forcing][`EvalState::force`] [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) if necessary.
473    ///
474    /// Forces evaluation if the value is an unevaluated [thunk](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness).
475    ///
476    /// Evaluation may fail, producing an [`Err`].
477    ///
478    /// Guarantees a definitive result if [`Ok`], thanks to the language being [pure](https://nix.dev/manual/nix/latest/language/index.html?highlight=pure#nix-language) and [lazy](https://nix.dev/manual/nix/latest/language/index.html?highlight=lazy#nix-language).
479    #[doc(alias = "type_of")]
480    #[doc(alias = "value_type_strict")]
481    #[doc(alias = "nix_get_type")]
482    #[doc(alias = "get_type")]
483    #[doc(alias = "nix_value_type_strict")]
484    pub fn value_type(&mut self, value: &Value) -> Result<ValueType> {
485        match self.value_type_unforced(value) {
486            Some(a) => Ok(a),
487            None => {
488                self.force(value)?;
489                match self.value_type_unforced(value) {
490                    Some(a) => Ok(a),
491                    None => {
492                        panic!("Nix value must not be thunk after being forced.")
493                    }
494                }
495            }
496        }
497    }
498    /// Extracts the value from an [integer][`ValueType::Int`] Nix value.
499    ///
500    /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is an integer.
501    ///
502    /// Returns the integer value if successful, or an [`Err`] if evaluation failed or the value is not an integer.
503    ///
504    /// # Examples
505    ///
506    /// ```rust
507    /// # use nix_bindings_expr::eval_state::{EvalState, test_init, gc_register_my_thread};
508    /// # use nix_bindings_store::store::Store;
509    /// # use std::collections::HashMap;
510    /// # fn example() -> anyhow::Result<()> {
511    /// # test_init();
512    /// # let guard = gc_register_my_thread()?;
513    /// let store = Store::open(None, HashMap::new())?;
514    /// let mut es = EvalState::new(store, [])?;
515    ///
516    /// let value = es.eval_from_string("42", "<example>")?;
517    /// let int_val = es.require_int(&value)?;
518    /// assert_eq!(int_val, 42);
519    /// # drop(guard);
520    /// # Ok(())
521    /// # }
522    /// ```
523    #[doc(alias = "integer")]
524    #[doc(alias = "number")]
525    #[doc(alias = "nix_get_int")]
526    #[doc(alias = "get_int")]
527    pub fn require_int(&mut self, v: &Value) -> Result<Int> {
528        let t = self.value_type(v)?;
529        if t != ValueType::Int {
530            bail!("expected an int, but got a {:?}", t);
531        }
532        unsafe { check_call!(raw::get_int(&mut self.context, v.raw_ptr())) }
533    }
534
535    /// Extracts the value from a [boolean][`ValueType::Bool`] Nix value.
536    ///
537    /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is a boolean.
538    ///
539    /// Returns the boolean value if successful, or an [`Err`] if evaluation failed or the value is not a boolean.
540    #[doc(alias = "boolean")]
541    #[doc(alias = "nix_get_bool")]
542    #[doc(alias = "get_bool")]
543    pub fn require_bool(&mut self, v: &Value) -> Result<bool> {
544        let t = self.value_type(v)?;
545        if t != ValueType::Bool {
546            bail!("expected a bool, but got a {:?}", t);
547        }
548        unsafe { check_call!(raw::get_bool(&mut self.context, v.raw_ptr())) }
549    }
550
551    /// Extracts all elements from a [list][`ValueType::List`] Nix value.
552    ///
553    /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is a list.
554    ///
555    /// Returns the contained values in the specified container type (e.g., [`Vec`], [`VecDeque`][`std::collections::VecDeque`], etc.).
556    ///
557    /// This is [strict](https://nix.dev/manual/nix/latest/language/evaluation.html#strictness) - all list elements will be evaluated.
558    ///
559    /// # Examples
560    ///
561    /// ```rust,no_run
562    /// # use nix_bindings_expr::value::Value;
563    /// # use std::collections::{VecDeque, LinkedList};
564    /// # fn example(es: &mut nix_bindings_expr::eval_state::EvalState, list_value: &Value) -> anyhow::Result<()> {
565    /// let vec: Vec<Value> = es.require_list_strict(&list_value)?;
566    /// let deque: VecDeque<Value> = es.require_list_strict(&list_value)?;
567    /// let linked_list = es.require_list_strict::<LinkedList<Value>>(&list_value)?;
568    /// # Ok(())
569    /// # }
570    /// ```
571    #[doc(alias = "collect")]
572    #[doc(alias = "to_vec")]
573    #[doc(alias = "all")]
574    #[doc(alias = "nix_get_list_size")]
575    #[doc(alias = "nix_get_list_byidx")]
576    pub fn require_list_strict<C>(&mut self, value: &Value) -> Result<C>
577    where
578        C: FromIterator<Value>,
579    {
580        let t = self.value_type(value)?;
581        if t != ValueType::List {
582            bail!("expected a list, but got a {:?}", t);
583        }
584        let size = unsafe { check_call!(raw::get_list_size(&mut self.context, value.raw_ptr())) }?;
585
586        (0..size)
587            .map(|i| {
588                let element_ptr = unsafe {
589                    check_call!(raw::get_list_byidx(
590                        &mut self.context,
591                        value.raw_ptr(),
592                        self.eval_state.as_ptr(),
593                        i
594                    ))
595                }?;
596                Ok(unsafe { Value::new(element_ptr) })
597            })
598            .collect()
599    }
600
601    /// Evaluate, and require that the [`Value`] is a Nix [`ValueType::AttrSet`].
602    ///
603    /// Returns a list of the keys in the attrset.
604    ///
605    /// NOTE: this currently implements its own sorting, which probably matches Nix's implementation, but is not guaranteed.
606    #[doc(alias = "keys")]
607    #[doc(alias = "attributes")]
608    #[doc(alias = "fields")]
609    pub fn require_attrs_names(&mut self, v: &Value) -> Result<Vec<String>> {
610        self.require_attrs_names_unsorted(v).map(|mut v| {
611            v.sort();
612            v
613        })
614    }
615
616    /// For when [`EvalState::require_attrs_names`] isn't fast enough.
617    ///
618    /// Only use when it's ok that the keys are returned in an arbitrary order.
619    #[doc(alias = "keys_unsorted")]
620    #[doc(alias = "attributes_unsorted")]
621    pub fn require_attrs_names_unsorted(&mut self, v: &Value) -> Result<Vec<String>> {
622        let t = self.value_type(v)?;
623        if t != ValueType::AttrSet {
624            bail!("expected an attrset, but got a {:?}", t);
625        }
626        let n = unsafe { check_call!(raw::get_attrs_size(&mut self.context, v.raw_ptr())) }?;
627        let mut attrs = Vec::with_capacity(n as usize);
628        for i in 0..n {
629            let cstr_ptr: *const c_char = unsafe {
630                check_call!(raw::get_attr_name_byidx(
631                    &mut self.context,
632                    v.raw_ptr(),
633                    self.eval_state.as_ptr(),
634                    i as c_uint
635                ))
636            }?;
637            let cstr = unsafe { std::ffi::CStr::from_ptr(cstr_ptr) };
638            let s = cstr
639                .to_str()
640                .map_err(|e| anyhow::format_err!("Nix attrset key is not valid UTF-8: {}", e))?;
641            attrs.insert(i as usize, s.to_owned());
642        }
643        Ok(attrs)
644    }
645
646    /// Extracts an attribute value from an [attribute set][`ValueType::AttrSet`] Nix value.
647    ///
648    /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is an attribute set.
649    ///
650    /// Returns the attribute value if found, or an [`Err`] if evaluation failed, the attribute doesn't exist, or the value is not an attribute set.
651    #[doc(alias = "get_attr")]
652    #[doc(alias = "attribute")]
653    #[doc(alias = "field")]
654    pub fn require_attrs_select(&mut self, v: &Value, attr_name: &str) -> Result<Value> {
655        let t = self.value_type(v)?;
656        if t != ValueType::AttrSet {
657            bail!("expected an attrset, but got a {:?}", t);
658        }
659        let attr_name = CString::new(attr_name)
660            .with_context(|| "require_attrs_select: attrName contains null byte")?;
661        unsafe {
662            let v2 = check_call!(raw::get_attr_byname(
663                &mut self.context,
664                v.raw_ptr(),
665                self.eval_state.as_ptr(),
666                attr_name.as_ptr()
667            ));
668            match v2 {
669                Ok(v2) => Ok(Value::new(v2)),
670                Err(e) => {
671                    // As of Nix 2.26, the error message is not helpful when it
672                    // is simply missing, so we provide a better one. (Note that
673                    // missing attributes requested by Nix expressions OTOH is a
674                    // different error message which works fine.)
675                    if e.to_string() == "missing attribute" {
676                        bail!("attribute `{}` not found", attr_name.to_string_lossy());
677                    } else {
678                        Err(e)
679                    }
680                }
681            }
682        }
683    }
684
685    /// Extracts an optional attribute value from an [attribute set][`ValueType::AttrSet`] Nix value.
686    ///
687    /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is an attribute set.
688    ///
689    /// Returns [`Err`] if evaluation failed or the value is not an attribute set.
690    ///
691    /// Returns `Ok(None)` if the attribute is not present.
692    ///
693    /// Returns `Ok(Some(value))` if the attribute is present.
694    #[doc(alias = "nix_get_attr_byname")]
695    #[doc(alias = "get_attr_byname")]
696    #[doc(alias = "get_attr_opt")]
697    #[doc(alias = "try_get")]
698    #[doc(alias = "maybe_get")]
699    pub fn require_attrs_select_opt(
700        &mut self,
701        v: &Value,
702        attr_name: &str,
703    ) -> Result<Option<Value>> {
704        let t = self.value_type(v)?;
705        if t != ValueType::AttrSet {
706            bail!("expected an attrset, but got a {:?}", t);
707        }
708        let attr_name = CString::new(attr_name)
709            .with_context(|| "require_attrs_select_opt: attrName contains null byte")?;
710        let v2 = unsafe {
711            check_call_opt_key!(raw::get_attr_byname(
712                &mut self.context,
713                v.raw_ptr(),
714                self.eval_state.as_ptr(),
715                attr_name.as_ptr()
716            ))
717        }?;
718        Ok(v2.map(|x| unsafe { Value::new(x) }))
719    }
720
721    /// Returns the number of elements in a [list][`ValueType::List`] Nix value.
722    ///
723    /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) of the list structure and verifies the value is a list.
724    ///
725    /// Individual elements remain as lazy [thunks](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness) and are not evaluated.
726    #[doc(alias = "length")]
727    #[doc(alias = "count")]
728    #[doc(alias = "len")]
729    #[doc(alias = "nix_get_list_size")]
730    #[doc(alias = "get_list_size")]
731    pub fn require_list_size(&mut self, v: &Value) -> Result<u32> {
732        let t = self.value_type(v)?;
733        if t != ValueType::List {
734            bail!("expected a list, but got a {:?}", t);
735        }
736        let ret = unsafe { check_call!(raw::get_list_size(&mut self.context, v.raw_ptr())) }?;
737        Ok(ret)
738    }
739
740    /// Extracts an element from a [list][`ValueType::List`] Nix value by index.
741    ///
742    /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is a list.
743    /// Forces evaluation of the selected element, similar to [`Self::require_attrs_select`].
744    ///
745    /// Returns `Ok(Some(value))` if the element is found.
746    ///
747    /// Returns `Ok(None)` if the index is out of bounds.
748    ///
749    /// Returns [`Err`] if evaluation failed, the element contains an error (e.g., `throw`), or the value is not a list.
750    #[doc(alias = "get")]
751    #[doc(alias = "index")]
752    #[doc(alias = "at")]
753    #[doc(alias = "nix_get_list_byidx")]
754    #[doc(alias = "get_list_byidx")]
755    pub fn require_list_select_idx_strict(&mut self, v: &Value, idx: u32) -> Result<Option<Value>> {
756        let t = self.value_type(v)?;
757        if t != ValueType::List {
758            bail!("expected a list, but got a {:?}", t);
759        }
760
761        // TODO: Remove this bounds checking once https://github.com/NixOS/nix/pull/14030
762        // is merged, which will add proper bounds checking to the underlying C API.
763        // Currently we perform bounds checking in Rust to avoid undefined behavior.
764        let size = unsafe { check_call!(raw::get_list_size(&mut self.context, v.raw_ptr())) }?;
765
766        if idx >= size {
767            return Ok(None);
768        }
769
770        let v2 = unsafe {
771            check_call_opt_key!(raw::get_list_byidx(
772                &mut self.context,
773                v.raw_ptr(),
774                self.eval_state.as_ptr(),
775                idx
776            ))
777        }?;
778        Ok(v2.map(|x| unsafe { Value::new(x) }))
779    }
780
781    /// Creates a new [string][`ValueType::String`] Nix value.
782    ///
783    /// Returns a string value without any [string context](https://nix.dev/manual/nix/latest/language/string-context.html).
784    #[doc(alias = "make_string")]
785    #[doc(alias = "create_string")]
786    #[doc(alias = "string_value")]
787    pub fn new_value_str(&mut self, s: &str) -> Result<Value> {
788        let s = CString::new(s).with_context(|| "new_value_str: contains null byte")?;
789        let v = unsafe {
790            let value = self.new_value_uninitialized()?;
791            check_call!(raw::init_string(
792                &mut self.context,
793                value.raw_ptr(),
794                s.as_ptr()
795            ))?;
796            value
797        };
798        Ok(v)
799    }
800
801    /// Creates a new [integer][`ValueType::Int`] Nix value.
802    #[doc(alias = "make_int")]
803    #[doc(alias = "create_int")]
804    #[doc(alias = "int_value")]
805    #[doc(alias = "integer_value")]
806    pub fn new_value_int(&mut self, i: Int) -> Result<Value> {
807        let v = unsafe {
808            let value = self.new_value_uninitialized()?;
809            check_call!(raw::init_int(&mut self.context, value.raw_ptr(), i))?;
810            value
811        };
812        Ok(v)
813    }
814
815    /// Creates a new [thunk](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness) Nix value.
816    ///
817    /// The [thunk](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness) will lazily evaluate to the result of the given Rust function when forced.
818    /// The Rust function will be called with the current [`EvalState`] and must not return a thunk.
819    ///
820    /// The name is shown in stack traces.
821    #[doc(alias = "make_thunk")]
822    #[doc(alias = "create_thunk")]
823    #[doc(alias = "lazy_value")]
824    pub fn new_value_thunk(
825        &mut self,
826        name: &str,
827        f: Box<dyn Fn(&mut EvalState) -> Result<Value>>,
828    ) -> Result<Value> {
829        // Nix doesn't have a function for creating a thunk, so we have to
830        // create a function and pass it a dummy argument.
831        let name = CString::new(name).with_context(|| "new_thunk: name contains null byte")?;
832        let primop = primop::PrimOp::new(
833            self,
834            primop::PrimOpMeta {
835                // name is observable in stack traces, ie if the thunk returns Err
836                name: name.as_c_str(),
837                // doc is unlikely to be observable, so we provide a constant one for simplicity.
838                doc: cstr!("Performs an on demand computation, implemented outside the Nix language in native code."),
839                // like doc, unlikely to be observed
840                args: [CString::new("internal_unused").unwrap().as_c_str()],
841            },
842            Box::new(move |eval_state, _dummy: &[Value; 1]| f(eval_state)),
843        )?;
844
845        let p = self.new_value_primop(primop)?;
846        self.new_value_apply(&p, &p)
847    }
848
849    /// Not exposed, because the caller must always explicitly handle the context or not accept one at all.
850    fn get_string(&mut self, value: &Value) -> Result<String> {
851        let mut r = result_string_init!();
852        unsafe {
853            check_call!(raw::get_string(
854                &mut self.context,
855                value.raw_ptr(),
856                Some(callback_get_result_string),
857                callback_get_result_string_data(&mut r)
858            ))?;
859        };
860        r
861    }
862    /// Extracts a string value from a [string][`ValueType::String`] Nix value.
863    ///
864    /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is a string.
865    /// Returns the string value if successful, or an [`Err`] if evaluation failed or the value is not a string.
866    ///
867    /// NOTE: this will be replaced by two methods, one that also returns the context, and one that checks that the context is empty.
868    #[doc(alias = "str")]
869    #[doc(alias = "text")]
870    #[doc(alias = "nix_get_string")]
871    #[doc(alias = "get_string")]
872    pub fn require_string(&mut self, value: &Value) -> Result<String> {
873        let t = self.value_type(value)?;
874        if t != ValueType::String {
875            bail!("expected a string, but got a {:?}", t);
876        }
877        self.get_string(value)
878    }
879    /// Realises a [string][`ValueType::String`] Nix value with context information.
880    ///
881    /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html), verifies the value is a string, and builds any derivations
882    /// referenced in the [string context](https://nix.dev/manual/nix/latest/language/string-context.html) if required.
883    #[doc(alias = "realize_string")]
884    #[doc(alias = "string_with_context")]
885    #[doc(alias = "build_string")]
886    pub fn realise_string(
887        &mut self,
888        value: &Value,
889        is_import_from_derivation: bool,
890    ) -> Result<RealisedString> {
891        let t = self.value_type(value)?;
892        if t != ValueType::String {
893            bail!("expected a string, but got a {:?}", t);
894        }
895
896        let rs = unsafe {
897            check_call!(raw::string_realise(
898                &mut self.context,
899                self.eval_state.as_ptr(),
900                value.raw_ptr(),
901                is_import_from_derivation
902            ))
903        }?;
904
905        let s = unsafe {
906            let start = raw::realised_string_get_buffer_start(rs) as *const u8;
907            let size = raw::realised_string_get_buffer_size(rs);
908            let slice = std::slice::from_raw_parts(start, size);
909            String::from_utf8(slice.to_vec())
910                .map_err(|e| anyhow::format_err!("Nix string is not valid UTF-8: {}", e))?
911        };
912
913        let paths = unsafe {
914            let n = raw::realised_string_get_store_path_count(rs);
915            let mut paths = Vec::with_capacity(n as usize);
916            for i in 0..n {
917                let path = raw::realised_string_get_store_path(rs, i);
918                let path = NonNull::new(path as *mut raw_store::StorePath).ok_or_else(|| {
919                    anyhow::format_err!(
920                        "nix_realised_string_get_store_path returned a null pointer"
921                    )
922                })?;
923                paths.push(StorePath::new_raw_clone(path));
924            }
925            paths
926        };
927
928        // We've converted the nix_realised_string to a native struct containing copies, so we can free it now.
929        unsafe {
930            raw::realised_string_free(rs);
931        }
932
933        Ok(RealisedString { s, paths })
934    }
935
936    /// Applies a function to an argument and returns the result.
937    ///
938    /// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) of the function application.
939    /// For a lazy version, see [`Self::new_value_apply`].
940    #[doc(alias = "nix_value_call")]
941    #[doc(alias = "value_call")]
942    #[doc(alias = "apply")]
943    #[doc(alias = "invoke")]
944    #[doc(alias = "execute")]
945    pub fn call(&mut self, f: Value, a: Value) -> Result<Value> {
946        let value = self.new_value_uninitialized()?;
947        unsafe {
948            check_call!(raw::value_call(
949                &mut self.context,
950                self.eval_state.as_ptr(),
951                f.raw_ptr(),
952                a.raw_ptr(),
953                value.raw_ptr()
954            ))
955        }?;
956        Ok(value)
957    }
958
959    /// Apply a sequence of [function applications](https://nix.dev/manual/nix/latest/language/operators.html#function-application).
960    ///
961    /// When argument `f` is a curried function, this applies each argument in sequence.
962    /// Equivalent to the Nix expression `f arg1 arg2 arg3`.
963    ///
964    /// Returns a [`Value`] in at least weak head normal form if successful.
965    ///
966    /// Returns an [`Err`]
967    /// - if `f` did not evaluate to a function
968    /// - if `f arg1` had any problems
969    /// - if `f arg1` did not evaluate to a function (for `(f arg1) arg2`)
970    /// - etc
971    ///
972    /// # Examples
973    ///
974    /// ```rust
975    /// # use nix_bindings_expr::eval_state::{EvalState, test_init, gc_register_my_thread};
976    /// # use nix_bindings_store::store::Store;
977    /// # use std::collections::HashMap;
978    /// # fn example() -> anyhow::Result<()> {
979    /// # test_init();
980    /// # let guard = gc_register_my_thread()?;
981    /// let store = Store::open(None, HashMap::new())?;
982    /// let mut es = EvalState::new(store, [])?;
983    ///
984    /// // Create a curried function: x: y: x + y
985    /// let f = es.eval_from_string("x: y: x + y", "<example>")?;
986    /// let arg1 = es.eval_from_string("5", "<example>")?;
987    /// let arg2 = es.eval_from_string("3", "<example>")?;
988    ///
989    /// // Equivalent to: (x: y: x + y) 5 3
990    /// let result = es.call_multi(&f, &[arg1, arg2])?;
991    /// let value = es.require_int(&result)?;
992    /// assert_eq!(value, 8);
993    /// # drop(guard);
994    /// # Ok(())
995    /// # }
996    /// ```
997    #[doc(alias = "nix_value_call_multi")]
998    #[doc(alias = "value_call_multi")]
999    #[doc(alias = "apply_multi")]
1000    #[doc(alias = "curry")]
1001    #[doc(alias = "call_with_args")]
1002    pub fn call_multi(&mut self, f: &Value, args: &[Value]) -> Result<Value> {
1003        let value = self.new_value_uninitialized()?;
1004        unsafe {
1005            let mut args_ptrs = args.iter().map(|a| a.raw_ptr()).collect::<Vec<_>>();
1006            check_call!(raw::value_call_multi(
1007                &mut self.context,
1008                self.eval_state.as_ptr(),
1009                f.raw_ptr(),
1010                args_ptrs.len(),
1011                args_ptrs.as_mut_ptr(),
1012                value.raw_ptr()
1013            ))
1014        }?;
1015        Ok(value)
1016    }
1017
1018    /// Applies a function to an argument lazily, creating a [thunk](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness).
1019    ///
1020    /// Does not force [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) of the function application.
1021    /// For an eager version, see [`Self::call`].
1022    #[doc(alias = "lazy_apply")]
1023    #[doc(alias = "thunk_apply")]
1024    #[doc(alias = "defer_call")]
1025    pub fn new_value_apply(&mut self, f: &Value, a: &Value) -> Result<Value> {
1026        let value = self.new_value_uninitialized()?;
1027        unsafe {
1028            check_call!(raw::init_apply(
1029                &mut self.context,
1030                value.raw_ptr(),
1031                f.raw_ptr(),
1032                a.raw_ptr()
1033            ))
1034        }?;
1035        Ok(value)
1036    }
1037
1038    fn new_value_uninitialized(&mut self) -> Result<Value> {
1039        unsafe {
1040            let value = check_call!(raw::alloc_value(
1041                &mut self.context,
1042                self.eval_state.as_ptr()
1043            ))?;
1044            Ok(Value::new(value))
1045        }
1046    }
1047
1048    /// Creates a new [function][`ValueType::Function`] Nix value implemented by a Rust function.
1049    ///
1050    /// This is also known as a "primop" in Nix, short for primitive operation.
1051    /// Most of the `builtins.*` values are examples of primops, but this function
1052    /// does not affect `builtins`.
1053    #[doc(alias = "make_primop")]
1054    #[doc(alias = "create_function")]
1055    #[doc(alias = "builtin")]
1056    pub fn new_value_primop(&mut self, primop: primop::PrimOp) -> Result<Value> {
1057        let value = self.new_value_uninitialized()?;
1058        unsafe {
1059            check_call!(raw::init_primop(
1060                &mut self.context,
1061                value.raw_ptr(),
1062                primop.ptr
1063            ))?;
1064        };
1065        Ok(value)
1066    }
1067
1068    /// Creates a new [attribute set][`ValueType::AttrSet`] Nix value from an iterator of name-value pairs.
1069    ///
1070    /// Accepts any iterator that yields `(String, Value)` pairs and has an exact size.
1071    /// Common usage includes [`Vec`], [`std::collections::HashMap`], and array literals.
1072    ///
1073    /// # Examples
1074    ///
1075    /// ```rust
1076    /// # use nix_bindings_expr::eval_state::{EvalState, test_init, gc_register_my_thread};
1077    /// # use nix_bindings_store::store::Store;
1078    /// # use std::collections::HashMap;
1079    /// # fn example() -> anyhow::Result<()> {
1080    /// # test_init();
1081    /// # let guard = gc_register_my_thread()?;
1082    /// let store = Store::open(None, HashMap::new())?;
1083    /// let mut es = EvalState::new(store, [])?;
1084    /// let a = es.new_value_int(1)?;
1085    /// let b = es.new_value_int(2)?;
1086    /// let c = es.new_value_int(3)?;
1087    /// let d = es.new_value_int(4)?;
1088    ///
1089    /// // From array
1090    /// let attrs1 = es.new_value_attrs([
1091    ///     ("x".to_string(), a),
1092    ///     ("y".to_string(), b)
1093    /// ])?;
1094    ///
1095    /// // From HashMap
1096    /// let mut map = HashMap::new();
1097    /// map.insert("foo".to_string(), c);
1098    /// map.insert("bar".to_string(), d);
1099    /// let attrs2 = es.new_value_attrs(map)?;
1100    /// # drop(guard);
1101    /// # Ok(())
1102    /// # }
1103    /// ```
1104    #[doc(alias = "make_attrs")]
1105    #[doc(alias = "create_attrset")]
1106    #[doc(alias = "object")]
1107    #[doc(alias = "record")]
1108    pub fn new_value_attrs<I>(&mut self, attrs: I) -> Result<Value>
1109    where
1110        I: IntoIterator<Item = (String, Value)>,
1111        I::IntoIter: ExactSizeIterator,
1112    {
1113        let iter = attrs.into_iter();
1114        let size = iter.len();
1115        let bindings_builder = BindingsBuilder::new(self, size)?;
1116        for (name, value) in iter {
1117            let name =
1118                CString::new(name).with_context(|| "new_value_attrs: name contains null byte")?;
1119            unsafe {
1120                check_call!(raw::bindings_builder_insert(
1121                    &mut self.context,
1122                    bindings_builder.ptr,
1123                    name.as_ptr(),
1124                    value.raw_ptr()
1125                ))?;
1126            }
1127        }
1128        let value = self.new_value_uninitialized()?;
1129        unsafe {
1130            check_call!(raw::make_attrs(
1131                &mut self.context,
1132                value.raw_ptr(),
1133                bindings_builder.ptr
1134            ))?;
1135        }
1136        Ok(value)
1137    }
1138}
1139
1140// Internal RAII helper; could be refactored and made pub
1141struct BindingsBuilder {
1142    ptr: *mut raw::BindingsBuilder,
1143}
1144impl Drop for BindingsBuilder {
1145    fn drop(&mut self) {
1146        unsafe {
1147            raw::bindings_builder_free(self.ptr);
1148        }
1149    }
1150}
1151impl BindingsBuilder {
1152    fn new(eval_state: &mut EvalState, capacity: usize) -> Result<Self> {
1153        let ptr = unsafe {
1154            check_call!(raw::make_bindings_builder(
1155                &mut eval_state.context,
1156                eval_state.eval_state.as_ptr(),
1157                capacity
1158            ))
1159        }?;
1160        Ok(BindingsBuilder { ptr })
1161    }
1162}
1163
1164/// Triggers garbage collection immediately.
1165#[doc(alias = "garbage_collect")]
1166#[doc(alias = "collect")]
1167#[doc(alias = "gc")]
1168pub fn gc_now() {
1169    unsafe {
1170        raw::gc_now();
1171    }
1172}
1173
1174/// RAII guard for thread registration with the garbage collector.
1175///
1176/// Automatically unregisters the thread when dropped.
1177pub struct ThreadRegistrationGuard {
1178    must_unregister: bool,
1179}
1180impl Drop for ThreadRegistrationGuard {
1181    fn drop(&mut self) {
1182        if self.must_unregister {
1183            unsafe {
1184                gc::GC_unregister_my_thread();
1185            }
1186        }
1187    }
1188}
1189
1190fn gc_register_my_thread_do_it() -> Result<()> {
1191    unsafe {
1192        let mut sb: gc::GC_stack_base = gc::GC_stack_base {
1193            mem_base: null_mut(),
1194        };
1195        let r = gc::GC_get_stack_base(&mut sb);
1196        if r as u32 != gc::GC_SUCCESS {
1197            Err(anyhow::format_err!("GC_get_stack_base failed: {}", r))?;
1198        }
1199        gc::GC_register_my_thread(&sb);
1200        Ok(())
1201    }
1202}
1203
1204#[doc(alias = "register_thread")]
1205#[doc(alias = "thread_setup")]
1206#[doc(alias = "gc_register")]
1207pub fn gc_register_my_thread() -> Result<ThreadRegistrationGuard> {
1208    init()?;
1209    unsafe {
1210        let already_done = gc::GC_thread_is_registered();
1211        if already_done != 0 {
1212            return Ok(ThreadRegistrationGuard {
1213                must_unregister: false,
1214            });
1215        }
1216        gc_register_my_thread_do_it()?;
1217        Ok(ThreadRegistrationGuard {
1218            must_unregister: true,
1219        })
1220    }
1221}
1222
1223impl Clone for EvalState {
1224    fn clone(&self) -> Self {
1225        EvalState {
1226            eval_state: self.eval_state.clone(),
1227            store: self.store.clone(),
1228            context: Context::new(),
1229        }
1230    }
1231}
1232
1233/// Initialize the Nix library for testing. This includes some modifications to the Nix settings, that must not be used in production.
1234/// Use at your own peril, in rust test suites.
1235#[doc(alias = "test_initialize")]
1236#[doc(alias = "test_setup")]
1237pub fn test_init() {
1238    init().unwrap();
1239
1240    // During development, we encountered a problem where the build hook
1241    // would cause the test suite to reinvokes itself, causing an infinite loop.
1242    // While _NIX_TEST_NO_SANDBOX=1 should prevent this, we may also set the
1243    // build hook to "" to prevent this.
1244    nix_bindings_util::settings::set("build-hook", "").unwrap();
1245
1246    // When testing in the sandbox, the default build dir would be a parent of the storeDir,
1247    // which causes an error. So we set a custom build dir here.
1248    // Only available on linux
1249    if cfg!(target_os = "linux") {
1250        nix_bindings_util::settings::set("sandbox-build-dir", "/custom-build-dir-for-test")
1251            .unwrap();
1252    }
1253    std::env::set_var("_NIX_TEST_NO_SANDBOX", "1");
1254
1255    // The tests run offline
1256    nix_bindings_util::settings::set("substituters", "").unwrap();
1257}
1258
1259#[cfg(test)]
1260mod tests {
1261    use super::*;
1262    use cstr::cstr;
1263    use ctor::ctor;
1264    use std::collections::HashMap;
1265    use std::fs::read_dir;
1266    use std::io::Write as _;
1267    use std::sync::{Arc, Mutex};
1268
1269    #[ctor]
1270    fn setup() {
1271        test_init();
1272
1273        // Configure Nix settings for the test suite
1274        // Set max-call-depth to 1000 (lower than default 10000) for the
1275        // eval_state_builder_loads_max_call_depth test case, while
1276        // giving other tests sufficient room for normal evaluation.
1277        std::env::set_var("NIX_CONFIG", "max-call-depth = 1000");
1278    }
1279
1280    /// Run a function while making sure that the current thread is registered with the GC.
1281    pub fn gc_registering_current_thread<F, R>(f: F) -> Result<R>
1282    where
1283        F: FnOnce() -> R,
1284    {
1285        let guard = gc_register_my_thread()?;
1286        let r = f();
1287        drop(guard);
1288        Ok(r)
1289    }
1290
1291    #[test]
1292    fn eval_state_new_and_drop() {
1293        gc_registering_current_thread(|| {
1294            // very basic test: make sure initialization doesn't crash
1295            let store = Store::open(None, HashMap::new()).unwrap();
1296            let _e = EvalState::new(store, []).unwrap();
1297        })
1298        .unwrap();
1299    }
1300
1301    #[test]
1302    fn weak_ref() {
1303        gc_registering_current_thread(|| {
1304            let store = Store::open(None, HashMap::new()).unwrap();
1305            let es = EvalState::new(store, []).unwrap();
1306            let weak = es.weak_ref();
1307            let _es = weak.upgrade().unwrap();
1308        })
1309        .unwrap();
1310    }
1311
1312    #[test]
1313    fn weak_ref_gone() {
1314        gc_registering_current_thread(|| {
1315            let weak = {
1316                // Use a slightly different URL which is unique in the test suite, to bypass the global store cache
1317                let store = Store::open(Some("auto?foo=bar"), HashMap::new()).unwrap();
1318                let es = EvalState::new(store, []).unwrap();
1319                es.weak_ref()
1320            };
1321            assert!(weak.upgrade().is_none());
1322            assert!(weak.store.upgrade().is_none());
1323            assert!(weak.inner.upgrade().is_none());
1324        })
1325        .unwrap();
1326    }
1327
1328    #[test]
1329    fn eval_state_lookup_path() {
1330        let import_expression = "import <test_file0> + import <test_file1>";
1331        let integer0 = 83;
1332        let integer1 = 103;
1333        let mut test_file0 = tempfile::NamedTempFile::new().unwrap();
1334        let mut test_file1 = tempfile::NamedTempFile::new().unwrap();
1335        writeln!(test_file0, "{integer0}").unwrap();
1336        writeln!(test_file1, "{integer1}").unwrap();
1337        gc_registering_current_thread(|| {
1338            let mut es = EvalState::new(Store::open(None, HashMap::new()).unwrap(), []).unwrap();
1339            assert!(es.eval_from_string(import_expression, "<test>").is_err());
1340
1341            let mut es = EvalState::new(
1342                Store::open(None, HashMap::new()).unwrap(),
1343                [
1344                    format!("test_file0={}", test_file0.path().to_str().unwrap()).as_str(),
1345                    format!("test_file1={}", test_file1.path().to_str().unwrap()).as_str(),
1346                ],
1347            )
1348            .unwrap();
1349            let ie = &es.eval_from_string(import_expression, "<test>").unwrap();
1350            let v = es.require_int(ie).unwrap();
1351            assert_eq!(v, integer0 + integer1);
1352        })
1353        .unwrap();
1354        test_file0.close().unwrap();
1355        test_file1.close().unwrap();
1356    }
1357
1358    #[test]
1359    fn eval_state_eval_from_string() {
1360        gc_registering_current_thread(|| {
1361            let store = Store::open(None, HashMap::new()).unwrap();
1362            let mut es = EvalState::new(store, []).unwrap();
1363            let v = es.eval_from_string("1", "<test>").unwrap();
1364            let v2 = v.clone();
1365            es.force(&v).unwrap();
1366            let t = es.value_type_unforced(&v);
1367            assert!(t == Some(ValueType::Int));
1368            let t2 = es.value_type_unforced(&v2);
1369            assert!(t2 == Some(ValueType::Int));
1370            gc_now();
1371        })
1372        .unwrap();
1373    }
1374
1375    #[test]
1376    fn eval_state_value_bool() {
1377        gc_registering_current_thread(|| {
1378            let store = Store::open(None, HashMap::new()).unwrap();
1379            let mut es = EvalState::new(store, []).unwrap();
1380            let v = es.eval_from_string("true", "<test>").unwrap();
1381            es.force(&v).unwrap();
1382            let t = es.value_type_unforced(&v);
1383            assert!(t == Some(ValueType::Bool));
1384            let b = es.require_bool(&v).unwrap();
1385            assert!(b);
1386
1387            let v = es.eval_from_string("false", "<test>").unwrap();
1388            es.require_bool(&v).unwrap();
1389            let b = es.require_bool(&v).unwrap();
1390            assert!(!b);
1391        })
1392        .unwrap();
1393    }
1394
1395    #[test]
1396    fn eval_state_value_int() {
1397        gc_registering_current_thread(|| {
1398            let store = Store::open(None, HashMap::new()).unwrap();
1399            let mut es = EvalState::new(store, []).unwrap();
1400            let v = es.eval_from_string("1", "<test>").unwrap();
1401            es.force(&v).unwrap();
1402            let t = es.value_type(&v).unwrap();
1403            assert!(t == ValueType::Int);
1404            let i = es.require_int(&v).unwrap();
1405            assert!(i == 1);
1406        })
1407        .unwrap();
1408    }
1409
1410    #[test]
1411    fn eval_state_require_int_forces_thunk() {
1412        gc_registering_current_thread(|| {
1413            let store = Store::open(None, HashMap::new()).unwrap();
1414            let mut es = EvalState::new(store, []).unwrap();
1415            let f = es.eval_from_string("x: x + 1", "<test>").unwrap();
1416            let a = es.eval_from_string("2", "<test>").unwrap();
1417            let v = es.new_value_apply(&f, &a).unwrap();
1418            let t = es.value_type_unforced(&v);
1419            assert!(t.is_none());
1420            let i = es.require_int(&v).unwrap();
1421            assert!(i == 3);
1422        })
1423        .unwrap();
1424    }
1425
1426    #[test]
1427    fn eval_state_require_bool_forces_thunk() {
1428        gc_registering_current_thread(|| {
1429            let store = Store::open(None, HashMap::new()).unwrap();
1430            let mut es = EvalState::new(store, []).unwrap();
1431            let f = es.eval_from_string("x: !x", "<test>").unwrap();
1432            let a = es.eval_from_string("true", "<test>").unwrap();
1433            let v = es.new_value_apply(&f, &a).unwrap();
1434            let t = es.value_type_unforced(&v);
1435            assert!(t.is_none());
1436            let i = es.require_bool(&v).unwrap();
1437            assert!(!i);
1438        })
1439        .unwrap();
1440    }
1441
1442    /// A helper that turns an expression into a thunk.
1443    fn make_thunk(es: &mut EvalState, expr: &str) -> Value {
1444        // This would be silly in real code, but it works for the current Nix implementation.
1445        // A Nix implementation that applies the identity function eagerly would be a valid
1446        // Nix implementation, but annoying because we'll have to change this helper to do
1447        // something more complicated that isn't optimized away.
1448        let f = es.eval_from_string("x: x", "<test>").unwrap();
1449        let v = es.eval_from_string(expr, "<test>").unwrap();
1450        es.new_value_apply(&f, &v).unwrap()
1451    }
1452
1453    #[test]
1454    fn make_thunk_helper_works() {
1455        gc_registering_current_thread(|| {
1456            let store = Store::open(None, HashMap::new()).unwrap();
1457            let mut es = EvalState::new(store, []).unwrap();
1458            let v = make_thunk(&mut es, "1");
1459            let t = es.value_type_unforced(&v);
1460            assert!(t.is_none());
1461        })
1462        .unwrap();
1463    }
1464
1465    #[test]
1466    fn eval_state_value_attrs_names_empty() {
1467        gc_registering_current_thread(|| {
1468            let store = Store::open(None, HashMap::new()).unwrap();
1469            let mut es = EvalState::new(store, []).unwrap();
1470            let v = es.eval_from_string("{ }", "<test>").unwrap();
1471            es.force(&v).unwrap();
1472            let t = es.value_type_unforced(&v);
1473            assert!(t == Some(ValueType::AttrSet));
1474            let attrs = es.require_attrs_names_unsorted(&v).unwrap();
1475            assert_eq!(attrs.len(), 0);
1476        })
1477        .unwrap()
1478    }
1479
1480    #[test]
1481    fn eval_state_require_attrs_names_unsorted_forces_thunk() {
1482        gc_registering_current_thread(|| {
1483            let store = Store::open(None, HashMap::new()).unwrap();
1484            let mut es = EvalState::new(store, []).unwrap();
1485            let v = make_thunk(&mut es, "{ a = 1; b = 2; }");
1486            let t = es.value_type_unforced(&v);
1487            assert!(t.is_none());
1488            let attrs = es.require_attrs_names_unsorted(&v).unwrap();
1489            assert_eq!(attrs.len(), 2);
1490        })
1491        .unwrap()
1492    }
1493
1494    #[test]
1495    fn eval_state_require_attrs_names_unsorted_bad_type() {
1496        gc_registering_current_thread(|| {
1497            let store = Store::open(None, HashMap::new()).unwrap();
1498            let mut es = EvalState::new(store, []).unwrap();
1499            let v = es.eval_from_string("1", "<test>").unwrap();
1500            es.force(&v).unwrap();
1501            let r = es.require_attrs_names_unsorted(&v);
1502            assert!(r.is_err());
1503            assert_eq!(
1504                r.unwrap_err().to_string(),
1505                "expected an attrset, but got a Int"
1506            );
1507        })
1508        .unwrap()
1509    }
1510
1511    #[test]
1512    fn eval_state_value_attrs_names_example() {
1513        gc_registering_current_thread(|| {
1514            let store = Store::open(None, HashMap::new()).unwrap();
1515            let mut es = EvalState::new(store, []).unwrap();
1516            let expr = r#"{ a = throw "nope a"; b = throw "nope b"; }"#;
1517            let v = es.eval_from_string(expr, "<test>").unwrap();
1518            let attrs = es.require_attrs_names(&v).unwrap();
1519            assert_eq!(attrs.len(), 2);
1520            assert_eq!(attrs[0], "a");
1521            assert_eq!(attrs[1], "b");
1522        })
1523        .unwrap();
1524    }
1525
1526    #[test]
1527    fn eval_state_require_attrs_select() {
1528        gc_registering_current_thread(|| {
1529            let store = Store::open(None, HashMap::new()).unwrap();
1530            let mut es = EvalState::new(store, []).unwrap();
1531            let expr = r#"{ a = "aye"; b = "bee"; }"#;
1532            let v = es.eval_from_string(expr, "<test>").unwrap();
1533            let a = es.require_attrs_select(&v, "a").unwrap();
1534            let b = es.require_attrs_select(&v, "b").unwrap();
1535            assert_eq!(es.require_string(&a).unwrap(), "aye");
1536            assert_eq!(es.require_string(&b).unwrap(), "bee");
1537            let missing = es.require_attrs_select(&v, "c");
1538            match missing {
1539                Ok(_) => panic!("expected an error"),
1540                Err(e) => {
1541                    let s = format!("{e:#}");
1542                    if !s.contains("attribute `c` not found") {
1543                        eprintln!("unexpected error message: {}", s);
1544                        panic!();
1545                    }
1546                }
1547            }
1548        })
1549        .unwrap()
1550    }
1551
1552    #[test]
1553    fn eval_state_require_attrs_select_forces_thunk() {
1554        gc_registering_current_thread(|| {
1555            let store = Store::open(None, HashMap::new()).unwrap();
1556            let mut es = EvalState::new(store, []).unwrap();
1557            let expr = r#"{ a = "aye"; b = "bee"; }"#;
1558            let v = make_thunk(&mut es, expr);
1559            assert!(es.value_type_unforced(&v).is_none());
1560            let r = es.require_attrs_select(&v, "a");
1561            assert!(r.is_ok());
1562        })
1563        .unwrap()
1564    }
1565
1566    #[test]
1567    fn eval_state_require_attrs_select_error() {
1568        gc_registering_current_thread(|| {
1569            let store = Store::open(None, HashMap::new()).unwrap();
1570            let mut es = EvalState::new(store, []).unwrap();
1571            let expr = r#"{ a = throw "oh no the error"; }"#;
1572            let v = es.eval_from_string(expr, "<test>").unwrap();
1573            let r = es.require_attrs_select(&v, "a");
1574            match r {
1575                Ok(_) => panic!("expected an error"),
1576                Err(e) => {
1577                    if !e.to_string().contains("oh no the error") {
1578                        eprintln!("unexpected error message: {}", e);
1579                        panic!();
1580                    }
1581                }
1582            }
1583        })
1584        .unwrap()
1585    }
1586
1587    #[test]
1588    fn eval_state_require_attrs_select_opt() {
1589        gc_registering_current_thread(|| {
1590            let store = Store::open(None, HashMap::new()).unwrap();
1591            let mut es = EvalState::new(store, []).unwrap();
1592            let expr = r#"{ a = "aye"; b = "bee"; }"#;
1593            let v = es.eval_from_string(expr, "<test>").unwrap();
1594            let a = es.require_attrs_select_opt(&v, "a").unwrap().unwrap();
1595            let b = es.require_attrs_select_opt(&v, "b").unwrap().unwrap();
1596            assert_eq!(es.require_string(&a).unwrap(), "aye");
1597            assert_eq!(es.require_string(&b).unwrap(), "bee");
1598            let c = es.require_attrs_select_opt(&v, "c").unwrap();
1599            assert!(c.is_none());
1600        })
1601        .unwrap()
1602    }
1603
1604    #[test]
1605    fn eval_state_require_attrs_select_opt_forces_thunk() {
1606        gc_registering_current_thread(|| {
1607            let store = Store::open(None, HashMap::new()).unwrap();
1608            let mut es = EvalState::new(store, []).unwrap();
1609            let expr = r#"{ a = "aye"; b = "bee"; }"#;
1610            let v = make_thunk(&mut es, expr);
1611            assert!(es.value_type_unforced(&v).is_none());
1612            let r = es.require_attrs_select_opt(&v, "a");
1613            assert!(r.is_ok());
1614        })
1615        .unwrap()
1616    }
1617
1618    #[test]
1619    fn eval_state_require_attrs_select_opt_error() {
1620        gc_registering_current_thread(|| {
1621            let store = Store::open(None, HashMap::new()).unwrap();
1622            let mut es = EvalState::new(store, []).unwrap();
1623            let expr = r#"{ a = throw "oh no the error"; }"#;
1624            let v = es.eval_from_string(expr, "<test>").unwrap();
1625            let r = es.require_attrs_select_opt(&v, "a");
1626            match r {
1627                Ok(_) => panic!("expected an error"),
1628                Err(e) => {
1629                    if !e.to_string().contains("oh no the error") {
1630                        eprintln!("unexpected error message: {}", e);
1631                        panic!();
1632                    }
1633                }
1634            }
1635        })
1636        .unwrap()
1637    }
1638
1639    #[test]
1640    fn eval_state_value_string() {
1641        gc_registering_current_thread(|| {
1642            let store = Store::open(None, HashMap::new()).unwrap();
1643            let mut es = EvalState::new(store, []).unwrap();
1644            let v = es.eval_from_string("\"hello\"", "<test>").unwrap();
1645            es.force(&v).unwrap();
1646            let t = es.value_type_unforced(&v);
1647            assert!(t == Some(ValueType::String));
1648            let s = es.require_string(&v).unwrap();
1649            assert!(s == "hello");
1650        })
1651        .unwrap();
1652    }
1653
1654    #[test]
1655    fn eval_state_value_string_forces_thunk() {
1656        gc_registering_current_thread(|| {
1657            let store = Store::open(None, HashMap::new()).unwrap();
1658            let mut es = EvalState::new(store, []).unwrap();
1659            let v = make_thunk(&mut es, "\"hello\"");
1660            assert!(es.value_type_unforced(&v).is_none());
1661            let s = es.require_string(&v).unwrap();
1662            assert!(s == "hello");
1663        })
1664        .unwrap();
1665    }
1666
1667    #[test]
1668    fn eval_state_value_string_unexpected_bool() {
1669        gc_registering_current_thread(|| {
1670            let store = Store::open(None, HashMap::new()).unwrap();
1671            let mut es = EvalState::new(store, []).unwrap();
1672            let v = es.eval_from_string("true", "<test>").unwrap();
1673            es.force(&v).unwrap();
1674            let r = es.require_string(&v);
1675            assert!(r.is_err());
1676            // TODO: safe print value (like Nix would)
1677            assert_eq!(
1678                r.unwrap_err().to_string(),
1679                "expected a string, but got a Bool"
1680            );
1681        })
1682        .unwrap()
1683    }
1684
1685    #[test]
1686    fn eval_state_value_string_unexpected_path_value() {
1687        gc_registering_current_thread(|| {
1688            let store = Store::open(None, HashMap::new()).unwrap();
1689            let mut es = EvalState::new(store, []).unwrap();
1690            let v = es.eval_from_string("/foo", "<test>").unwrap();
1691            es.force(&v).unwrap();
1692            let r = es.require_string(&v);
1693            assert!(r.is_err());
1694            assert_eq!(
1695                r.unwrap_err().to_string(),
1696                "expected a string, but got a Path"
1697            );
1698        })
1699        .unwrap()
1700    }
1701
1702    #[test]
1703    fn eval_state_value_string_bad_utf() {
1704        gc_registering_current_thread(|| {
1705            let store = Store::open(None, HashMap::new()).unwrap();
1706            let mut es = EvalState::new(store, []).unwrap();
1707            let v = es
1708                .eval_from_string("builtins.substring 0 1 \"ü\"", "<test>")
1709                .unwrap();
1710            es.force(&v).unwrap();
1711            let t = es.value_type_unforced(&v);
1712            assert!(t == Some(ValueType::String));
1713            let r = es.require_string(&v);
1714            assert!(r.is_err());
1715            assert!(r
1716                .unwrap_err()
1717                .to_string()
1718                .contains("Nix string is not valid UTF-8"));
1719        })
1720        .unwrap();
1721    }
1722
1723    #[test]
1724    fn eval_state_value_string_unexpected_context() {
1725        gc_registering_current_thread(|| {
1726            let store = Store::open(None, HashMap::new()).unwrap();
1727            let mut es = EvalState::new(store, []).unwrap();
1728            let v = es
1729                .eval_from_string("(derivation { name = \"hello\"; system = \"dummy\"; builder = \"cmd.exe\"; }).outPath", "<test>")
1730                .unwrap();
1731            es.force(&v).unwrap();
1732            let t = es.value_type_unforced(&v);
1733            assert!(t == Some(ValueType::String));
1734            // TODO
1735            // let r = es.require_string_without_context(&v);
1736            // assert!(r.is_err());
1737            // assert!(r.unwrap_err().to_string().contains("unexpected context"));
1738        })
1739        .unwrap();
1740    }
1741
1742    #[test]
1743    fn eval_state_new_string() {
1744        gc_registering_current_thread(|| {
1745            let store = Store::open(None, HashMap::new()).unwrap();
1746            let mut es = EvalState::new(store, []).unwrap();
1747            let v = es.new_value_str("hello").unwrap();
1748            es.force(&v).unwrap();
1749            let t = es.value_type_unforced(&v);
1750            assert!(t == Some(ValueType::String));
1751            let s = es.require_string(&v).unwrap();
1752            assert!(s == "hello");
1753        })
1754        .unwrap();
1755    }
1756
1757    #[test]
1758    fn eval_state_new_string_empty() {
1759        gc_registering_current_thread(|| {
1760            let store = Store::open(None, HashMap::new()).unwrap();
1761            let mut es = EvalState::new(store, []).unwrap();
1762            let v = es.new_value_str("").unwrap();
1763            es.force(&v).unwrap();
1764            let t = es.value_type_unforced(&v);
1765            assert!(t == Some(ValueType::String));
1766            let s = es.require_string(&v).unwrap();
1767            assert!(s.is_empty());
1768        })
1769        .unwrap();
1770    }
1771
1772    #[test]
1773    fn eval_state_new_string_invalid() {
1774        gc_registering_current_thread(|| {
1775            let store = Store::open(None, HashMap::new()).unwrap();
1776            let mut es = EvalState::new(store, []).unwrap();
1777            let r = es.new_value_str("hell\0no");
1778            match r {
1779                Ok(_) => panic!("expected an error"),
1780                Err(e) => {
1781                    if !e.to_string().contains("contains null byte") {
1782                        eprintln!("{}", e);
1783                        panic!();
1784                    }
1785                }
1786            }
1787        })
1788        .unwrap();
1789    }
1790
1791    #[test]
1792    fn eval_state_new_int() {
1793        gc_registering_current_thread(|| {
1794            let store = Store::open(None, HashMap::new()).unwrap();
1795            let mut es = EvalState::new(store, []).unwrap();
1796            let v = es.new_value_int(42).unwrap();
1797            es.force(&v).unwrap();
1798            let t = es.value_type_unforced(&v);
1799            assert!(t == Some(ValueType::Int));
1800            let i = es.require_int(&v).unwrap();
1801            assert!(i == 42);
1802        })
1803        .unwrap();
1804    }
1805
1806    #[test]
1807    fn eval_state_value_attrset() {
1808        gc_registering_current_thread(|| {
1809            let store = Store::open(None, HashMap::new()).unwrap();
1810            let mut es = EvalState::new(store, []).unwrap();
1811            let v = es.eval_from_string("{ }", "<test>").unwrap();
1812            es.force(&v).unwrap();
1813            let t = es.value_type_unforced(&v);
1814            assert!(t == Some(ValueType::AttrSet));
1815        })
1816        .unwrap();
1817    }
1818
1819    #[test]
1820    fn eval_state_value_list() {
1821        gc_registering_current_thread(|| {
1822            let store = Store::open(None, HashMap::new()).unwrap();
1823            let mut es = EvalState::new(store, []).unwrap();
1824            let v = es.eval_from_string("[ ]", "<test>").unwrap();
1825            es.force(&v).unwrap();
1826            let t = es.value_type_unforced(&v);
1827            assert!(t == Some(ValueType::List));
1828        })
1829        .unwrap();
1830    }
1831
1832    #[test]
1833    fn eval_state_value_list_strict_empty() {
1834        gc_registering_current_thread(|| {
1835            let store = Store::open(None, HashMap::new()).unwrap();
1836            let mut es = EvalState::new(store, []).unwrap();
1837            let v = es.eval_from_string("[]", "<test>").unwrap();
1838            es.force(&v).unwrap();
1839            let list: Vec<Value> = es.require_list_strict(&v).unwrap();
1840            assert_eq!(list.len(), 0);
1841        })
1842        .unwrap();
1843    }
1844
1845    #[test]
1846    fn eval_state_value_list_strict_int() {
1847        gc_registering_current_thread(|| {
1848            let store = Store::open(None, HashMap::new()).unwrap();
1849            let mut es = EvalState::new(store, []).unwrap();
1850            let v = es.eval_from_string("[42]", "<test>").unwrap();
1851            es.force(&v).unwrap();
1852            let list: Vec<Value> = es.require_list_strict(&v).unwrap();
1853            assert_eq!(list.len(), 1);
1854            assert_eq!(es.require_int(&list[0]).unwrap(), 42);
1855        })
1856        .unwrap();
1857    }
1858
1859    #[test]
1860    fn eval_state_value_list_strict_int_bool() {
1861        gc_registering_current_thread(|| {
1862            let store = Store::open(None, HashMap::new()).unwrap();
1863            let mut es = EvalState::new(store, []).unwrap();
1864            let v = es.eval_from_string("[42 true]", "<test>").unwrap();
1865            es.force(&v).unwrap();
1866            let list: Vec<Value> = es.require_list_strict(&v).unwrap();
1867            assert_eq!(list.len(), 2);
1868            assert_eq!(es.require_int(&list[0]).unwrap(), 42);
1869            assert!(es.require_bool(&list[1]).unwrap());
1870        })
1871        .unwrap();
1872    }
1873
1874    #[test]
1875    fn eval_state_value_list_strict_error() {
1876        gc_registering_current_thread(|| {
1877            let store = Store::open(None, HashMap::new()).unwrap();
1878            let mut es = EvalState::new(store, []).unwrap();
1879            let v = es.eval_from_string(r#"[(throw "_evaluated_item_")]"#, "<test>").unwrap();
1880            es.force(&v).unwrap();
1881            // This should fail because require_list_strict evaluates all elements
1882            let result: Result<Vec<Value>, _> = es.require_list_strict(&v);
1883            assert!(result.is_err());
1884            match result {
1885                Err(error_msg) => {
1886                    let error_str = error_msg.to_string();
1887                    assert!(error_str.contains("_evaluated_item_"));
1888                }
1889                Ok(_) => panic!("unexpected success. The item should have been evaluated and its error propagated.")
1890            }
1891        })
1892        .unwrap();
1893    }
1894
1895    #[test]
1896    fn eval_state_value_list_strict_generic_container() {
1897        gc_registering_current_thread(|| {
1898            let store = Store::open(None, HashMap::new()).unwrap();
1899            let mut es = EvalState::new(store, []).unwrap();
1900            let v = es.eval_from_string("[1 2 3]", "<test>").unwrap();
1901
1902            // Test with Vec
1903            let vec: Vec<Value> = es.require_list_strict(&v).unwrap();
1904            assert_eq!(vec.len(), 3);
1905
1906            // Test with VecDeque
1907            let deque: std::collections::VecDeque<Value> = es.require_list_strict(&v).unwrap();
1908            assert_eq!(deque.len(), 3);
1909
1910            // Verify contents are the same
1911            assert_eq!(es.require_int(&vec[0]).unwrap(), 1);
1912            assert_eq!(es.require_int(&deque[0]).unwrap(), 1);
1913        })
1914        .unwrap();
1915    }
1916
1917    #[test]
1918    fn eval_state_realise_string() {
1919        gc_registering_current_thread(|| {
1920            let store = Store::open(None, HashMap::new()).unwrap();
1921            let mut es = EvalState::new(store, []).unwrap();
1922            let expr = r#"
1923                ''
1924                    a derivation output: ${
1925                        derivation { name = "letsbuild";
1926                            system = builtins.currentSystem;
1927                            builder = "/bin/sh";
1928                            args = [ "-c" "echo foo > $out" ];
1929                            }}
1930                    a path: ${builtins.toFile "just-a-file" "ooh file good"}
1931                    a derivation path by itself: ${
1932                        builtins.unsafeDiscardOutputDependency 
1933                            (derivation {
1934                                name = "not-actually-built-yet";
1935                                system = builtins.currentSystem;
1936                                builder = "/bin/sh";
1937                                args = [ "-c" "echo foo > $out" ];
1938                            }).drvPath}
1939                ''
1940            "#;
1941            let v = es.eval_from_string(expr, "<test>").unwrap();
1942            es.force(&v).unwrap();
1943            let rs = es.realise_string(&v, false).unwrap();
1944
1945            assert!(rs.s.starts_with("a derivation output:"));
1946            assert!(rs.s.contains("-letsbuild\n"));
1947            assert!(!rs.s.contains("-letsbuild.drv"));
1948            assert!(rs.s.contains("a path:"));
1949            assert!(rs.s.contains("-just-a-file"));
1950            assert!(!rs.s.contains("-just-a-file.drv"));
1951            assert!(!rs.s.contains("ooh file good"));
1952            assert!(rs.s.ends_with("-not-actually-built-yet.drv\n"));
1953
1954            assert_eq!(rs.paths.len(), 3);
1955            let mut names: Vec<String> = rs.paths.iter().map(|p| p.name().unwrap()).collect();
1956            names.sort();
1957            assert_eq!(names[0], "just-a-file");
1958            assert_eq!(names[1], "letsbuild");
1959            assert_eq!(names[2], "not-actually-built-yet.drv");
1960        })
1961        .unwrap();
1962    }
1963
1964    #[test]
1965    fn eval_state_call() {
1966        gc_registering_current_thread(|| {
1967            let store = Store::open(None, HashMap::new()).unwrap();
1968            let mut es = EvalState::new(store, []).unwrap();
1969            let f = es.eval_from_string("x: x + 1", "<test>").unwrap();
1970            let a = es.eval_from_string("2", "<test>").unwrap();
1971            let v = es.call(f, a).unwrap();
1972            es.force(&v).unwrap();
1973            let t = es.value_type_unforced(&v);
1974            assert!(t == Some(ValueType::Int));
1975            let i = es.require_int(&v).unwrap();
1976            assert!(i == 3);
1977        })
1978        .unwrap();
1979    }
1980
1981    #[test]
1982    fn eval_state_call_multi() {
1983        gc_registering_current_thread(|| {
1984            let store = Store::open(None, HashMap::new()).unwrap();
1985            let mut es = EvalState::new(store, []).unwrap();
1986            // This is a function that takes two arguments.
1987            let f = es.eval_from_string("x: y: x - y", "<test>").unwrap();
1988            let a = es.eval_from_string("2", "<test>").unwrap();
1989            let b = es.eval_from_string("3", "<test>").unwrap();
1990            let v = es.call_multi(&f, &[a, b]).unwrap();
1991            let t = es.value_type_unforced(&v);
1992            assert!(t == Some(ValueType::Int));
1993            let i = es.require_int(&v).unwrap();
1994            assert!(i == -1);
1995        })
1996        .unwrap();
1997    }
1998
1999    #[test]
2000    fn eval_state_apply() {
2001        gc_registering_current_thread(|| {
2002            let store = Store::open(None, HashMap::new()).unwrap();
2003            let mut es = EvalState::new(store, []).unwrap();
2004            // This is a function that takes two arguments.
2005            let f = es.eval_from_string("x: x + 1", "<test>").unwrap();
2006            let a = es.eval_from_string("2", "<test>").unwrap();
2007            let v = es.new_value_apply(&f, &a).unwrap();
2008            assert!(es.value_type_unforced(&v).is_none());
2009            es.force(&v).unwrap();
2010            let t = es.value_type_unforced(&v);
2011            assert!(t == Some(ValueType::Int));
2012            let i = es.require_int(&v).unwrap();
2013            assert!(i == 3);
2014        })
2015        .unwrap();
2016    }
2017
2018    #[test]
2019    fn eval_state_call_fail_body() {
2020        gc_registering_current_thread(|| {
2021            let store = Store::open(None, HashMap::new()).unwrap();
2022            let mut es = EvalState::new(store, []).unwrap();
2023            let f = es.eval_from_string("x: x + 1", "<test>").unwrap();
2024            let a = es.eval_from_string("true", "<test>").unwrap();
2025            let r = es.call(f, a);
2026            match r {
2027                Ok(_) => panic!("expected an error"),
2028                Err(e) => {
2029                    if !e.to_string().contains("cannot coerce") {
2030                        eprintln!("{}", e);
2031                        panic!();
2032                    }
2033                }
2034            }
2035        })
2036        .unwrap();
2037    }
2038
2039    #[test]
2040    fn eval_state_call_multi_fail_body() {
2041        gc_registering_current_thread(|| {
2042            let store = Store::open(None, HashMap::new()).unwrap();
2043            let mut es = EvalState::new(store, []).unwrap();
2044            // This is a function that takes two arguments.
2045            let f = es.eval_from_string("x: y: x - y", "<test>").unwrap();
2046            let a = es.eval_from_string("2", "<test>").unwrap();
2047            let b = es.eval_from_string("true", "<test>").unwrap();
2048            let r = es.call_multi(&f, &[a, b]);
2049            match r {
2050                Ok(_) => panic!("expected an error"),
2051                Err(e) => {
2052                    if !e.to_string().contains("expected an integer but found") {
2053                        eprintln!("{}", e);
2054                        panic!();
2055                    }
2056                }
2057            }
2058        })
2059        .unwrap();
2060    }
2061
2062    #[test]
2063    fn eval_state_apply_fail_body() {
2064        gc_registering_current_thread(|| {
2065            let store = Store::open(None, HashMap::new()).unwrap();
2066            let mut es = EvalState::new(store, []).unwrap();
2067            let f = es.eval_from_string("x: x + 1", "<test>").unwrap();
2068            let a = es.eval_from_string("true", "<test>").unwrap();
2069            // Lazy => no error
2070            let r = es.new_value_apply(&f, &a).unwrap();
2071            // Force it => error
2072            let res = es.force(&r);
2073            match res {
2074                Ok(_) => panic!("expected an error"),
2075                Err(e) => {
2076                    if !e.to_string().contains("cannot coerce") {
2077                        eprintln!("{}", e);
2078                        panic!();
2079                    }
2080                }
2081            }
2082        })
2083        .unwrap();
2084    }
2085
2086    /// This tests the behavior of `call`, which is strict, unlike `new_value_apply`.
2087    #[test]
2088    fn eval_state_call_fail_args() {
2089        gc_registering_current_thread(|| {
2090            let store = Store::open(None, HashMap::new()).unwrap();
2091            let mut es = EvalState::new(store, []).unwrap();
2092            let f = es.eval_from_string("{x}: x + 1", "<test>").unwrap();
2093            let a = es.eval_from_string("{}", "<test>").unwrap();
2094            let r = es.call(f, a);
2095            match r {
2096                Ok(_) => panic!("expected an error"),
2097                Err(e) => {
2098                    if !e.to_string().contains("called without required argument") {
2099                        eprintln!("{}", e);
2100                        panic!();
2101                    }
2102                }
2103            }
2104        })
2105        .unwrap();
2106    }
2107
2108    #[test]
2109    fn eval_state_call_multi_fail_args() {
2110        gc_registering_current_thread(|| {
2111            let store = Store::open(None, HashMap::new()).unwrap();
2112            let mut es = EvalState::new(store, []).unwrap();
2113            // This is a function that takes two arguments.
2114            let f = es.eval_from_string("{x}: {y}: x - y", "<test>").unwrap();
2115            let a = es.eval_from_string("{x = 2;}", "<test>").unwrap();
2116            let b = es.eval_from_string("{}", "<test>").unwrap();
2117            let r = es.call_multi(&f, &[a, b]);
2118            match r {
2119                Ok(_) => panic!("expected an error"),
2120                Err(e) => {
2121                    if !e.to_string().contains("called without required argument") {
2122                        eprintln!("{}", e);
2123                        panic!();
2124                    }
2125                }
2126            }
2127        })
2128        .unwrap();
2129    }
2130
2131    /// This tests the behavior of `new_value_apply`, which is lazy, unlike `call`.
2132    #[test]
2133    fn eval_state_apply_fail_args_lazy() {
2134        gc_registering_current_thread(|| {
2135            let store = Store::open(None, HashMap::new()).unwrap();
2136            let mut es = EvalState::new(store, []).unwrap();
2137            let f = es.eval_from_string("{x}: x + 1", "<test>").unwrap();
2138            let a = es.eval_from_string("{}", "<test>").unwrap();
2139            // Lazy => no error
2140            let r = es.new_value_apply(&f, &a).unwrap();
2141            // Force it => error
2142            let res = es.force(&r);
2143            match res {
2144                Ok(_) => panic!("expected an error"),
2145                Err(e) => {
2146                    if !e.to_string().contains("called without required argument") {
2147                        eprintln!("{}", e);
2148                        panic!();
2149                    }
2150                }
2151            }
2152        })
2153        .unwrap();
2154    }
2155
2156    #[test]
2157    fn store_open_params() {
2158        gc_registering_current_thread(|| {
2159            let store = tempfile::tempdir().unwrap();
2160            let store_path = store.path().to_str().unwrap();
2161            let state = tempfile::tempdir().unwrap();
2162            let state_path = state.path().to_str().unwrap();
2163            let log = tempfile::tempdir().unwrap();
2164            let log_path = log.path().to_str().unwrap();
2165
2166            let mut es = EvalState::new(
2167                Store::open(
2168                    Some("local"),
2169                    HashMap::from([
2170                        ("store", store_path),
2171                        ("state", state_path),
2172                        ("log", log_path),
2173                    ])
2174                    .iter()
2175                    .map(|(a, b)| (*a, *b)),
2176                )
2177                .unwrap(),
2178                [],
2179            )
2180            .unwrap();
2181
2182            let expr = r#"
2183                ''
2184                    a derivation output: ${
2185                        derivation { name = "letsbuild";
2186                            system = builtins.currentSystem;
2187                            builder = "/bin/sh";
2188                            args = [ "-c" "echo foo > $out" ];
2189                            }}
2190                    a path: ${builtins.toFile "just-a-file" "ooh file good"}
2191                    a derivation path by itself: ${
2192                        builtins.unsafeDiscardOutputDependency
2193                            (derivation {
2194                                name = "not-actually-built-yet";
2195                                system = builtins.currentSystem;
2196                                builder = "/bin/sh";
2197                                args = [ "-c" "echo foo > $out" ];
2198                            }).drvPath}
2199                ''
2200            "#;
2201            let derivations: [&[u8]; 3] = [
2202                b"letsbuild.drv",
2203                b"just-a-file",
2204                b"not-actually-built-yet.drv",
2205            ];
2206            let _ = es.eval_from_string(expr, "<test>").unwrap();
2207
2208            // assert that all three `derivations` are inside the store and the `state` directory is not empty either.
2209            let store_contents: Vec<_> = read_dir(store.path())
2210                .unwrap()
2211                .map(|dir_entry| dir_entry.unwrap().file_name())
2212                .collect();
2213            for derivation in derivations {
2214                assert!(store_contents
2215                    .iter()
2216                    .any(|f| f.as_encoded_bytes().ends_with(derivation)));
2217            }
2218            assert!(!empty(read_dir(state.path()).unwrap()));
2219
2220            store.close().unwrap();
2221            state.close().unwrap();
2222            log.close().unwrap();
2223        })
2224        .unwrap();
2225    }
2226
2227    fn empty(foldable: impl IntoIterator) -> bool {
2228        foldable.into_iter().all(|_| false)
2229    }
2230
2231    #[test]
2232    fn eval_state_primop_anon_call() {
2233        gc_registering_current_thread(|| {
2234            let store = Store::open(None, []).unwrap();
2235            let mut es = EvalState::new(store, []).unwrap();
2236            let bias: Arc<Mutex<Int>> = Arc::new(Mutex::new(0));
2237            let bias_control = bias.clone();
2238
2239            let primop = primop::PrimOp::new(
2240                &mut es,
2241                primop::PrimOpMeta {
2242                    name: cstr!("testFunction"),
2243                    args: [cstr!("a"), cstr!("b")],
2244                    doc: cstr!("anonymous test function"),
2245                },
2246                Box::new(move |es, [a, b]| {
2247                    let a = es.require_int(a)?;
2248                    let b = es.require_int(b)?;
2249                    let c = *bias.lock().unwrap();
2250                    es.new_value_int(a + b + c)
2251                }),
2252            )
2253            .unwrap();
2254
2255            let f = es.new_value_primop(primop).unwrap();
2256
2257            {
2258                *bias_control.lock().unwrap() = 10;
2259            }
2260            let a = es.new_value_int(2).unwrap();
2261            let b = es.new_value_int(3).unwrap();
2262            let fa = es.call(f, a).unwrap();
2263            let v = es.call(fa, b).unwrap();
2264            es.force(&v).unwrap();
2265            let t = es.value_type(&v).unwrap();
2266            assert!(t == ValueType::Int);
2267            let i = es.require_int(&v).unwrap();
2268            assert!(i == 15);
2269        })
2270        .unwrap();
2271    }
2272
2273    #[test]
2274    fn eval_state_primop_anon_call_throw() {
2275        gc_registering_current_thread(|| {
2276            let store = Store::open(None, []).unwrap();
2277            let mut es = EvalState::new(store, []).unwrap();
2278            let f = {
2279                let es: &mut EvalState = &mut es;
2280                let prim = primop::PrimOp::new(
2281                    es,
2282                    primop::PrimOpMeta {
2283                        name: cstr!("throwingTestFunction"),
2284                        args: [cstr!("arg")],
2285                        doc: cstr!("anonymous test function"),
2286                    },
2287                    Box::new(move |es, [a]| {
2288                        let a = es.require_int(a)?;
2289                        bail!("error with arg [{}]", a);
2290                    }),
2291                )
2292                .unwrap();
2293
2294                es.new_value_primop(prim)
2295            }
2296            .unwrap();
2297            let a = es.new_value_int(2).unwrap();
2298            let r = es.call(f, a);
2299            match r {
2300                Ok(_) => panic!("expected an error"),
2301                Err(e) => {
2302                    if !e.to_string().contains("error with arg [2]") {
2303                        eprintln!("unexpected error message: {}", e);
2304                        panic!();
2305                    }
2306                }
2307            }
2308        })
2309        .unwrap();
2310    }
2311
2312    #[test]
2313    fn eval_state_primop_anon_call_no_args() {
2314        gc_registering_current_thread(|| {
2315            let store = Store::open(None, []).unwrap();
2316            let mut es = EvalState::new(store, []).unwrap();
2317            let v = es
2318                .new_value_thunk(
2319                    "test_thunk",
2320                    Box::new(move |es: &mut EvalState| es.new_value_int(42)),
2321                )
2322                .unwrap();
2323            es.force(&v).unwrap();
2324            let t = es.value_type(&v).unwrap();
2325            eprintln!("{:?}", t);
2326            assert!(t == ValueType::Int);
2327            let i = es.require_int(&v).unwrap();
2328            assert!(i == 42);
2329        })
2330        .unwrap();
2331    }
2332
2333    #[test]
2334    fn eval_state_primop_anon_call_no_args_lazy() {
2335        gc_registering_current_thread(|| {
2336            let store = Store::open(None, []).unwrap();
2337            let mut es = EvalState::new(store, []).unwrap();
2338            let v = es
2339                .new_value_thunk(
2340                    "test_thunk",
2341                    Box::new(move |_| {
2342                        bail!("error message in test case eval_state_primop_anon_call_no_args_lazy")
2343                    }),
2344                )
2345                .unwrap();
2346            let r = es.force(&v);
2347            match r {
2348                Ok(_) => panic!("expected an error"),
2349                Err(e) => {
2350                    if !e.to_string().contains(
2351                        "error message in test case eval_state_primop_anon_call_no_args_lazy",
2352                    ) {
2353                        eprintln!("unexpected error message: {}", e);
2354                        panic!();
2355                    }
2356                    if !e.to_string().contains("test_thunk") {
2357                        eprintln!("unexpected error message: {}", e);
2358                        panic!();
2359                    }
2360                }
2361            }
2362        })
2363        .unwrap();
2364    }
2365
2366    #[test]
2367    pub fn eval_state_primop_custom() {
2368        gc_registering_current_thread(|| {
2369            let store = Store::open(None, []).unwrap();
2370            let mut es = EvalState::new(store, []).unwrap();
2371            let primop = primop::PrimOp::new(
2372                &mut es,
2373                primop::PrimOpMeta {
2374                    name: cstr!("frobnicate"),
2375                    doc: cstr!("Frobnicates widgets"),
2376                    args: [cstr!("x"), cstr!("y")],
2377                },
2378                Box::new(|es, args| {
2379                    let a = es.require_int(&args[0])?;
2380                    let b = es.require_int(&args[1])?;
2381                    es.new_value_int(a + b)
2382                }),
2383            )
2384            .unwrap();
2385            let f = es.new_value_primop(primop).unwrap();
2386            let a = es.new_value_int(2).unwrap();
2387            let b = es.new_value_int(3).unwrap();
2388            let fa = es.call(f, a).unwrap();
2389            let fb = es.call(fa, b).unwrap();
2390            es.force(&fb).unwrap();
2391            let t = es.value_type(&fb).unwrap();
2392            assert!(t == ValueType::Int);
2393            let i = es.require_int(&fb).unwrap();
2394            assert!(i == 5);
2395        })
2396        .unwrap();
2397    }
2398
2399    #[test]
2400    pub fn eval_state_primop_custom_throw() {
2401        gc_registering_current_thread(|| {
2402            let store = Store::open(None, []).unwrap();
2403            let mut es = EvalState::new(store, []).unwrap();
2404            let primop = primop::PrimOp::new(
2405                &mut es,
2406                primop::PrimOpMeta {
2407                    name: cstr!("frobnicate"),
2408                    doc: cstr!("Frobnicates widgets"),
2409                    args: [cstr!("x")],
2410                },
2411                Box::new(|_es, _args| bail!("The frob unexpectedly fizzled")),
2412            )
2413            .unwrap();
2414            let f = es.new_value_primop(primop).unwrap();
2415            let a = es.new_value_int(0).unwrap();
2416            match es.call(f, a) {
2417                Ok(_) => panic!("expected an error"),
2418                Err(e) => {
2419                    if !e.to_string().contains("The frob unexpectedly fizzled") {
2420                        eprintln!("unexpected error message: {}", e);
2421                        panic!();
2422                    }
2423                    if !e.to_string().contains("frobnicate") {
2424                        eprintln!("unexpected error message: {}", e);
2425                        panic!();
2426                    }
2427                }
2428            }
2429        })
2430        .unwrap();
2431    }
2432
2433    #[test]
2434    pub fn eval_state_new_value_attrs_from_slice_empty() {
2435        gc_registering_current_thread(|| {
2436            let store = Store::open(None, []).unwrap();
2437            let mut es = EvalState::new(store, []).unwrap();
2438            let attrs = es.new_value_attrs([]).unwrap();
2439            let t = es.value_type(&attrs).unwrap();
2440            assert!(t == ValueType::AttrSet);
2441            let names = es.require_attrs_names(&attrs).unwrap();
2442            assert!(names.is_empty());
2443        })
2444        .unwrap();
2445    }
2446
2447    #[test]
2448    pub fn eval_state_new_value_attrs_from_vec() {
2449        gc_registering_current_thread(|| {
2450            let store = Store::open(None, []).unwrap();
2451            let mut es = EvalState::new(store, []).unwrap();
2452            let attrs = {
2453                let a = es.new_value_int(1).unwrap();
2454                let b = es.new_value_int(2).unwrap();
2455                es.new_value_attrs(vec![("a".to_string(), a), ("b".to_string(), b)])
2456                    .unwrap()
2457            };
2458            let t = es.value_type(&attrs).unwrap();
2459            assert!(t == ValueType::AttrSet);
2460            let names = es.require_attrs_names(&attrs).unwrap();
2461            assert_eq!(names.len(), 2);
2462            assert_eq!(names[0], "a");
2463            assert_eq!(names[1], "b");
2464            let a = es.require_attrs_select(&attrs, "a").unwrap();
2465            let b = es.require_attrs_select(&attrs, "b").unwrap();
2466            let i = es.require_int(&a).unwrap();
2467            assert_eq!(i, 1);
2468            let i = es.require_int(&b).unwrap();
2469            assert_eq!(i, 2);
2470        })
2471        .unwrap();
2472    }
2473
2474    #[test]
2475    pub fn eval_state_new_value_attrs_from_hashmap() {
2476        gc_registering_current_thread(|| {
2477            let store = Store::open(None, []).unwrap();
2478            let mut es = EvalState::new(store, []).unwrap();
2479            let attrs = {
2480                let a = es.new_value_int(1).unwrap();
2481                let b = es.new_value_int(2).unwrap();
2482                es.new_value_attrs(HashMap::from([("a".to_string(), a), ("b".to_string(), b)]))
2483                    .unwrap()
2484            };
2485            let t = es.value_type(&attrs).unwrap();
2486            assert!(t == ValueType::AttrSet);
2487            let names = es.require_attrs_names(&attrs).unwrap();
2488            assert_eq!(names.len(), 2);
2489            assert_eq!(names[0], "a");
2490            assert_eq!(names[1], "b");
2491            let a = es.require_attrs_select(&attrs, "a").unwrap();
2492            let b = es.require_attrs_select(&attrs, "b").unwrap();
2493            let i = es.require_int(&a).unwrap();
2494            assert_eq!(i, 1);
2495            let i = es.require_int(&b).unwrap();
2496            assert_eq!(i, 2);
2497        })
2498        .unwrap();
2499    }
2500
2501    #[test]
2502    fn eval_state_require_list_select_idx_strict_basic() {
2503        gc_registering_current_thread(|| {
2504            let store = Store::open(None, HashMap::new()).unwrap();
2505            let mut es = EvalState::new(store, []).unwrap();
2506            let v = es.eval_from_string("[ 10 20 30 ]", "<test>").unwrap();
2507
2508            let elem0 = es.require_list_select_idx_strict(&v, 0).unwrap().unwrap();
2509            let elem1 = es.require_list_select_idx_strict(&v, 1).unwrap().unwrap();
2510            let elem2 = es.require_list_select_idx_strict(&v, 2).unwrap().unwrap();
2511
2512            assert_eq!(es.require_int(&elem0).unwrap(), 10);
2513            assert_eq!(es.require_int(&elem1).unwrap(), 20);
2514            assert_eq!(es.require_int(&elem2).unwrap(), 30);
2515        })
2516        .unwrap();
2517    }
2518
2519    #[test]
2520    fn eval_state_require_list_select_idx_strict_out_of_bounds() {
2521        gc_registering_current_thread(|| {
2522            let store = Store::open(None, HashMap::new()).unwrap();
2523            let mut es = EvalState::new(store, []).unwrap();
2524            let v = es.eval_from_string("[ 1 2 3 ]", "<test>").unwrap();
2525
2526            let out_of_bounds = es.require_list_select_idx_strict(&v, 3).unwrap();
2527            assert!(out_of_bounds.is_none());
2528
2529            // Test boundary case - the last valid index
2530            let last_elem = es.require_list_select_idx_strict(&v, 2).unwrap().unwrap();
2531            assert_eq!(es.require_int(&last_elem).unwrap(), 3);
2532        })
2533        .unwrap();
2534    }
2535
2536    #[test]
2537    fn eval_state_require_list_select_idx_strict_empty_list() {
2538        gc_registering_current_thread(|| {
2539            let store = Store::open(None, HashMap::new()).unwrap();
2540            let mut es = EvalState::new(store, []).unwrap();
2541            let v = es.eval_from_string("[ ]", "<test>").unwrap();
2542
2543            // Test that the safe version properly handles empty list access
2544            let elem = es.require_list_select_idx_strict(&v, 0).unwrap();
2545            assert!(elem.is_none());
2546
2547            // Verify we can get the size of an empty list
2548            let size = es.require_list_size(&v).unwrap();
2549            assert_eq!(size, 0);
2550        })
2551        .unwrap();
2552    }
2553
2554    #[test]
2555    fn eval_state_require_list_select_idx_strict_forces_thunk() {
2556        gc_registering_current_thread(|| {
2557            let store = Store::open(None, HashMap::new()).unwrap();
2558            let mut es = EvalState::new(store, []).unwrap();
2559            let v = make_thunk(&mut es, "[ 42 ]");
2560            assert!(es.value_type_unforced(&v).is_none());
2561
2562            let elem = es.require_list_select_idx_strict(&v, 0).unwrap().unwrap();
2563            assert_eq!(es.require_int(&elem).unwrap(), 42);
2564        })
2565        .unwrap();
2566    }
2567
2568    #[test]
2569    fn eval_state_require_list_select_idx_strict_error_element() {
2570        gc_registering_current_thread(|| {
2571            let store = Store::open(None, HashMap::new()).unwrap();
2572            let mut es = EvalState::new(store, []).unwrap();
2573
2574            let v = es
2575                .eval_from_string("[ (1 + 1) (throw \"error\") (3 + 3) ]", "<test>")
2576                .unwrap();
2577
2578            let elem0 = es.require_list_select_idx_strict(&v, 0).unwrap().unwrap();
2579            assert_eq!(es.require_int(&elem0).unwrap(), 2);
2580
2581            let elem2 = es.require_list_select_idx_strict(&v, 2).unwrap().unwrap();
2582            assert_eq!(es.require_int(&elem2).unwrap(), 6);
2583
2584            let elem1_result = es.require_list_select_idx_strict(&v, 1);
2585            match elem1_result {
2586                Ok(_) => panic!("expected an error from throw during selection"),
2587                Err(e) => {
2588                    assert!(e.to_string().contains("error"));
2589                }
2590            }
2591        })
2592        .unwrap();
2593    }
2594
2595    #[test]
2596    fn eval_state_require_list_select_idx_strict_wrong_type() {
2597        gc_registering_current_thread(|| {
2598            let store = Store::open(None, HashMap::new()).unwrap();
2599            let mut es = EvalState::new(store, []).unwrap();
2600            let v = es.eval_from_string("42", "<test>").unwrap();
2601
2602            let r = es.require_list_select_idx_strict(&v, 0);
2603            match r {
2604                Ok(_) => panic!("expected an error"),
2605                Err(e) => {
2606                    let err_msg = e.to_string();
2607                    assert!(err_msg.contains("expected a list, but got a"));
2608                }
2609            }
2610        })
2611        .unwrap();
2612    }
2613
2614    #[test]
2615    fn eval_state_require_list_size_basic() {
2616        gc_registering_current_thread(|| {
2617            let store = Store::open(None, HashMap::new()).unwrap();
2618            let mut es = EvalState::new(store, []).unwrap();
2619
2620            let empty = es.eval_from_string("[ ]", "<test>").unwrap();
2621            assert_eq!(es.require_list_size(&empty).unwrap(), 0);
2622
2623            let three_elem = es.eval_from_string("[ 1 2 3 ]", "<test>").unwrap();
2624            assert_eq!(es.require_list_size(&three_elem).unwrap(), 3);
2625        })
2626        .unwrap();
2627    }
2628
2629    #[test]
2630    fn eval_state_require_list_size_forces_thunk() {
2631        gc_registering_current_thread(|| {
2632            let store = Store::open(None, HashMap::new()).unwrap();
2633            let mut es = EvalState::new(store, []).unwrap();
2634            let v = make_thunk(&mut es, "[ 1 2 3 4 5 ]");
2635            assert!(es.value_type_unforced(&v).is_none());
2636
2637            let size = es.require_list_size(&v).unwrap();
2638            assert_eq!(size, 5);
2639        })
2640        .unwrap();
2641    }
2642
2643    #[test]
2644    fn eval_state_require_list_size_lazy_elements() {
2645        gc_registering_current_thread(|| {
2646            let store = Store::open(None, HashMap::new()).unwrap();
2647            let mut es = EvalState::new(store, []).unwrap();
2648
2649            let v = es
2650                .eval_from_string(
2651                    "[ (throw \"error1\") (throw \"error2\") (throw \"error3\") ]",
2652                    "<test>",
2653                )
2654                .unwrap();
2655
2656            let size = es.require_list_size(&v).unwrap();
2657            assert_eq!(size, 3);
2658        })
2659        .unwrap();
2660    }
2661
2662    #[test]
2663    fn eval_state_require_list_size_wrong_type() {
2664        gc_registering_current_thread(|| {
2665            let store = Store::open(None, HashMap::new()).unwrap();
2666            let mut es = EvalState::new(store, []).unwrap();
2667            let v = es.eval_from_string("\"not a list\"", "<test>").unwrap();
2668
2669            let r = es.require_list_size(&v);
2670            match r {
2671                Ok(_) => panic!("expected an error"),
2672                Err(e) => {
2673                    let err_msg = e.to_string();
2674                    assert!(err_msg.contains("expected a list, but got a"));
2675                }
2676            }
2677        })
2678        .unwrap();
2679    }
2680
2681    /// Test for path coercion fix (commit 8f6ec2e, <https://github.com/nixops4/nix-bindings-rust/pull/35>).
2682    ///
2683    /// This test verifies that path coercion works correctly with EvalStateBuilder.
2684    /// Path coercion requires readOnlyMode = false, which is loaded from global
2685    /// settings by calling eval_state_builder_load().
2686    ///
2687    /// # Background
2688    ///
2689    /// Without the eval_state_builder_load() call, settings from global Nix
2690    /// configuration are never loaded, leaving readOnlyMode = true (the default).
2691    /// This prevents Nix from adding paths to the store during evaluation,
2692    /// which could cause errors like: "error: path '/some/local/path' does not exist"
2693    ///
2694    /// # Test Coverage
2695    ///
2696    /// This test exercises store file creation:
2697    /// 1. builtins.toFile successfully creates files in the store
2698    /// 2. Files are actually written to /nix/store
2699    /// 3. Content is written correctly
2700    ///
2701    /// Note: This test may not reliably fail without the fix in all environments.
2702    /// Use eval_state_builder_loads_max_call_depth for a deterministic test.
2703    #[test]
2704    #[cfg(nix_at_least = "2.26" /* real_path, eval_state_builder_load */)]
2705    fn eval_state_builder_path_coercion() {
2706        gc_registering_current_thread(|| {
2707            let mut store = Store::open(None, HashMap::new()).unwrap();
2708            let mut es = EvalStateBuilder::new(store.clone())
2709                .unwrap()
2710                .build()
2711                .unwrap();
2712
2713            // Use builtins.toFile to create a file in the store.
2714            // This operation requires readOnlyMode = false to succeed.
2715            let expr = r#"builtins.toFile "test-file.txt" "test content""#;
2716
2717            // Evaluate the expression
2718            let value = es.eval_from_string(expr, "<test>").unwrap();
2719
2720            // Realise the string to get the path and associated store paths
2721            let realised = es.realise_string(&value, false).unwrap();
2722
2723            // Verify we got exactly one store path
2724            assert_eq!(
2725                realised.paths.len(),
2726                1,
2727                "Expected 1 store path, got {}",
2728                realised.paths.len()
2729            );
2730
2731            // Get the physical filesystem path for the store path
2732            // In a relocated store, this differs from realised.s
2733            let physical_path = store.real_path(&realised.paths[0]).unwrap();
2734
2735            // Verify the store path actually exists on disk
2736            assert!(
2737                std::path::Path::new(&physical_path).exists(),
2738                "Store path should exist: {}",
2739                physical_path
2740            );
2741
2742            // Verify the content was written correctly
2743            let store_content = std::fs::read_to_string(&physical_path).unwrap();
2744            assert_eq!(store_content, "test content");
2745        })
2746        .unwrap();
2747    }
2748
2749    /// Test that eval_state_builder_load() loads settings.
2750    ///
2751    /// Uses max-call-depth as the test setting. The test suite sets
2752    /// max-call-depth = 1000 via NIX_CONFIG in setup() for the purpose of this test case.
2753    /// This test creates a recursive function that calls itself 1100 times.
2754    ///
2755    /// - WITH the fix: Settings are loaded, max-call-depth=1000 is enforced,
2756    ///   recursion fails at depth 1000
2757    /// - WITHOUT the fix: Settings aren't loaded, default max-call-depth=10000
2758    ///   is used, recursion of 1100 succeeds when it should fail
2759    ///
2760    /// Complementary to eval_state_builder_ignores_ambient_when_disabled which verifies
2761    /// that ambient settings are NOT loaded when disabled.
2762    #[test]
2763    #[cfg(nix_at_least = "2.26")]
2764    fn eval_state_builder_loads_max_call_depth() {
2765        gc_registering_current_thread(|| {
2766            let store = Store::open(None, HashMap::new()).unwrap();
2767            let mut es = EvalStateBuilder::new(store).unwrap().build().unwrap();
2768
2769            // Create a recursive function that calls itself 1100 times
2770            // This should fail because max-call-depth is 1000 (set in setup())
2771            let expr = r#"
2772                let
2773                  recurse = n: if n == 0 then "done" else recurse (n - 1);
2774                in
2775                  recurse 1100
2776            "#;
2777
2778            let result = es.eval_from_string(expr, "<test>");
2779
2780            match result {
2781                Err(e) => {
2782                    let err_str = e.to_string();
2783                    assert!(
2784                        err_str.contains("max-call-depth"),
2785                        "Expected max-call-depth error, got: {}",
2786                        err_str
2787                    );
2788                }
2789                Ok(_) => {
2790                    panic!(
2791                        "Expected recursion to fail with max-call-depth=1000, but it succeeded. \
2792                         This indicates eval_state_builder_load() was not called."
2793                    );
2794                }
2795            }
2796        })
2797        .unwrap();
2798    }
2799
2800    /// Test that load_ambient_settings(false) ignores the ambient environment.
2801    ///
2802    /// The test suite sets max-call-depth = 1000 via NIX_CONFIG in setup().
2803    /// When we disable loading ambient settings, this should be ignored and
2804    /// the default max-call-depth = 10000 should be used instead.
2805    ///
2806    /// Complementary to eval_state_builder_loads_max_call_depth which verifies
2807    /// that ambient settings ARE loaded when enabled.
2808    #[test]
2809    #[cfg(nix_at_least = "2.26")]
2810    fn eval_state_builder_ignores_ambient_when_disabled() {
2811        gc_registering_current_thread(|| {
2812            let store = Store::open(None, HashMap::new()).unwrap();
2813            let mut es = EvalStateBuilder::new(store)
2814                .unwrap()
2815                .load_ambient_settings(false)
2816                .build()
2817                .unwrap();
2818
2819            // Create a recursive function that calls itself 1100 times
2820            // With ambient settings disabled, default max-call-depth=10000 is used,
2821            // so this should succeed (unlike eval_state_builder_loads_max_call_depth)
2822            let expr = r#"
2823                let
2824                  recurse = n: if n == 0 then "done" else recurse (n - 1);
2825                in
2826                  recurse 1100
2827            "#;
2828
2829            let result = es.eval_from_string(expr, "<test>");
2830
2831            match result {
2832                Ok(value) => {
2833                    // Success expected - ambient NIX_CONFIG was ignored
2834                    let result_str = es.require_string(&value).unwrap();
2835                    assert_eq!(result_str, "done");
2836                }
2837                Err(e) => {
2838                    panic!(
2839                        "Expected recursion to succeed with default max-call-depth=10000, \
2840                         but it failed: {}. This indicates ambient settings were not ignored.",
2841                        e
2842                    );
2843                }
2844            }
2845        })
2846        .unwrap();
2847    }
2848}