From 97f7dca8866b04d29bf1f3393ad13893382c500f Mon Sep 17 00:00:00 2001 From: nihilist-narwhal <240798405+nihilist-narwhal@users.noreply.github.com> Date: Sat, 10 Oct 2026 04:59:27 -0500 Subject: [PATCH] Recalc: INDIRECT and OFFSET see spills laid out in the same pass A formula whose references are only known while evaluating isn't in the dependency graph, so when it read a spill area before the array was laid out in the same pass, nothing evaluated it again. With the full recalculation now laying every spill out afresh, =INDIRECT("F7") over a spill moved by an inserted row read 0 after Calculate Now. Such formulas are evaluated once more when a pass computed any array. --- crates/calc/src/recalc.rs | 7 +++++++ crates/engine/src/tests.rs | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/crates/calc/src/recalc.rs b/crates/calc/src/recalc.rs index cfef5f478..9f3e1e51c 100644 --- a/crates/calc/src/recalc.rs +++ b/crates/calc/src/recalc.rs @@ -481,6 +481,7 @@ impl Calc { /// Evaluates the dirty set and writes results (and spills) back. fn run(&mut self, wb: &mut Workbook, mut dirty: Vec, full: bool) { + let mut dynamic_again = !self.graph.dynamic.is_empty(); for _round in 0..8 { if dirty.is_empty() { break; @@ -579,6 +580,12 @@ impl Calc { freed.push((k.0, o)); } } + // Formulas with references only known while evaluating (INDIRECT, OFFSET) may have read + // a spill area before it was laid out: they are evaluated once more. + if dynamic_again && !spills.is_empty() { + dynamic_again = false; + spill_changes.extend(self.graph.dynamic.iter().copied()); + } // 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() { diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 72d67f5cd..e0db140a6 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -219,6 +219,17 @@ fn an_array_reading_another_arrays_spill_is_updated() { assert_eq!((v(&s, "G6"), v(&s, "G7")), (Value::Number(12.0), Value::Number(10.0))); } +#[test] +fn indirect_reads_a_spill_laid_out_in_the_same_pass() { + let mut s = s(); + s.execute("cell.set", json!({"cell": "F5", "input": "=SEQUENCE(2,2,7)"})).unwrap(); + s.execute("home.insertRows", json!({"rows": "3:3"})).unwrap(); + s.execute("cell.set", json!({"cell": "E5", "input": "=INDIRECT(\"F7\")"})).unwrap(); + assert_eq!(v(&s, "E5"), Value::Number(9.0)); + s.execute("formulas.calculateNow", json!({})).unwrap(); + assert_eq!(v(&s, "E5"), Value::Number(9.0)); +} + #[test] fn every_command_survives_empty_params() { let mut s = Session::new();