From dc01d3731fdc9e60123e8d31640b70cb02c388d8 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Sun, 1 Dec 2024 18:56:41 +0100 Subject: [PATCH] feat: Add EvalState::call_multi (cherry picked from commit e1866f0c61011ffb57b607abe871340aca294f19) --- rust/nix-expr/src/eval_state.rs | 82 +++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/rust/nix-expr/src/eval_state.rs b/rust/nix-expr/src/eval_state.rs index 510d095..258dd48 100644 --- a/rust/nix-expr/src/eval_state.rs +++ b/rust/nix-expr/src/eval_state.rs @@ -433,6 +433,24 @@ impl EvalState { Ok(value) } + /// Eagerly apply a function with multiple curried arguments. + #[doc(alias = "nix_value_call_multi")] + pub fn call_multi(&mut self, f: &Value, args: &[Value]) -> Result { + let value = self.new_value_uninitialized()?; + let mut args_ptrs = args.iter().map(|a| a.raw_ptr()).collect::>(); + unsafe { + 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) + } + /// Apply a function to an argument, but don't evaluate the result just yet. /// /// For an eager version, see [`call`][`EvalState::call`]. @@ -1166,6 +1184,24 @@ mod tests { .unwrap(); } + #[test] + fn eval_state_call_multi() { + gc_registering_current_thread(|| { + let store = Store::open("auto", HashMap::new()).unwrap(); + 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", "").unwrap(); + let a = es.eval_from_string("2", "").unwrap(); + let b = es.eval_from_string("3", "").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(); + } + #[test] fn eval_state_apply() { gc_registering_current_thread(|| { @@ -1206,6 +1242,29 @@ mod tests { .unwrap(); } + #[test] + fn eval_state_call_multi_fail_body() { + gc_registering_current_thread(|| { + let store = Store::open("auto", HashMap::new()).unwrap(); + 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", "").unwrap(); + let a = es.eval_from_string("2", "").unwrap(); + let b = es.eval_from_string("true", "").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); + assert!(false); + } + } + } + }) + .unwrap(); + } + #[test] fn eval_state_apply_fail_body() { gc_registering_current_thread(|| { @@ -1252,6 +1311,29 @@ mod tests { .unwrap(); } + #[test] + fn eval_state_call_multi_fail_args() { + gc_registering_current_thread(|| { + let store = Store::open("auto", HashMap::new()).unwrap(); + 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", "").unwrap(); + let a = es.eval_from_string("{x = 2;}", "").unwrap(); + let b = es.eval_from_string("{}", "").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); + assert!(false); + } + } + } + }) + .unwrap(); + } + /// This tests the behavior of `new_value_apply`, which is lazy, unlike `call`. #[test] fn eval_state_apply_fail_args_lazy() {