Skip to content

feat(document): answer which cells a formula reads - #886

Open
andiwand wants to merge 1 commit into
feat/shared-formulasfrom
feat/sheet-dependencies
Open

feat(document): answer which cells a formula reads#886
andiwand wants to merge 1 commit into
feat/shared-formulasfrom
feat/sheet-dependencies

Conversation

@andiwand

@andiwand andiwand commented Sep 11, 2026

Copy link
Copy Markdown
Member

🤖 Generated with Claude Code

Stacked on #885, which is stacked on #884 — this PR's diff is the third commit.

Step 3.2 of docs/design/spreadsheet-editing.md.

What it does

document.dependents(SheetPosition(0, 0, 0));   // who reads Sheet 1's A1?
document.dependents({a, b, c});                // one walk for a batch of edits
document.unresolved_formulas();                // who might read anything?

internal::SheetDependencies walks every sheet, parses every formula, and resolves each reference to the rectangle of a sheet it reads. A query then walks that reverse map transitively, so a formula reading a formula is named too.

Where it lives, and why

The graph is built the first time it is asked and kept on the document: writing a formula is refused until step 4, so nothing a write does changes it. It is reached through abstract::Document::sheet_dependencies() and held by internal::Document, which every engine derives from — so no engine writes the graph itself.

What each engine does write is one hook: SheetAdapter::sheet_visit_formulas, which hands out the cells the file spells rather than the positions they cover. That distinction is not cosmetic — a repeated ODS row of 1024 columns over 1048576 rows is a handful of nodes and a billion positions. odf walks the runs the parser indexed, ooxml its cell map, and an engine that drops the expression at parse time (.xls, .numbers) visits none.

The honest answer for what it cannot read

A formula that names something no position can be read out of — one that does not parse, a named expression (SUM(Sales)), a reference over several sheets (Sheet1:Sheet3!A1) — is in unresolved_formulas() rather than silently reading nothing. It may read anything, and a caller that has to be right (#888, dropping a stale cached value in an .ods) treats those as dependent on everything.

A reference into another document is neither a dependent nor unresolved: no edit here reaches it.

Public surface

SheetPosition — the sheet by its place among the document's sheets, which is how an edit operation names one, and the cell by TablePosition. Ordered and hashable, like its neighbour.

Not bound yet (python/jni/apple/wasm). The host-facing shape of this is step 4's recalculate(operations) → changed cells; binding the graph query on its own can follow if it's wanted sooner.

Test

14 cases in test/src/sheet_dependencies_test.cpp, from inline .fods and .xlsx fixtures: the chain, ranges, whole columns, cross-sheet by name (without case), the batch, both unresolved kinds, and that every member of a shared group reads its own row.

`internal::SheetDependencies` walks every sheet of a decoded document, parses
every formula it states and resolves each reference to the rectangle of a
sheet it reads. `Document::dependents(position)` then answers which cells read
that position, directly or through another formula, sorted and each named
once. A batch of positions costs one walk rather than one per position.

The graph is built the first time it is asked and kept on the document:
writing a formula is refused, so nothing a write does changes it. It is
reached through `abstract::Document`, and `internal::Document` holds it, so
no engine writes any of this - the walk goes through the element adapter
every engine already fills.

A formula that names something no position can be read out of - one that does
not parse, a named expression, a reference over several sheets - is in
`unresolved_formulas()` instead. It may read anything, and the graph says so
rather than claiming it reads nothing. A reference into another document is
neither: no edit here reaches it.

A position is the new `SheetPosition`: the sheet by its place among the
document's sheets, which is how an edit operation names one, and the cell by
`TablePosition`.

Step 3.2 of `docs/design/spreadsheet-editing.md`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVmjmddv2Ui17Nptc1ggue
@andiwand
andiwand force-pushed the feat/sheet-dependencies branch from de13e68 to 8f6aaa9 Compare September 11, 2026 22:15
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.

1 participant