feat(document): answer which cells a formula reads - #886
Open
andiwand wants to merge 1 commit into
Open
Conversation
This was referenced Sep 11, 2026
`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
force-pushed
the
feat/sheet-dependencies
branch
from
September 11, 2026 22:15
de13e68 to
8f6aaa9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 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
internal::SheetDependencieswalks 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 byinternal::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 inunresolved_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 byTablePosition. 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.fodsand.xlsxfixtures: 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.