feat: Reintroduce EvalState::new_value_thunk

(cherry picked from commit a2c49a524cf76ca2c9a6759d271d58076756ac09)
This commit is contained in:
Robert Hensing 2024-07-19 18:29:56 +02:00
parent da9bb4c885
commit ee6c04a941
2 changed files with 39 additions and 15 deletions

View file

@ -2,6 +2,7 @@ use crate::primop;
use crate::value::{Int, Value, ValueType};
use anyhow::Context as _;
use anyhow::{bail, Result};
use cstr::cstr;
use lazy_static::lazy_static;
use nix_c_raw as raw;
use nix_store::path::StorePath;
@ -307,6 +308,36 @@ impl EvalState {
Ok(v)
}
/// Create a new thunk that will evaluate to the result of the given function.
/// The function will be called with the current EvalState.
/// The function must not return a thunk.
///
/// The name is shown in stack traces.
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)
}
/// Not exposed, because the caller must always explicitly handle the context or not accept one at all.
fn get_string(&mut self, value: &Value) -> Result<String> {
let mut r = result_string_init!();
@ -523,19 +554,6 @@ mod tests {
static ref FUNCTION_ANONYMOUS_ARG: CString = CString::new("x").unwrap();
static ref FUNCTION_ANONYMOUS_DOC: CString = CString::new("anonymous primop").unwrap();
}
pub fn thunk(
es: &mut EvalState,
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 f = new_value_function(
es,
FUNCTION_ANONYMOUS.as_ptr(),
Box::new(move |eval_state, _dummy: &[Value; 1]| f(eval_state)),
)?;
es.new_value_apply(&f, &f)
}
/// A worse quality shortcut for calling [new_value_primop].
pub fn new_value_function<const N: usize>(
@ -544,8 +562,8 @@ mod tests {
f: Box<dyn Fn(&mut EvalState, &[Value; N]) -> Result<Value>>,
) -> Result<Value> {
if N == 0 {
return thunk(
es,
return es.new_value_thunk(
"test_thunk",
Box::new(move |eval_state| {
f(eval_state, {
let empty: &[Value] = &[];
@ -1450,6 +1468,10 @@ mod tests {
eprintln!("unexpected error message: {}", e);
assert!(false);
}
if !e.to_string().contains("test_thunk") {
eprintln!("unexpected error message: {}", e);
assert!(false);
}
}
}
})

View file

@ -40,6 +40,8 @@ impl PrimOp {
meta: PrimOpMeta<N>,
f: Box<dyn Fn(&mut EvalState, &[Value; N]) -> Result<Value>>,
) -> Result<PrimOp> {
assert!(N != 0);
let mut args = Vec::new();
for arg in meta.args {
args.push(arg.as_ptr());