From e6c16472e430af8a8a6f6808c4c54f9da8da1087 Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Mon, 12 Oct 2026 08:29:09 +0200 Subject: [PATCH] Return #VALUE! when a LAMBDA call has too few argument positions (#557) --- crates/calc/src/eval.rs | 7 ++++++- crates/calc/src/tests.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/crates/calc/src/eval.rs b/crates/calc/src/eval.rs index 92a0a39fd..063043a0d 100644 --- a/crates/calc/src/eval.rs +++ b/crates/calc/src/eval.rs @@ -860,7 +860,12 @@ impl<'h> Evaluator<'h> { } pub fn apply_lambda(&mut self, l: &Lambda, args: &[Expr]) -> Ev { - if args.len() > l.params.len() { + // Too many or too few argument positions is #VALUE!. An explicitly empty position, as in + // `(1,)`, is `Expr::Missing` and still counts as a position (it binds as omitted). `f()` + // parses to no arguments, which for a one-parameter lambda can also be read as one empty + // position, so that call still binds the parameter as omitted. + let positions_ok = args.len() == l.params.len() || (args.is_empty() && l.params.len() == 1); + if !positions_ok { return err(CellError::Value); } let mut bound = Vec::with_capacity(l.params.len()); diff --git a/crates/calc/src/tests.rs b/crates/calc/src/tests.rs index 513340983..e2f4a3a29 100644 --- a/crates/calc/src/tests.rs +++ b/crates/calc/src/tests.rs @@ -501,6 +501,38 @@ fn let_lambda() { assert_eq!(t.num("A4"), 14.0); } +#[test] +fn lambda_argument_count() { + let mut t = T::new(); + t.wb.names.push(gridcraft_model::DefinedName { + name: "AddPair".into(), + scope: None, + formula: "LAMBDA(a,b,a+b)".into(), + comment: String::new(), + hidden: false, + }); + t.calc.rebuild(&t.wb); + let omit = "LAMBDA(x,y,IF(ISOMITTED(y),\"Missing\",x+y))"; + t.set("A1", "=LAMBDA(a,b,a+b)(2)"); + t.set("A2", "=LAMBDA(a,b,a+b)(2,3,4)"); + t.set("A3", &format!("={omit}(1,)")); + t.set("A4", &format!("={omit}(1)")); + t.set("A5", &format!("={omit}(1,2)")); + t.set("A6", "=LAMBDA(7)()"); + t.set("A7", "=AddPair(2)"); + t.set("A8", "=AddPair(2,3)"); + assert_eq!(t.get("A1"), Value::Error(CellError::Value)); + assert_eq!(t.get("A2"), Value::Error(CellError::Value)); + assert_eq!(t.get("A3"), Value::Text("Missing".into())); + assert_eq!(t.get("A4"), Value::Error(CellError::Value)); + assert_eq!(t.num("A5"), 3.0); + assert_eq!(t.num("A6"), 7.0); + assert_eq!(t.get("A7"), Value::Error(CellError::Value)); + assert_eq!(t.num("A8"), 5.0); + t.set("A9", "=LAMBDA(x,ISOMITTED(x))()"); + assert_eq!(t.get("A9"), Value::Bool(true)); +} + #[test] fn names_and_sheets() { let mut t = T::new();