2025-09-30 15:50:42 +02:00
//! # Nix Expression Evaluation
//!
//! This module provides the core [`EvalState`] type for evaluating Nix expressions
//! and extracting typed values from the results.
//!
//! ## Overview
//!
//! The [`EvalState`] manages the evaluation context for Nix expressions, including:
//! - Expression parsing and evaluation with [`eval_from_string`](EvalState::eval_from_string)
//! - Type-safe value extraction with [`require_*`](EvalState#implementations) methods
//! - Memory management and garbage collection integration
//! - Store integration for derivations and store paths
//! - Custom function creation with [`new_value_primop`](EvalState::new_value_primop) and [`new_value_thunk`](EvalState::new_value_thunk)
//!
//! ### Construction
//!
//! Create an [`EvalState`] using [`EvalState::new`] or [`EvalStateBuilder`] for advanced configuration:
//!
//! ```rust
2025-10-04 02:44:22 +02:00
//! # use nix_bindings_expr::eval_state::{EvalState, EvalStateBuilder, test_init, gc_register_my_thread};
//! # use nix_bindings_store::store::Store;
2025-09-30 15:50:42 +02:00
//! # use std::collections::HashMap;
//! # fn example() -> anyhow::Result<()> {
//! # test_init(); let guard = gc_register_my_thread()?;
//! let store = Store::open(None, HashMap::new())?;
//!
//! // Simple creation
//! let mut es = EvalState::new(store.clone(), [])?;
//!
//! // With custom lookup paths
//! let mut es = EvalStateBuilder::new(store)?
//! .lookup_path(["nixpkgs=/path/to/nixpkgs"])?
//! .build()?;
//! # drop(guard);
//! # Ok(())
//! # }
//! ```
//!
//! ## Value Extraction
//!
//! All `require_*` methods perform these steps:
//! 1. **Evaluation**: Force evaluation of thunks as needed
//! 2. **Type checking**: Verify the value matches the expected type
//! 3. **Extraction**: Return the typed Rust value or an error
//!
//! Methods with `_strict` in their name also evaluate their return values before returning them.
//!
//! ### Evaluation Strictness
//!
//! - **Lazy methods** (e.g., [`require_list_size`](EvalState::require_list_size)):
//! Evaluate only the structure needed
//! - **Strict methods** (e.g., [`require_list_strict`](EvalState::require_list_strict)):
//! Force full evaluation of all contained values
//! - **Selective methods** (e.g., [`require_list_select_idx_strict`](EvalState::require_list_select_idx_strict)):
//! Evaluate only the accessed elements
//!
//! ## Laziness and Strictness
//!
//! 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)
//! evaluation model, not the kind of deep strictness that is exercised by functions such as `builtins.toJSON` or `builtins.deepSeq`.
//!
//! - **WHNF evaluation**: Values are evaluated just enough to determine their type and basic structure
//! - **Deep evaluation**: All nested values are recursively forced (like `builtins.deepSeq`)
//!
//! For example, a list in WHNF has its length determined but individual elements may remain unevaluated thunks.
//! Methods marked as "strict" in this API force WHNF evaluation of their results, but do not perform deep evaluation
//! of arbitrarily nested structures unless explicitly documented otherwise.
//!
//! ### Thread Safety and Memory Management
//!
//! Before using [`EvalState`] in a thread, register it with the (process memory) garbage collector:
//!
//! ```rust,no_run
2025-10-04 02:44:22 +02:00
//! # use nix_bindings_expr::eval_state::{init, gc_register_my_thread, test_init};
2025-09-30 15:50:42 +02:00
//! # fn example() -> anyhow::Result<()> {
//! # test_init(); // Use test_init() in tests
//! init()?; // Initialize Nix library
//! let guard = gc_register_my_thread()?; // Register thread with GC
//! // Now safe to use EvalState in this thread
//! drop(guard);
//! # Ok(())
//! # }
//! ```
//!
//! ## Error Handling
//!
//! Evaluation methods return [`Result`] types. Common error scenarios include:
//! - **Type mismatches**: Expected type doesn't match actual value type
//! - **Evaluation errors**: Nix expressions that throw or have undefined behavior
//! - **Bounds errors**: Out-of-range access for indexed operations
//!
//! ## Examples
//!
//! ```rust
2025-10-04 02:44:22 +02:00
//! use nix_bindings_expr::eval_state::{EvalState, test_init, gc_register_my_thread};
//! use nix_bindings_store::store::Store;
2025-09-30 15:50:42 +02:00
//! use std::collections::HashMap;
//!
//! # fn main() -> anyhow::Result<()> {
//! test_init(); // init() in non-test code
//! let guard = gc_register_my_thread()?;
//!
//! let store = Store::open(None, HashMap::new())?;
//! let mut es = EvalState::new(store, [])?;
//!
//! // Evaluate a list expression
//! let list_value = es.eval_from_string("[1 2 3]", "<example>")?;
//!
//! // Check the size (lazy - doesn't evaluate elements)
//! let size = es.require_list_size(&list_value)?;
//! println!("List has {} elements", size);
//!
//! // Access specific elements (evaluates only accessed elements)
//! if let Some(first) = es.require_list_select_idx_strict(&list_value, 0)? {
//! let value = es.require_int(&first)?;
//! println!("First element: {}", value);
//! }
//!
//! // Process all elements (evaluates all elements)
//! let all_elements: Vec<_> = es.require_list_strict(&list_value)?;
//! for element in all_elements {
//! let value = es.require_int(&element)?;
//! println!("Element: {}", value);
//! }
//!
//! drop(guard);
//! # Ok(())
//! # }
//! ```
2024-07-19 17:54:31 +02:00
use crate ::primop ;
2024-06-14 18:42:23 +02:00
use crate ::value ::{ Int , Value , ValueType } ;
2024-03-19 14:43:01 +01:00
use anyhow ::Context as _ ;
use anyhow ::{ bail , Result } ;
2024-07-19 18:29:56 +02:00
use cstr ::cstr ;
2024-03-19 14:43:01 +01:00
use lazy_static ::lazy_static ;
2025-10-04 02:44:22 +02:00
use nix_bindings_store ::path ::StorePath ;
use nix_bindings_store ::store ::{ Store , StoreWeak } ;
use nix_bindings_util ::context ::Context ;
2025-12-05 16:51:15 -05:00
use nix_bindings_util ::string_return ::{
callback_get_result_string , callback_get_result_string_data ,
} ;
2025-10-04 02:44:22 +02:00
use nix_bindings_util ::{ check_call , check_call_opt_key , result_string_init } ;
2026-01-12 19:56:04 +01:00
use nix_bindings_util_sys as raw ;
2024-07-19 18:04:34 +02:00
use std ::ffi ::{ c_char , CString } ;
2025-09-20 01:28:48 +02:00
use std ::iter ::FromIterator ;
2024-04-09 16:20:18 +02:00
use std ::os ::raw ::c_uint ;
2024-05-13 19:26:58 +02:00
use std ::ptr ::{ null , null_mut , NonNull } ;
2024-06-27 16:01:40 +02:00
use std ::sync ::{ Arc , Weak } ;
2024-03-19 14:43:01 +01:00
lazy_static! {
static ref INIT : Result < ( ) > = {
unsafe {
raw ::GC_allow_register_threads ( ) ;
2024-07-02 16:35:32 -04:00
check_call! ( raw ::libexpr_init ( & mut Context ::new ( ) ) ) ? ;
2024-06-15 14:51:26 +02:00
Ok ( ( ) )
2024-03-19 14:43:01 +01:00
}
} ;
}
2025-09-30 15:50:42 +02:00
2024-03-19 14:43:01 +01:00
pub fn init ( ) -> Result < ( ) > {
let x = INIT . as_ref ( ) ;
match x {
Ok ( _ ) = > Ok ( ( ) ) ,
Err ( e ) = > {
// Couldn't just clone the error, so we have to print it here.
2025-10-04 02:44:22 +02:00
Err ( anyhow ::format_err! ( " nix_bindings_expr::init error: {} " , e ) )
2024-03-19 14:43:01 +01:00
}
}
}
2025-09-30 15:50:42 +02:00
/// A string value with its associated [store paths](https://nix.dev/manual/nix/stable/store/store-path.html).
///
/// Represents a Nix string with references to store paths.
2024-04-08 16:54:36 +02:00
pub struct RealisedString {
2025-09-30 15:50:42 +02:00
/// The string content.
2024-04-08 16:54:36 +02:00
pub s : String ,
2025-09-30 15:50:42 +02:00
/// Store paths referenced by the string.
2024-04-08 16:54:36 +02:00
pub paths : Vec < StorePath > ,
}
2025-09-30 15:50:42 +02:00
/// A [Weak] reference to an [EvalState].
2024-06-27 16:01:40 +02:00
pub struct EvalStateWeak {
inner : Weak < EvalStateRef > ,
store : StoreWeak ,
}
impl EvalStateWeak {
/// Upgrade the weak reference to a proper [EvalState].
///
/// If no normal reference to the [EvalState] is around anymore elsewhere, this fails by returning `None`.
pub fn upgrade ( & self ) -> Option < EvalState > {
self . inner . upgrade ( ) . and_then ( | eval_state | {
self . store . upgrade ( ) . map ( | store | EvalState {
2024-11-27 10:26:39 +01:00
eval_state ,
store ,
2024-06-27 16:01:40 +02:00
context : Context ::new ( ) ,
} )
} )
}
}
2024-06-26 21:00:54 +02:00
struct EvalStateRef {
2024-03-19 14:43:01 +01:00
eval_state : NonNull < raw ::EvalState > ,
2024-06-26 21:00:54 +02:00
}
impl EvalStateRef {
2025-09-30 15:50:42 +02:00
/// Returns a raw pointer to the underlying EvalState.
///
2024-12-17 10:40:03 +01:00
/// # Safety
///
2025-09-30 15:50:42 +02:00
/// The caller must ensure that the pointer is not used beyond the lifetime of the underlying [raw::EvalState].
2024-12-17 10:40:03 +01:00
unsafe fn as_ptr ( & self ) -> * mut raw ::EvalState {
2024-06-26 21:00:54 +02:00
self . eval_state . as_ptr ( )
}
}
impl Drop for EvalStateRef {
fn drop ( & mut self ) {
unsafe {
raw ::state_free ( self . eval_state . as_ptr ( ) ) ;
}
}
}
2025-09-30 15:50:42 +02:00
/// Builder for configuring and creating an [`EvalState`].
///
/// Provides advanced configuration options for evaluation context setup.
/// Use [`EvalState::new`] for simple cases or this builder for custom configuration.
///
2026-01-07 09:20:01 +01:00
/// Requires Nix 2.26.0 or later.
///
2025-09-30 15:50:42 +02:00
/// # Examples
///
/// ```rust
2025-10-04 02:44:22 +02:00
/// # use nix_bindings_expr::eval_state::{EvalState, EvalStateBuilder, test_init, gc_register_my_thread};
/// # use nix_bindings_store::store::Store;
2025-09-30 15:50:42 +02:00
/// # use std::collections::HashMap;
/// # fn example() -> anyhow::Result<()> {
/// # test_init();
/// # let guard = gc_register_my_thread()?;
/// let store = Store::open(None, HashMap::new())?;
///
/// let mut es: EvalState = EvalStateBuilder::new(store)?
/// .lookup_path(["nixpkgs=/path/to/nixpkgs", "home-manager=/path/to/hm"])?
/// .build()?;
///
/// let value = es.eval_from_string("<nixpkgs>", /* path display: */ "in-memory")?;
/// # drop(guard);
/// # Ok(())
/// # }
/// ```
2026-01-07 09:20:01 +01:00
#[ cfg(nix_at_least = " 2.26 " ) ]
2025-04-02 12:22:48 +02:00
pub struct EvalStateBuilder {
eval_state_builder : * mut raw ::eval_state_builder ,
lookup_path : Vec < CString > ,
2024-03-19 14:43:01 +01:00
store : Store ,
}
2026-01-07 09:20:01 +01:00
#[ cfg(nix_at_least = " 2.26 " ) ]
2025-04-02 12:22:48 +02:00
impl Drop for EvalStateBuilder {
fn drop ( & mut self ) {
unsafe {
raw ::eval_state_builder_free ( self . eval_state_builder ) ;
}
}
}
2026-01-07 09:20:01 +01:00
#[ cfg(nix_at_least = " 2.26 " ) ]
2025-04-02 12:22:48 +02:00
impl EvalStateBuilder {
2025-09-30 15:50:42 +02:00
/// Creates a new [`EvalStateBuilder`].
2025-04-02 12:22:48 +02:00
pub fn new ( store : Store ) -> Result < EvalStateBuilder > {
2024-06-15 12:40:45 +02:00
let mut context = Context ::new ( ) ;
2025-04-02 12:22:48 +02:00
let eval_state_builder =
unsafe { check_call! ( raw ::eval_state_builder_new ( & mut context , store . raw_ptr ( ) ) ) } ? ;
Ok ( EvalStateBuilder {
store ,
eval_state_builder ,
lookup_path : Vec ::new ( ) ,
} )
}
2025-09-30 15:50:42 +02:00
/// Sets the [lookup path](https://nix.dev/manual/nix/latest/language/constructs/lookup-path.html) for Nix expression evaluation.
2025-04-02 12:22:48 +02:00
pub fn lookup_path < ' a > ( mut self , path : impl IntoIterator < Item = & ' a str > ) -> Result < Self > {
let lookup_path : Vec < CString > = path
2024-05-13 19:26:58 +02:00
. into_iter ( )
. map ( | path | {
CString ::new ( path ) . with_context ( | | {
2025-04-02 12:22:48 +02:00
format! ( " EvalStateBuilder::lookup_path: path ` {path} ` contains null byte " )
2024-05-13 19:26:58 +02:00
} )
} )
. collect ::< Result < _ > > ( ) ? ;
2025-04-02 12:22:48 +02:00
self . lookup_path = lookup_path ;
Ok ( self )
}
2025-09-30 15:50:42 +02:00
/// Builds the configured [`EvalState`].
2025-04-02 12:22:48 +02:00
pub fn build ( & self ) -> Result < EvalState > {
// Make sure the library is initialized
init ( ) ? ;
2024-05-13 19:26:58 +02:00
2025-04-02 12:22:48 +02:00
let mut context = Context ::new ( ) ;
2026-01-05 20:57:51 -05:00
// Load settings from global configuration (including readOnlyMode = false).
// This is necessary for path coercion to work (adding files to the store).
unsafe {
check_call! ( raw ::eval_state_builder_load (
& mut context ,
self . eval_state_builder
) ) ? ;
}
2025-04-02 12:22:48 +02:00
// Note: these raw C string pointers borrow from self.lookup_path
let mut lookup_path : Vec < * const c_char > = self
. lookup_path
2024-05-13 19:26:58 +02:00
. iter ( )
. map ( | s | s . as_ptr ( ) )
. chain ( std ::iter ::once ( null ( ) ) ) // signal the end of the array
. collect ( ) ;
2025-04-02 12:22:48 +02:00
unsafe {
check_call! ( raw ::eval_state_builder_set_lookup_path (
2024-07-02 16:35:32 -04:00
& mut context ,
2025-04-02 12:22:48 +02:00
self . eval_state_builder ,
lookup_path . as_mut_ptr ( )
) ) ? ;
}
let eval_state =
unsafe { check_call! ( raw ::eval_state_build ( & mut context , self . eval_state_builder ) ) } ? ;
2024-03-19 14:43:01 +01:00
Ok ( EvalState {
2024-06-26 21:00:54 +02:00
eval_state : Arc ::new ( EvalStateRef {
eval_state : NonNull ::new ( eval_state ) . unwrap_or_else ( | | {
panic! ( " nix_state_create returned a null pointer without an error " )
} ) ,
2024-06-14 17:28:50 +02:00
} ) ,
2025-04-02 12:22:48 +02:00
store : self . store . clone ( ) ,
2024-03-19 14:43:01 +01:00
context ,
} )
}
2025-09-30 15:50:42 +02:00
/// Returns a raw pointer to the underlying eval state builder.
///
/// # Safety
///
/// The caller must ensure that the pointer is not used beyond the lifetime of this builder.
// TODO: This function should be marked `unsafe`.
2025-04-02 12:43:28 +02:00
pub fn raw_ptr ( & self ) -> * mut raw ::eval_state_builder {
self . eval_state_builder
}
2025-04-02 12:22:48 +02:00
}
pub struct EvalState {
eval_state : Arc < EvalStateRef > ,
store : Store ,
pub ( crate ) context : Context ,
}
impl EvalState {
2025-09-30 15:50:42 +02:00
/// Creates a new EvalState with basic configuration.
///
2025-04-02 12:22:48 +02:00
/// For more options, use [EvalStateBuilder].
pub fn new < ' a > ( store : Store , lookup_path : impl IntoIterator < Item = & ' a str > ) -> Result < Self > {
EvalStateBuilder ::new ( store ) ?
. lookup_path ( lookup_path ) ?
. build ( )
}
2024-12-17 10:40:03 +01:00
2025-09-30 15:50:42 +02:00
/// Returns a raw pointer to the raw Nix C API EvalState.
///
2024-12-17 10:40:03 +01:00
/// # Safety
///
2025-09-30 15:50:42 +02:00
/// The caller must ensure that the pointer is not used beyond the lifetime of this `EvalState`.
2024-12-17 10:40:03 +01:00
pub unsafe fn raw_ptr ( & self ) -> * mut raw ::EvalState {
2024-03-19 14:43:01 +01:00
self . eval_state . as_ptr ( )
}
2025-09-30 15:50:42 +02:00
/// Returns a reference to the Store that's used for instantiation, import from derivation, etc.
2024-03-19 14:43:01 +01:00
pub fn store ( & self ) -> & Store {
& self . store
}
2025-09-30 15:50:42 +02:00
/// Creates a weak reference to this EvalState.
2024-06-27 16:01:40 +02:00
pub fn weak_ref ( & self ) -> EvalStateWeak {
EvalStateWeak {
inner : Arc ::downgrade ( & self . eval_state ) ,
store : self . store . weak_ref ( ) ,
}
}
2024-05-29 02:52:43 +02:00
/// Parses and evaluates a Nix expression `expr`.
///
/// Expressions can contain relative paths such as `./.` that are resolved relative to the given `path`.
///
/// # Examples
///
/// ```
2025-10-04 02:44:22 +02:00
/// # use nix_bindings_expr::eval_state::{EvalState, test_init, gc_register_my_thread};
/// use nix_bindings_store::store::Store;
/// use nix_bindings_expr::value::Value;
2025-09-30 15:50:42 +02:00
/// use std::collections::HashMap;
2024-05-29 02:52:43 +02:00
///
/// # fn main() -> anyhow::Result<()> {
2025-09-30 15:50:42 +02:00
/// # test_init();
/// # let guard = gc_register_my_thread()?;
/// # let mut es = EvalState::new(Store::open(None, HashMap::new())?, [])?;
2024-05-29 02:52:43 +02:00
/// let v: Value = es.eval_from_string("42", ".")?;
/// assert_eq!(es.require_int(&v)?, 42);
2025-09-30 15:50:42 +02:00
/// # drop(guard);
2024-05-29 02:52:43 +02:00
/// # Ok(())
/// # }
/// ```
#[ doc(alias = " nix_expr_eval_from_string " ) ]
2025-09-30 15:50:42 +02:00
#[ doc(alias = " parse " ) ]
#[ doc(alias = " eval " ) ]
#[ doc(alias = " evaluate " ) ]
2024-06-15 12:40:45 +02:00
pub fn eval_from_string ( & mut self , expr : & str , path : & str ) -> Result < Value > {
2024-03-19 14:43:01 +01:00
let expr_ptr =
CString ::new ( expr ) . with_context ( | | " eval_from_string: expr contains null byte " ) ? ;
let path_ptr =
CString ::new ( path ) . with_context ( | | " eval_from_string: path contains null byte " ) ? ;
unsafe {
2024-06-14 17:28:50 +02:00
let value = self . new_value_uninitialized ( ) ? ;
2024-07-02 16:35:32 -04:00
check_call! ( raw ::expr_eval_from_string (
2024-06-15 14:34:30 +02:00
& mut self . context ,
self . eval_state . as_ptr ( ) ,
2024-06-15 12:40:45 +02:00
expr_ptr . as_ptr ( ) ,
path_ptr . as_ptr ( ) ,
value . raw_ptr ( )
2024-07-02 16:35:32 -04:00
) ) ? ;
2024-06-14 17:28:50 +02:00
Ok ( value )
}
2024-03-19 14:43:01 +01:00
}
2025-09-30 15:50:42 +02:00
/// 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).
///
/// Converts [thunks](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness) to their evaluated form. Does not modify already-evaluated values.
///
/// Does not perform deep evaluation of nested structures.
#[ doc(alias = " evaluate " ) ]
#[ doc(alias = " strict " ) ]
2024-06-15 12:40:45 +02:00
pub fn force ( & mut self , v : & Value ) -> Result < ( ) > {
2024-06-15 14:34:30 +02:00
unsafe {
2024-07-02 16:35:32 -04:00
check_call! ( raw ::value_force (
& mut self . context ,
self . eval_state . as_ptr ( ) ,
v . raw_ptr ( )
) )
2024-06-15 14:34:30 +02:00
} ? ;
2024-06-14 17:28:50 +02:00
Ok ( ( ) )
2024-03-19 14:43:01 +01:00
}
2025-09-30 15:50:42 +02:00
/// Returns the type of a value without forcing [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html).
///
/// Returns [`None`] if the value is an unevaluated [thunk](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness).
///
/// Returns [`Some`] if the value is already evaluated.
#[ doc(alias = " type_of " ) ]
#[ doc(alias = " value_type_lazy " ) ]
#[ doc(alias = " nix_get_type " ) ]
#[ doc(alias = " get_type " ) ]
#[ doc(alias = " nix_value_type " ) ]
2024-06-15 12:40:45 +02:00
pub fn value_type_unforced ( & mut self , value : & Value ) -> Option < ValueType > {
2024-07-02 16:35:32 -04:00
let r = unsafe { check_call! ( raw ::get_type ( & mut self . context , value . raw_ptr ( ) ) ) } ;
2024-06-14 15:50:07 +02:00
// .unwrap(): no reason for this to fail, as it does not evaluate
2024-06-14 17:28:50 +02:00
ValueType ::from_raw ( r . unwrap ( ) )
2024-03-19 14:43:01 +01:00
}
2025-09-30 15:50:42 +02:00
/// Returns the [type][`ValueType`] of a value, [forcing][`EvalState::force`] [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) if necessary.
///
/// Forces evaluation if the value is an unevaluated [thunk](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness).
///
/// Evaluation may fail, producing an [`Err`].
///
/// 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).
#[ doc(alias = " type_of " ) ]
#[ doc(alias = " value_type_strict " ) ]
#[ doc(alias = " nix_get_type " ) ]
#[ doc(alias = " get_type " ) ]
#[ doc(alias = " nix_value_type_strict " ) ]
2024-06-15 12:40:45 +02:00
pub fn value_type ( & mut self , value : & Value ) -> Result < ValueType > {
2024-06-03 04:18:58 +02:00
match self . value_type_unforced ( value ) {
2024-06-14 18:42:23 +02:00
Some ( a ) = > Ok ( a ) ,
None = > {
2024-06-03 04:18:58 +02:00
self . force ( value ) ? ;
match self . value_type_unforced ( value ) {
2024-06-14 18:42:23 +02:00
Some ( a ) = > Ok ( a ) ,
None = > {
panic! ( " Nix value must not be thunk after being forced. " )
2024-06-03 04:18:58 +02:00
}
}
}
2024-03-19 14:43:01 +01:00
}
}
2025-09-30 15:50:42 +02:00
/// Extracts the value from an [integer][`ValueType::Int`] Nix value.
///
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is an integer.
///
/// Returns the integer value if successful, or an [`Err`] if evaluation failed or the value is not an integer.
///
/// # Examples
///
/// ```rust
2025-10-04 02:44:22 +02:00
/// # use nix_bindings_expr::eval_state::{EvalState, test_init, gc_register_my_thread};
/// # use nix_bindings_store::store::Store;
2025-09-30 15:50:42 +02:00
/// # use std::collections::HashMap;
/// # fn example() -> anyhow::Result<()> {
/// # test_init();
/// # let guard = gc_register_my_thread()?;
/// let store = Store::open(None, HashMap::new())?;
/// let mut es = EvalState::new(store, [])?;
///
/// let value = es.eval_from_string("42", "<example>")?;
/// let int_val = es.require_int(&value)?;
/// assert_eq!(int_val, 42);
/// # drop(guard);
/// # Ok(())
/// # }
/// ```
#[ doc(alias = " integer " ) ]
#[ doc(alias = " number " ) ]
#[ doc(alias = " nix_get_int " ) ]
#[ doc(alias = " get_int " ) ]
2024-06-15 12:40:45 +02:00
pub fn require_int ( & mut self , v : & Value ) -> Result < Int > {
2024-06-14 15:50:07 +02:00
let t = self . value_type ( v ) ? ;
2024-04-09 13:13:52 +02:00
if t ! = ValueType ::Int {
bail! ( " expected an int, but got a {:?} " , t ) ;
}
2024-07-02 16:35:32 -04:00
unsafe { check_call! ( raw ::get_int ( & mut self . context , v . raw_ptr ( ) ) ) }
2024-04-09 13:13:52 +02:00
}
2024-12-02 14:02:51 +01:00
2025-09-30 15:50:42 +02:00
/// Extracts the value from a [boolean][`ValueType::Bool`] Nix value.
///
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is a boolean.
///
/// Returns the boolean value if successful, or an [`Err`] if evaluation failed or the value is not a boolean.
#[ doc(alias = " boolean " ) ]
#[ doc(alias = " nix_get_bool " ) ]
#[ doc(alias = " get_bool " ) ]
2025-04-02 13:30:57 +02:00
pub fn require_bool ( & mut self , v : & Value ) -> Result < bool > {
let t = self . value_type ( v ) ? ;
if t ! = ValueType ::Bool {
bail! ( " expected a bool, but got a {:?} " , t ) ;
}
unsafe { check_call! ( raw ::get_bool ( & mut self . context , v . raw_ptr ( ) ) ) }
}
2025-09-30 15:50:42 +02:00
/// Extracts all elements from a [list][`ValueType::List`] Nix value.
///
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is a list.
///
/// Returns the contained values in the specified container type (e.g., [`Vec`], [`VecDeque`][`std::collections::VecDeque`], etc.).
///
/// This is [strict](https://nix.dev/manual/nix/latest/language/evaluation.html#strictness) - all list elements will be evaluated.
2025-09-20 01:28:48 +02:00
///
/// # Examples
///
/// ```rust,no_run
2025-10-04 02:44:22 +02:00
/// # use nix_bindings_expr::value::Value;
2025-09-30 15:50:42 +02:00
/// # use std::collections::{VecDeque, LinkedList};
2025-10-04 02:44:22 +02:00
/// # fn example(es: &mut nix_bindings_expr::eval_state::EvalState, list_value: &Value) -> anyhow::Result<()> {
2025-09-20 01:28:48 +02:00
/// let vec: Vec<Value> = es.require_list_strict(&list_value)?;
/// let deque: VecDeque<Value> = es.require_list_strict(&list_value)?;
2025-09-30 15:50:42 +02:00
/// let linked_list = es.require_list_strict::<LinkedList<Value>>(&list_value)?;
2025-09-20 01:28:48 +02:00
/// # Ok(())
/// # }
/// ```
2025-09-30 15:50:42 +02:00
#[ doc(alias = " collect " ) ]
#[ doc(alias = " to_vec " ) ]
#[ doc(alias = " all " ) ]
#[ doc(alias = " nix_get_list_size " ) ]
#[ doc(alias = " nix_get_list_byidx " ) ]
2025-09-20 01:28:48 +02:00
pub fn require_list_strict < C > ( & mut self , value : & Value ) -> Result < C >
where
C : FromIterator < Value > ,
{
let t = self . value_type ( value ) ? ;
if t ! = ValueType ::List {
bail! ( " expected a list, but got a {:?} " , t ) ;
}
let size = unsafe { check_call! ( raw ::get_list_size ( & mut self . context , value . raw_ptr ( ) ) ) } ? ;
( 0 .. size )
. map ( | i | {
let element_ptr = unsafe {
check_call! ( raw ::get_list_byidx (
& mut self . context ,
value . raw_ptr ( ) ,
self . eval_state . as_ptr ( ) ,
i
) )
} ? ;
Ok ( unsafe { Value ::new ( element_ptr ) } )
} )
. collect ( )
}
2025-09-30 15:50:42 +02:00
/// Evaluate, and require that the [`Value`] is a Nix [`ValueType::AttrSet`].
///
2024-04-09 16:20:18 +02:00
/// Returns a list of the keys in the attrset.
2024-12-02 14:02:51 +01:00
///
/// NOTE: this currently implements its own sorting, which probably matches Nix's implementation, but is not guaranteed.
2025-09-30 15:50:42 +02:00
#[ doc(alias = " keys " ) ]
#[ doc(alias = " attributes " ) ]
#[ doc(alias = " fields " ) ]
2024-12-02 14:02:51 +01:00
pub fn require_attrs_names ( & mut self , v : & Value ) -> Result < Vec < String > > {
self . require_attrs_names_unsorted ( v ) . map ( | mut v | {
v . sort ( ) ;
v
} )
}
2025-09-30 15:50:42 +02:00
/// For when [`EvalState::require_attrs_names`] isn't fast enough.
///
2024-12-02 14:02:51 +01:00
/// Only use when it's ok that the keys are returned in an arbitrary order.
2025-09-30 15:50:42 +02:00
#[ doc(alias = " keys_unsorted " ) ]
#[ doc(alias = " attributes_unsorted " ) ]
2024-12-02 13:52:17 +01:00
pub fn require_attrs_names_unsorted ( & mut self , v : & Value ) -> Result < Vec < String > > {
2024-06-14 15:40:11 +02:00
let t = self . value_type ( v ) ? ;
2024-04-09 16:20:18 +02:00
if t ! = ValueType ::AttrSet {
bail! ( " expected an attrset, but got a {:?} " , t ) ;
}
2024-07-02 16:35:32 -04:00
let n = unsafe { check_call! ( raw ::get_attrs_size ( & mut self . context , v . raw_ptr ( ) ) ) } ? ;
2024-06-15 14:51:26 +02:00
let mut attrs = Vec ::with_capacity ( n as usize ) ;
2024-06-14 17:28:50 +02:00
for i in 0 .. n {
2025-09-19 18:10:50 +02:00
let cstr_ptr : * const c_char = unsafe {
2024-07-02 16:35:32 -04:00
check_call! ( raw ::get_attr_name_byidx (
2024-06-15 14:34:30 +02:00
& mut self . context ,
2024-06-15 12:40:45 +02:00
v . raw_ptr ( ) ,
2024-06-15 14:34:30 +02:00
self . eval_state . as_ptr ( ) ,
2024-06-15 12:40:45 +02:00
i as c_uint
2024-07-02 16:35:32 -04:00
) )
2024-06-15 12:40:45 +02:00
} ? ;
2024-06-14 17:28:50 +02:00
let cstr = unsafe { std ::ffi ::CStr ::from_ptr ( cstr_ptr ) } ;
let s = cstr
. to_str ( )
. map_err ( | e | anyhow ::format_err! ( " Nix attrset key is not valid UTF-8: {} " , e ) ) ? ;
2024-06-15 14:51:26 +02:00
attrs . insert ( i as usize , s . to_owned ( ) ) ;
2024-04-09 16:20:18 +02:00
}
Ok ( attrs )
}
2024-04-09 13:13:52 +02:00
2025-09-30 15:50:42 +02:00
/// Extracts an attribute value from an [attribute set][`ValueType::AttrSet`] Nix value.
///
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is an attribute set.
///
/// 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.
#[ doc(alias = " get_attr " ) ]
#[ doc(alias = " attribute " ) ]
#[ doc(alias = " field " ) ]
2024-06-15 12:40:45 +02:00
pub fn require_attrs_select ( & mut self , v : & Value , attr_name : & str ) -> Result < Value > {
2024-06-14 18:21:13 +02:00
let t = self . value_type ( v ) ? ;
if t ! = ValueType ::AttrSet {
bail! ( " expected an attrset, but got a {:?} " , t ) ;
2024-04-10 14:32:42 +02:00
}
2024-06-14 18:21:13 +02:00
let attr_name = CString ::new ( attr_name )
. with_context ( | | " require_attrs_select: attrName contains null byte " ) ? ;
2024-12-17 10:40:03 +01:00
unsafe {
let v2 = check_call! ( raw ::get_attr_byname (
2024-06-15 14:34:30 +02:00
& mut self . context ,
2024-06-15 12:40:45 +02:00
v . raw_ptr ( ) ,
2024-06-15 14:34:30 +02:00
self . eval_state . as_ptr ( ) ,
2024-06-15 12:40:45 +02:00
attr_name . as_ptr ( )
2025-01-31 07:38:11 +01:00
) ) ;
match v2 {
Ok ( v2 ) = > Ok ( Value ::new ( v2 ) ) ,
Err ( e ) = > {
// As of Nix 2.26, the error message is not helpful when it
// is simply missing, so we provide a better one. (Note that
// missing attributes requested by Nix expressions OTOH is a
// different error message which works fine.)
if e . to_string ( ) = = " missing attribute " {
bail! ( " attribute `{}` not found " , attr_name . to_string_lossy ( ) ) ;
} else {
Err ( e )
}
}
}
2024-12-17 10:40:03 +01:00
}
2024-04-10 14:32:42 +02:00
}
2025-09-30 15:50:42 +02:00
/// Extracts an optional attribute value from an [attribute set][`ValueType::AttrSet`] Nix value.
///
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is an attribute set.
2024-06-14 18:21:13 +02:00
///
2025-09-30 15:50:42 +02:00
/// Returns [`Err`] if evaluation failed or the value is not an attribute set.
2024-06-14 18:21:13 +02:00
///
2026-01-12 20:17:40 +01:00
/// Returns `Ok(None)` if the attribute is not present.
2024-06-14 18:21:13 +02:00
///
2026-01-12 20:17:40 +01:00
/// Returns `Ok(Some(value))` if the attribute is present.
2025-09-30 15:50:42 +02:00
#[ doc(alias = " nix_get_attr_byname " ) ]
#[ doc(alias = " get_attr_byname " ) ]
#[ doc(alias = " get_attr_opt " ) ]
#[ doc(alias = " try_get " ) ]
#[ doc(alias = " maybe_get " ) ]
2024-06-15 12:40:45 +02:00
pub fn require_attrs_select_opt (
& mut self ,
v : & Value ,
attr_name : & str ,
) -> Result < Option < Value > > {
2024-06-14 15:40:11 +02:00
let t = self . value_type ( v ) ? ;
2024-04-10 14:32:42 +02:00
if t ! = ValueType ::AttrSet {
bail! ( " expected an attrset, but got a {:?} " , t ) ;
}
let attr_name = CString ::new ( attr_name )
. with_context ( | | " require_attrs_select_opt: attrName contains null byte " ) ? ;
2024-06-15 12:40:45 +02:00
let v2 = unsafe {
2024-07-02 16:44:04 -04:00
check_call_opt_key! ( raw ::get_attr_byname (
2024-06-15 14:55:23 +02:00
& mut self . context ,
2024-06-15 12:40:45 +02:00
v . raw_ptr ( ) ,
2024-06-15 14:55:23 +02:00
self . eval_state . as_ptr ( ) ,
2024-06-15 12:40:45 +02:00
attr_name . as_ptr ( )
2024-07-02 16:44:04 -04:00
) )
2024-06-15 12:40:45 +02:00
} ? ;
2024-12-17 10:40:03 +01:00
Ok ( v2 . map ( | x | unsafe { Value ::new ( x ) } ) )
2024-04-10 14:32:42 +02:00
}
2025-09-30 15:50:42 +02:00
/// Returns the number of elements in a [list][`ValueType::List`] Nix value.
2025-09-20 01:12:32 +02:00
///
2025-09-30 15:50:42 +02:00
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) of the list structure and verifies the value is a list.
///
/// Individual elements remain as lazy [thunks](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness) and are not evaluated.
#[ doc(alias = " length " ) ]
#[ doc(alias = " count " ) ]
#[ doc(alias = " len " ) ]
#[ doc(alias = " nix_get_list_size " ) ]
#[ doc(alias = " get_list_size " ) ]
2025-09-20 01:12:32 +02:00
pub fn require_list_size ( & mut self , v : & Value ) -> Result < u32 > {
let t = self . value_type ( v ) ? ;
if t ! = ValueType ::List {
bail! ( " expected a list, but got a {:?} " , t ) ;
}
let ret = unsafe { check_call! ( raw ::get_list_size ( & mut self . context , v . raw_ptr ( ) ) ) } ? ;
Ok ( ret )
}
2025-09-30 15:50:42 +02:00
/// Extracts an element from a [list][`ValueType::List`] Nix value by index.
///
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is a list.
2026-01-12 20:17:40 +01:00
/// Forces evaluation of the selected element, similar to [`Self::require_attrs_select`].
2025-09-20 01:12:32 +02:00
///
2026-01-12 20:17:40 +01:00
/// Returns `Ok(Some(value))` if the element is found.
2025-09-20 01:12:32 +02:00
///
2026-01-12 20:17:40 +01:00
/// Returns `Ok(None)` if the index is out of bounds.
2025-09-20 01:12:32 +02:00
///
2025-09-30 15:50:42 +02:00
/// Returns [`Err`] if evaluation failed, the element contains an error (e.g., `throw`), or the value is not a list.
#[ doc(alias = " get " ) ]
#[ doc(alias = " index " ) ]
#[ doc(alias = " at " ) ]
#[ doc(alias = " nix_get_list_byidx " ) ]
#[ doc(alias = " get_list_byidx " ) ]
2025-09-20 01:12:32 +02:00
pub fn require_list_select_idx_strict ( & mut self , v : & Value , idx : u32 ) -> Result < Option < Value > > {
2025-09-19 10:16:17 -07:00
let t = self . value_type ( v ) ? ;
if t ! = ValueType ::List {
bail! ( " expected a list, but got a {:?} " , t ) ;
}
2025-09-20 01:12:32 +02:00
// TODO: Remove this bounds checking once https://github.com/NixOS/nix/pull/14030
// is merged, which will add proper bounds checking to the underlying C API.
// Currently we perform bounds checking in Rust to avoid undefined behavior.
let size = unsafe { check_call! ( raw ::get_list_size ( & mut self . context , v . raw_ptr ( ) ) ) } ? ;
if idx > = size {
return Ok ( None ) ;
}
2025-09-19 10:16:17 -07:00
let v2 = unsafe {
check_call_opt_key! ( raw ::get_list_byidx (
& mut self . context ,
v . raw_ptr ( ) ,
self . eval_state . as_ptr ( ) ,
idx
) )
} ? ;
Ok ( v2 . map ( | x | unsafe { Value ::new ( x ) } ) )
}
2025-09-30 15:50:42 +02:00
/// Creates a new [string][`ValueType::String`] Nix value.
///
/// Returns a string value without any [string context](https://nix.dev/manual/nix/latest/language/string-context.html).
#[ doc(alias = " make_string " ) ]
#[ doc(alias = " create_string " ) ]
#[ doc(alias = " string_value " ) ]
2024-06-15 12:40:45 +02:00
pub fn new_value_str ( & mut self , s : & str ) -> Result < Value > {
2024-04-09 13:27:27 +02:00
let s = CString ::new ( s ) . with_context ( | | " new_value_str: contains null byte " ) ? ;
let v = unsafe {
2024-06-14 17:28:50 +02:00
let value = self . new_value_uninitialized ( ) ? ;
2024-07-02 16:35:32 -04:00
check_call! ( raw ::init_string (
& mut self . context ,
value . raw_ptr ( ) ,
s . as_ptr ( )
) ) ? ;
2024-04-09 13:27:27 +02:00
value
} ;
Ok ( v )
}
2025-09-30 15:50:42 +02:00
/// Creates a new [integer][`ValueType::Int`] Nix value.
#[ doc(alias = " make_int " ) ]
#[ doc(alias = " create_int " ) ]
#[ doc(alias = " int_value " ) ]
#[ doc(alias = " integer_value " ) ]
2024-06-15 12:40:45 +02:00
pub fn new_value_int ( & mut self , i : Int ) -> Result < Value > {
2024-04-09 13:29:14 +02:00
let v = unsafe {
2024-06-14 17:28:50 +02:00
let value = self . new_value_uninitialized ( ) ? ;
2024-07-02 16:35:32 -04:00
check_call! ( raw ::init_int ( & mut self . context , value . raw_ptr ( ) , i ) ) ? ;
2024-04-09 13:29:14 +02:00
value
} ;
Ok ( v )
}
2025-09-30 15:50:42 +02:00
/// Creates a new [thunk](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness) Nix value.
///
/// 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.
/// The Rust function will be called with the current [`EvalState`] and must not return a thunk.
2024-07-19 18:29:56 +02:00
///
/// The name is shown in stack traces.
2025-09-30 15:50:42 +02:00
#[ doc(alias = " make_thunk " ) ]
#[ doc(alias = " create_thunk " ) ]
#[ doc(alias = " lazy_value " ) ]
2024-07-19 18:29:56 +02:00
pub fn new_value_thunk (
& mut self ,
name : & str ,
f : Box < dyn Fn ( & mut EvalState ) -> Result < Value > > ,
) -> Result < Value > {
// Nix doesn't have a function for creating a thunk, so we have to
// create a function and pass it a dummy argument.
let name = CString ::new ( name ) . with_context ( | | " new_thunk: name contains null byte " ) ? ;
let primop = primop ::PrimOp ::new (
self ,
primop ::PrimOpMeta {
// name is observable in stack traces, ie if the thunk returns Err
name : name . as_c_str ( ) ,
// doc is unlikely to be observable, so we provide a constant one for simplicity.
doc : cstr ! ( " Performs an on demand computation, implemented outside the Nix language in native code. " ) ,
// like doc, unlikely to be observed
args : [ CString ::new ( " internal_unused " ) . unwrap ( ) . as_c_str ( ) ] ,
} ,
Box ::new ( move | eval_state , _dummy : & [ Value ; 1 ] | f ( eval_state ) ) ,
) ? ;
let p = self . new_value_primop ( primop ) ? ;
self . new_value_apply ( & p , & p )
}
2024-04-04 16:38:12 +02:00
/// Not exposed, because the caller must always explicitly handle the context or not accept one at all.
2024-06-15 12:40:45 +02:00
fn get_string ( & mut self , value : & Value ) -> Result < String > {
2024-05-22 14:45:12 +02:00
let mut r = result_string_init! ( ) ;
2024-04-23 13:23:42 +02:00
unsafe {
2024-07-02 16:35:32 -04:00
check_call! ( raw ::get_string (
2024-06-15 14:51:26 +02:00
& mut self . context ,
value . raw_ptr ( ) ,
Some ( callback_get_result_string ) ,
callback_get_result_string_data ( & mut r )
2024-07-02 16:35:32 -04:00
) ) ? ;
2024-04-23 13:23:42 +02:00
} ;
2024-05-22 14:45:12 +02:00
r
2024-04-04 16:38:12 +02:00
}
2025-09-30 15:50:42 +02:00
/// Extracts a string value from a [string][`ValueType::String`] Nix value.
///
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) and verifies the value is a string.
/// Returns the string value if successful, or an [`Err`] if evaluation failed or the value is not a string.
///
/// NOTE: this will be replaced by two methods, one that also returns the context, and one that checks that the context is empty.
#[ doc(alias = " str " ) ]
#[ doc(alias = " text " ) ]
#[ doc(alias = " nix_get_string " ) ]
#[ doc(alias = " get_string " ) ]
2024-06-15 12:40:45 +02:00
pub fn require_string ( & mut self , value : & Value ) -> Result < String > {
2024-06-14 15:40:11 +02:00
let t = self . value_type ( value ) ? ;
2024-04-04 16:38:12 +02:00
if t ! = ValueType ::String {
bail! ( " expected a string, but got a {:?} " , t ) ;
}
self . get_string ( value )
}
2025-09-30 15:50:42 +02:00
/// Realises a [string][`ValueType::String`] Nix value with context information.
///
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html), verifies the value is a string, and builds any derivations
/// referenced in the [string context](https://nix.dev/manual/nix/latest/language/string-context.html) if required.
#[ doc(alias = " realize_string " ) ]
#[ doc(alias = " string_with_context " ) ]
#[ doc(alias = " build_string " ) ]
2024-04-08 16:54:36 +02:00
pub fn realise_string (
2024-06-15 12:40:45 +02:00
& mut self ,
2024-04-08 16:54:36 +02:00
value : & Value ,
is_import_from_derivation : bool ,
) -> Result < RealisedString > {
2024-06-14 15:40:11 +02:00
let t = self . value_type ( value ) ? ;
2024-04-08 16:54:36 +02:00
if t ! = ValueType ::String {
bail! ( " expected a string, but got a {:?} " , t ) ;
}
2024-06-15 12:40:45 +02:00
let rs = unsafe {
2024-07-02 16:35:32 -04:00
check_call! ( raw ::string_realise (
2024-06-15 14:34:30 +02:00
& mut self . context ,
self . eval_state . as_ptr ( ) ,
2024-04-08 16:54:36 +02:00
value . raw_ptr ( ) ,
2024-06-15 12:40:45 +02:00
is_import_from_derivation
2024-07-02 16:35:32 -04:00
) )
2024-06-15 12:40:45 +02:00
} ? ;
2024-04-08 16:54:36 +02:00
let s = unsafe {
let start = raw ::realised_string_get_buffer_start ( rs ) as * const u8 ;
let size = raw ::realised_string_get_buffer_size ( rs ) ;
let slice = std ::slice ::from_raw_parts ( start , size ) ;
String ::from_utf8 ( slice . to_vec ( ) )
. map_err ( | e | anyhow ::format_err! ( " Nix string is not valid UTF-8: {} " , e ) ) ?
} ;
let paths = unsafe {
let n = raw ::realised_string_get_store_path_count ( rs ) ;
let mut paths = Vec ::with_capacity ( n as usize ) ;
for i in 0 .. n {
let path = raw ::realised_string_get_store_path ( rs , i ) ;
2024-12-16 14:11:17 +01:00
let path = NonNull ::new ( path as * mut raw ::StorePath ) . ok_or_else ( | | {
anyhow ::format_err! (
" nix_realised_string_get_store_path returned a null pointer "
)
} ) ? ;
2024-04-08 16:54:36 +02:00
paths . push ( StorePath ::new_raw_clone ( path ) ) ;
}
paths
} ;
2024-05-22 13:38:55 +02:00
// We've converted the nix_realised_string to a native struct containing copies, so we can free it now.
unsafe {
raw ::realised_string_free ( rs ) ;
}
2024-04-08 16:54:36 +02:00
Ok ( RealisedString { s , paths } )
}
2024-03-19 14:43:01 +01:00
2025-09-30 15:50:42 +02:00
/// Applies a function to an argument and returns the result.
2024-05-24 09:34:20 +02:00
///
2025-09-30 15:50:42 +02:00
/// Forces [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) of the function application.
2026-01-12 20:17:40 +01:00
/// For a lazy version, see [`Self::new_value_apply`].
2025-09-30 15:50:42 +02:00
#[ doc(alias = " nix_value_call " ) ]
#[ doc(alias = " value_call " ) ]
#[ doc(alias = " apply " ) ]
#[ doc(alias = " invoke " ) ]
#[ doc(alias = " execute " ) ]
2024-06-15 12:40:45 +02:00
pub fn call ( & mut self , f : Value , a : Value ) -> Result < Value > {
let value = self . new_value_uninitialized ( ) ? ;
2024-06-14 17:28:50 +02:00
unsafe {
2024-07-02 16:35:32 -04:00
check_call! ( raw ::value_call (
2024-06-15 14:34:30 +02:00
& mut self . context ,
self . eval_state . as_ptr ( ) ,
2024-06-15 12:40:45 +02:00
f . raw_ptr ( ) ,
a . raw_ptr ( ) ,
value . raw_ptr ( )
2024-07-02 16:35:32 -04:00
) )
2024-06-15 12:40:45 +02:00
} ? ;
Ok ( value )
2024-04-09 13:14:53 +02:00
}
2025-09-30 15:50:42 +02:00
/// Apply a sequence of [function applications](https://nix.dev/manual/nix/latest/language/operators.html#function-application).
///
/// When argument `f` is a curried function, this applies each argument in sequence.
/// Equivalent to the Nix expression `f arg1 arg2 arg3`.
///
/// Returns a [`Value`] in at least weak head normal form if successful.
///
/// Returns an [`Err`]
/// - if `f` did not evaluate to a function
/// - if `f arg1` had any problems
/// - if `f arg1` did not evaluate to a function (for `(f arg1) arg2`)
/// - etc
///
/// # Examples
///
/// ```rust
2025-10-04 02:44:22 +02:00
/// # use nix_bindings_expr::eval_state::{EvalState, test_init, gc_register_my_thread};
/// # use nix_bindings_store::store::Store;
2025-09-30 15:50:42 +02:00
/// # use std::collections::HashMap;
/// # fn example() -> anyhow::Result<()> {
/// # test_init();
/// # let guard = gc_register_my_thread()?;
/// let store = Store::open(None, HashMap::new())?;
/// let mut es = EvalState::new(store, [])?;
///
/// // Create a curried function: x: y: x + y
/// let f = es.eval_from_string("x: y: x + y", "<example>")?;
/// let arg1 = es.eval_from_string("5", "<example>")?;
/// let arg2 = es.eval_from_string("3", "<example>")?;
///
/// // Equivalent to: (x: y: x + y) 5 3
/// let result = es.call_multi(&f, &[arg1, arg2])?;
/// let value = es.require_int(&result)?;
/// assert_eq!(value, 8);
/// # drop(guard);
/// # Ok(())
/// # }
/// ```
2024-12-01 18:56:41 +01:00
#[ doc(alias = " nix_value_call_multi " ) ]
2025-09-30 15:50:42 +02:00
#[ doc(alias = " value_call_multi " ) ]
#[ doc(alias = " apply_multi " ) ]
#[ doc(alias = " curry " ) ]
#[ doc(alias = " call_with_args " ) ]
2024-12-01 18:56:41 +01:00
pub fn call_multi ( & mut self , f : & Value , args : & [ Value ] ) -> Result < Value > {
let value = self . new_value_uninitialized ( ) ? ;
unsafe {
2024-12-17 10:40:03 +01:00
let mut args_ptrs = args . iter ( ) . map ( | a | a . raw_ptr ( ) ) . collect ::< Vec < _ > > ( ) ;
2024-12-01 18:56:41 +01:00
check_call! ( raw ::value_call_multi (
& mut self . context ,
self . eval_state . as_ptr ( ) ,
f . raw_ptr ( ) ,
args_ptrs . len ( ) ,
args_ptrs . as_mut_ptr ( ) ,
value . raw_ptr ( )
) )
} ? ;
Ok ( value )
}
2025-09-30 15:50:42 +02:00
/// Applies a function to an argument lazily, creating a [thunk](https://nix.dev/manual/nix/latest/language/evaluation.html#laziness).
2024-05-24 09:34:20 +02:00
///
2025-09-30 15:50:42 +02:00
/// Does not force [evaluation](https://nix.dev/manual/nix/latest/language/evaluation.html) of the function application.
2026-01-12 20:17:40 +01:00
/// For an eager version, see [`Self::call`].
2025-09-30 15:50:42 +02:00
#[ doc(alias = " lazy_apply " ) ]
#[ doc(alias = " thunk_apply " ) ]
#[ doc(alias = " defer_call " ) ]
2024-06-15 12:40:45 +02:00
pub fn new_value_apply ( & mut self , f : & Value , a : & Value ) -> Result < Value > {
2024-06-14 17:28:50 +02:00
let value = self . new_value_uninitialized ( ) ? ;
2024-06-15 12:40:45 +02:00
unsafe {
2024-07-02 16:35:32 -04:00
check_call! ( raw ::init_apply (
2024-06-15 14:34:30 +02:00
& mut self . context ,
2024-06-15 12:40:45 +02:00
value . raw_ptr ( ) ,
f . raw_ptr ( ) ,
a . raw_ptr ( )
2024-07-02 16:35:32 -04:00
) )
2024-06-15 12:40:45 +02:00
} ? ;
Ok ( value )
2024-05-24 09:34:20 +02:00
}
2024-06-15 12:40:45 +02:00
fn new_value_uninitialized ( & mut self ) -> Result < Value > {
2024-12-17 10:40:03 +01:00
unsafe {
let value = check_call! ( raw ::alloc_value (
2024-07-02 16:35:32 -04:00
& mut self . context ,
self . eval_state . as_ptr ( )
2024-12-17 10:40:03 +01:00
) ) ? ;
Ok ( Value ::new ( value ) )
}
2024-03-19 14:43:01 +01:00
}
2024-06-27 18:46:54 +02:00
2025-09-30 15:50:42 +02:00
/// Creates a new [function][`ValueType::Function`] Nix value implemented by a Rust function.
///
2024-06-27 18:46:54 +02:00
/// This is also known as a "primop" in Nix, short for primitive operation.
2025-09-30 15:50:42 +02:00
/// Most of the `builtins.*` values are examples of primops, but this function
/// does not affect `builtins`.
#[ doc(alias = " make_primop " ) ]
#[ doc(alias = " create_function " ) ]
#[ doc(alias = " builtin " ) ]
2024-07-19 17:54:31 +02:00
pub fn new_value_primop ( & mut self , primop : primop ::PrimOp ) -> Result < Value > {
let value = self . new_value_uninitialized ( ) ? ;
unsafe {
check_call! ( raw ::init_primop (
& mut self . context ,
value . raw_ptr ( ) ,
primop . ptr
) ) ? ;
} ;
Ok ( value )
}
2024-12-01 21:50:07 +01:00
2026-01-12 20:17:40 +01:00
/// Creates a new [attribute set][`ValueType::AttrSet`] Nix value from an iterator of name-value pairs.
2025-09-30 15:50:42 +02:00
///
/// Accepts any iterator that yields `(String, Value)` pairs and has an exact size.
2026-01-12 20:17:40 +01:00
/// Common usage includes [`Vec`], [`std::collections::HashMap`], and array literals.
2025-09-30 15:50:42 +02:00
///
/// # Examples
///
/// ```rust
2025-10-04 02:44:22 +02:00
/// # use nix_bindings_expr::eval_state::{EvalState, test_init, gc_register_my_thread};
/// # use nix_bindings_store::store::Store;
2025-09-30 15:50:42 +02:00
/// # use std::collections::HashMap;
/// # fn example() -> anyhow::Result<()> {
/// # test_init();
/// # let guard = gc_register_my_thread()?;
/// let store = Store::open(None, HashMap::new())?;
/// let mut es = EvalState::new(store, [])?;
/// let a = es.new_value_int(1)?;
/// let b = es.new_value_int(2)?;
/// let c = es.new_value_int(3)?;
/// let d = es.new_value_int(4)?;
///
/// // From array
/// let attrs1 = es.new_value_attrs([
/// ("x".to_string(), a),
/// ("y".to_string(), b)
/// ])?;
///
/// // From HashMap
/// let mut map = HashMap::new();
/// map.insert("foo".to_string(), c);
/// map.insert("bar".to_string(), d);
/// let attrs2 = es.new_value_attrs(map)?;
/// # drop(guard);
/// # Ok(())
/// # }
/// ```
#[ doc(alias = " make_attrs " ) ]
#[ doc(alias = " create_attrset " ) ]
#[ doc(alias = " object " ) ]
#[ doc(alias = " record " ) ]
2024-12-01 21:50:07 +01:00
pub fn new_value_attrs < I > ( & mut self , attrs : I ) -> Result < Value >
where
I : IntoIterator < Item = ( String , Value ) > ,
I ::IntoIter : ExactSizeIterator ,
{
let iter = attrs . into_iter ( ) ;
let size = iter . len ( ) ;
let bindings_builder = BindingsBuilder ::new ( self , size ) ? ;
for ( name , value ) in iter {
let name =
CString ::new ( name ) . with_context ( | | " new_value_attrs: name contains null byte " ) ? ;
unsafe {
check_call! ( raw ::bindings_builder_insert (
& mut self . context ,
bindings_builder . ptr ,
name . as_ptr ( ) ,
value . raw_ptr ( )
) ) ? ;
}
}
let value = self . new_value_uninitialized ( ) ? ;
unsafe {
check_call! ( raw ::make_attrs (
& mut self . context ,
value . raw_ptr ( ) ,
bindings_builder . ptr
) ) ? ;
}
Ok ( value )
}
}
2025-09-30 15:50:42 +02:00
// Internal RAII helper; could be refactored and made pub
2024-12-01 21:50:07 +01:00
struct BindingsBuilder {
ptr : * mut raw ::BindingsBuilder ,
}
impl Drop for BindingsBuilder {
fn drop ( & mut self ) {
unsafe {
raw ::bindings_builder_free ( self . ptr ) ;
}
}
}
impl BindingsBuilder {
fn new ( eval_state : & mut EvalState , capacity : usize ) -> Result < Self > {
let ptr = unsafe {
check_call! ( raw ::make_bindings_builder (
& mut eval_state . context ,
eval_state . eval_state . as_ptr ( ) ,
capacity
) )
} ? ;
Ok ( BindingsBuilder { ptr } )
}
2024-03-19 14:43:01 +01:00
}
2025-09-30 15:50:42 +02:00
/// Triggers garbage collection immediately.
#[ doc(alias = " garbage_collect " ) ]
#[ doc(alias = " collect " ) ]
#[ doc(alias = " gc " ) ]
2024-03-19 14:43:01 +01:00
pub fn gc_now ( ) {
unsafe {
2024-05-09 12:29:32 -04:00
raw ::gc_now ( ) ;
2024-03-19 14:43:01 +01:00
}
}
2025-09-30 15:50:42 +02:00
/// RAII guard for thread registration with the garbage collector.
///
/// Automatically unregisters the thread when dropped.
2024-10-09 00:53:11 +02:00
pub struct ThreadRegistrationGuard {
must_unregister : bool ,
}
impl Drop for ThreadRegistrationGuard {
fn drop ( & mut self ) {
if self . must_unregister {
unsafe {
raw ::GC_unregister_my_thread ( ) ;
}
}
}
}
fn gc_register_my_thread_do_it ( ) -> Result < ( ) > {
2024-03-19 14:43:01 +01:00
unsafe {
let mut sb : raw ::GC_stack_base = raw ::GC_stack_base {
2024-05-09 12:41:54 -04:00
mem_base : null_mut ( ) ,
2024-03-19 14:43:01 +01:00
} ;
let r = raw ::GC_get_stack_base ( & mut sb ) ;
if r as u32 ! = raw ::GC_SUCCESS {
Err ( anyhow ::format_err! ( " GC_get_stack_base failed: {} " , r ) ) ? ;
}
raw ::GC_register_my_thread ( & sb ) ;
Ok ( ( ) )
}
}
2025-09-30 15:50:42 +02:00
#[ doc(alias = " register_thread " ) ]
#[ doc(alias = " thread_setup " ) ]
#[ doc(alias = " gc_register " ) ]
2024-10-09 00:53:11 +02:00
pub fn gc_register_my_thread ( ) -> Result < ThreadRegistrationGuard > {
init ( ) ? ;
unsafe {
let already_done = raw ::GC_thread_is_registered ( ) ;
if already_done ! = 0 {
return Ok ( ThreadRegistrationGuard {
must_unregister : false ,
} ) ;
}
gc_register_my_thread_do_it ( ) ? ;
Ok ( ThreadRegistrationGuard {
must_unregister : true ,
} )
}
}
2024-06-26 21:00:54 +02:00
impl Clone for EvalState {
fn clone ( & self ) -> Self {
EvalState {
eval_state : self . eval_state . clone ( ) ,
store : self . store . clone ( ) ,
context : Context ::new ( ) ,
2024-03-19 14:43:01 +01:00
}
}
}
2024-04-10 14:34:51 +02:00
/// Initialize the Nix library for testing. This includes some modifications to the Nix settings, that must not be used in production.
/// Use at your own peril, in rust test suites.
2025-09-30 15:50:42 +02:00
#[ doc(alias = " test_initialize " ) ]
#[ doc(alias = " test_setup " ) ]
2024-04-10 14:34:51 +02:00
pub fn test_init ( ) {
init ( ) . unwrap ( ) ;
// During development, we encountered a problem where the build hook
// would cause the test suite to reinvokes itself, causing an infinite loop.
// While _NIX_TEST_NO_SANDBOX=1 should prevent this, we may also set the
// build hook to "" to prevent this.
2025-10-04 02:44:22 +02:00
nix_bindings_util ::settings ::set ( " build-hook " , " " ) . unwrap ( ) ;
2024-04-10 14:34:51 +02:00
// When testing in the sandbox, the default build dir would be a parent of the storeDir,
// which causes an error. So we set a custom build dir here.
2025-01-02 07:53:55 +01:00
// Only available on linux
if cfg! ( target_os = " linux " ) {
2025-12-05 16:51:15 -05:00
nix_bindings_util ::settings ::set ( " sandbox-build-dir " , " /custom-build-dir-for-test " )
. unwrap ( ) ;
2025-01-02 07:53:55 +01:00
}
2024-04-10 14:34:51 +02:00
std ::env ::set_var ( " _NIX_TEST_NO_SANDBOX " , " 1 " ) ;
2024-11-26 14:52:49 +01:00
// The tests run offline
2025-10-04 02:44:22 +02:00
nix_bindings_util ::settings ::set ( " substituters " , " " ) . unwrap ( ) ;
2024-04-10 14:34:51 +02:00
}
2024-03-19 14:43:01 +01:00
#[ cfg(test) ]
mod tests {
2024-05-17 01:56:16 +02:00
use super ::* ;
2024-07-19 17:54:31 +02:00
use cstr ::cstr ;
2024-03-19 14:43:01 +01:00
use ctor ::ctor ;
2024-05-17 01:56:16 +02:00
use std ::collections ::HashMap ;
use std ::fs ::read_dir ;
2024-05-13 19:29:48 +02:00
use std ::io ::Write as _ ;
2024-06-27 18:46:54 +02:00
use std ::sync ::{ Arc , Mutex } ;
2024-03-19 14:43:01 +01:00
#[ ctor ]
fn setup ( ) {
2024-04-10 14:34:51 +02:00
test_init ( ) ;
2026-01-07 09:38:28 +01:00
// Configure Nix settings for the test suite
// Set max-call-depth to 1000 (lower than default 10000) for the
// eval_state_builder_loads_max_call_depth test case, while
// giving other tests sufficient room for normal evaluation.
std ::env ::set_var ( " NIX_CONFIG " , " max-call-depth = 1000 " ) ;
2024-03-19 14:43:01 +01:00
}
2024-10-16 12:14:37 +02:00
/// Run a function while making sure that the current thread is registered with the GC.
pub fn gc_registering_current_thread < F , R > ( f : F ) -> Result < R >
where
F : FnOnce ( ) -> R ,
{
let guard = gc_register_my_thread ( ) ? ;
let r = f ( ) ;
drop ( guard ) ;
Ok ( r )
}
2024-03-19 14:43:01 +01:00
#[ test ]
fn eval_state_new_and_drop ( ) {
gc_registering_current_thread ( | | {
// very basic test: make sure initialization doesn't crash
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-05-13 19:26:58 +02:00
let _e = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-03-19 14:43:01 +01:00
} )
. unwrap ( ) ;
}
2024-06-27 16:01:40 +02:00
#[ test ]
fn weak_ref ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-27 16:01:40 +02:00
let es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let weak = es . weak_ref ( ) ;
let _es = weak . upgrade ( ) . unwrap ( ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn weak_ref_gone ( ) {
gc_registering_current_thread ( | | {
let weak = {
2024-11-27 14:13:54 +01:00
// Use a slightly different URL which is unique in the test suite, to bypass the global store cache
2024-12-13 00:54:35 +01:00
let store = Store ::open ( Some ( " auto?foo=bar " ) , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-27 16:01:40 +02:00
let es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
es . weak_ref ( )
} ;
assert! ( weak . upgrade ( ) . is_none ( ) ) ;
assert! ( weak . store . upgrade ( ) . is_none ( ) ) ;
assert! ( weak . inner . upgrade ( ) . is_none ( ) ) ;
} )
. unwrap ( ) ;
}
2024-05-13 19:29:48 +02:00
#[ test ]
fn eval_state_lookup_path ( ) {
let import_expression = " import <test_file0> + import <test_file1> " ;
let integer0 = 83 ;
let integer1 = 103 ;
let mut test_file0 = tempfile ::NamedTempFile ::new ( ) . unwrap ( ) ;
let mut test_file1 = tempfile ::NamedTempFile ::new ( ) . unwrap ( ) ;
writeln! ( test_file0 , " {integer0} " ) . unwrap ( ) ;
writeln! ( test_file1 , " {integer1} " ) . unwrap ( ) ;
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let mut es = EvalState ::new ( Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) , [ ] ) . unwrap ( ) ;
2024-05-13 19:29:48 +02:00
assert! ( es . eval_from_string ( import_expression , " <test> " ) . is_err ( ) ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new (
2024-12-13 00:54:35 +01:00
Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ,
2024-05-13 19:29:48 +02:00
[
format! ( " test_file0= {} " , test_file0 . path ( ) . to_str ( ) . unwrap ( ) ) . as_str ( ) ,
format! ( " test_file1= {} " , test_file1 . path ( ) . to_str ( ) . unwrap ( ) ) . as_str ( ) ,
] ,
)
. unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let ie = & es . eval_from_string ( import_expression , " <test> " ) . unwrap ( ) ;
let v = es . require_int ( ie ) . unwrap ( ) ;
2024-05-13 19:29:48 +02:00
assert_eq! ( v , integer0 + integer1 ) ;
} )
. unwrap ( ) ;
test_file0 . close ( ) . unwrap ( ) ;
test_file1 . close ( ) . unwrap ( ) ;
}
2024-03-19 14:43:01 +01:00
#[ test ]
fn eval_state_eval_from_string ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-08 16:23:59 +02:00
let v = es . eval_from_string ( " 1 " , " <test> " ) . unwrap ( ) ;
2024-03-19 14:43:01 +01:00
let v2 = v . clone ( ) ;
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::Int ) ) ;
2024-06-03 04:20:17 +02:00
let t2 = es . value_type_unforced ( & v2 ) ;
2024-06-14 18:42:23 +02:00
assert! ( t2 = = Some ( ValueType ::Int ) ) ;
2024-03-19 14:43:01 +01:00
gc_now ( ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_value_bool ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-08 16:23:59 +02:00
let v = es . eval_from_string ( " true " , " <test> " ) . unwrap ( ) ;
2024-03-19 14:43:01 +01:00
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::Bool ) ) ;
2025-04-02 13:30:57 +02:00
let b = es . require_bool ( & v ) . unwrap ( ) ;
assert! ( b ) ;
let v = es . eval_from_string ( " false " , " <test> " ) . unwrap ( ) ;
es . require_bool ( & v ) . unwrap ( ) ;
let b = es . require_bool ( & v ) . unwrap ( ) ;
assert! ( ! b ) ;
2024-03-19 14:43:01 +01:00
} )
. unwrap ( ) ;
}
2024-04-09 13:13:52 +02:00
#[ test ]
fn eval_state_value_int ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 13:13:52 +02:00
let v = es . eval_from_string ( " 1 " , " <test> " ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
let t = es . value_type ( & v ) . unwrap ( ) ;
assert! ( t = = ValueType ::Int ) ;
let i = es . require_int ( & v ) . unwrap ( ) ;
assert! ( i = = 1 ) ;
} )
. unwrap ( ) ;
}
2024-06-14 19:17:35 +02:00
#[ test ]
fn eval_state_require_int_forces_thunk ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-06-14 19:17:35 +02:00
let f = es . eval_from_string ( " x: x + 1 " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " 2 " , " <test> " ) . unwrap ( ) ;
let v = es . new_value_apply ( & f , & a ) . unwrap ( ) ;
let t = es . value_type_unforced ( & v ) ;
2025-12-15 19:48:30 -05:00
assert! ( t . is_none ( ) ) ;
2024-06-14 19:17:35 +02:00
let i = es . require_int ( & v ) . unwrap ( ) ;
assert! ( i = = 3 ) ;
} )
. unwrap ( ) ;
}
2025-04-02 13:30:57 +02:00
#[ test ]
fn eval_state_require_bool_forces_thunk ( ) {
gc_registering_current_thread ( | | {
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let f = es . eval_from_string ( " x: !x " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " true " , " <test> " ) . unwrap ( ) ;
let v = es . new_value_apply ( & f , & a ) . unwrap ( ) ;
let t = es . value_type_unforced ( & v ) ;
2025-12-15 19:48:30 -05:00
assert! ( t . is_none ( ) ) ;
2025-04-02 13:30:57 +02:00
let i = es . require_bool ( & v ) . unwrap ( ) ;
2025-12-15 19:48:30 -05:00
assert! ( ! i ) ;
2025-04-02 13:30:57 +02:00
} )
. unwrap ( ) ;
}
2024-06-14 19:29:33 +02:00
/// A helper that turns an expression into a thunk.
2024-06-15 12:40:45 +02:00
fn make_thunk ( es : & mut EvalState , expr : & str ) -> Value {
2024-06-14 19:29:33 +02:00
// This would be silly in real code, but it works for the current Nix implementation.
// A Nix implementation that applies the identity function eagerly would be a valid
// Nix implementation, but annoying because we'll have to change this helper to do
// something more complicated that isn't optimized away.
let f = es . eval_from_string ( " x: x " , " <test> " ) . unwrap ( ) ;
let v = es . eval_from_string ( expr , " <test> " ) . unwrap ( ) ;
es . new_value_apply ( & f , & v ) . unwrap ( )
}
#[ test ]
fn make_thunk_helper_works ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let v = make_thunk ( & mut es , " 1 " ) ;
2024-06-14 19:29:33 +02:00
let t = es . value_type_unforced ( & v ) ;
2025-12-15 19:48:30 -05:00
assert! ( t . is_none ( ) ) ;
2024-06-14 19:29:33 +02:00
} )
. unwrap ( ) ;
}
2024-04-09 16:20:18 +02:00
#[ test ]
fn eval_state_value_attrs_names_empty ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 16:20:18 +02:00
let v = es . eval_from_string ( " { } " , " <test> " ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::AttrSet ) ) ;
2024-12-02 13:52:17 +01:00
let attrs = es . require_attrs_names_unsorted ( & v ) . unwrap ( ) ;
2024-04-09 16:20:18 +02:00
assert_eq! ( attrs . len ( ) , 0 ) ;
} )
. unwrap ( )
}
2024-06-14 19:29:33 +02:00
#[ test ]
2024-12-02 13:52:17 +01:00
fn eval_state_require_attrs_names_unsorted_forces_thunk ( ) {
2024-06-14 19:29:33 +02:00
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let v = make_thunk ( & mut es , " { a = 1; b = 2; } " ) ;
2024-06-14 19:29:33 +02:00
let t = es . value_type_unforced ( & v ) ;
2025-12-15 19:48:30 -05:00
assert! ( t . is_none ( ) ) ;
2024-12-02 13:52:17 +01:00
let attrs = es . require_attrs_names_unsorted ( & v ) . unwrap ( ) ;
2024-06-14 19:29:33 +02:00
assert_eq! ( attrs . len ( ) , 2 ) ;
} )
. unwrap ( )
}
2024-04-09 16:20:18 +02:00
#[ test ]
2024-12-02 13:52:17 +01:00
fn eval_state_require_attrs_names_unsorted_bad_type ( ) {
2024-04-09 16:20:18 +02:00
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 16:20:18 +02:00
let v = es . eval_from_string ( " 1 " , " <test> " ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
2024-12-02 13:52:17 +01:00
let r = es . require_attrs_names_unsorted ( & v ) ;
2024-04-09 16:20:18 +02:00
assert! ( r . is_err ( ) ) ;
assert_eq! (
r . unwrap_err ( ) . to_string ( ) ,
" expected an attrset, but got a Int "
) ;
} )
. unwrap ( )
}
#[ test ]
fn eval_state_value_attrs_names_example ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 16:20:18 +02:00
let expr = r # "{ a = throw "nope a"; b = throw "nope b"; }"# ;
let v = es . eval_from_string ( expr , " <test> " ) . unwrap ( ) ;
2024-12-02 14:08:03 +01:00
let attrs = es . require_attrs_names ( & v ) . unwrap ( ) ;
2024-04-09 16:20:18 +02:00
assert_eq! ( attrs . len ( ) , 2 ) ;
assert_eq! ( attrs [ 0 ] , " a " ) ;
assert_eq! ( attrs [ 1 ] , " b " ) ;
} )
. unwrap ( ) ;
}
2024-04-10 14:32:42 +02:00
#[ test ]
fn eval_state_require_attrs_select ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-10 14:32:42 +02:00
let expr = r # "{ a = "aye"; b = "bee"; }"# ;
let v = es . eval_from_string ( expr , " <test> " ) . unwrap ( ) ;
let a = es . require_attrs_select ( & v , " a " ) . unwrap ( ) ;
let b = es . require_attrs_select ( & v , " b " ) . unwrap ( ) ;
assert_eq! ( es . require_string ( & a ) . unwrap ( ) , " aye " ) ;
assert_eq! ( es . require_string ( & b ) . unwrap ( ) , " bee " ) ;
2024-06-14 18:21:13 +02:00
let missing = es . require_attrs_select ( & v , " c " ) ;
match missing {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
let s = format! ( " {e:#} " ) ;
2025-01-31 07:38:11 +01:00
if ! s . contains ( " attribute `c` not found " ) {
2024-06-14 18:21:13 +02:00
eprintln! ( " unexpected error message: {} " , s ) ;
2025-12-15 19:48:30 -05:00
panic! ( ) ;
2024-06-14 18:21:13 +02:00
}
}
}
2024-04-10 14:32:42 +02:00
} )
. unwrap ( )
}
2024-06-14 19:29:33 +02:00
#[ test ]
fn eval_state_require_attrs_select_forces_thunk ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-06-14 19:29:33 +02:00
let expr = r # "{ a = "aye"; b = "bee"; }"# ;
2024-06-15 12:40:45 +02:00
let v = make_thunk ( & mut es , expr ) ;
2024-06-14 19:29:33 +02:00
assert! ( es . value_type_unforced ( & v ) . is_none ( ) ) ;
let r = es . require_attrs_select ( & v , " a " ) ;
assert! ( r . is_ok ( ) ) ;
} )
. unwrap ( )
}
2024-04-10 14:32:42 +02:00
#[ test ]
fn eval_state_require_attrs_select_error ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-10 14:32:42 +02:00
let expr = r # "{ a = throw "oh no the error"; }"# ;
let v = es . eval_from_string ( expr , " <test> " ) . unwrap ( ) ;
let r = es . require_attrs_select ( & v , " a " ) ;
match r {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " oh no the error " ) {
eprintln! ( " unexpected error message: {} " , e ) ;
2025-12-15 19:48:30 -05:00
panic! ( ) ;
2024-04-10 14:32:42 +02:00
}
}
}
} )
. unwrap ( )
}
#[ test ]
fn eval_state_require_attrs_select_opt ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-10 14:32:42 +02:00
let expr = r # "{ a = "aye"; b = "bee"; }"# ;
let v = es . eval_from_string ( expr , " <test> " ) . unwrap ( ) ;
let a = es . require_attrs_select_opt ( & v , " a " ) . unwrap ( ) . unwrap ( ) ;
let b = es . require_attrs_select_opt ( & v , " b " ) . unwrap ( ) . unwrap ( ) ;
assert_eq! ( es . require_string ( & a ) . unwrap ( ) , " aye " ) ;
assert_eq! ( es . require_string ( & b ) . unwrap ( ) , " bee " ) ;
let c = es . require_attrs_select_opt ( & v , " c " ) . unwrap ( ) ;
assert! ( c . is_none ( ) ) ;
} )
. unwrap ( )
}
2024-06-14 19:29:33 +02:00
#[ test ]
fn eval_state_require_attrs_select_opt_forces_thunk ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-06-14 19:29:33 +02:00
let expr = r # "{ a = "aye"; b = "bee"; }"# ;
2024-06-15 12:40:45 +02:00
let v = make_thunk ( & mut es , expr ) ;
2024-06-14 19:29:33 +02:00
assert! ( es . value_type_unforced ( & v ) . is_none ( ) ) ;
let r = es . require_attrs_select_opt ( & v , " a " ) ;
assert! ( r . is_ok ( ) ) ;
} )
. unwrap ( )
}
2024-04-10 14:32:42 +02:00
#[ test ]
fn eval_state_require_attrs_select_opt_error ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-10 14:32:42 +02:00
let expr = r # "{ a = throw "oh no the error"; }"# ;
let v = es . eval_from_string ( expr , " <test> " ) . unwrap ( ) ;
let r = es . require_attrs_select_opt ( & v , " a " ) ;
match r {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " oh no the error " ) {
eprintln! ( " unexpected error message: {} " , e ) ;
2025-12-15 19:48:30 -05:00
panic! ( ) ;
2024-04-10 14:32:42 +02:00
}
}
}
} )
. unwrap ( )
}
2024-03-19 14:43:01 +01:00
#[ test ]
fn eval_state_value_string ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-08 16:23:59 +02:00
let v = es . eval_from_string ( " \" hello \" " , " <test> " ) . unwrap ( ) ;
2024-03-19 14:43:01 +01:00
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::String ) ) ;
2024-04-04 16:38:12 +02:00
let s = es . require_string ( & v ) . unwrap ( ) ;
assert! ( s = = " hello " ) ;
} )
. unwrap ( ) ;
}
2024-06-14 19:29:33 +02:00
#[ test ]
fn eval_state_value_string_forces_thunk ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let v = make_thunk ( & mut es , " \" hello \" " ) ;
2024-06-14 19:29:33 +02:00
assert! ( es . value_type_unforced ( & v ) . is_none ( ) ) ;
let s = es . require_string ( & v ) . unwrap ( ) ;
assert! ( s = = " hello " ) ;
} )
. unwrap ( ) ;
}
2024-04-04 16:38:12 +02:00
#[ test ]
fn eval_state_value_string_unexpected_bool ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-08 16:23:59 +02:00
let v = es . eval_from_string ( " true " , " <test> " ) . unwrap ( ) ;
2024-04-04 16:38:12 +02:00
es . force ( & v ) . unwrap ( ) ;
let r = es . require_string ( & v ) ;
assert! ( r . is_err ( ) ) ;
// TODO: safe print value (like Nix would)
assert_eq! (
r . unwrap_err ( ) . to_string ( ) ,
" expected a string, but got a Bool "
) ;
} )
. unwrap ( )
}
#[ test ]
fn eval_state_value_string_unexpected_path_value ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-08 16:23:59 +02:00
let v = es . eval_from_string ( " /foo " , " <test> " ) . unwrap ( ) ;
2024-04-04 16:38:12 +02:00
es . force ( & v ) . unwrap ( ) ;
let r = es . require_string ( & v ) ;
assert! ( r . is_err ( ) ) ;
assert_eq! (
r . unwrap_err ( ) . to_string ( ) ,
" expected a string, but got a Path "
) ;
} )
. unwrap ( )
}
#[ test ]
fn eval_state_value_string_bad_utf ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-04 16:38:12 +02:00
let v = es
2024-04-08 16:23:59 +02:00
. eval_from_string ( " builtins.substring 0 1 \" ü \" " , " <test> " )
2024-04-04 16:38:12 +02:00
. unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::String ) ) ;
2024-04-04 16:38:12 +02:00
let r = es . require_string ( & v ) ;
assert! ( r . is_err ( ) ) ;
assert! ( r
. unwrap_err ( )
. to_string ( )
. contains ( " Nix string is not valid UTF-8 " ) ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_value_string_unexpected_context ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-04 16:38:12 +02:00
let v = es
2024-04-08 16:23:59 +02:00
. eval_from_string ( " (derivation { name = \" hello \" ; system = \" dummy \" ; builder = \" cmd.exe \" ; }).outPath " , " <test> " )
2024-04-04 16:38:12 +02:00
. unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::String ) ) ;
2024-04-04 16:38:12 +02:00
// TODO
// let r = es.require_string_without_context(&v);
// assert!(r.is_err());
// assert!(r.unwrap_err().to_string().contains("unexpected context"));
2024-03-19 14:43:01 +01:00
} )
. unwrap ( ) ;
}
2024-04-09 13:27:27 +02:00
#[ test ]
fn eval_state_new_string ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 13:27:27 +02:00
let v = es . new_value_str ( " hello " ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::String ) ) ;
2024-04-09 13:27:27 +02:00
let s = es . require_string ( & v ) . unwrap ( ) ;
assert! ( s = = " hello " ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_new_string_empty ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 13:27:27 +02:00
let v = es . new_value_str ( " " ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::String ) ) ;
2024-04-09 13:27:27 +02:00
let s = es . require_string ( & v ) . unwrap ( ) ;
2025-12-15 19:48:30 -05:00
assert! ( s . is_empty ( ) ) ;
2024-04-09 13:27:27 +02:00
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_new_string_invalid ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 13:27:27 +02:00
let r = es . new_value_str ( " hell \0 no " ) ;
match r {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " contains null byte " ) {
eprintln! ( " {} " , e ) ;
2025-12-15 19:48:30 -05:00
panic! ( ) ;
2024-04-09 13:27:27 +02:00
}
}
}
} )
. unwrap ( ) ;
}
2024-04-09 13:29:14 +02:00
#[ test ]
fn eval_state_new_int ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 13:29:14 +02:00
let v = es . new_value_int ( 42 ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::Int ) ) ;
2024-04-09 13:29:14 +02:00
let i = es . require_int ( & v ) . unwrap ( ) ;
assert! ( i = = 42 ) ;
} )
. unwrap ( ) ;
}
2024-03-19 14:43:01 +01:00
#[ test ]
fn eval_state_value_attrset ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-08 16:23:59 +02:00
let v = es . eval_from_string ( " { } " , " <test> " ) . unwrap ( ) ;
2024-03-19 14:43:01 +01:00
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::AttrSet ) ) ;
2024-03-19 14:43:01 +01:00
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_value_list ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2025-12-05 16:51:15 -05:00
let v = es . eval_from_string ( " [ ] " , " <test> " ) . unwrap ( ) ;
2024-03-19 14:43:01 +01:00
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::List ) ) ;
2024-03-19 14:43:01 +01:00
} )
. unwrap ( ) ;
}
2024-04-08 16:54:36 +02:00
2025-09-20 01:28:48 +02:00
#[ test ]
fn eval_state_value_list_strict_empty ( ) {
gc_registering_current_thread ( | | {
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let v = es . eval_from_string ( " [] " , " <test> " ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
let list : Vec < Value > = es . require_list_strict ( & v ) . unwrap ( ) ;
assert_eq! ( list . len ( ) , 0 ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_value_list_strict_int ( ) {
gc_registering_current_thread ( | | {
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let v = es . eval_from_string ( " [42] " , " <test> " ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
let list : Vec < Value > = es . require_list_strict ( & v ) . unwrap ( ) ;
assert_eq! ( list . len ( ) , 1 ) ;
assert_eq! ( es . require_int ( & list [ 0 ] ) . unwrap ( ) , 42 ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_value_list_strict_int_bool ( ) {
gc_registering_current_thread ( | | {
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let v = es . eval_from_string ( " [42 true] " , " <test> " ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
let list : Vec < Value > = es . require_list_strict ( & v ) . unwrap ( ) ;
assert_eq! ( list . len ( ) , 2 ) ;
assert_eq! ( es . require_int ( & list [ 0 ] ) . unwrap ( ) , 42 ) ;
2025-12-15 19:48:30 -05:00
assert! ( es . require_bool ( & list [ 1 ] ) . unwrap ( ) ) ;
2025-09-20 01:28:48 +02:00
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_value_list_strict_error ( ) {
gc_registering_current_thread ( | | {
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let v = es . eval_from_string ( r # "[(throw "_evaluated_item_")]"# , " <test> " ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
// This should fail because require_list_strict evaluates all elements
let result : Result < Vec < Value > , _ > = es . require_list_strict ( & v ) ;
assert! ( result . is_err ( ) ) ;
match result {
Err ( error_msg ) = > {
let error_str = error_msg . to_string ( ) ;
assert! ( error_str . contains ( " _evaluated_item_ " ) ) ;
}
Ok ( _ ) = > panic! ( " unexpected success. The item should have been evaluated and its error propagated. " )
}
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_value_list_strict_generic_container ( ) {
gc_registering_current_thread ( | | {
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let v = es . eval_from_string ( " [1 2 3] " , " <test> " ) . unwrap ( ) ;
// Test with Vec
let vec : Vec < Value > = es . require_list_strict ( & v ) . unwrap ( ) ;
assert_eq! ( vec . len ( ) , 3 ) ;
// Test with VecDeque
let deque : std ::collections ::VecDeque < Value > = es . require_list_strict ( & v ) . unwrap ( ) ;
assert_eq! ( deque . len ( ) , 3 ) ;
// Verify contents are the same
assert_eq! ( es . require_int ( & vec [ 0 ] ) . unwrap ( ) , 1 ) ;
assert_eq! ( es . require_int ( & deque [ 0 ] ) . unwrap ( ) , 1 ) ;
} )
. unwrap ( ) ;
}
2024-04-08 16:54:36 +02:00
#[ test ]
fn eval_state_realise_string ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-08 16:54:36 +02:00
let expr = r #"
' '
a derivation output : $ {
derivation { name = " letsbuild " ;
system = builtins . currentSystem ;
builder = " /bin/sh " ;
args = [ " -c " " echo foo > $out " ] ;
} }
a path : $ { builtins . toFile " just-a-file " " ooh file good " }
a derivation path by itself : $ {
builtins . unsafeDiscardOutputDependency
( derivation {
name = " not-actually-built-yet " ;
system = builtins . currentSystem ;
builder = " /bin/sh " ;
args = [ " -c " " echo foo > $out " ] ;
} ) . drvPath }
' '
" #;
let v = es . eval_from_string ( expr , " <test> " ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
let rs = es . realise_string ( & v , false ) . unwrap ( ) ;
assert! ( rs . s . starts_with ( " a derivation output: " ) ) ;
assert! ( rs . s . contains ( " -letsbuild \n " ) ) ;
assert! ( ! rs . s . contains ( " -letsbuild.drv " ) ) ;
assert! ( rs . s . contains ( " a path: " ) ) ;
assert! ( rs . s . contains ( " -just-a-file " ) ) ;
assert! ( ! rs . s . contains ( " -just-a-file.drv " ) ) ;
assert! ( ! rs . s . contains ( " ooh file good " ) ) ;
assert! ( rs . s . ends_with ( " -not-actually-built-yet.drv \n " ) ) ;
assert_eq! ( rs . paths . len ( ) , 3 ) ;
let mut names : Vec < String > = rs . paths . iter ( ) . map ( | p | p . name ( ) . unwrap ( ) ) . collect ( ) ;
names . sort ( ) ;
assert_eq! ( names [ 0 ] , " just-a-file " ) ;
assert_eq! ( names [ 1 ] , " letsbuild " ) ;
assert_eq! ( names [ 2 ] , " not-actually-built-yet.drv " ) ;
} )
. unwrap ( ) ;
}
2024-04-09 13:14:53 +02:00
#[ test ]
fn eval_state_call ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 13:14:53 +02:00
let f = es . eval_from_string ( " x: x + 1 " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " 2 " , " <test> " ) . unwrap ( ) ;
let v = es . call ( f , a ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::Int ) ) ;
2024-04-09 13:14:53 +02:00
let i = es . require_int ( & v ) . unwrap ( ) ;
assert! ( i = = 3 ) ;
} )
. unwrap ( ) ;
}
2024-12-01 18:56:41 +01:00
#[ test ]
fn eval_state_call_multi ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-12-01 18:56:41 +01:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
// This is a function that takes two arguments.
let f = es . eval_from_string ( " x: y: x - y " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " 2 " , " <test> " ) . unwrap ( ) ;
let b = es . eval_from_string ( " 3 " , " <test> " ) . unwrap ( ) ;
let v = es . call_multi ( & f , & [ a , b ] ) . unwrap ( ) ;
let t = es . value_type_unforced ( & v ) ;
assert! ( t = = Some ( ValueType ::Int ) ) ;
let i = es . require_int ( & v ) . unwrap ( ) ;
assert! ( i = = - 1 ) ;
} )
. unwrap ( ) ;
}
2024-05-24 09:34:20 +02:00
#[ test ]
fn eval_state_apply ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-05-24 09:34:20 +02:00
// This is a function that takes two arguments.
let f = es . eval_from_string ( " x: x + 1 " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " 2 " , " <test> " ) . unwrap ( ) ;
let v = es . new_value_apply ( & f , & a ) . unwrap ( ) ;
2025-12-15 19:48:30 -05:00
assert! ( es . value_type_unforced ( & v ) . is_none ( ) ) ;
2024-05-24 09:34:20 +02:00
es . force ( & v ) . unwrap ( ) ;
2024-06-03 04:20:17 +02:00
let t = es . value_type_unforced ( & v ) ;
2024-06-14 18:42:23 +02:00
assert! ( t = = Some ( ValueType ::Int ) ) ;
2024-05-24 09:34:20 +02:00
let i = es . require_int ( & v ) . unwrap ( ) ;
assert! ( i = = 3 ) ;
} )
. unwrap ( ) ;
}
2024-04-09 13:14:53 +02:00
#[ test ]
fn eval_state_call_fail_body ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 13:14:53 +02:00
let f = es . eval_from_string ( " x: x + 1 " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " true " , " <test> " ) . unwrap ( ) ;
let r = es . call ( f , a ) ;
match r {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " cannot coerce " ) {
eprintln! ( " {} " , e ) ;
2025-12-15 19:48:30 -05:00
panic! ( ) ;
2024-04-09 13:14:53 +02:00
}
}
}
} )
. unwrap ( ) ;
}
2024-12-01 18:56:41 +01:00
#[ test ]
fn eval_state_call_multi_fail_body ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-12-01 18:56:41 +01:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
// This is a function that takes two arguments.
let f = es . eval_from_string ( " x: y: x - y " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " 2 " , " <test> " ) . unwrap ( ) ;
let b = es . eval_from_string ( " true " , " <test> " ) . unwrap ( ) ;
let r = es . call_multi ( & f , & [ a , b ] ) ;
match r {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " expected an integer but found " ) {
eprintln! ( " {} " , e ) ;
2025-12-15 19:48:30 -05:00
panic! ( ) ;
2024-12-01 18:56:41 +01:00
}
}
}
} )
. unwrap ( ) ;
}
2024-05-24 09:34:20 +02:00
#[ test ]
fn eval_state_apply_fail_body ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-05-24 09:34:20 +02:00
let f = es . eval_from_string ( " x: x + 1 " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " true " , " <test> " ) . unwrap ( ) ;
// Lazy => no error
let r = es . new_value_apply ( & f , & a ) . unwrap ( ) ;
// Force it => error
let res = es . force ( & r ) ;
match res {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " cannot coerce " ) {
eprintln! ( " {} " , e ) ;
2025-12-15 19:48:30 -05:00
panic! ( ) ;
2024-05-24 09:34:20 +02:00
}
}
}
} )
. unwrap ( ) ;
}
2024-06-14 19:32:56 +02:00
/// This tests the behavior of `call`, which is strict, unlike `new_value_apply`.
2024-04-09 13:14:53 +02:00
#[ test ]
fn eval_state_call_fail_args ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-04-09 13:14:53 +02:00
let f = es . eval_from_string ( " {x}: x + 1 " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " {} " , " <test> " ) . unwrap ( ) ;
let r = es . call ( f , a ) ;
match r {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " called without required argument " ) {
eprintln! ( " {} " , e ) ;
2025-12-15 19:48:30 -05:00
panic! ( ) ;
2024-04-09 13:14:53 +02:00
}
}
2024-12-01 18:56:41 +01:00
}
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_call_multi_fail_args ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-12-01 18:56:41 +01:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
// This is a function that takes two arguments.
let f = es . eval_from_string ( " {x}: {y}: x - y " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " {x = 2;} " , " <test> " ) . unwrap ( ) ;
let b = es . eval_from_string ( " {} " , " <test> " ) . unwrap ( ) ;
let r = es . call_multi ( & f , & [ a , b ] ) ;
match r {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " called without required argument " ) {
eprintln! ( " {} " , e ) ;
2025-12-15 19:48:30 -05:00
panic! ( ) ;
2024-12-01 18:56:41 +01:00
}
}
2024-04-09 13:14:53 +02:00
}
} )
. unwrap ( ) ;
}
2024-05-24 09:34:20 +02:00
2024-06-14 19:32:56 +02:00
/// This tests the behavior of `new_value_apply`, which is lazy, unlike `call`.
2024-05-24 09:34:20 +02:00
#[ test ]
2024-06-14 19:32:56 +02:00
fn eval_state_apply_fail_args_lazy ( ) {
2024-05-24 09:34:20 +02:00
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-05-24 09:34:20 +02:00
let f = es . eval_from_string ( " {x}: x + 1 " , " <test> " ) . unwrap ( ) ;
let a = es . eval_from_string ( " {} " , " <test> " ) . unwrap ( ) ;
// Lazy => no error
let r = es . new_value_apply ( & f , & a ) . unwrap ( ) ;
// Force it => error
let res = es . force ( & r ) ;
match res {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " called without required argument " ) {
eprintln! ( " {} " , e ) ;
2025-12-15 19:48:30 -05:00
panic! ( ) ;
2024-05-24 09:34:20 +02:00
}
}
}
} )
. unwrap ( ) ;
}
2024-05-17 01:56:16 +02:00
#[ test ]
fn store_open_params ( ) {
gc_registering_current_thread ( | | {
let store = tempfile ::tempdir ( ) . unwrap ( ) ;
let store_path = store . path ( ) . to_str ( ) . unwrap ( ) ;
let state = tempfile ::tempdir ( ) . unwrap ( ) ;
let state_path = state . path ( ) . to_str ( ) . unwrap ( ) ;
let log = tempfile ::tempdir ( ) . unwrap ( ) ;
let log_path = log . path ( ) . to_str ( ) . unwrap ( ) ;
2024-06-15 12:40:45 +02:00
let mut es = EvalState ::new (
2024-05-17 01:56:16 +02:00
Store ::open (
2024-12-13 00:54:35 +01:00
Some ( " local " ) ,
2024-05-17 01:56:16 +02:00
HashMap ::from ( [
( " store " , store_path ) ,
( " state " , state_path ) ,
( " log " , log_path ) ,
] )
. iter ( )
. map ( | ( a , b ) | ( * a , * b ) ) ,
)
. unwrap ( ) ,
[ ] ,
)
. unwrap ( ) ;
let expr = r #"
' '
a derivation output : $ {
derivation { name = " letsbuild " ;
system = builtins . currentSystem ;
builder = " /bin/sh " ;
args = [ " -c " " echo foo > $out " ] ;
} }
a path : $ { builtins . toFile " just-a-file " " ooh file good " }
a derivation path by itself : $ {
builtins . unsafeDiscardOutputDependency
( derivation {
name = " not-actually-built-yet " ;
system = builtins . currentSystem ;
builder = " /bin/sh " ;
args = [ " -c " " echo foo > $out " ] ;
} ) . drvPath }
' '
" #;
let derivations : [ & [ u8 ] ; 3 ] = [
b " letsbuild.drv " ,
b " just-a-file " ,
b " not-actually-built-yet.drv " ,
] ;
let _ = es . eval_from_string ( expr , " <test> " ) . unwrap ( ) ;
// assert that all three `derivations` are inside the store and the `state` directory is not empty either.
let store_contents : Vec < _ > = read_dir ( store . path ( ) )
. unwrap ( )
. map ( | dir_entry | dir_entry . unwrap ( ) . file_name ( ) )
. collect ( ) ;
for derivation in derivations {
assert! ( store_contents
. iter ( )
2025-12-15 19:48:30 -05:00
. any ( | f | f . as_encoded_bytes ( ) . ends_with ( derivation ) ) ) ;
2024-05-17 01:56:16 +02:00
}
assert! ( ! empty ( read_dir ( state . path ( ) ) . unwrap ( ) ) ) ;
store . close ( ) . unwrap ( ) ;
state . close ( ) . unwrap ( ) ;
log . close ( ) . unwrap ( ) ;
} )
. unwrap ( ) ;
}
2024-06-27 18:46:54 +02:00
2024-05-17 01:56:16 +02:00
fn empty ( foldable : impl IntoIterator ) -> bool {
foldable . into_iter ( ) . all ( | _ | false )
}
2024-06-27 18:46:54 +02:00
#[ test ]
fn eval_state_primop_anon_call ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , [ ] ) . unwrap ( ) ;
2024-06-27 18:46:54 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let bias : Arc < Mutex < Int > > = Arc ::new ( Mutex ::new ( 0 ) ) ;
let bias_control = bias . clone ( ) ;
2024-07-19 18:50:10 +02:00
let primop = primop ::PrimOp ::new (
2024-07-19 18:04:34 +02:00
& mut es ,
2024-07-19 18:50:10 +02:00
primop ::PrimOpMeta {
name : cstr ! ( " testFunction " ) ,
args : [ cstr! ( " a " ) , cstr! ( " b " ) ] ,
doc : cstr ! ( " anonymous test function " ) ,
} ,
2024-07-19 18:04:34 +02:00
Box ::new ( move | es , [ a , b ] | {
let a = es . require_int ( a ) ? ;
let b = es . require_int ( b ) ? ;
let c = * bias . lock ( ) . unwrap ( ) ;
2025-12-15 19:48:30 -05:00
es . new_value_int ( a + b + c )
2024-07-19 18:04:34 +02:00
} ) ,
)
. unwrap ( ) ;
2024-07-19 18:50:10 +02:00
let f = es . new_value_primop ( primop ) . unwrap ( ) ;
2024-06-27 18:46:54 +02:00
{
* bias_control . lock ( ) . unwrap ( ) = 10 ;
}
let a = es . new_value_int ( 2 ) . unwrap ( ) ;
let b = es . new_value_int ( 3 ) . unwrap ( ) ;
let fa = es . call ( f , a ) . unwrap ( ) ;
let v = es . call ( fa , b ) . unwrap ( ) ;
es . force ( & v ) . unwrap ( ) ;
let t = es . value_type ( & v ) . unwrap ( ) ;
assert! ( t = = ValueType ::Int ) ;
let i = es . require_int ( & v ) . unwrap ( ) ;
assert! ( i = = 15 ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_primop_anon_call_throw ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , [ ] ) . unwrap ( ) ;
2024-06-27 18:46:54 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-07-19 18:50:10 +02:00
let f = {
let es : & mut EvalState = & mut es ;
let prim = primop ::PrimOp ::new (
es ,
primop ::PrimOpMeta {
name : cstr ! ( " throwingTestFunction " ) ,
args : [ cstr! ( " arg " ) ] ,
doc : cstr ! ( " anonymous test function " ) ,
} ,
Box ::new ( move | es , [ a ] | {
let a = es . require_int ( a ) ? ;
bail! ( " error with arg [{}] " , a ) ;
} ) ,
)
. unwrap ( ) ;
es . new_value_primop ( prim )
}
2024-07-19 18:04:34 +02:00
. unwrap ( ) ;
2024-06-27 18:46:54 +02:00
let a = es . new_value_int ( 2 ) . unwrap ( ) ;
let r = es . call ( f , a ) ;
match r {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " error with arg [2] " ) {
eprintln! ( " unexpected error message: {} " , e ) ;
2025-12-15 19:48:30 -05:00
panic! ( ) ;
2024-06-27 18:46:54 +02:00
}
}
}
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_primop_anon_call_no_args ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , [ ] ) . unwrap ( ) ;
2024-06-27 18:46:54 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-07-19 18:50:10 +02:00
let v = es
. new_value_thunk (
" test_thunk " ,
2025-12-15 19:48:30 -05:00
Box ::new ( move | es : & mut EvalState | es . new_value_int ( 42 ) ) ,
2024-07-19 18:50:10 +02:00
)
. unwrap ( ) ;
2024-06-27 18:46:54 +02:00
es . force ( & v ) . unwrap ( ) ;
let t = es . value_type ( & v ) . unwrap ( ) ;
eprintln! ( " {:?} " , t ) ;
assert! ( t = = ValueType ::Int ) ;
let i = es . require_int ( & v ) . unwrap ( ) ;
assert! ( i = = 42 ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_primop_anon_call_no_args_lazy ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , [ ] ) . unwrap ( ) ;
2024-06-27 18:46:54 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
2024-07-19 18:50:10 +02:00
let v = es
. new_value_thunk (
" test_thunk " ,
Box ::new ( move | _ | {
bail! ( " error message in test case eval_state_primop_anon_call_no_args_lazy " )
} ) ,
)
. unwrap ( ) ;
2024-06-27 18:46:54 +02:00
let r = es . force ( & v ) ;
match r {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains (
" error message in test case eval_state_primop_anon_call_no_args_lazy " ,
) {
eprintln! ( " unexpected error message: {} " , e ) ;
2025-12-15 19:48:30 -05:00
panic! ( ) ;
2024-06-27 18:46:54 +02:00
}
2024-07-19 18:29:56 +02:00
if ! e . to_string ( ) . contains ( " test_thunk " ) {
eprintln! ( " unexpected error message: {} " , e ) ;
2025-12-15 19:48:30 -05:00
panic! ( ) ;
2024-07-19 18:29:56 +02:00
}
2024-06-27 18:46:54 +02:00
}
}
} )
. unwrap ( ) ;
}
2024-07-19 17:54:31 +02:00
#[ test ]
pub fn eval_state_primop_custom ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , [ ] ) . unwrap ( ) ;
2024-07-19 17:54:31 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let primop = primop ::PrimOp ::new (
& mut es ,
primop ::PrimOpMeta {
name : cstr ! ( " frobnicate " ) ,
doc : cstr ! ( " Frobnicates widgets " ) ,
args : [ cstr! ( " x " ) , cstr! ( " y " ) ] ,
} ,
Box ::new ( | es , args | {
let a = es . require_int ( & args [ 0 ] ) ? ;
let b = es . require_int ( & args [ 1 ] ) ? ;
2025-12-15 19:48:30 -05:00
es . new_value_int ( a + b )
2024-07-19 17:54:31 +02:00
} ) ,
)
. unwrap ( ) ;
let f = es . new_value_primop ( primop ) . unwrap ( ) ;
let a = es . new_value_int ( 2 ) . unwrap ( ) ;
let b = es . new_value_int ( 3 ) . unwrap ( ) ;
let fa = es . call ( f , a ) . unwrap ( ) ;
let fb = es . call ( fa , b ) . unwrap ( ) ;
es . force ( & fb ) . unwrap ( ) ;
let t = es . value_type ( & fb ) . unwrap ( ) ;
assert! ( t = = ValueType ::Int ) ;
let i = es . require_int ( & fb ) . unwrap ( ) ;
assert! ( i = = 5 ) ;
} )
. unwrap ( ) ;
}
#[ test ]
pub fn eval_state_primop_custom_throw ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , [ ] ) . unwrap ( ) ;
2024-07-19 17:54:31 +02:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let primop = primop ::PrimOp ::new (
& mut es ,
primop ::PrimOpMeta {
name : cstr ! ( " frobnicate " ) ,
doc : cstr ! ( " Frobnicates widgets " ) ,
args : [ cstr! ( " x " ) ] ,
} ,
Box ::new ( | _es , _args | bail! ( " The frob unexpectedly fizzled " ) ) ,
)
. unwrap ( ) ;
let f = es . new_value_primop ( primop ) . unwrap ( ) ;
let a = es . new_value_int ( 0 ) . unwrap ( ) ;
match es . call ( f , a ) {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
if ! e . to_string ( ) . contains ( " The frob unexpectedly fizzled " ) {
eprintln! ( " unexpected error message: {} " , e ) ;
2025-12-15 19:48:30 -05:00
panic! ( ) ;
2024-07-19 17:54:31 +02:00
}
if ! e . to_string ( ) . contains ( " frobnicate " ) {
eprintln! ( " unexpected error message: {} " , e ) ;
2025-12-15 19:48:30 -05:00
panic! ( ) ;
2024-07-19 17:54:31 +02:00
}
}
}
} )
. unwrap ( ) ;
}
2024-12-01 21:50:07 +01:00
#[ test ]
pub fn eval_state_new_value_attrs_from_slice_empty ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , [ ] ) . unwrap ( ) ;
2024-12-01 21:50:07 +01:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let attrs = es . new_value_attrs ( [ ] ) . unwrap ( ) ;
let t = es . value_type ( & attrs ) . unwrap ( ) ;
assert! ( t = = ValueType ::AttrSet ) ;
let names = es . require_attrs_names ( & attrs ) . unwrap ( ) ;
assert! ( names . is_empty ( ) ) ;
} )
. unwrap ( ) ;
}
#[ test ]
pub fn eval_state_new_value_attrs_from_vec ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , [ ] ) . unwrap ( ) ;
2024-12-01 21:50:07 +01:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let attrs = {
let a = es . new_value_int ( 1 ) . unwrap ( ) ;
let b = es . new_value_int ( 2 ) . unwrap ( ) ;
es . new_value_attrs ( vec! [ ( " a " . to_string ( ) , a ) , ( " b " . to_string ( ) , b ) ] )
. unwrap ( )
} ;
let t = es . value_type ( & attrs ) . unwrap ( ) ;
assert! ( t = = ValueType ::AttrSet ) ;
let names = es . require_attrs_names ( & attrs ) . unwrap ( ) ;
assert_eq! ( names . len ( ) , 2 ) ;
assert_eq! ( names [ 0 ] , " a " ) ;
assert_eq! ( names [ 1 ] , " b " ) ;
let a = es . require_attrs_select ( & attrs , " a " ) . unwrap ( ) ;
let b = es . require_attrs_select ( & attrs , " b " ) . unwrap ( ) ;
let i = es . require_int ( & a ) . unwrap ( ) ;
assert_eq! ( i , 1 ) ;
let i = es . require_int ( & b ) . unwrap ( ) ;
assert_eq! ( i , 2 ) ;
} )
. unwrap ( ) ;
}
#[ test ]
pub fn eval_state_new_value_attrs_from_hashmap ( ) {
gc_registering_current_thread ( | | {
2024-12-13 00:54:35 +01:00
let store = Store ::open ( None , [ ] ) . unwrap ( ) ;
2024-12-01 21:50:07 +01:00
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let attrs = {
let a = es . new_value_int ( 1 ) . unwrap ( ) ;
let b = es . new_value_int ( 2 ) . unwrap ( ) ;
es . new_value_attrs ( HashMap ::from ( [ ( " a " . to_string ( ) , a ) , ( " b " . to_string ( ) , b ) ] ) )
. unwrap ( )
} ;
let t = es . value_type ( & attrs ) . unwrap ( ) ;
assert! ( t = = ValueType ::AttrSet ) ;
let names = es . require_attrs_names ( & attrs ) . unwrap ( ) ;
assert_eq! ( names . len ( ) , 2 ) ;
assert_eq! ( names [ 0 ] , " a " ) ;
assert_eq! ( names [ 1 ] , " b " ) ;
let a = es . require_attrs_select ( & attrs , " a " ) . unwrap ( ) ;
let b = es . require_attrs_select ( & attrs , " b " ) . unwrap ( ) ;
let i = es . require_int ( & a ) . unwrap ( ) ;
assert_eq! ( i , 1 ) ;
let i = es . require_int ( & b ) . unwrap ( ) ;
assert_eq! ( i , 2 ) ;
} )
. unwrap ( ) ;
}
2025-09-20 01:12:32 +02:00
#[ test ]
fn eval_state_require_list_select_idx_strict_basic ( ) {
gc_registering_current_thread ( | | {
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let v = es . eval_from_string ( " [ 10 20 30 ] " , " <test> " ) . unwrap ( ) ;
let elem0 = es . require_list_select_idx_strict ( & v , 0 ) . unwrap ( ) . unwrap ( ) ;
let elem1 = es . require_list_select_idx_strict ( & v , 1 ) . unwrap ( ) . unwrap ( ) ;
let elem2 = es . require_list_select_idx_strict ( & v , 2 ) . unwrap ( ) . unwrap ( ) ;
assert_eq! ( es . require_int ( & elem0 ) . unwrap ( ) , 10 ) ;
assert_eq! ( es . require_int ( & elem1 ) . unwrap ( ) , 20 ) ;
assert_eq! ( es . require_int ( & elem2 ) . unwrap ( ) , 30 ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_require_list_select_idx_strict_out_of_bounds ( ) {
gc_registering_current_thread ( | | {
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let v = es . eval_from_string ( " [ 1 2 3 ] " , " <test> " ) . unwrap ( ) ;
let out_of_bounds = es . require_list_select_idx_strict ( & v , 3 ) . unwrap ( ) ;
assert! ( out_of_bounds . is_none ( ) ) ;
// Test boundary case - the last valid index
let last_elem = es . require_list_select_idx_strict ( & v , 2 ) . unwrap ( ) . unwrap ( ) ;
assert_eq! ( es . require_int ( & last_elem ) . unwrap ( ) , 3 ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_require_list_select_idx_strict_empty_list ( ) {
gc_registering_current_thread ( | | {
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let v = es . eval_from_string ( " [ ] " , " <test> " ) . unwrap ( ) ;
// Test that the safe version properly handles empty list access
let elem = es . require_list_select_idx_strict ( & v , 0 ) . unwrap ( ) ;
assert! ( elem . is_none ( ) ) ;
// Verify we can get the size of an empty list
let size = es . require_list_size ( & v ) . unwrap ( ) ;
assert_eq! ( size , 0 ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_require_list_select_idx_strict_forces_thunk ( ) {
gc_registering_current_thread ( | | {
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let v = make_thunk ( & mut es , " [ 42 ] " ) ;
assert! ( es . value_type_unforced ( & v ) . is_none ( ) ) ;
let elem = es . require_list_select_idx_strict ( & v , 0 ) . unwrap ( ) . unwrap ( ) ;
assert_eq! ( es . require_int ( & elem ) . unwrap ( ) , 42 ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_require_list_select_idx_strict_error_element ( ) {
gc_registering_current_thread ( | | {
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let v = es
. eval_from_string ( " [ (1 + 1) (throw \" error \" ) (3 + 3) ] " , " <test> " )
. unwrap ( ) ;
let elem0 = es . require_list_select_idx_strict ( & v , 0 ) . unwrap ( ) . unwrap ( ) ;
assert_eq! ( es . require_int ( & elem0 ) . unwrap ( ) , 2 ) ;
let elem2 = es . require_list_select_idx_strict ( & v , 2 ) . unwrap ( ) . unwrap ( ) ;
assert_eq! ( es . require_int ( & elem2 ) . unwrap ( ) , 6 ) ;
let elem1_result = es . require_list_select_idx_strict ( & v , 1 ) ;
match elem1_result {
Ok ( _ ) = > panic! ( " expected an error from throw during selection " ) ,
Err ( e ) = > {
assert! ( e . to_string ( ) . contains ( " error " ) ) ;
}
}
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_require_list_select_idx_strict_wrong_type ( ) {
gc_registering_current_thread ( | | {
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let v = es . eval_from_string ( " 42 " , " <test> " ) . unwrap ( ) ;
let r = es . require_list_select_idx_strict ( & v , 0 ) ;
match r {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
let err_msg = e . to_string ( ) ;
assert! ( err_msg . contains ( " expected a list, but got a " ) ) ;
}
}
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_require_list_size_basic ( ) {
gc_registering_current_thread ( | | {
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let empty = es . eval_from_string ( " [ ] " , " <test> " ) . unwrap ( ) ;
assert_eq! ( es . require_list_size ( & empty ) . unwrap ( ) , 0 ) ;
let three_elem = es . eval_from_string ( " [ 1 2 3 ] " , " <test> " ) . unwrap ( ) ;
assert_eq! ( es . require_list_size ( & three_elem ) . unwrap ( ) , 3 ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_require_list_size_forces_thunk ( ) {
gc_registering_current_thread ( | | {
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let v = make_thunk ( & mut es , " [ 1 2 3 4 5 ] " ) ;
assert! ( es . value_type_unforced ( & v ) . is_none ( ) ) ;
let size = es . require_list_size ( & v ) . unwrap ( ) ;
assert_eq! ( size , 5 ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_require_list_size_lazy_elements ( ) {
gc_registering_current_thread ( | | {
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let v = es
. eval_from_string (
" [ (throw \" error1 \" ) (throw \" error2 \" ) (throw \" error3 \" ) ] " ,
" <test> " ,
)
. unwrap ( ) ;
let size = es . require_list_size ( & v ) . unwrap ( ) ;
assert_eq! ( size , 3 ) ;
} )
. unwrap ( ) ;
}
#[ test ]
fn eval_state_require_list_size_wrong_type ( ) {
gc_registering_current_thread ( | | {
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
let mut es = EvalState ::new ( store , [ ] ) . unwrap ( ) ;
let v = es . eval_from_string ( " \" not a list \" " , " <test> " ) . unwrap ( ) ;
let r = es . require_list_size ( & v ) ;
match r {
Ok ( _ ) = > panic! ( " expected an error " ) ,
Err ( e ) = > {
let err_msg = e . to_string ( ) ;
assert! ( err_msg . contains ( " expected a list, but got a " ) ) ;
}
}
} )
. unwrap ( ) ;
}
2026-01-07 09:38:28 +01:00
/// Test for path coercion fix (commit 8f6ec2e, <https://github.com/nixops4/nix-bindings-rust/pull/35>).
///
/// This test verifies that path coercion works correctly with EvalStateBuilder.
/// Path coercion requires readOnlyMode = false, which is loaded from global
/// settings by calling eval_state_builder_load().
///
/// # Background
///
/// Without the eval_state_builder_load() call, settings from global Nix
/// configuration are never loaded, leaving readOnlyMode = true (the default).
/// This prevents Nix from adding paths to the store during evaluation,
/// which could cause errors like: "error: path '/some/local/path' does not exist"
///
/// # Test Coverage
///
/// This test exercises store file creation:
/// 1. builtins.toFile successfully creates files in the store
/// 2. Files are actually written to /nix/store
/// 3. Content is written correctly
///
/// Note: This test may not reliably fail without the fix in all environments.
/// Use eval_state_builder_loads_max_call_depth for a deterministic test.
#[ test ]
#[ cfg(nix_at_least = " 2.26 " /* real_path, eval_state_builder_load */) ]
fn eval_state_builder_path_coercion ( ) {
gc_registering_current_thread ( | | {
let mut store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
let mut es = EvalStateBuilder ::new ( store . clone ( ) )
. unwrap ( )
. build ( )
. unwrap ( ) ;
// Use builtins.toFile to create a file in the store.
// This operation requires readOnlyMode = false to succeed.
let expr = r # "builtins.toFile "test-file.txt" "test content""# ;
// Evaluate the expression
let value = es . eval_from_string ( expr , " <test> " ) . unwrap ( ) ;
// Realise the string to get the path and associated store paths
let realised = es . realise_string ( & value , false ) . unwrap ( ) ;
// Verify we got exactly one store path
assert_eq! (
realised . paths . len ( ) ,
1 ,
" Expected 1 store path, got {} " ,
realised . paths . len ( )
) ;
// Get the physical filesystem path for the store path
// In a relocated store, this differs from realised.s
let physical_path = store . real_path ( & realised . paths [ 0 ] ) . unwrap ( ) ;
// Verify the store path actually exists on disk
assert! (
std ::path ::Path ::new ( & physical_path ) . exists ( ) ,
" Store path should exist: {} " ,
physical_path
) ;
// Verify the content was written correctly
let store_content = std ::fs ::read_to_string ( & physical_path ) . unwrap ( ) ;
assert_eq! ( store_content , " test content " ) ;
} )
. unwrap ( ) ;
}
/// Test that eval_state_builder_load() loads settings.
///
/// Uses max-call-depth as the test setting. The test suite sets
/// max-call-depth = 1000 via NIX_CONFIG in setup() for the purpose of this test case.
/// This test creates a recursive function that calls itself 1100 times.
///
/// - WITH the fix: Settings are loaded, max-call-depth=1000 is enforced,
/// recursion fails at depth 1000
/// - WITHOUT the fix: Settings aren't loaded, default max-call-depth=10000
/// is used, recursion of 1100 succeeds when it should fail
#[ test ]
#[ cfg(nix_at_least = " 2.26 " ) ]
fn eval_state_builder_loads_max_call_depth ( ) {
gc_registering_current_thread ( | | {
let store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
let mut es = EvalStateBuilder ::new ( store ) . unwrap ( ) . build ( ) . unwrap ( ) ;
// Create a recursive function that calls itself 1100 times
// This should fail because max-call-depth is 1000 (set in setup())
let expr = r #"
let
recurse = n : if n = = 0 then " done " else recurse ( n - 1 ) ;
in
recurse 1100
" #;
let result = es . eval_from_string ( expr , " <test> " ) ;
match result {
Err ( e ) = > {
let err_str = e . to_string ( ) ;
assert! (
err_str . contains ( " max-call-depth " ) ,
" Expected max-call-depth error, got: {} " ,
err_str
) ;
}
Ok ( _ ) = > {
panic! (
" Expected recursion to fail with max-call-depth=1000, but it succeeded. \
This indicates eval_state_builder_load ( ) was not called . "
) ;
}
}
} )
. unwrap ( ) ;
}
2024-03-19 14:43:01 +01:00
}