Skip to content

HF-131 (5/7): no error reports a cell that did not produce it - #1765

Open
marcin-kordas-hoc wants to merge 17 commits into
feat/hf-131-origin-functionfrom
fix/hf-131-rootless-origin
Open

marcin-kordas-hoc wants to merge 17 commits into
feat/hf-131-origin-functionfrom
fix/hf-131-rootless-origin

Conversation

@marcin-kordas-hoc

@marcin-kordas-hoc marcin-kordas-hoc commented Sep 10, 2026 •

Copy link
Copy Markdown
Collaborator

Context

The PR stops a function or operator from claiming an error it only read, and gives #SPILL! errors an address. It is part 5 of 7 of HF-131, stacked on #1764.

  • Why: an error that exists before any function sees it, such as a broken reference, reached the enclosing call without an origin, so that call claimed it. =SUM(A2:A99999999999) reported SUM. #SPILL! errors had no address.
  • What changed:
    • The parser, the interpreter, DependencyGraph.ts, the dependency transformers and NumericAggregationPlugin.ts set the origin where they build the error: reference, removed reference, literal or OFFSET.
    • ParsingErrorVertex.ts and CellContentParser.ts: parser and user input.
    • Interpreter.ts: an array literal whose rows differ in length reports array literal. An error inside an array result names the operation that built the array.
    • FormulaVertex.ts: setNoSpace() makes the formula vertex the root of its #SPILL! error.
    • JSDoc of originFunction and argumentIndex, and docs/guide/known-limitations.md: the full list of origin names.
  • Result:
