Repository navigation
feat(config): add arrayFunctionResultOverwritesData opt-in overwrite flag (HF-305) - #1714
marcin-kordas-hoc wants to merge 11 commits into
Conversation
✅ Deploy Preview for hyperformula-dev-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
9009463 to
b2da92e
Compare
Performance comparison of head (7aecb9f) vs base (d143456) |
|
bugbot run |
4c02791 to
d2a2671
Compare
|
bugbot run |
d2a2671 to
5e397d1
Compare
|
bugbot run |
5e397d1 to
2f48fba
Compare
2f48fba to
b877ed4
Compare
|
bugbot run |
b877ed4 to
467c186
Compare
|
bugbot run |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
hyperformula-docs | 7aecb9f | Commit Preview URL Branch Preview URL |
Oct 23 2026, 07:27 AM |
…flag (HF-305) Adds a public boolean Config option `arrayFunctionResultOverwritesData` (default `false`). When `false`, behavior is unchanged: an array spill onto an occupied cell yields `#SPILL!` and leaves the occupant intact. When `true`, the spill overwrites the occupied cells (clears occupants, spills the array, reroutes dependents) instead of emitting `#SPILL!`. Implementation reuses the existing spill-placement exchange primitive: - ConfigParams/Config: new option, mirrored on `useArrayArithmetic`. - DependencyGraph gains `config` + two guarded early-returns in exchangeOrAddFormulaVertex / setAddressMappingForArrayVertex. - Evaluator: recompute no longer emits `#SPILL!` when overwrite is allowed. Array-vs-array safety: a spill colliding with ANOTHER array always keeps `#SPILL!` and leaves that array intact, even in overwrite mode (canOverwriteArrayResult / overwriteWouldHitArray). This matches Excel and avoids corrupting a pre-existing array; overwrite only clears static data. Default `false` guarantees no existing embedder loses data silently. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 53 cases now live in hyperformula-tests on the same-name branch (feat/hf-305-overwrite-flag), which is what test/fetch-tests.sh pairs on. They arrived here as test/hf-305-overwrite.spec.ts, and #1612 took every spec out of this repository: on develop, test/ carries only the setup files, fetch-tests.sh and smoke.spec.ts. Jest's testMatch (test/**/*spec.(ts|js)) did pick the file up, so the suite was running - it was running from the wrong repository, where it is invisible to reviewers of the tests repo and ships in the public tree. Coverage is not lost by the move: measured against this commit, every executable line this PR adds to Operations.ts (29) and DependencyGraph.ts (17) is exercised by the ported spec alone, which is what the red codecov/patch was about. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The rebase onto develop surfaced this: the entry was written when the branch's base still had 3.4.0 open, so replaying it landed the line inside the released 3.4.0 section. Moved to [Unreleased], and given the PR link every neighbouring entry carries. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
f051ab3 to
63e2137
Compare
The overwrite snapshot guarded on `width * height <= 1`, but placement (FormulaVertex.fromAst) treats every ref-flavored size as a non-spilling scalar via ArraySize.isScalar(). A bare range formula such as =C1:C3 therefore snapshotted neighbours it never overwrites, and undoing that edit spuriously restored them (re-entering formulas, re-rolling volatiles). Guard on size.isScalar() so the snapshot scope matches the placement scope, as the JSDoc already described. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This morning's develop merge landed the TEXT/numberFormat entry inside the already-published 3.4.0 Fixed list, putting CHANGELOG.md out of sync with docs/guide/release-notes.md for that released version. The fix is unreleased, so the entry belongs under [Unreleased]; the 3.4.0 lists in the two files match again. Same correction as 63e2137 on #1714. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
|
Known-gap disclosure from writing the paired suite (tests #45), re-verified today on this head (3e2a099): with the flag ON, an array whose spill range cannot be represented silently loses Repro: Root cause: The paired suite deliberately pins only the stable half (occupants below are not clobbered), so it stays green whichever way this is resolved. |
…ill ranges The overwrite feature resolves a spill collision when the array formula is placed: exchangeOrAddFormulaVertex claims the whole spill range in the address mapping before evaluation, so isThereSpaceForArray is already true when the array is recomputed. The canOverwriteArrayResult bypass added to the Evaluator (and to the build-path setAddressMappingForArrayVertex) could therefore only ever fire for arrays whose overwrite never happened - an array blocked by an occupant at buildFromArray/updateConfig time, or an array whose spill range cannot be represented on the sheet (e.g. =2*(B:B) anchored outside row 1, where getRangeOrUndef() is undefined and overwriteWouldHitArray vacuously returns false). In both cases the array silently truncated to its anchor value instead of yielding #SPILL!. Remove the bypass from both sites so an array that does not own its spill range keeps #SPILL! exactly like with the flag off, and make canOverwriteArrayResult itself refuse unrepresentable ranges - overwrite mode bypasses occupancy collisions only, never a range that cannot be spanned at all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Fixed in |
Tobiadefami
left a comment
There was a problem hiding this comment.
Confirmed the structural-undo issue noted in the description. With overwrite enabled, A1 contains =TRANSPOSE(D1:E1), D1:E1 contains 10, 20, and A3 contains 99.
| State | Formula in A1 | A1 | A2 | A3 |
|---|---|---|---|---|
| Calculated sheet before insertion | =TRANSPOSE(D1:E1) |
10 | 20 | 99 |
| After inserting a column before E | =TRANSPOSE(D1:F1) |
10 | blank | 20 |
| After undo | =TRANSPOSE(D1:E1) |
10 | 20 | blank — expected 99 |
Could we preserve and restore the overwritten cells in the column-insertion undo path?
…shorten the changelog entry Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
… re-checking it canOverwriteArrayResult already returns false for an unrepresentable range, so the second undefined check in overwriteWouldHitArray could never be reached. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ns are undone With arrayFunctionResultOverwritesData on, an array that grows because rows or columns were added inside the range it reads overwrites the cells it reaches. Undo now restores them: the add operation snapshots those cells, maps them back to the coordinates and references from before the added span, and undo writes them back right after removing the span. Undo of one setCellContents call now reverts its writes in reverse order, so a write into the spill range of an array entered earlier in the same call no longer brings that array formula back. This also applies with the option off. Row and column moves no longer overwrite data: a move adds the moved rows or columns before it removes them, and the array that grew for that moment cleared cells it does not cover once the move ends. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cupied cells Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 7aecb9f. Configure here.
| */ | ||
| private restoreCellsOverwrittenBySpan(overwrittenCells: [SimpleCellAddress, ClipboardCell][] = []) { | ||
| for (const [address, clipboardCell] of overwrittenCells) { | ||
| this.operations.restoreCell(address, clipboardCell) |
There was a problem hiding this comment.
Undo leaves stale column-index values
Medium Severity
Undoing addRows or addColumns that grew an array over a cell restores the occupant, but the vacated spill value can stay in the column index. With useColumnIndex on, MATCH and VLOOKUP can still hit that spill after undo().
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 7aecb9f. Configure here.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #1714 +/- ##
===========================================
+ Coverage 97.28% 97.30% +0.02%
===========================================
Files 207 207
Lines 16218 16304 +86
Branches 3556 3610 +54
===========================================
+ Hits 15777 15865 +88
+ Misses 433 432 -1
+ Partials 8 7 -1
🚀 New features to boost your workflow:
|


Context
Adds the opt-in configuration option
arrayFunctionResultOverwritesData(defaultfalse). When it istrue, an array whose result spills onto occupied cells overwrites them instead of returning#SPILL!. When it isfalse, spills behave as before.The overwrite reuses the step that places every spill: when the array formula is placed, it claims its whole spill range in the dependency graph, drops the occupants, and records their old values so the column index forgets them. A spill that would reach another array keeps
#SPILL!and leaves that array intact, whatever the option says.undo()restores the overwritten cells:setCellContentssnapshots the occupants before the spill;=E1*2comes back as=E1*2, not=F1*2), and written back right after undo removes the span.The option does not overwrite in two places, on purpose:
buildFromArraycall as the array: the build path keeps#SPILL!, because claiming cells there would leave nothing to undo;moveRowsandmoveColumns: a move adds the moved rows or columns before it removes them, so an array that reads them grows for a moment. Overwriting in that moment cleared the cell next to the spill for good, even when the array ended at its old size. A move that makes an array grow now leaves the occupant in place and the array shows#SPILL!.Also fixed here, because the option made it reachable more often: undoing one
setCellContentscall that enters an array formula and also writes a cell of its spill range brought the array formula back. This happened with the option off too. Undo now reverts the writes of one call in reverse order. It has its own CHANGELOG line under Fixed.How did you test your changes?
Paired tests: handsontable/hyperformula-tests#45, branch
feat/hf-305-overwrite-flag.unit/array-function-result-overwrites-data.spec.ts: 74 tests (configuration, the default, overwriting, array-vs-array collisions, undo and redo, undo of one call that writes an array and a cell of its spill, undo of added rows and columns, column index, structural operations including row moves, cycles, unrepresentable spill ranges).unit/undo-redo.spec.ts: one new test, with the option off, for undoing one call that writes an array formula and a cell of its spill range.Compatibility with Excel and Google Sheets
Not applicable to the default: with the option off, calculation results do not change. The option is opt-in and changes only what happens to cells an array spills onto.
Public API changes
arrayFunctionResultOverwritesData: boolean, defaultfalse.undo()of onesetCellContentscall reverts its writes in reverse order. The result differs only when a later write in the call lands in the spill range of an array an earlier write entered.Out of scope and follow-ups
moveRowsandmoveColumns, with undo, is not implemented: moves never overwrite. No follow-up ticket yet.developwith the option off, not changed here: an array that shows#SPILL!keeps it after removing a column, or an undo, gives it room again; and undoing a row move inside the range an array reads changes that range (=TRANSPOSE(A3:A5)becomes=TRANSPOSE(A4:A5)). No follow-up ticket yet.Types of changes
Related issues
Checklist
dev-docs/pages this change affects – or none needed it.Note
Medium Risk
Opt-in destructive cell clearing on array spill affects dependency graph, evaluation, structural ops, and undo; default
falsepreserves Excel-compatible behavior.Overview
Adds an opt-in engine flag
arrayFunctionResultOverwritesData(defaultfalse). When enabled, array formulas that would normally hit#SPILL!on occupied non-array cells clear those occupants and spill instead; collisions with another array still return#SPILL!.DependencyGraphwires the config in, gates overwrite viacanOverwriteArrayResult/isOverwritingDataAllowed, suspends overwriting during row/column moves (withoutOverwritingData), and records content changes for cleared spill cells so the column index stays consistent. InitialbuildFromArrayplacement is unchanged—occupants declared in the same build still win.Operationssnapshots spill victims before placing formulas, returns them fromsetCellContentand fromaddRows/addColumnswhen arrays grow over static data, and updates column search on formula restore.Undo/redo restores snapshotted cells after reverting anchors or undoing row/column inserts that triggered growth overwrites;
setCellContentsundo order is fixed so a formula plus writes into its spill range no longer leaves a stray array.Docs and changelog document the option and the “Spilling onto occupied cells” behavior.
Reviewed by Cursor Bugbot for commit 7aecb9f. Bugbot is set up for automated code reviews on this repo. Configure here.