functions: share the argument-count rule between call and entry

FnSpec::accepts holds the arity check (including Excel's 255-argument
limit) that functions::call and the entry check both use, so entry also
refuses 256 arguments to a variadic function. The evaluator's own check
in calc is unchanged.

Claude-Session: https://claude.ai/code/session_01BGBBGBC2gRpQ7GDuwQ6WPt
This commit is contained in:
Shirish Veerabattini 2026-10-11 15:37:09 -05:00
parent 9b59966cc9
commit 993c108660
3 changed files with 13 additions and 2 deletions

View File

@ -200,7 +200,7 @@ fn check_arg_counts(expr: &gridcraft_formula::Expr) -> Result<()> {
if bad.is_none()
&& let gridcraft_formula::Expr::Call(name, args) = e
&& let Some(spec) = gridcraft_functions::lookup(name)
&& (args.len() < spec.min_args || args.len() > spec.max_args)
&& !spec.accepts(args.len())
{
let few = args.len() < spec.min_args;
bad = Some(format!("{} has too {} arguments. Use {}", spec.name, if few { "few" } else { "many" }, spec.signature));

View File

@ -219,6 +219,12 @@ fn formulas_with_the_wrong_argument_count_are_refused_on_entry() {
assert!(e.contains(&format!("too {word} arguments")), "{input}: {e}");
assert_eq!(v(&s, "A1"), Value::text("kept"), "{input} must not replace the cell");
}
// Excel's 255-argument limit applies to variadic functions too.
let many = format!("=SUM({})", vec!["1"; 256].join(","));
let e = s.execute("cell.set", json!({"cell": "A1", "input": many})).unwrap_err().to_string();
assert!(e.contains("too many arguments"), "{e}");
s.execute("cell.set", json!({"cell": "A2", "input": format!("=SUM({})", vec!["1"; 255].join(","))})).unwrap();
assert_eq!(v(&s, "A2"), Value::Number(255.0));
let e = s.execute("cell.set", json!({"cell": "A1", "input": "=DATEDIF(1,2)"})).unwrap_err().to_string();
assert!(e.contains("DATEDIF(start_date, end_date, unit)"), "{e}");
// Correct counts, optional arguments left empty, special forms, LAMBDA names and unknown

View File

@ -160,6 +160,11 @@ impl FnSpec {
None => self.scalar_args.last().copied().unwrap_or(false),
}
}
/// Whether a call with `n` arguments is valid (Excel allows at most 255).
pub fn accepts(&self, n: usize) -> bool {
(self.min_args..=self.max_args).contains(&n) && n <= 255
}
}
/// Variadic marker for `max_args`.
@ -215,7 +220,7 @@ pub fn all() -> &'static [FnSpec] {
/// Calls a function with array lifting applied. Wrong arity gives `#VALUE!`.
pub fn call(spec: &FnSpec, args: &[Arg], ctx: &mut dyn Ctx) -> Value {
if args.len() < spec.min_args || args.len() > spec.max_args || args.len() > 255 {
if !spec.accepts(args.len()) {
return Value::Error(CellError::Value);
}
lift::call_lifted(spec, args, ctx)