Formula Before (#1764) After
=SUM(A2:A99999999999) SUM reference
=DATE(2020,A99999999999,1) DATE, argumentIndex: 1 reference, no index
=SUM(Sheet1!A2:Sheet2!B2) SUM reference
=SUM(A2) after row 2 is removed SUM removed reference
=SUM({1,2;3}) SUM array literal
=INDEX(A1:A2+B1:B3,3,1), array arithmetic on INDEX addWithEpsilon
=1+ none parser
#REF! typed into a cell none user input
=#REF! none literal
=SEQUENCE(2) blocked by a value below address: none address: Sheet1!A1

How did you test your changes?

  • Paired tests: handsontable/hyperformula-tests#63 on fix/hf-131-rootless-origin, in unit/error-origin-function.spec.ts and unit/error-address-preservation.spec.ts.
  • At the previous head, these two specs passed against this PR's code (45 tests); 15 of them failed against HF-131 (4/7): expose which function produced an error, and which argument #1764's head.
  • The two new cases for a range that SUM or MAX reads (several sheets, a missing sheet) fail without the NumericAggregationPlugin.ts change, reporting SUM.
  • CI is green on this head, except the repository-wide FOSSA license check.

Compatibility with Excel and Google Sheets

Not applicable: error types and displayed values do not change.

Public API changes

  • New values of DetailedCellError.originFunction: reference, removed reference, parser, user input, literal, array literal.
  • DetailedCellError.address of a #SPILL! error is now the cell whose array could not spill, instead of undefined.

Out of scope and follow-ups

  • #SPILL! and #CYCLE! keep originFunction undefined, because no function produced them. This is stated in known-limitations.md.
  • The name removed reference is open for review: it follows the other lowercase names (parser, user input, literal).

Types of changes

  • Breaking change (existing functionality no longer works as before) – agreed with the maintainers
  • New feature or improvement (a non-breaking change that adds functionality)
  • Bug fix (a non-breaking change that fixes an issue)
  • Additional language file, or a change to an existing language file (translations)
  • Change to the documentation

Related issues

  1. Part of Improve the formula error messages #1547 (HF-131).

Checklist

An error read out of a neighbouring cell is not produced by the cell that
read it. This closes the remaining rootless-origin cases the choke-point
redesign's propagated-skip needed but did not itself provide identities for:

- A parsing error is attributed to 'parser' (ParsingErrorVertex.getCellValue).
- An error value typed directly into a cell is attributed to 'user input'
  (CellContentParser's CellContent.Error constructor).
- An error literal written into a formula (e.g. =#VALUE!+1) is attributed to
  'literal' (FormulaParser's ErrorLiteral alternative).
- A #SPILL! error reports the cell that failed to spill, not no address at
  all: setNoSpace() attaches itself as root, since it already has 'this' in
  hand.

getScalarValue's range-to-scalar coercion class was measured, not assumed:
the requesting cell is genuinely the one that made the invalid request, so
'no address' there is honest, not a bug, and is left unchanged.

known-limitations.md gains one bullet: the six buildErrorWithRawInputAst
sites in FormulaParser.ts (malformed references) stay unattributed rather
than mislabeled, pending a future task.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@qunabu

qunabu commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 10, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
hyperformula-docs ae97e21 Commit Preview URL

Branch Preview URL
Oct 23 2026, 07:18 AM

@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown

Performance comparison of head (ae97e21) vs base (e745be2)

                                     testName |    base |    head | change
--------------------------------------------------------------------------
                                      Sheet A |  486.32 |  491.26 | +1.02%
                                      Sheet B |  155.64 |  153.02 | -1.68%
                                      Sheet T |  140.13 |  138.64 | -1.06%
                                Column ranges |  464.97 |  466.52 | +0.33%
                                Sorted lookup | 13714.2 | 14803.9 | +7.95%
Sheet A:  change value, add/remove row/column |   14.63 |   15.33 | +4.78%
 Sheet B: change value, add/remove row/column |  124.81 |  125.58 | +0.62%
                   Column ranges - add column |  148.27 |   145.5 | -1.87%
                Column ranges - without batch |   449.4 |  456.22 | +1.52%
                        Column ranges - batch |  114.06 |  115.17 | +0.97%

@marcin-kordas-hoc

Copy link
Copy Markdown
Collaborator Author

Ran a closed-loop comparison of this stack's origin/address behavior against real Excel via
MS Graph (13 scenarios, full write-up + raw evidence in a private handoff repo — happy to share the
methodology if useful). Everything on the CRUD-address side of this PR checked out — a REF error
from a deleted column reports the same type as Excel and the address stays consistent before/after
the delete.

One thing surfaced that isn't a bug, but is worth a product call: the CRUD-produced #REF! from
Transformer.ts's column/row-removal branches never calls .withOrigin(...), so originFunction
comes back undefined for it — confirmed live, the field is genuinely absent, not empty. That's
consistent with how the mechanism works (an AstNodeType.ERROR node has no entry in
originNameForAstNode, so the stamp is a no-op), but neither this PR's own "deliberate limits"
nor known-limitations.md's existing bullet (which only covers the 6 parser reference-checking
branches) actually says whether this specific case is intentional.

Should a CRUD-removed reference get an originFunction too (e.g. something like 'row/column removed'), or is leaving it unattributed here the right call, same as the parser branches? Either
answer is fine by me — just flagging that it isn't currently written down either way.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread CHANGELOG.md Outdated
marcin-kordas-hoc and others added 8 commits September 11, 2026 05:31
An error that describes a broken reference exists before any function sees it, so
the enclosing call is only a reader. originNameForAstNode returns undefined for
reference and error nodes, so such an error reached the enclosing FUNCTION_CALL
unclaimed and non-propagated, and the postprocessing tail handed it to that call:
=SUM(A2:A99999999999) reported SUM, =ABS(A2:A99999999999) reported ABS with an
argument index that belonged to nothing, and =SUM(#REF!) after removing rows
reported SUM.

The identity is now attached where the error is built, which lets the existing
first-wins rule block the reader by itself. A reference that cannot be resolved
reports 'reference', whether the parser rejected it, the sheet is missing, the
range spans several sheets, or the name is unknown; one destroyed by removing rows
or columns reports 'removed reference'; the out-of-sheet error raised inside the
OFFSET heuristic reports 'OFFSET', because OFFSET really did produce it.

Errors that arise inside an array result are unchanged and still take the reading
function's name; that needs either a hook over every ad-hoc array element or the
function name threaded through three methods of the plugin base class, so it is
left out and stated in the known limitations.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…that made it

An error that is one element of a returned array never reached the scalar branch of
evaluateAst's tail, so it arrived at the enclosing call unclaimed and that call took
it: =INDEX(A1:A2+B1:B3, 3, 1) reported INDEX for an #N/A the + operator produced, and
a per-element coercion failure carried an argumentIndex with no originFunction at all,
which the public JSDoc promises never happens.

The tail now stamps the elements of an array the node itself built, which is the same
first-wins rule applied one level down. A range-backed value is skipped: it is a view
over cells whose errors were already marked when they were read, and reading its data
would materialise the whole range on the hot path. That guard is not cosmetic —
without it 'Sheet A' measured +5.7% against a 0.6% base spread; with it, four
alternating rounds against the branch point give -1.09% overall and nothing outside
its own spread.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2a81d2f. Configure here.

Comment thread hyperformula/src/interpreter/Interpreter.ts
Bugbot caught a site the previous commit missed, in the same family. An array literal
whose rows do not line up builds a #REF! before any call sees it, and originNameForAstNode
had no case for that node, so the enclosing call took it: =SUM({1,2;3}) reported SUM and
=ABS({1,2;3}) reported ABS with an argument index that belonged to nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@Tobiadefami Tobiadefami left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation looks good.

marcin-kordas-hoc and others added 5 commits October 22, 2026 17:19
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…elog to released behaviour

The originFunction JSDoc and the known-limitations entry now name every origin the engine
produces, including `array literal`, and no longer describe two argumentIndex gaps that this
change closes. The changelog drops the entries for origin behaviour that was never released,
folds the origin names into the entry that adds the field, and keeps the `#SPILL!` address fix.
The JSDoc of the scalar origin stamp sits above its own function again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hat spans sheets or a missing sheet

`SUM`, `MAX` and the other aggregates read a range through their own path, which built the
`#REF!` for a range spanning several sheets or pointing at a missing sheet without an origin,
so the aggregate claimed it. Both errors now report `reference`, like the same errors built
by the interpreter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.87234% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 97.38%. Comparing base (e745be2) to head (ae97e21).

Files with missing lines Patch % Lines
hyperformula/src/interpreter/Interpreter.ts 96.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@                       Coverage Diff                       @@
##           feat/hf-131-origin-function    #1765      +/-   ##
===============================================================
+ Coverage                        97.36%   97.38%   +0.02%     
===============================================================
  Files                              207      207              
  Lines                            16327    16343      +16     
  Branches                          3624     3630       +6     
===============================================================
+ Hits                             15896    15916      +20     
+ Misses                             423      419       -4     
  Partials                             8        8              
Files with missing lines Coverage Δ
hyperformula/src/CellContentParser.ts 100.00% <100.00%> (ø)
hyperformula/src/CellValue.ts 83.33% <ø> (ø)
...yperformula/src/DependencyGraph/DependencyGraph.ts 98.82% <100.00%> (ø)
hyperformula/src/DependencyGraph/FormulaVertex.ts 84.25% <100.00%> (ø)
...rformula/src/DependencyGraph/ParsingErrorVertex.ts 100.00% <100.00%> (ø)
...rformula/src/dependencyTransformers/Transformer.ts 98.70% <100.00%> (ø)
...src/interpreter/plugin/NumericAggregationPlugin.ts 97.23% <100.00%> (ø)
hyperformula/src/parser/FormulaParser.ts 98.09% <100.00%> (+0.47%) ⬆️
hyperformula/src/interpreter/Interpreter.ts 96.39% <96.00%> (+0.45%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants