HF-131 (4/7): expose which function produced an error, and which argument - #1764
marcin-kordas-hoc wants to merge 13 commits into
Conversation
DetailedCellError.originFunction and .argumentIndex ride along from CellError, which already carried them internally since the origin/ propagation redesign -- this task is the public-exposure half. Two gaps found and closed while verifying, not assumed fixed by that earlier redesign: - FunctionPlugin.coerceArgumentsToRequiredTypes's two error-return sites never called withArgumentIndex, so a genuine per-argument coercion failure (e.g. `=DATE(2020,"x",1)`) reported argumentIndex: undefined instead of 1. Both sites now call it. - withArgumentIndex's gate checked only propagated/argumentIndex-already-set, missing the same originFunction check withOrigin already has. A nested call's own error (SQRT(-1) inside `=DATE(1,1,SQRT(-1))`) stamps its origin before DATE's own coercion loop ever sees it; without the extra guard that loop would still attach its own argument index to SQRT's error -- incoherent, since SQRT never had that argument. Verified via a same-formula, no-cell, no-propagation repro (the cross-cell version does not reproduce the bug, since the propagated gate alone already covers it). Every operator/function origin name asserted in the new specs was verified by direct probe against this worktree before being committed, including correcting one substituted-in test formula that turned out to exercise a different operator (multiply, not addWithEpsilon) than intended. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Task linked: HF-131 Verbose and actionable formula error messages |
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 1bb9db5. Configure here.
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
hyperformula-docs | efefce3 | Sep 22 2026, 04:12 AM |
Performance comparison of head (efefce3) vs base (915ca17) |
The doc predated the identities the later commits added, so it said a parsing error or a value typed into a cell reports undefined, when they report 'parser' and 'user input'. It now lists the vocabulary, and names the one case where an argument index can appear without an origin. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tobiadefami
left a comment
There was a problem hiding this comment.
Could we preserve the original argument index through range expansion? With A1:A3 containing 1, =AND(A1:A3,"x") reports argumentIndex: 3, but "x" is the second argument, so the index should be 1. The .withArgumentIndex(i) calls in FunctionPlugin.ts:488–492 use the flattened position.
Carrying the original index alongside each expanded value fixed this for AND, OR, and XOR in a temporary patch. All 164 tests in the selected suites passed, along with TypeScript and lint.
…nsion @Tobiadefami on PR #1764: with A1:A3 holding 1, =AND(A1:A3,"x") reported argumentIndex 3, but "x" is the second argument, so it should be 1. Reproduced first, before touching anything, and measured at 3. Cause: for a function with `expandRanges`, listOfScalarValues flattens every range into its cells before coercion runs, and coerceArgumentsToRequiredTypes then called withArgumentIndex with its position in that flattened list. The reported argument therefore depended on how many cells a preceding range happened to cover -- the same formula over A1:A2 and A1:A5 blamed arguments 2 and 5 for the same "x". Fix: listOfScalarValues now carries the originating argument index alongside each scalar, every value expanded out of one range keeping that range's own index, and runFunction threads the list down to the two withArgumentIndex call sites. The fourth tuple element is additive: a custom plugin that overrides listOfScalarValues with the old three-element shape yields undefined and falls back to the loop position, i.e. today's behaviour. Measured after the fix, on the same probes: =AND(A1:A3,"x") 1 (was 3) =AND(A1:A2,B1:B3,"x") 2 (was 5) =AND("x",A1:A2) 0 (unchanged -- already correct) =AND(TRUE(),"x") 1 (unchanged -- no range involved) Full suite 507 suites / 6297 tests green; eslint 0 errors and one warning fewer than before. Paired tests: hyperformula-tests 8604992. The changelog bullet is extended rather than given a Fixed entry, because argumentIndex has never shipped -- the guarantee it now states is that the index counts arguments as written, so it does not shift with the size of a range. The public JSDoc says the same, and also records the measured case where there is deliberately no index at all: a bad value INSIDE a range that the function ignores (=AND(A1:A2,TRUE()) over text returns TRUE). Worth naming, because it is the third time on this task: every worked example in the design, the ADR and this file's existing tests used scalar arguments. Nobody passed a range. Same shape as "first occurrence wins" (every example was a function inside a function) and originAddress (no CRUD probe was ever run against the claim). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up to the independent re-derivation. The fix also changed where a coercion failure INSIDE a range is attributed, and neither the JSDoc nor the changelog said so. The JSDoc's only inside-a-range sentence described the case where a function IGNORES the value (AND over text returns TRUE, so there is no error). For a function that coerces every value the error is real, and it is now attributed to the range's own argument slot rather than to a position derived from cell count -- which is the same contract, stated for the case the sentence did not cover. Also says plainly what the field is not: it names the argument to look at, not the offending cell, whose address this field never carried. Paired test: hyperformula-tests 275e955. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… stack
Two sentences described behaviour that arrives in 5/7 (fix/hf-131-rootless-origin),
not here. Measured at this branch's tip:
=DATE(2020,A99999999999,1) -> {originFunction:'DATE', argumentIndex:1}
while the JSDoc said an unresolvable reference "reports itself"
=DATE(2020,A1:A2,1) over text, array arithmetic
-> every element {argumentIndex:1, originFunction:undefined}
while the JSDoc said the element "carries the index alongside the
function's own name"
Both identical at 624a8e4, so neither is caused by this change -- but 624a8e4
is the commit that deleted the known-limitation wording for the second one, with
the message "now that it is fixed", and it was not fixed at that commit. So the
false sentences are this PR's, even though the behaviour is not.
Rewritten to state what the branch does and to name both cases as addressed later
in the change set. That way every sentence shipped is true at every tip of the
stack rather than only at the last one, which is what a per-PR reviewer needs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## feat/hf-131-has-message #1764 +/- ##
===========================================================
+ Coverage 97.39% 97.40% +0.01%
===========================================================
Files 195 195
Lines 15844 15848 +4
Branches 3494 3497 +3
===========================================================
+ Hits 15431 15437 +6
+ Misses 405 403 -2
Partials 8 8
🚀 New features to boost your workflow:
|

