Skip to main content

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