mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-28 20:56:54 -06:00
Recalculation: a spill anchor given a formula that reads its old spill area sees it empty
When a dynamic array's anchor gets a new formula, the formula is evaluated while the old array is still laid out, so it can read the old spilled values. The anchor was then never evaluated again: it is left out of its own spill area's dependents, so as not to read its own array. - `A1` was `=SEQUENCE(3)` and becomes `=SUM(A2:A3)+1`: it showed 6 (2 + 3 + 1) with A2:A3 empty; recalculating from scratch gives 1. - `A1` was `=SEQUENCE(3)` and becomes `=SEQUENCE(1,A2+1)`: it read A2 as 2 and spilled over A1:C1 instead of showing 1. The anchor is now evaluated again when its old array is gone or partly freed. It is still left out when its new array covers the old one, where it would read its own array. Regression test in calc (`an_anchor_given_a_formula_that_reads_its_old_spill_area_sees_it_empty`: the new formula stops spilling, spills elsewhere, or spills over part of the old area); it fails without the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d9d09f0baa
commit
cd94a6d7b4
+3
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
**Stage: alpha** · next: beta, ~25 points of "ready for real work" and ~160–240 h away
|
||||
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-12 · **Change:** minor (rows auto-fit oversized fonts on open and whole-row/column formatting) · **Target:** Microsoft Excel (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-14 · **Change:** minor (a spill anchor given a formula that reads its old spill area sees it empty) · **Target:** Microsoft Excel (Microsoft 365)
|
||||
|
||||
GridCraft aims at full Microsoft Excel parity, and at being better in places: faster, open (XLSX
|
||||
is the native format), fully scriptable by agents (MCP, control channel, CLI), and on the web.
|
||||
@@ -132,6 +132,7 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in
|
||||
|
||||
| Date | What landed |
|
||||
|---|---|
|
||||
| 2026-10-14 | Recalculation: a dynamic array's anchor given a new formula that reads its old spill area (`A1` was `=SEQUENCE(3)`, becomes `=SUM(A2:A3)+1` or `=SEQUENCE(1,A2+1)`) sees that area empty instead of the old values |
|
||||
| 2026-10-12 | Rows auto-fit oversized fonts: opening a file and whole-row/column formatting grow non-custom rows to their tallest font (#833) |
|
||||
| 2026-10-12 | Number formats: parsing a format code is linear in its length (long runs of `[`, `,` or `/` no longer take quadratic time), and an unclosed `[` is a literal when splitting sections as well as when tokenizing (#810) |
|
||||
| 2026-10-12 | Number formats: a fraction denominator with 65,536 zeros no longer crashes; format codes are capped at 255 characters like Excel (longer codes display as General, `TEXT` gives `#VALUE!`) (#807) |
|
||||
@@ -193,6 +194,7 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-14 | minor | Progress log: spill anchor reading its old spill area |
|
||||
| 2026-10-12 | minor | Rows auto-fit oversized fonts on open and whole-row/column formatting (#833); progress log row |
|
||||
| 2026-10-12 | minor | Progress log: multi-area row/column delete (#758) |
|
||||
| 2026-10-12 | trivial | Progress log: XLSX chart sheets kept on save (#727) |
|
||||
|
||||
@@ -1036,7 +1036,15 @@ impl Calc {
|
||||
if let Some(r) = changed_range {
|
||||
let mut deps = Vec::new();
|
||||
self.graph.dependents_of_range(k.0, r, &mut deps);
|
||||
spill_changes.extend(deps.into_iter().filter(|d| d != k));
|
||||
// Not the anchor itself (that would read its own array), unless its old array
|
||||
// is gone or partly freed: its new formula may have read the old values, still
|
||||
// laid out while it was evaluated.
|
||||
let freed = match (old, sheet.spill_ranges.get(&k.1)) {
|
||||
(Some(o), Some(n)) => !(n.contains(o.start) && n.contains(o.end)),
|
||||
(_, None) => true,
|
||||
(None, Some(_)) => false,
|
||||
};
|
||||
spill_changes.extend(deps.into_iter().filter(|d| d != k || freed));
|
||||
}
|
||||
if let Some(o) = old
|
||||
&& !sheet.spill_ranges.get(&k.1).is_some_and(|n| n.contains(o.start) && n.contains(o.end))
|
||||
|
||||
@@ -543,6 +543,25 @@ fn spills() {
|
||||
assert_eq!(t.num("D2"), 40.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_anchor_given_a_formula_that_reads_its_old_spill_area_sees_it_empty() {
|
||||
// The new formula was evaluated while the old array was still laid out, and only the
|
||||
// anchor's dependents were evaluated again once it was gone.
|
||||
let mut t = T::new();
|
||||
t.set("A1", "=SEQUENCE(3)");
|
||||
assert_eq!(t.num("A3"), 3.0);
|
||||
t.set("A1", "=SUM(A2:A3)+1");
|
||||
assert_eq!((t.num("A1"), t.get("A3")), (1.0, Value::Empty));
|
||||
// Still an array, now spilling elsewhere: it read A2 as 2 and spilled three columns.
|
||||
t.set("A1", "=SEQUENCE(3)");
|
||||
t.set("A1", "=SEQUENCE(1,A2+1)");
|
||||
assert_eq!((t.num("A1"), t.get("B1"), t.get("A2")), (1.0, Value::Empty, Value::Empty));
|
||||
// A smaller array inside the old area: it read A3 as 3 and spilled over A1:A2.
|
||||
t.set("A1", "=SEQUENCE(3)");
|
||||
t.set("A1", "=SEQUENCE(MAX(1,A3-1))");
|
||||
assert_eq!((t.num("A1"), t.get("A2"), t.get("A3")), (1.0, Value::Empty, Value::Empty));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_keep_their_size_past_the_used_range() {
|
||||
let mut t = T::new();
|
||||
|
||||
Reference in New Issue
Block a user