What and why
Stacked on #1763. Exposes on the public
DetailedCellErrortype the two fields theorigin/propagation mechanism (introduced in #1762) already computes internally:
originFunction?: stringandargumentIndex?: number— which function or operator produced acell error, and, when it failed coercing one of its own arguments, which argument that was.
Revision note: an earlier version of this description said this PR built the mechanism
itself (the two choke points,
Cell.ts's builders, etc.). That was wrong — the mechanism landedin #1762 (see its revised description). This PR only does the public-facing exposure step the
mechanism commit's own message called out as a separate, later concern:
This PR is that boundary: expose the two fields, and wire the one direct caller outside the two
choke points.
What actually changes here (4 files, +32/-3):
src/CellValue.ts—DetailedCellErrorgainsoriginFunction/argumentIndex, copied from theunderlying
CellErrorin the constructor. Additive only: enumerable property count onDetailedCellErrorgrows from 5 (type/message/value/address/hasMessagefrom HF-131 (3/7): a consumer can tell 'no message' from 'empty message' #1763) to 7.src/interpreter/plugin/FunctionPlugin.ts—coerceArgumentsToRequiredTypes's two error-returnpaths (wrong-type, non-scalar) now call
.withArgumentIndex(i)— the only direct callers ofthat builder outside the two choke points HF-131 (2/7): the origin/propagation mechanism, plus the same guarantee outside the interpreter #1762 introduced.
src/Cell.ts—withArgumentIndex's no-op gate also checksoriginFunction !== undefined, notjust
argumentIndex: needed so a nested call's own error (SQRT(-1)inside=DATE(1,1,SQRT(-1))) doesn't getDATE's argument index attached after already claiming itsown origin.
CHANGELOG.md— documents the two new public fields.Verified against nested/propagated cases directly:
=SUM(SQRT(-1))reportsSQRT, notSUM;=1/0then=SUM(A1)reportsdivide,propagated: true, not re-claimed bySUM;=DATE(1,1,SQRT(-1))reports{originFunction: 'SQRT', argumentIndex: undefined}, not a leakedindex on
DATE's argument slot.Known, deliberate limit: a binary operator's error names the operator (
addWithEpsilon,divide, etc.), not which operand failed —argumentIndexis only set for a function's owncoercion loop; the two operands of a binary op have no equivalent loop. Not solved here.
Verified
npx tsc --noEmit— cleannpx eslint src/— 0 errorspre-existing branch-pinning mismatch as the rest of this stack
CellErrorconstruction sites insrc/(carried over from HF-131 (2/7): the origin/propagation mechanism, plus the same guarantee outside the interpreter #1762;re-confirmed at this branch's tip)
Stack
4 of 7 — stacked on #1763. Next:
fix/hf-131-rootless-origin.🤖 Generated with Claude Code
Note
Medium Risk
Changes public error metadata and attribution rules during argument coercion; formula results should be unchanged but consumers and diagnostics depend on the new fields.
Overview
Exposes
originFunctionandargumentIndexon publicDetailedCellError, copying them from the internalCellErrorso callers can see which function/operator produced an error and which formula argument failed coercion.FunctionPluginnow tracks the user-written argument index through range expansion (listOfScalarValues/evaluateArgumentsadd a 4th tuple field) and passes it intocoerceArgumentsToRequiredTypes, which calls.withArgumentIndex(...)on wrong-type and non-scalar coercion failures so indices match the formula (e.g. a range counts as one argument, not N flattened cells).Cell.withArgumentIndexis tightened so it is a no-op onceoriginFunctionis already set, preventing outer functions from attaching their argument slot to errors that nested calls already claimed (e.g.SQRT(-1)insideDATE(...)).CHANGELOG documents the new fields and related error-metadata work in the HF-131 stack.
Reviewed by Cursor Bugbot for commit efefce3. Bugbot is set up for automated code reviews on this repo. Configure here.
Performance
The hot-path change lives in #1762 and is measured there: total median −0.24% over three alternating
base/head rounds against
origin/develop, within the run-to-run spread, with one CRUD benchmark("Column ranges - add column") consistently about 3% slower. This PR adds fields to an object that
is only built when an error is exported, so it carries no measurable cost of its own.