Recalc: replacing or clearing a spilling formula removes its spill

Typing a value over a dynamic array formula, or clearing it, left the
values it had spilled: with =SEQUENCE(3) in A1, typing 2 into A1 kept
2 and 3 in A2:A3, and =SUM(A1:A3) gave 7. Only formulas are evaluated
again, so the old spill of a cell that no longer holds a formula was
never written back. It is now removed when the formula goes, and the
formulas reading its area are recalculated.
This commit is contained in:
nihilist-narwhal
2026-10-10 04:24:41 -05:00
parent 13664fe66b
commit 48910d71d2
2 changed files with 25 additions and 0 deletions
+13
View File
@@ -351,6 +351,7 @@ impl Calc {
/// Updates the graph for cells whose content changed and recalculates what depends on them.
pub fn cells_changed(&mut self, wb: &mut Workbook, changed: &[Key]) {
let mut cleared: Vec<(usize, RangeRef)> = Vec::new();
for &k in changed {
let formula = wb.sheet(k.0).and_then(|s| s.cell(k.1)).and_then(|c| c.formula.clone());
match formula.and_then(|f| f.expr()) {
@@ -360,6 +361,15 @@ impl Calc {
}
None => {
self.graph.remove(k);
// A formula replaced by a constant (or cleared) takes its spilled values with it.
if let Some(sh) = wb.sheet_mut(k.0)
&& let Some(r) = sh.spill_ranges.remove(&k.1)
{
for c in r.iter() {
sh.spill.remove(&c);
}
cleared.push((k.0, r));
}
// A formula that doesn't parse evaluates to #NAME?.
if let Some(cell) = wb.sheet_mut(k.0).and_then(|s| s.cells.get_mut(k.1))
&& cell.formula.is_some()
@@ -398,6 +408,9 @@ impl Calc {
seeds.extend(r.iter().take(65536).map(|c| (k.0, c)));
}
}
for (si, r) in &cleared {
seeds.extend(r.iter().take(65536).map(|c| (*si, c)));
}
let t0 = prof_now();
let dirty = self.dirty_closure(wb, &seeds);
let t1 = prof_now();
+12
View File
@@ -153,6 +153,18 @@ fn samples_build() {
}
}
#[test]
fn replacing_a_spilling_formula_clears_its_spill() {
let mut s = s();
s.execute("cell.set", json!({"cell": "A1", "input": "=SEQUENCE(3)"})).unwrap();
s.execute("cell.set", json!({"cell": "B1", "input": "=SUM(A1:A3)"})).unwrap();
s.execute("cell.set", json!({"cell": "A1", "input": "2"})).unwrap();
assert_eq!((v(&s, "A2"), v(&s, "A3"), v(&s, "B1")), (Value::Empty, Value::Empty, Value::Number(2.0)));
s.execute("cell.set", json!({"cell": "A1", "input": "=SEQUENCE(3)"})).unwrap();
s.execute("edit.clearContents", json!({"range": "A1"})).unwrap();
assert_eq!((v(&s, "A2"), v(&s, "B1")), (Value::Empty, Value::Number(0.0)));
}
#[test]
fn every_command_survives_empty_params() {
let mut s = Session::new();