oficina / sheets Public

Fix round 2: reword two comments to name real mechanisms

graph.go's collectRefs comment claimed an over-cap range's precedents
become stale until 'a full recalc walks it again' — no such
full-recalc primitive exists in this API (only Recalc(origin), scoped
to transitive dependents, and Value(key), a single fresh walk).
Reworded to state what actually makes the missing edge safe: evalCallArg
applies the identical maxRangeCells cap to the same Range at evaluation
time, so an untracked range's formula is always #REF! and never reads
live data from it — there is no stale-but-plausible value for the
missing edge to fail to invalidate.

eval.go's maxRangeCells comment called the trigger a 'malformed or
pathological reference'; the realistic trigger is an ordinary
whole-column formula (e.g. SUM over A1:A200000) on a tab at its own
200,000-cell cap, not a malformed one. Reworded to describe that
trigger accurately.

Comment-only changes; no behaviour differs.
Paul Campbell 3d47a5824b7667f64b377aebda4bc727be1d438b parent 828c1f2
2 files changed, +19 −15
  • internal/sheets/calc/eval.go +11 −10
  • internal/sheets/calc/graph.go +8 −5
diff --git a/internal/sheets/calc/eval.go b/internal/sheets/calc/eval.go
index c7ad758..7910ec8 100644
--- a/internal/sheets/calc/eval.go
+++ b/internal/sheets/calc/eval.go
@@ -70,16 +70,17 @@ func refKey(r *Ref) CellKey {
// maxRangeCells caps how many cells a single Range expands to in
// rangeCells. The spec's own hard ceiling is 200,000 cells per tab (see
-// spec §4.4's budgets), so no legitimately addressable range can cover
-// more than that — this cap sits at exactly that ceiling, which means it
-// can only ever fire on a malformed or pathological reference, never on a
-// real one. Without it, a single formula's Range — e.g. one spanning a
-// tab at its own cap — would synchronously materialize up to 200,000
-// CellKeys, twice over: once as call arguments when a range-taking
-// function evaluates it (evalCallArg), and again as dependency-graph
-// precedent edges every time SetFormula parses that same formula
-// (collectRefs). This is rangeCells' fan-in counterpart to Recalc's
-// maxDirtyPerPass, which caps the symmetric fan-out case.
+// spec §4.4's budgets), so no addressable range can cover more than
+// that — this cap sits at exactly that ceiling. The realistic way to hit
+// it is not a malformed reference but an ordinary whole-column formula —
+// e.g. a SUM over A1:A200000 on a tab sized at its own cap — from a user
+// who has no reason to know they've asked for every cell the tab has.
+// Without this cap, that single formula's Range would synchronously
+// materialize up to 200,000 CellKeys, twice over: once as call arguments
+// when a range-taking function evaluates it (evalCallArg), and again as
+// dependency-graph precedent edges every time SetFormula parses that same
+// formula (collectRefs). This is rangeCells' fan-in counterpart to
+// Recalc's maxDirtyPerPass, which caps the symmetric fan-out case.
const maxRangeCells = 200_000
// rangeCells expands a Range into every CellKey it covers, normalizing a
diff --git a/internal/sheets/calc/graph.go b/internal/sheets/calc/graph.go
index 9dff996..8b881c8 100644
--- a/internal/sheets/calc/graph.go
+++ b/internal/sheets/calc/graph.go
@@ -77,11 +77,14 @@ func (g *Graph) SetFormula(key CellKey, n Node) {
// precedent/dependent edges.
//
// A *Range over maxRangeCells is left untracked here rather than adding
-// hundreds of thousands of edges for one formula: the formula still
-// evaluates correctly (evalCallArg turns the same oversized range into a
-// #REF! argument), but a change to a cell inside that range will not by
-// itself dirty this formula until a full recalc walks it again. Disclosed
-// trade-off, not a silent one — see maxRangeCells.
+// hundreds of thousands of edges for one formula. This is safe, not just
+// cheap: evalCallArg applies the exact same cap to the same Range when
+// the formula actually evaluates, turning it into a single #REF!
+// argument (see maxRangeCells) — so a formula with an untracked range
+// never reads live data from that range in the first place. There is no
+// stale-but-plausible value for a missing edge to fail to invalidate;
+// the formula is #REF! until its Range shrinks under the cap, at which
+// point re-parsing it (SetFormula) tracks it again.
func collectRefs(n Node) []CellKey {
var out []CellKey
var walk func(Node)