Recalc: a spill that goes away unblocks the arrays it blocked

With =SEQUENCE(5) in F8 and =SEQUENCE(1,3) in E10 (blocked by F10),
typing 0 into F9 blocks F8's array, which frees F10, but E10 stayed
#SPILL! until a full recalculation. When a formula stops spilling over
an area, #SPILL! anchors above or to the left of it are evaluated again.
This commit is contained in:
nihilist-narwhal
2026-10-10 04:36:06 -05:00
parent e51c1d98fb
commit 880163d135
2 changed files with 30 additions and 0 deletions
+18
View File
@@ -524,6 +524,7 @@ impl Calc {
self.circular = cycles;
// Write back.
let mut spill_changes: Vec<Key> = Vec::new();
let mut freed: Vec<(usize, RangeRef)> = Vec::new();
for (k, v) in results {
let Some(sheet) = wb.sheet_mut(k.0) else { continue };
if let Some(cell) = sheet.cells.get_mut(k.1)
@@ -572,6 +573,23 @@ impl Calc {
self.graph.dependents_of_range(k.0, r, &mut deps);
spill_changes.extend(deps);
}
if let Some(o) = old
&& !sheet.spill_ranges.get(&k.1).is_some_and(|n| n.contains(o.start) && n.contains(o.end))
{
freed.push((k.0, o));
}
}
// An area a formula no longer spills over may unblock another formula's array.
for (si, o) in freed {
for &(s, c) in self.graph.nodes.keys() {
if s == si
&& c.row <= o.end.row
&& c.col <= o.end.col
&& wb.sheet(s).is_some_and(|sh| sh.value(c) == Value::Error(CellError::Spill))
{
spill_changes.push((s, c));
}
}
}
if full {
// Everything was already evaluated once; spill dependents need another pass.
+12
View File
@@ -194,6 +194,18 @@ fn clearing_a_distant_blocker_unblocks_the_spill() {
assert_eq!((v(&s, "A1"), v(&s, "A100")), (Value::Number(1.0), Value::Number(100.0)));
}
#[test]
fn a_spill_that_goes_away_unblocks_another() {
let mut s = s();
s.execute("cell.set", json!({"cell": "F8", "input": "=SEQUENCE(5)"})).unwrap();
s.execute("cell.set", json!({"cell": "E10", "input": "=SEQUENCE(1,3)"})).unwrap();
assert_eq!(v(&s, "E10"), Value::Error(gridcraft_core::CellError::Spill));
// Blocking F8's array frees F10 for E10's.
s.execute("cell.set", json!({"cell": "F9", "input": "0"})).unwrap();
assert_eq!(v(&s, "F8"), Value::Error(gridcraft_core::CellError::Spill));
assert_eq!((v(&s, "E10"), v(&s, "G10")), (Value::Number(1.0), Value::Number(3.0)));
}
#[test]
fn every_command_survives_empty_params() {
let mut s = Session::new();