From: Javier Sagredo Date: Thu, 7 May 2026 00:03:36 +0000 (+0200) Subject: Document data-fam-instance skip in reduceTypeArg X-Git-Url: https://git.sagredo.dev/?a=commitdiff_plain;h=0d363423e88c0e8df2f3ca8bbbeb80e3e31a588e;p=classgraph.git Document data-fam-instance skip in reduceTypeArg Append a bullet to the resolution-determinism section explaining why reduceTypeArg skips fiIsData entries: the data-family R: rewrite makes their fiRhs structurally equal to the input FamilyApp, so naïve recursion looped forever. Also clarify the prior UndecidableInstances bullet — the reducer DOES recurse, it relies on structural progress in the RHS to terminate. Co-Authored-By: Claude Opus 4.7 (1M context) --- diff --git a/docs/INTERNALS.md b/docs/INTERNALS.md index f772fbf..72cd3b6 100644 --- a/docs/INTERNALS.md +++ b/docs/INTERNALS.md @@ -331,13 +331,28 @@ more nuanced. Here's what can and cannot vary: `1 + 1` where `(+)` is `GHC.TypeNats.+`, the viewer will stop at `+` rather than producing `2`. -- **`UndecidableInstances` / cyclic reduction.** The reducer is - iterative, not fixed-point: each call visits the structure once and - then bails. We don't loop forever on a divergent chain. In practice - this means a circular family like `type instance F a = G a` / - `type instance G a = F a` shows up as two separate fam-instance - nodes pointing at each other via `fiRhs` references, but no edge in - the resolution-chain layer. +- **`UndecidableInstances` / cyclic reduction.** `reduceTypeArg` + recurses on `applySubst(fi.fiRhs, subst)` after a matching + fam-instance is found, so a `type instance F a = G a` / `type + instance G a = F a` pair *would* loop forever if it weren't for + the fact that each step requires structural progress (the RHS + isn't structurally equal to the LHS). In practice such a circular + pair shows up as two separate fam-instance nodes pointing at each + other via `fiRhs` references, with no resolution-chain edge. + +- **Data fam-instances are skipped by `reduceTypeArg` entirely.** + After the data-family R: rewrite at extraction time (see + `Classgraph.Extract`'s `typeArg`), a data fam-instance's `fiRhs` + is structurally equal to its LHS — for `data instance Crate Int`, + `fiRhs = FamilyApp Crate [Int]`. Reducing `FamilyApp Crate [Int]` + through that fam-instance just produces `FamilyApp Crate [Int]` + again, so naïvely recursing through it loops forever. The reducer + guards against this by skipping any fam-instance with + `fiIsData = true`; data-family use sites then fall through + unchanged and are surfaced by the predicate-node + chain machinery + as the use sites they actually are. The synthetic R: TyCon (the + pre-rewrite form) was opaque for the same reason — we just never + hit the loop because the rewriter wasn't yet circular. So: *the data is deterministic and faithful, the chains are an honest best-effort visualization*. When in doubt, the chain edges in the