feat(config): add arrayFunctionResultOverwritesData opt-in overwrite flag (HF-305) - #1714
feat(config): add arrayFunctionResultOverwritesData opt-in overwrite flag (HF-305)#1714marcin-kordas-hoc wants to merge 6 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 (e410ed0) vs base (114fd5d) |
|
bugbot run |
4c02791 to
d2a2671
Compare
|
bugbot run |
d2a2671 to
5e397d1
Compare
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 5e397d1. Configure here.
5e397d1 to
2f48fba
Compare
2f48fba to
b877ed4
Compare
|
bugbot run |
b877ed4 to
467c186
Compare
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 467c186. Configure here.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
hyperformula-docs | e410ed0 | Commit Preview URL Branch Preview URL |
Aug 31 2026, 07:37 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. |
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.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 08f5a06. Configure here.
…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 |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #1714 +/- ##
===========================================
+ Coverage 97.32% 97.34% +0.01%
===========================================
Files 195 195
Lines 15739 15790 +51
Branches 3390 3402 +12
===========================================
+ Hits 15318 15370 +52
+ Misses 421 420 -1
🚀 New features to boost your workflow:
|

Implements HF-305. Paired tests: hyperformula-tests#45 (branch
feat/hf-305-overwrite-flag, same name — merge it first).What & why
Adds an opt-in boolean config option
arrayFunctionResultOverwritesData(defaultfalse).false(default): unchanged behavior — an array function spilling onto an occupied cell yields#SPILL!and leaves the occupant intact.true: the spill overwrites the occupied cells (clears occupants, spills the array, reroutes dependents) instead of emitting#SPILL!.Requested by a customer; the feature + option name were pre-agreed (~8 SP).
How
Reuses the existing spill-placement exchange primitive rather than inventing a mechanism:
ConfigParams/Config: new option, mirrored onuseArrayArithmetic.DependencyGraphgainsconfig+ two guarded early-returns (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). Matches Excel; overwrite only clears static data. Defaultfalseguarantees no silent data loss.Tests
They live in hyperformula-tests#45,
unit/array-function-result-overwrites-data.spec.ts— 53 cases in nine groups: configuration parsing · disabled (default) parity · enabled overwrite incl. dependent reroute and 2-D blocks · array-vs-array collisions keeping#SPILL!· undo and redo · column-index staleness (incl.addColumnsgrowth) · structural operations · cycles · unrepresentable spill ranges.They arrived on this branch as
test/hf-305-overwrite.spec.ts, in the public repository. Specs do not live here — #1612 took every test out ofhandsontable/hyperformula, which is whytest/ondevelopcarries only the setup files,fetch-tests.shandsmoke.spec.ts. Jest'stestMatchdid pick the file up, so the suite was running; it was running from the wrong repository. Moved inf051ab3.Verification of the move:
src/Operations.ts(29) andsrc/DependencyGraph/DependencyGraph.ts(17) is exercised by that spec alone — measured by intersecting this PR's added lines with a coverage run of the single file. That is what the redcodecov/patchwas reporting.canOverwriteArrayResultforced tofalse→ 22 failures; the snapshot guard flipped (<= 1→>= 1) → 6; theEmptyCellVertexguard on the overwrite content-change inverted → 3; the array-vs-array guard removed → 3.Known limitations
The opt-in overwrite behavior is intentionally scoped to the direct spill/recalc path. The following edges are not fully handled yet; each is a nuance to weigh, not a silent-corruption risk (the default
falseconfig is fully backward compatible and untouched by all of these):buildFromArrayinitial-build path: declaring an array formula and a conflicting occupant in the same initialbuildFromArraycall still lets the occupant win — overwrite only applies on the recalc /setCellContentspath (existing data, then an array formula lands on it), not the un-gated initial-build placement path.Undo does not restore cells overwritten via direct— no longer true, and this line was stale.setCellContentssetCellContentnow returns{ oldContent, overwrittenCells }from a pre-spill snapshot, andSetCellContentsUndoEntryrestores them after the anchor formula is reverted. Pinned by eight undo/redo cases (value, formula, multiple occupants, rerouted dependent, redo, expand-over-static, blocked array-vs-array).addColumns/addRows/removeColumns/removeRowsshifts a dependency so an existing array grows over a previously-static cell, the overwrite itself is handled correctly for evaluation and foruseColumnIndex(the stale value is dropped from the index at the moment of overwrite — seerewriteAffectedArrays), and both are pinned by tests. What no test covers either way is whether undoing that structural op restores the static cell. Measured for the direct-edit expand path — it does restore — but the structural path was not reproduced. This is the item for the reviewer to weigh, and it is narrower than the version this section previously described.useColumnIndex+ undo, on the structural path only: a workflow relying onundo()to roll back an overwrite that a structural op caused may see the array formula disappear while the previously-static data stays gone; the index and the data would then be consistent with each other but not with the pre-overwrite state. On the direct-edit path this no longer applies —undorestores both the cell and its index entry, and that is pinned.None of these limitations can produce the crash or stale-lookup-index bugs originally flagged by Bugbot on this PR — both were reproduced with throwaway probes and fixed at the root cause (see review thread replies on commit
b877ed4). What remains out of scope is exclusively the undo leg of overwrite bookkeeping.Note
Medium Risk
Opt-in destructive sheet mutation in dependency graph and CRUD/undo paths; default
falsepreserves Excel-compatible behavior, but enabled mode can clear live cell data and depends on correct column-index and undo bookkeeping.Overview
Adds
arrayFunctionResultOverwritesData(defaultfalse). When enabled, array formulas that would spill onto occupied non-array cells clear those cells and place the spill instead of returning#SPILL!. Collisions with another array or an unrepresentable spill range still yield#SPILL!; initialbuildFromArrayplacement is unchanged (occupant wins).Dependency graph:
DependencyGraphnow holdsconfigand usescanOverwriteArrayResult/overwriteWouldHitArraysoexchangeOrAddFormulaVertexcan claim occupied spill cells, record content changes for the column index, and only then evaluate. The graph build path (setAddressMappingForArrayVertex) does not use overwrite mode.CRUD & undo:
setCellContentreturns{ oldContent, overwrittenCells }with a pre-spill snapshot of cleared occupants;SetCellContentsUndoEntryrestores them after reverting the anchor formula.restoreCell/rewriteAffectedArraysapply column-index updates so stale lookup values are not left after overwrite or undo on the direct-edit path.Reviewed by Cursor Bugbot for commit e410ed0. Bugbot is set up for automated code reviews on this repo. Configure here.