The specification · Normative text

Divergence between the shape classifier and the folder

Normative draft. One subset and two consumers with unequal information. This file states the direction in which they may disagree as a claim with a proof obligation, enumerates every known divergence, and states the two exceptions with the reason each is tolerated. Derived from subset.ts’s module doc and findSubsetViolation, and fold(), at e4074c17 with the chant-v0.63.0 addition.


F-Direction (the claim)

Let shape(e, ρ) be the classifier’s verdict on expression e given an optional registry ρ, and fold(e, Γ, ρ) the folder’s, given also an environment Γ.

Proposition. For every e, Γ, ρ: if fold(e, Γ, ρ) succeeds, then shape(e, ρ) accepts, except in the two cases F-Exc-Lazy and F-Exc-Registry below.

Equivalently, the classifier may accept what the folder rejects (a false negative relative to fold, visible only as a later fallback), and must never reject what the folder accepts (a false positive, visible as a lint error on folding code), outside the two named exceptions.

Why this direction. A false negative costs a fallback the author learns about from [fold:run]. A false positive costs a lint error on correct source and a lint that cries wolf gets disabled. The classifier is also the predicate a downstream tool asks “will this fold?”, a tool with no registry must get an answer that is safe to act on, which means erring toward “it may run” (subset.ts module doc, point 2c).

Discharge. By construction, both consumers import one classifier (subset.ts), so they cannot disagree on node kinds, operators or key shapes; every remaining disagreement is a resolution the classifier does not perform, enumerated below. And by fixture, one accepting and one rejecting fixture per row of F-Div, asserting the classifier’s and the folder’s verdicts side by side. chant’s subset-doc-parity.test.ts is the precedent.

F-Div (the divergences in the permitted direction)

Shape accepts; the folder may reject because it resolves.

RuleShape seesFolder additionally requiresRow
F-Div-Identany bare identifier is validresolves in consts then externals; process is a pointed rejectionL2.3, L3.6, L3.7
F-Div-Tagany tagged-template tag; interior opaquetag is a registered, tag-foldable intrinsic; interior foldsL2.4
F-Div-Provenancea registered helper namethe name is unbound or bound by an import from the host; a project binding is F-Eval-CallLocal’s instead, any other falls backL2.11
F-Div-SpreadTypea spread operand of valid shapeoperand folds to an object (object spread) or array (array spread)L3.4, L3.5
F-Div-SameFileNewa bare identifierif bound to a same-file new, only externals may answer, and F-Prebuild is what puts the instance there; a folder that cannot construct rejects rather than build a duplicateL3.8
F-Div-Nullisha.b, a[k]the object does not fold to null/undefined; ?. short-circuits instead (chant-v0.63.0)L3.10, L3.21
F-Div-NsNewnew ⟨Expr⟩(…) with any calleethe callee is a plain identifierL3.15
F-Div-Methodx.m(…)the receiver folds to a real value that is not an envelope and whose m is a functionL3.18, L3.19
F-Div-Step⟨call⟩.stepthe callee is an unclaimed bare identifierL3.20
F-Div-Deptha new, tagged template, helper call, intrinsic call, or .stepis not inside a folded function bodyL3.16
F-Div-Eagera registered eager intrinsic nameresolves to a function; is called, not referencedL3.17
F-Div-TemplateEnvelopea member access or call inside a template spanthe span does not fold to an envelope; chant refuses all five reachable kinds since v0.69.0L3.23

Every row is a fallback. That property, a divergence in this direction can only lose coverage, is what makes the direction the safe one.

F-Exc (the two exceptions)

Shape rejects; the folder accepts. Each is stated with why it is tolerated rather than fixed.

F-Exc-Lazy. Short-circuit laziness. The folder evaluates &&, ||, ?? and ?: lazily and never folds the untaken operand; the classifier is flow-insensitive and requires every operand to be shape-valid (L2.9, L3.13). false && sideEffect() folds to false and is a lint error. Tolerated because: a flow-sensitive classifier is an evaluator, and the cost of the false positive is a visible lint error on code whose untaken branch is suspect anyway. The implementation’s own module doc calls it a wart, and this file keeps the word.

F-Exc-Registry. Call-form registration without a registry. The classifier takes ρ as an optional parameter. Given one, its answer for a plain call is the folder’s own; without one, every plain call is a violation, including Ref(bucket) that the folder accepts (L2.12, L2.13). Tolerated because: the registry-less answer is the conservative one, it is a parameter rather than a guess, and the lint engine now threads the active lexicons’ registry through so the exception is closed in practice for chant lint while remaining true of the classifier in isolation.

There is no third exception. An implementation that discovers one has found either a bug or a new rule and must add it here before shipping it.

What this file does not claim

That the classifier and the folder agree. They do not and the enumeration above is the point. The claim is narrower and more useful: every disagreement outside F-Exc costs coverage, never correctness, and the two that go the other way are named, bounded, and justified.