ADR-0021 · Effect/grade typing corrections surfaced by the STD block
-
Status: Accepted
-
Summary: Effect/grade typing corrections surfaced by the STD block — makes preservation/progress/type_safety provable; advances Q4.
-
Depends-on: 0019, 0020
-
Status: Accepted
-
Date: 2026-06-22
-
Layer: K (kernel — typing rules, multiplicity algebra, theorem statements)
-
Amends: the Phase-A part-2 typing rules in
Bang/Core/Typing.lean(the "first cut"lam/handlerules) and theMultbound inBang/Core/IR.lean -
Advances: OPEN_QUESTIONS Q4 (
handlerule) — the F-type restriction lands here; the label-removing refinement stays deferred -
Related: Torczon et al. OOPSLA 2024 —
effects/CBPV/typing.v(effect threading) +resource/CBPV/typing.v(grade arithmetic); we merge both variants (effect onU, coeffect onF, per ADR-0019/0020)
Context
subst_value proven (ADR-0020, e00ee9a), the handoff framed the rest of the
STD block — preservation → progress → type_safety — as "downhill, largely
mechanical." Attempting preservation showed it is not: the Phase-A part-2
typing rules (a deliberate "first cut") diverge from the cited Torczon source in
four ways that make the frozen statements false as written. Each was traced
against /tmp/cbpv-ec (the plclub/cbpv-effects-coeffects port).
C1 — lam discards the body's latent effect
first cut: HasCTy (q :: γ) (A :: Γ) M φ B → HasCTy γ Γ (lam M) ⊥ (arr q A B)
^^^ φ dropped
The β-redex app (lam M) v ↦ M[v] (Comp.subst v M) then breaks preservation:
inverting app then lam, the redex is typed at effect ⊥, but the reduct
M[v] genuinely carries the body effect φ. The conclusion
∃ e', e' ≤ e ∧ HasCTy γ Γ M[v] e' B with e = ⊥ demands φ ≤ ⊥, i.e.
φ = ⊥ — false for any effectful function body.
Torczon effects/CBPV/typing.v:
T_Abs M A B ϕ : CWt (A .: Γ) M B ϕ → CWt Γ (cAbs M) (CAbs A B) ϕ
The lambda carries its body's effect ϕ. This is the effect-on-judgment
discipline our Core already commits to: VTy.U carries Eff, CTy.arr does
not, and HasCTy threads a separate effect index. force/vthunk already
honour it (effect rides U); lam was the lone violator. Constructing a closure
is operationally pure, but its type-level effect is the latent body effect —
exactly as a thunk's type carries the effect it will release when forced.
C2 — handle over a non-F body is a stuck normal form
first cut: HasCTy γ Γ M φ B → HasCTy γ Γ (handle h M) φ B (any B)
With B general, handle h (lam M') is well-typed at arr type, Source.step
has no rule for it (the body is a lam, not ret/up, and lam doesn't step),
and it is not a ret — a stuck non-terminal. Worse, handle h (handle h' (lam …))
nests, so the well-typed-closed normal forms at arr type are an unbounded family
handle* (lam …) — progress cannot hold for general B. Handlers handle
returners (the return clause consumes the produced value); restricting the
body to F q A removes the pathological normal forms and is the standard CBPV
handler typing.
C3 — the letC β grade reshape needs commutative multiplicity
letC (ret v) N ↦ N[v]. Inverting ret gives γ₁ = q1 • γ_v; N's bound slot
is graded q1 * q' (q' = q_or_1 q2); subst_value yields grade
γ₂ + (q1*q') • γ_v, while the letC rule's own grade is
q' • γ₁ + γ₂ = q' • (q1 • γ_v) + γ₂ = (q'*q1) • γ_v + γ₂ (associativity only).
Equating them needs q1*q' = q'*q1 — commutativity of *.
Torczon's POSR (common/coeffects.v) deliberately omits mult assoc/comm ("some
of our proofs do not require … commutativity") — but Torczon's resource variant
proves soundness semantically (semtyping.v/soundness.v, a logical
relation), never a syntactic letC preservation, so it never incurs this
obligation. We do syntactic preservation, so we incur it. The concrete instance
Bang.QTT is already a CommSemiring, and quantitative-type-theory multiplicity
semirings are commutative across the literature (Atkey ICFP'18, McBride,
Granule/Orchard ICFP'19). Strengthening the bound is the honest statement of the
algebra we actually use.
C4 — progress is stated for general B with isReturn
first cut: HasCTy [] [] c e B → isReturn c ∨ ∃ c', step c = some c'
A bare lam M is closed-typeable at arr type, is not a ret (isReturn is
false), and does not step — a direct counterexample. progress is true only when
the conclusion's terminal predicate matches the type: at F q A, the closed
terminal forms are exactly ret v. State it at F q A, which is also all
type_safety needs.
Decision
Align the four rules/statements with the merged Torczon discipline:
| # | site | from | to |
|---|---|---|---|
| C1 | HasCTy.lam (Syntax.lean) | conclusion effect ⊥ | conclusion effect φ (body effect) |
| C2 | HasCTy.handle (Syntax.lean) | body B (any CTy) | body CTy.F q A |
| C3 | Mult bound (Core.lean + all modules) | [Semiring Mult] | [CommSemiring Mult] |
| C4 | progress (Spec.lean) | general B, isReturn | CTy.F q A, isReturn |
With these, the STD block proves:
- preservation — case analysis on
Source.step+ inversion; β-cases use the provensubst_value; C1 makesappgivee' = e, C3 makes theletCgrades align, theup-headhandlecases are vacuous (upis untypable, Q5). - progress — induction on the derivation with the generalized terminal motive
isReturn c ∨ isLam c ∨ ∃ c', step c; C2 keepshandlebodiesF-typed so the case reduces; specialized toF q A,isLamis excluded by inversion, leavingisReturn ∨ steps. - type_safety — fuel induction over
progress(F) +preservation.
Rationale
- Make the false statement true, never weaken to dodge (proof discipline,
docs/notes/OPEN_QUESTIONS.mdpreamble). These are corrections toward the reference, not away from it — eachtocell is what Torczon's mechanization does. - Effect-on-judgment is already the architecture. C1 is not a new design; it
restores the one rule that violated the
U-carries-effect /arr-doesn't discipline baked intoCore(ADR-0019/0020). - Correctness by construction. C2 removes the stuck normal forms structurally (ill-typed), rather than detecting them in a side-condition.
- Honest algebra. C3 names the commutativity we already rely on (QTT) instead
of hiding it by reordering a rule's
*(which would diverge from Torczon'sT_Letshape for no reason).
Rejected alternatives
| option | why not |
|---|---|
C1: annotate CTy.arr with the latent effect (arr q A φ B), keep lam pure | A second home for effects, contradicting Core's commitment that effects ride U/the judgment and arr is effect-free. Two mechanisms for one fact (SoT violation). The lam-judgment fix is one mechanism, matches T_Abs. |
C2: keep handle general, state progress with a broad normal-form predicate covering handle* (lam …) | The terminal set becomes an unbounded syntactic family; every downstream theorem reasons about non-returner handles that have no operational meaning. F-restriction is the standard, finite story. |
C3: reorder letC's cons-head to q' * q1 so associativity alone closes it | A representation hack that hides the real requirement and diverges from resource/…T_Let. Multiplicities are commutative here; say so. |
C4: keep general B, add an effect/type-subsumption rule to absorb the mismatch | Subsumption is an orthogonal, ordered-Mult feature (already deferred for lam's q' ≤ q); it does not fix lam-at-arr being a non-ret normal form. |
Consequences
- (+) The STD block (
preservation,progress,type_safety) becomes provable on the de Bruijn base with the already-provensubst_value. - (+) Q4 advances: the
handleF-type restriction lands; only the label-removing refinement remains deferred (still needed foreffect_sound). - (~) Existing proofs port mechanically.
Metatheory.lean'slength_eq,weaken, andsubst_genlam/handlecases need updatedintro/casespatterns (the constructor signatures change) andsubst_lam_case's conclusion changes⊥ → φ; the proof bodies are unchanged.[CommSemiring]strictly adds instances, so no[Semiring]-era proof breaks. - (−) Generality narrows, deliberately.
subst_valueet al. now require[CommSemiring Mult]rather than[Semiring Mult]; the canonical instance (QTT) satisfies it, and no current proof needed the extra generality. - (=) Unaffected:
Coretype syntax (arrstays effect-free — C1 is a judgment fix, not a type change), the effect-row algebra, the LR/compile statements.
Revisit if
effect_soundforces the label-removinghandlerule (Q4) — extend C2's F-restricted rule to subtract the handled label fromφ, don't revert it.- A multiplicity instance that is genuinely non-commutative becomes interesting
(none on the roadmap) — then re-derive the
letCreshape without C3, or carry a non-commutativeletCvariant. - Subsumption (ordered
Mult) lands — foldlam'sq' ≤ qand any effect subsumption in together; it composes with C1, doesn't replace it.