Recalc: clearing a distant blocker unblocks the spill

Clearing a cell re-evaluated #SPILL! anchors only within 64 rows and
columns above and to the left of it, so =SEQUENCE(100) in A1 stayed
#SPILL! after the blocker in A90 was cleared. Emptied cells now look
for blocked anchors any distance above or to the left, with one pass
over the formulas per sheet however many cells were cleared.

Fixes #139.
This commit is contained in:
nihilist-narwhal
2026-10-10 04:31:28 -05:00
parent dcc5434bc8
commit e51c1d98fb
2 changed files with 25 additions and 0 deletions
+15
View File
@@ -396,6 +396,7 @@ impl Calc {
return;
}
let mut seeds: Vec<Key> = changed.to_vec();
let mut emptied: Vec<Key> = Vec::new();
// Typing into (or clearing) a cell a dynamic array spills over (or would spill over)
// re-evaluates the anchor, which may now be blocked or unblocked.
for &k in changed {
@@ -410,6 +411,20 @@ impl Calc {
seeds.push((k.0, c));
}
}
if !sh.cells.has(k.1) {
match emptied.iter_mut().find(|x| x.0 == k.0) {
Some(x) => x.1 = CellRef::new(x.1.row.max(k.1.row), x.1.col.max(k.1.col)),
None => emptied.push(k),
}
}
}
}
// An emptied cell can unblock an anchor any distance above or to the left of it.
for (si, end) in emptied {
for &(s, c) in self.graph.nodes.keys() {
if s == si && c.row <= end.row && c.col <= end.col && wb.sheet(s).is_some_and(|sh| sh.value(c) == Value::Error(CellError::Spill)) {
seeds.push((s, c));
}
}
}
// Spill areas of changed anchors also change.
+10
View File
@@ -184,6 +184,16 @@ fn structural_edits_move_spills_with_their_formula() {
assert_eq!(col(&s), [Value::Empty, Value::Empty, Value::Empty, Value::Empty]);
}
#[test]
fn clearing_a_distant_blocker_unblocks_the_spill() {
let mut s = s();
s.execute("cell.set", json!({"cell": "A1", "input": "=SEQUENCE(100)"})).unwrap();
s.execute("cell.set", json!({"cell": "A90", "input": "x"})).unwrap();
assert_eq!(v(&s, "A1"), Value::Error(gridcraft_core::CellError::Spill));
s.execute("edit.clearContents", json!({"range": "A90"})).unwrap();
assert_eq!((v(&s, "A1"), v(&s, "A100")), (Value::Number(1.0), Value::Number(100.0)));
}
#[test]
fn every_command_survives_empty_params() {
let mut s = Session::new();