From fc2f00bd501a1ffa6fc8500eb3c1609c2bc2c2b1 Mon Sep 17 00:00:00 2001 From: nihilist-narwhal <240798405+nihilist-narwhal@users.noreply.github.com> Date: Sat, 10 Oct 2026 04:24:42 -0500 Subject: [PATCH] Recalc: a range ending in INDEX depends on the cells it spans =SUM(C3:INDEX(B9:C11,2,2)) adds C3:C10, but only C3 and B9:C11 were recorded as its precedents, so typing into C5 left the total unchanged until a full recalculation. A range whose ends are function results now depends on the box spanned by the references inside it, which contains any cell those functions can return. --- crates/calc/src/eval.rs | 14 ++++++++++++++ crates/engine/src/tests.rs | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/crates/calc/src/eval.rs b/crates/calc/src/eval.rs index abef54ffd..c010020a6 100644 --- a/crates/calc/src/eval.rs +++ b/crates/calc/src/eval.rs @@ -1337,6 +1337,20 @@ pub fn precedents(wb: &Workbook, sheet: usize, e: &Expr) -> (Vec, bool) { } Expr::Name(_) | Expr::Struct(_) => dynamic = true, Expr::Call(n, _) if gridcraft_functions::lookup(n).is_none() && !is_special(n) => dynamic = true, + // A range ending in a function's result (`A1:INDEX(B1:B9,n)`) covers the cells between + // the references inside it, not only those references. + Expr::Binary(BinOp::Range, a, b) if !matches!((&**a, &**b), (Expr::Ref(_), Expr::Ref(_))) => { + let mut inner = precedents(wb, sheet, a).0; + inner.extend(precedents(wb, sheet, b).0); + let mut boxes: Vec = Vec::new(); + for area in inner { + match boxes.iter_mut().find(|x| x.sheet == area.sheet) { + Some(x) => x.range = x.range.union(&area.range), + None => boxes.push(area), + } + } + out.extend(boxes); + } _ => {} }); (out, dynamic) diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 361fbce48..9fe3fa8fa 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -165,6 +165,14 @@ fn replacing_a_spilling_formula_clears_its_spill() { assert_eq!((v(&s, "A2"), v(&s, "B1")), (Value::Empty, Value::Number(0.0))); } +#[test] +fn range_ending_in_index_recalculates() { + let mut s = s(); + s.execute("cell.set", json!({"cell": "B6", "input": "=SUM(C3:INDEX(B9:C11,2,2))"})).unwrap(); + s.execute("cell.set", json!({"cell": "C5", "input": "2"})).unwrap(); + assert_eq!(v(&s, "B6"), Value::Number(2.0)); +} + #[test] fn every_command_survives_empty_params() { let mut s = Session::new();