cubical-transport-hott-lean4/CubicalTransport/Syntax.lean
Maximus Gorog 6e4936d6ee
Some checks are pending
Lean Action CI / build (push) Waiting to run
Refactor Phase 2: modal unification — Lean engine cascade
Per the elegance pass: 9 ad-hoc per-modality constructors collapse into
3 ModalityKind-parameterised constructors.  Future modalities (Phase
4's ʃ_EML, ℑ infinitesimal) extend ModalityKind by adding cases —
no new constructors, no new ABI bump.

New Lean enum (Syntax.lean):
  inductive ModalityKind | flat | sharp | shape
    deriving DecidableEq, Repr, Inhabited

Constructor unification:
  · CType: 3 (.flat / .sharp / .shape) → 1 (.modal k A)
  · CTerm: 6 (.flatIntro / .sharpIntro / .shapeIntro / .flatElim /
            .sharpElim / .shapeElim) → 2 (.modalIntro k a, .modalElim k f m)
  · CVal:  3 (vFlatIntro / vSharpIntro / vShapeIntro) → 1 (vModalIntro)
  · CNeu:  3 (nflatElim / nsharpElim / nshapeElim) → 1 (nModalElim)
  · SkeletalCType: 3 (skFlat / skSharp / skShape) → 1 (skModal k)

Engine cascade across 12 files (DecEq, DimLine, Eval, FFITest, Modal,
Question, Readback, Reflect, Subst, Syntax, Typing, Value): every
match site collapsed from 3-per-modality arms to 1 k-parameterised arm.

Reflect.lean: new `reflectModalityKind` / `reifyModalityKind` helpers
+ ModalityKind dispatch arm in classifyFieldType.  The Phase 1 macro
auto-derived per-constructor reflect/reify for the new unified
constructors — no manual cascade needed there.

Eval.lean β-rule: `.modalElim k f (.modalIntro k' a)` β-reduces only
when k = k' (kind-discrimination preserves cross-kind correctness even
if typing is bypassed); cross-kind case produces a marker neutral.

Modal.lean transient alias block (top of file, outside namespace) for
backward dot-syntax reference (`.flatIntro a` resolves to
`.modalIntro .flat a` via abbrev).  Phase 3 will rewrite Modal.lean
properly to use the unified constructors directly + forModalityKind-
derived functor.

Net: −145 lines across the cascade (-478 deletions, +333 insertions).

Build: lake build (48 jobs) + lake build CubicalTransport (43 jobs) PASS.
Runtime: lake exe cubical-test 49/49 + 46/46 = 95/95 PASS.
Sorry count: Modal.lean 3 (unchanged), total engine 33 (no new sorries
from this phase, all annotated).

The Rust ABI v6 still uses 9 modal tags — diverges from the Lean side
after this commit but FFI tests don't exercise modal paths so no
runtime regression.  Phase 4 will sync to ABI v7.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 02:01:52 -06:00

555 lines
25 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
CubicalTransport.Syntax
=======================
Deep embedding of the cubical term language (CCHM §23),
universe-stratified and dependently-typed.
Grammar:
A, B ::= U | Π (x : A) B | Σ (x : A) B | Path A a b
| Glue [φ ↦ (T, e)] A | ind S params | 𝕀 | lift A
t, u ::= x | λx.t | t u | ⟨i⟩t | t@r
| transpⁱ A φ t | compⁱ A φ u t | compNⁱ A clauses t
| glue [φ ↦ t] a | unglue [φ ↦ f] g
| (a, b) | t.1 | t.2
| ⟦r⟧ | ctor S c params args | indElim S params motive branches target
This is the universe-stratified successor to the original
monomorphic `CType` (THEORY.md Layer 0 §0.1).
## Design (ratified before refactor)
1. **CType is universe-indexed.** `CType : ULevel → Type`.
The level of each constructor is determined by CCHM rules:
· `univ ` lives at `succ `
· `pi`, `sigma` at `max _A _B`
· `path A` at level of A
· `glue T A` requires T and A at the same level; result at that level
· `ind` at the level the user picks (schemas don't yet
constrain their level)
· `interval` at `zero`
· `lift A` at `succ` of A's level
2. **CTerm is un-indexed.** Universe levels live entirely on
CType. CTerm constructors that carry a CType payload (transp,
comp, compN, ctor, indElim) take it at an implicit `{ : ULevel}`
so callers don't have to thread the level explicitly.
3. **Pi and sigma are dependent.** `pi var A B` binds `var : A`
in the codomain CType `B`. Inside `B`, the variable appears
as occurrences of `CTerm.var var`. Application substitutes
the argument for the bound variable (via `substTerm`,
defined in `Subst.lean`). Existing call sites that wrote
`.pi A B` (non-dependent) become `.pi "_" A B` — the
binder name is unused, so substitution does nothing.
4. **Cumulativity is a `.lift` constructor.** `lift (A : CType )`
produces a `CType .succ`. The underlying data is `A`; the
level is bumped. Evaluation unfolds `lift A` to `A`
(the lift is data-preserving — cumulativity as identity coercion).
5. **`params` lists carry heterogeneous-level CTypes.** A schema
parameter can live at any universe level. We use a Lean Σ
type — `Σ : ULevel, CType ` — packaging each param with
its level. Existing call sites wrap each param: `params := [⟨ℓ, A⟩]`.
CType and CTerm are mutually inductive (path endpoints carry CTerms;
CTerm constructors carry CTypes). The five-way mutual block also
includes `CTypeArg`, `CtorSpec`, and `CTypeSchema` (REL1 schema
machinery for inductive types).
The path β-rule `(⟨i⟩ t) @ r ↝ t[i := r]`
and the four "fully-reducing" transport/comp cases (T1, T2, C1, C2)
are NbE theorems in `Cubical/Readback.lean`. The residual step-level
axioms — T3, T5, C4 (subject reduction + face congruence) and T4
(path-line shape preservation) — live in `TransportLaws.lean` /
`CompLaws.lean`.
-/
import CubicalTransport.Face
import CubicalTransport.Universe
-- ── Modality kind (Refactor Phase 2) ────────────────────────────────────────
-- A level-erased enum tagging which modality of the cohesive triple we
-- are talking about. Replaces the Phase-1 set of nine ad-hoc per-modality
-- constructors with three unified `ModalityKind`-parameterised constructors
-- (`CType.modal`, `CTerm.modalIntro`, `CTerm.modalElim`, plus the value-
-- level `CVal.vModalIntro` and `CNeu.nModalElim`).
--
-- Future modalities (e.g. Phase-4's `sharp_EML`, an `infinitesimal` arm)
-- extend this enum by adding cases — the engine modal layer is henceforth
-- parameterised over `ModalityKind`.
/-- The three modalities of the cohesive triple `ʃ ⊣ ♭ ⊣ ♯`
(Schreiber/Shulman cohesive HoTT). Per THEORY.md §3.1.
· `flat` — the discrete reflection (`♭`), middle modality, right
adjoint to `shape`.
· `sharp` — the codiscrete coreflection (`♯`), right adjoint to `flat`.
· `shape` — the shape modality (`ʃ`), left adjoint to `flat`.
`DecidableEq` is structural; future modalities (extra enum arms)
inherit decidable equality automatically. `Repr` and `Inhabited`
are likewise standard. -/
inductive ModalityKind : Type where
/-- ♭, the discrete reflection (right adjoint to shape). -/
| flat
/-- ♯, the codiscrete coreflection (right adjoint to flat). -/
| sharp
/-- ʃ, the shape modality (left adjoint to flat). -/
| shape
deriving DecidableEq, Repr, Inhabited
-- ── Universe-stratified syntax ──────────────────────────────────────────────
mutual
/-- Types in the cubical calculus, stratified by universe level.
Each constructor's universe-level annotation follows the CCHM
typing rules. See the file-level comment for the full table. -/
inductive CType : ULevel → Type where
/-- The universe `U` is itself a type at the next level up.
Russell-paradox avoidance: `U : U.succ`, never `U : U`. -/
| univ { : ULevel}
: CType (ULevel.succ )
/-- Dependent function type `Π (var : A), B`.
`var` is the binding name (a Lean `String`); `A` is the
domain at level ``; `B` is the codomain CType at level `'`,
in scope where `var : A`. Inside `B`, references to the
bound variable appear as `CTerm.var var`.
The result lives at `max '` (CCHM Π rule).
Non-dependent function `A → B` is the special case where
`B` does not mention `var`; conventionally written
`.pi "_" A B`. -/
| pi { ' : ULevel} (var : String) (A : CType ) (B : CType ')
: CType (ULevel.max ')
/-- Dependent product type `Σ (var : A), B`. Same shape as `pi`.
`var : A` is bound in `B`; result at `max '`. -/
| sigma { ' : ULevel} (var : String) (A : CType ) (B : CType ')
: CType (ULevel.max ')
/-- Path type `Path A a b` — paths in A from a to b. Path types
are at the same level as their underlying type. -/
| path { : ULevel} (A : CType ) (a b : CTerm)
: CType
/-- Glue type (CCHM §6). `Glue [φ ↦ (T, e)] A` — on face `φ`
the type is `T` with `e : T ≃ A`; off face `φ` the type is
`A`. T and A live at the same level (the equivalence is
between same-universe types). -/
| glue { : ULevel} (φ : FaceFormula) (T : CType )
(f fInv sec ret coh : CTerm) (A : CType )
: CType
/-- Schema-defined inductive type (REL1, INDUCTIVE_TYPES.md).
`ind S params` instantiates schema `S` at type parameters
`params`. Each parameter is paired with its universe level
via `Σ ' : ULevel, CType '` so heterogeneous-level
parameters are supported (e.g. `List U` has a level-1
parameter while `List Nat` has a level-0 parameter).
The result level `` is user-specified at instantiation
time (the schema does not currently constrain the level). -/
| ind { : ULevel} (S : CTypeSchema)
(params : List (Σ ' : ULevel, CType '))
: CType
/-- The cubical interval `𝕀` as a first-class type (REL2).
Lives at the bottom universe `.zero`. -/
| interval
: CType ULevel.zero
/-- Cumulativity (Layer 0 §0.1). `lift A` is the same data as
`A`, but its CType-level index is bumped by one. Reduction
unfolds `lift A` to `A` (semantically the inclusion is
identity; the level is metadata). -/
| lift { : ULevel} (A : CType )
: CType (ULevel.succ )
/-- The decoder constructor: turn a CTerm-of-type-univ into a CType.
For any CType A : CType encoded via `CTerm.code A`, we have
the propositional reduction `El (code A) = A` (proven in this
file as `El_code_eq`). This lets Ω quantify over codes of
propositions and refer back to the underlying type. -/
| El { : ULevel} (P : CTerm)
: CType
/-- **Modal type former (Refactor Phase 2).** Given a modality kind
`k : ModalityKind` and `A : CType `, the modal type
`modal k A` lives at the same universe level ``. Replaces the
Phase-1 ad-hoc trio `.flat`/`.sharp`/`.shape` with a single
`ModalityKind`-parameterised constructor.
At the engine layer we add the data constructor; the modal
cohesion content (Crisp variables, the `ʃ ⊣ ♭ ⊣ ♯` adjunctions,
modal-shape commutation diagrams) is the Phase 3 module.
Per THEORY.md §3.1; mirrors `path` in level preservation. -/
| modal { : ULevel} (k : ModalityKind) (A : CType )
: CType
/-- Terms in the cubical calculus. Un-indexed by universe level —
the level discipline lives in the typing judgment (`HasType`,
see `Typing.lean`). Type-bearing constructors carry a CType
payload at an implicit `{ : ULevel}`. -/
inductive CTerm : Type where
/-- Variable reference. -/
| var (x : String) : CTerm
/-- Lambda abstraction `λx. t`. -/
| lam (x : String) (t : CTerm) : CTerm
/-- Function application `f a`. -/
| app (f a : CTerm) : CTerm
/-- Dimension abstraction `⟨i⟩ t`. -/
| plam (i : DimVar) (t : CTerm) : CTerm
/-- Path application `t @ r`. -/
| papp (t : CTerm) (r : DimExpr) : CTerm
/-- Transport `transpⁱ A φ t` — transport `t` along the line
`λi. A`, with `φ` being a stuck face. -/
| transp (i : DimVar) { : ULevel} (A : CType )
(φ : FaceFormula) (t : CTerm)
: CTerm
/-- Heterogeneous composition `compⁱ A φ u t`. -/
| comp (i : DimVar) { : ULevel} (A : CType )
(φ : FaceFormula) (u t : CTerm)
: CTerm
/-- Multi-clause heterogeneous composition. -/
| compN (i : DimVar) { : ULevel} (A : CType )
(clauses : List (FaceFormula × CTerm))
(t : CTerm)
: CTerm
/-- Glue introduction `glue [φ ↦ t] a`. -/
| glueIn (φ : FaceFormula) (t a : CTerm) : CTerm
/-- Glue elimination `unglue [φ ↦ f] g`. -/
| unglue (φ : FaceFormula) (f g : CTerm) : CTerm
/-- Σ introduction (pair). -/
| pair (a b : CTerm) : CTerm
/-- Σ first projection. -/
| fst (t : CTerm) : CTerm
/-- Σ second projection. -/
| snd (t : CTerm) : CTerm
/-- A dimension expression lifted into the term language (REL1). -/
| dimExpr (r : DimExpr) : CTerm
/-- Schema constructor application (REL1). -/
| ctor (S : CTypeSchema) (ctorName : String)
(params : List (Σ : ULevel, CType ))
(args : List CTerm)
: CTerm
/-- Inductive eliminator (REL1). -/
| indElim (S : CTypeSchema)
(params : List (Σ : ULevel, CType ))
(motive : CTerm)
(branches : List (String × CTerm))
(target : CTerm)
: CTerm
/-- The encoder constructor: turn a CType into a CTerm of type
`.univ ( := )`. Carries the underlying type as data. -/
| code { : ULevel} (A : CType )
: CTerm
/-- **Modal introduction (Refactor Phase 2).** Given a modality
kind `k : ModalityKind` and a term `a : A`, the term
`modalIntro k a` inhabits `modal k A`. Replaces the Phase-1
trio `.flatIntro`/`.sharpIntro`/`.shapeIntro` with a single
unified constructor parameterised over `k`.
Reduction: `modalElim k f (modalIntro k a)` ↝ `app f a` (β
fires only when both elim and intro carry the same kind). -/
| modalIntro (k : ModalityKind) (a : CTerm)
: CTerm
/-- **Modal elimination (Refactor Phase 2).** Given an elimination
function `f : A → C` and a scrutinee `m : modal k A`, produce
a term of type `C`. Replaces the Phase-1 trio `.flatElim` /
`.sharpElim` / `.shapeElim` with one unified
`ModalityKind`-parameterised constructor.
Reduction: `modalElim k f (modalIntro k a)` ↝ `app f a` (β-rule
on matching kinds). Otherwise: stuck `nModalElim k` neutral. -/
| modalElim (k : ModalityKind) (f m : CTerm)
: CTerm
/-- Argument shape for a schema constructor (REL1, §2.1). -/
inductive CTypeArg where
/-- A non-recursive arg whose type is a closed CType at any
universe level. -/
| type { : ULevel} (A : CType ) : CTypeArg
/-- The `i`th schema parameter (zero-indexed). -/
| param (i : Nat) : CTypeArg
/-- Recursive reference to the inductive type being defined. -/
| self : CTypeArg
/-- A dimension binder, used by path constructors. -/
| dim : CTypeArg
/-- Constructor specification (REL1, §2.2).
`name` unique within schema; `args` positional; `boundary` is
the partial-element system for path constructors (empty for
point constructors). -/
inductive CtorSpec where
| mk (name : String) (args : List CTypeArg)
(boundary : List (FaceFormula × CTerm))
: CtorSpec
/-- Schema for an inductive (or higher-inductive) type (REL1, §2.3). -/
inductive CTypeSchema where
| mk (name : String) (numParams : Nat)
(ctors : List CtorSpec)
: CTypeSchema
end
-- ── Repr derivations ──────────────────────────────────────────────────────────
deriving instance Repr for CType
deriving instance Repr for CTerm
deriving instance Repr for CTypeArg
deriving instance Repr for CtorSpec
deriving instance Repr for CTypeSchema
-- DecidableEq for the 5-way mutual block lives in `CubicalTransport.DecEq`
-- (Lean's `deriving instance DecidableEq` doesn't currently support mutual
-- inductives — has to be written manually).
-- ── Level-erased skeletal classifier ─────────────────────────────────────────
-- A non-indexed enum tagging a CType by its head constructor. The level
-- index is stripped — `SkeletalCType` is a plain `Type` with `DecidableEq`
-- and is therefore safe to compare directly via `Eq` without HEq.
--
-- Used to formulate constructor-disjointness preconditions on stuck
-- axioms (`vTransp_stuck`, `eval_comp_stuck`, etc.) in a way that's
-- discharge-able by structural pattern matching, without resorting to
-- HEq elimination across distinct universe indices (which requires K
-- and is not available in Lean 4 without classical axioms).
--
-- This replaces the prior HEq-based formulation
-- `h_not_pi : ∀ {_d _c} (var) (domA : CType _d) (codA : CType _c),
-- HEq A (.pi var domA codA) → False`
-- with the structurally equivalent
-- `h_not_pi : A.skeleton ≠ SkeletalCType.pi`
-- which is decidable, computable, and trivially provable for any
-- non-pi constructor.
inductive SkeletalCType : Type where
| univ
| pi
| sigma
| path
| glue
| ind
| interval
| lift
| El
/-- Modal skeleton (Refactor Phase 2). Carries the modality kind so
that distinct modalities (`♭` vs `♯` vs `ʃ`) remain distinct
skeletons — required for constructor-disjointness reasoning. -/
| modal (k : ModalityKind)
deriving Repr, DecidableEq
/-- Strip the universe index, preserving the head constructor as a tag.
The cornerstone of the structural-disjointness machinery: each CType
constructor maps to its corresponding skeletal tag, and the tag is
a non-indexed enum with decidable equality. -/
def CType.skeleton { : ULevel} : CType → SkeletalCType
| .univ => .univ
| .pi _ _ _ => .pi
| .sigma _ _ _ => .sigma
| .path _ _ _ => .path
| .glue _ _ _ _ _ _ _ _ => .glue
| .ind _ _ => .ind
| .interval => .interval
| .lift _ => .lift
| .El _ => .El
| .modal k _ => .modal k
-- ── Skeleton equations (rfl-provable) ────────────────────────────────────────
/-- The skeleton of `.ind` is `.ind`. -/
@[simp]
theorem CType.skeleton_ind { : ULevel} (S : CTypeSchema)
(params : List (Σ ' : ULevel, CType ')) :
(CType.ind ( := ) S params).skeleton = SkeletalCType.ind := rfl
/-- The skeleton of `.pi` is `.pi`. -/
@[simp]
theorem CType.skeleton_pi {_d _c : ULevel}
(var : String) (domA : CType _d) (codA : CType _c) :
(CType.pi var domA codA).skeleton = SkeletalCType.pi := rfl
/-- The skeleton of `.sigma` is `.sigma`. -/
@[simp]
theorem CType.skeleton_sigma {_a _b : ULevel}
(var : String) (A : CType _a) (B : CType _b) :
(CType.sigma var A B).skeleton = SkeletalCType.sigma := rfl
/-- The skeleton of `.path` is `.path`. -/
@[simp]
theorem CType.skeleton_path { : ULevel} (A : CType ) (a b : CTerm) :
(CType.path A a b).skeleton = SkeletalCType.path := rfl
/-- The skeleton of `.glue` is `.glue`. -/
@[simp]
theorem CType.skeleton_glue { : ULevel} (φ : FaceFormula) (T : CType )
(f fInv s r c : CTerm) (A : CType ) :
(CType.glue φ T f fInv s r c A).skeleton = SkeletalCType.glue := rfl
/-- The skeleton of `.interval` is `.interval`. -/
@[simp]
theorem CType.skeleton_interval :
(CType.interval).skeleton = SkeletalCType.interval := rfl
/-- The skeleton of `.univ` is `.univ`. -/
@[simp]
theorem CType.skeleton_univ { : ULevel} :
(CType.univ ( := )).skeleton = SkeletalCType.univ := rfl
/-- The skeleton of `.lift` is `.lift`. -/
@[simp]
theorem CType.skeleton_lift { : ULevel} (A : CType ) :
(CType.lift A).skeleton = SkeletalCType.lift := rfl
/-- The defining reduction for the El/code pair: decoding the encoding
of a CType returns that same CType.
Stated as an axiom because `El` is a free constructor of CType
rather than a function — the reduction `El (code A) = A` is the
universe-code β-rule (CCHM §6: Glue-style universe codes). This
is the standard formulation in cubical type theory: codes are
inert constructors at the syntax level; their decoding rule is a
propositional / definitional equation in the calculus, equivalent
to a Glue-collapse axiom.
The Rust backend implements this rule by inspecting `CType.El`
targets and folding through `CTerm.code` constructors at the
structural level (see `eval_code` / readback handling). -/
@[simp] axiom CType.El_code_eq { : ULevel} (A : CType ) :
CType.El (CTerm.code A) = A
/-- Skeleton-tag for the new `.El` constructor — used by the
structural-disjointness framework. -/
@[simp] theorem CType.skeleton_El { : ULevel} (P : CTerm) :
(CType.El ( := ) P).skeleton = SkeletalCType.El := rfl
/-- The skeleton of `.modal k A` is `.modal k`. Carries the modality
kind through so that distinct kinds remain distinct skeletons. -/
@[simp]
theorem CType.skeleton_modal { : ULevel} (k : ModalityKind) (A : CType ) :
(CType.modal k A).skeleton = SkeletalCType.modal k := rfl
-- ── Constructor disjointness via skeleton ────────────────────────────────────
/-- Skeletons of distinct constructors are distinct. This is the
foundational disjointness fact, decided structurally on
SkeletalCType (which has DecidableEq derived). -/
theorem SkeletalCType.ind_ne_pi : (SkeletalCType.ind : SkeletalCType) ≠ SkeletalCType.pi := by
intro h; cases h
/-- An `.ind` body is *structurally* not a `.pi` body, in the
skeleton-based formulation that avoids cross-level HEq.
Used by `eval_transp_ind` (TransportLaws.lean) and `eval_comp_ind`
(CompLaws.lean) to discharge the `h_not_pi` premise of
`vTransp_stuck` / `eval_comp_stuck`. -/
theorem CType.ind_skeleton_ne_pi { : ULevel}
(S : CTypeSchema) (params : List (Σ ' : ULevel, CType ')) :
(CType.ind ( := ) S params).skeleton ≠ SkeletalCType.pi := by
rw [CType.skeleton_ind]
exact SkeletalCType.ind_ne_pi
-- ── Convenience: non-dependent pi/sigma sugar ────────────────────────────────
namespace CType
/-- Non-dependent function type `A → B`. The bound variable name
`"_"` is reserved (by convention) for unused binders; substitution
does nothing on it. -/
abbrev arrow { ' : ULevel} (A : CType ) (B : CType ') : CType (ULevel.max ') :=
.pi "_" A B
/-- Non-dependent product type `A × B`. -/
abbrev prod { ' : ULevel} (A : CType ) (B : CType ') : CType (ULevel.max ') :=
.sigma "_" A B
end CType
-- ── Dimension substitution ────────────────────────────────────────────────────
-- Substitute dimension variable i with DimExpr r throughout a term.
--
-- Scope inside transp/comp:
-- · j is the binder of the transport line, bound in A and in φ.
-- · The base term t (and system u) are in outer scope — we substitute there.
--
-- Approximation: `substDim` does NOT descend into A or φ — even when j ≠ i
-- and i would be free under the binder. Consequence: this substitution is
-- only faithful for *endpoint* calls (`substDimBool`), where downstream
-- uses the dimension-absent predicate to justify correctness. Full
-- DimExpr-in-FaceFormula substitution is deferred (see cells-spec §5.5).
--
-- The new universe-stratified CType constructors (pi, sigma with named
-- binders; lift) do NOT change substDim's behavior at the CTerm level
-- because CTerm doesn't recurse into CType payloads.
mutual
def CTerm.substDim (i : DimVar) (r : DimExpr) : CTerm → CTerm
| .var x => .var x
| .lam x t => .lam x (t.substDim i r)
| .app f a => .app (f.substDim i r) (a.substDim i r)
| .plam j t => if j = i then .plam j t -- i bound; stop
else .plam j (t.substDim i r)
| .papp t s => .papp (t.substDim i r) (DimExpr.subst i r s)
-- transp/comp: leave A alone (approximation); descend into t, u and
-- substitute in φ via the general DimExpr face-formula substitution.
| .transp j A φ t => .transp j A (φ.substDim i r) (t.substDim i r)
| .comp j A φ u t => .comp j A (φ.substDim i r) (u.substDim i r) (t.substDim i r)
| .compN j A clauses t =>
.compN j A (CTerm.substDim.clauses i r clauses) (t.substDim i r)
| .glueIn φ t a => .glueIn (φ.substDim i r) (t.substDim i r) (a.substDim i r)
| .unglue φ f g => .unglue (φ.substDim i r) (f.substDim i r) (g.substDim i r)
| .pair a b => .pair (a.substDim i r) (b.substDim i r)
| .fst t => .fst (t.substDim i r)
| .snd t => .snd (t.substDim i r)
| .dimExpr s => .dimExpr (DimExpr.subst i r s)
| .ctor S c params args =>
.ctor S c params (CTerm.substDim.list i r args)
| .indElim S params motive branches target =>
.indElim S params
(motive.substDim i r)
(CTerm.substDim.branches i r branches)
(target.substDim i r)
-- Universe-code constructor: `code A` carries a CType payload.
-- Same approximation as transp/comp: A is not recursed into.
| .code A => .code A
-- Modal introductions: structural recursion into the wrapped term.
| .modalIntro k a => .modalIntro k (a.substDim i r)
-- Modal eliminations: structural recursion into both subterms
-- (eliminator function and scrutinee).
| .modalElim k f m => .modalElim k (f.substDim i r) (m.substDim i r)
/-- Helper: apply `CTerm.substDim i r` to each clause body (and
`FaceFormula.substDim` to each face) in a system's clause list. -/
def CTerm.substDim.clauses (i : DimVar) (r : DimExpr) :
List (FaceFormula × CTerm) → List (FaceFormula × CTerm)
| [] => []
| (φ, u) :: rest =>
(φ.substDim i r, u.substDim i r) :: CTerm.substDim.clauses i r rest
/-- Helper: apply `CTerm.substDim i r` to each element of a CTerm
list (ctor argument lists). -/
def CTerm.substDim.list (i : DimVar) (r : DimExpr) :
List CTerm → List CTerm
| [] => []
| t :: rest => t.substDim i r :: CTerm.substDim.list i r rest
/-- Helper: apply `CTerm.substDim i r` to the body of each branch
in an `indElim`. Branch *names* are unaffected; only bodies. -/
def CTerm.substDim.branches (i : DimVar) (r : DimExpr) :
List (String × CTerm) → List (String × CTerm)
| [] => []
| (n, b) :: rest =>
(n, b.substDim i r) :: CTerm.substDim.branches i r rest
end
-- ── One-step reduction ────────────────────────────────────────────────────────
-- `CTerm.step` is left *opaque*. Semantically it is what a CCHM-style
-- evaluator will compute. Keeping `step` opaque is required for
-- soundness: if `step` were a concrete `def` with a wildcard identity
-- arm, any axiom of the shape `step (.transp …) = t` would rfl-collapse
-- to `.transp … = t`, contradicting `CTerm.noConfusion`.
--
-- See the original Syntax.lean's commentary for the Stage 4.4 history:
-- only `transp_plam_is_plam_path` (T4) remains as a step-level axiom;
-- the Rust backend can implement `step` directly or as `readback ∘ eval .nil`.
opaque CTerm.step : CTerm → CTerm := id