diff --git a/crates/calc/src/recalc.rs b/crates/calc/src/recalc.rs index 32d588705..e537ccf0b 100644 --- a/crates/calc/src/recalc.rs +++ b/crates/calc/src/recalc.rs @@ -396,6 +396,7 @@ impl Calc { return; } let mut seeds: Vec = changed.to_vec(); + let mut emptied: Vec = 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. diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 9343faeda..b7f7323fd 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -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();