Skip to content

Eliminate need for forward type declarations in recursive datatypes#443

Open
joehendrix wants to merge 1 commit intomainfrom
jhx/ddm_preregister
Open

Eliminate need for forward type declarations in recursive datatypes#443
joehendrix wants to merge 1 commit intomainfrom
jhx/ddm_preregister

Conversation

@joehendrix
Copy link
Contributor

@joehendrix joehendrix commented Feb 18, 2026

Summary

Mutually recursive datatypes previously required users to manually write
forward type declarations before a mutual block. This PR eliminates
that boilerplate by having the elaborator automatically discover and
pre-register type names before elaborating the block's children.

Details

  • Automatic pre-registration replaces forward declarations. The
    @[preRegisterTypes(scope)] annotation on command_mutual triggers
    a two-phase elaboration: Phase 1 partially elaborates each child to
    extract type names and params, then pre-registers them in the
    GlobalContext so Phase 2 can resolve mutual references. This removes
    a common source of user confusion — forgetting or misordering forward
    declarations — and makes the mutual block self-contained.
  • GlobalContext simplified. The DeclState enum
    (.forward / .defined) and its associated bookkeeping
    (declareForward, isForward, three-element vars tuples) are
    removed. Symbols are now either present or absent, tracked by a single
    ensureDefined operation. This eliminates a class of bugs where
    forward-declared symbols could be left in a half-defined state.
  • O(1) argument lookup via argElabIndex. A precomputed
    Array (Option Nat) maps each argLevel to its position in
    argElaborators, replacing linear searches through
    argLevelToSyntaxLevel. Callers get the full ArgElaborator,
    not just one field.
  • Translation layer adapted. Translate.lean no longer requires
    forward declarations before mutual blocks. Instead it allocates
    placeholder entries on the fly when a datatype isn't already
    pre-registered, making the AST-to-CST path work for both DDM-parsed
    and programmatically-constructed programs.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@joehendrix joehendrix changed the title Replace forward type declarations with automatic pre-registration WIP: Replace forward type declarations with automatic pre-registration Feb 18, 2026
@joehendrix joehendrix force-pushed the jhx/ddm_preregister branch 2 times, most recently from 4971e76 to 64accb0 Compare February 19, 2026 23:28
@joehendrix joehendrix changed the title WIP: Replace forward type declarations with automatic pre-registration Replace forward type declarations with automatic pre-registration Feb 26, 2026
Mutually recursive datatypes previously required users to manually write
`forward type` declarations before a `mutual` block. This eliminates
that boilerplate by having the elaborator automatically discover and
pre-register type names before elaborating the block's children.

- Add @[preRegisterTypes(scope)] annotation triggering two-phase
  elaboration: Phase 1 extracts type names/params, Phase 2 elaborates
  with mutual references resolved
- Simplify GlobalContext by removing DeclState enum and forward
  declaration bookkeeping; symbols are now present or absent
- Add precomputed argElabIndex array to SyntaxElaborator for O(1)
  argument lookup by argLevel
- Adapt Translate.lean to allocate placeholder entries on the fly
  instead of requiring forward declarations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@joehendrix joehendrix marked this pull request as ready for review February 26, 2026 20:49
@joehendrix joehendrix requested a review from a team February 26, 2026 20:49
Copy link
Contributor

@MikaelMayer MikaelMayer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-motivated PR that removes a real source of user friction. The two-phase elaboration design in elaborateWithPreRegistration is sound, the GlobalContext simplification is a clear win, and the removal of DeclState/forward declarations is thorough — no dangling references remain.

One minor inconsistency between extractParamNames and the inline code in runSyntaxElaborator (.tvar handling) is worth a look. The rest is solid.

I'd approve after the one question below is addressed.

let newBindings := tpCtx.bindings.toArray.extract inheritedCount tpCtx.bindings.size
let names := newBindings.filterMap fun (b : Binding) =>
match b.kind with
| .type _ [] _ => some b.ident
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extractParamNames matches both .type _ [] _ and .tvar _ _, but the analogous inline code in runSyntaxElaborator (around line 1101) only matches .type _ [] _:

|>.filterMap fun b =>
  match b.kind with
  | .type _ [] _ => some b.ident
  | _ => none

Is the .tvar case intentionally included here but not there? If both should behave the same, consider extracting the shared logic into extractParamNames and calling it from both sites.

@joehendrix joehendrix changed the title Replace forward type declarations with automatic pre-registration Eliminate need for forward type declarations in recursive datatypes Feb 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants