Eliminate need for forward type declarations in recursive datatypes#443
Eliminate need for forward type declarations in recursive datatypes#443joehendrix wants to merge 9 commits intomainfrom
Conversation
4971e76 to
64accb0
Compare
ab52636 to
ad51d1d
Compare
MikaelMayer
left a comment
There was a problem hiding this comment.
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.
Strata/DDM/Elab/Core.lean
Outdated
| 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 |
There was a problem hiding this comment.
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
| _ => noneIs 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.
There was a problem hiding this comment.
I've cleaned up this code quite a bit and added additional test cases.
6496d19 to
d056ba0
Compare
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pass `gctx` directly through the template expansion monad instead of building an intermediate `Std.HashSet String` of existing names and folding `ensureDefined` over results afterward. This removes `TemplateExpansionResult` and the two unnecessary folds in `addDatatypeBindings`, using `GlobalContext` membership for duplicate detection and `ensureDefined` as functions are generated. The template expansion machinery is moved after the `GlobalContext` definition to resolve forward-reference issues. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Change extractFieldsFromBindings, extractSingleConstructor, and extractConstructorInfo to return Except String instead of silently returning empty arrays/none on errors. This surfaces clear error messages for bad arg shapes, missing annotations, and out-of-bounds indices. Update callers: - addDatatypeBindings! (renamed with !) uses panic! on error - collectConstructorNames returns #[] on error (non-fatal for pre-registration) - translateConstructorList forwards errors via TransM.error Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add GlobalContext.defineChecked with preRegistered flag for explicit membership/freshness checking at define sites - Replace ensureDefined in addDatatypeBindings!, addCommand.addBinding, and preRegisterType with defineChecked or direct define-with-proof - Add atomic buildAndDefine for template function expansion (no TOCTOU) - Thread preRegistered : Bool through addCommand → addBinding - Extract elabSyntaxArg and resultContext from runSyntaxElaborator - Add docstrings to preRegisterType, runSyntaxElaborator, extractDatatypeInfo Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…review items - Move constructor info extraction functions out of GlobalContext namespace (they only depend on DialectMap, not GlobalContext) - Move GlobalContext definition after constructor info extraction - Lift addBinding from where clause to standalone private def - Move collect helper and checkConstructorUniqueness to Elab.lean - Add elaboration-time constructor uniqueness check in evalBindingSpec - Remove callerInfo parameter and FIXME debug diagnostic from runSyntaxElaborator - Fix typos (datype, assignemnt) and stale function name references - Wrap long lines to stay within 100 char limit - Make OperationF.foldBindingSpecs public Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rUniqueness Calls expandFunctionTemplates in evalBindingSpec's .datatype branch so name collisions (duplicate constructors, duplicate template functions) are reported via logError during elaboration rather than panicking in addDatatypeBindings!. Removes checkConstructorUniqueness from Elab.lean since constructor names no longer need to be globally unique — collisions are now caught by the template expansion check which covers both constructor and generated function names. Adds function templates (perConstructor tester, perField accessor) to the DDM MutualDatatypes test dialect and adds Test 5 for complex mutual types with multiple constructors and fields. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds runChecked to TemplateExpandM to track which constructors failed registration. Skips template expansion for failed constructors so we report only the root cause (duplicate constructor name) rather than cascading into duplicate template function errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add @[prec(30), leftassoc] to constructorListPush so constructor arguments (at maxPrec) don't get unnecessarily parenthesized. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
a622fb4 to
5347405
Compare
Summary
Mutually recursive datatypes previously required users to manually write
forward typedeclarations before amutualblock. This PR eliminatesthat boilerplate by having the elaborator automatically discover and
pre-register type names before elaborating the block's children.
Key changes
@[preRegisterTypes(scope)]annotation oncommand_mutualtriggerstwo-phase elaboration: Phase 1 partially elaborates each child to
extract type names and params, then pre-registers them in the
GlobalContext; Phase 2 elaborates the full block with mutualreferences already resolved.
GlobalContextsimplified. TheDeclStateenum(
.forward/.defined) and its bookkeeping are removed.definenow requires a non-membership proof, making illegal states
unrepresentable.
defineCheckedtakes apreRegisteredflag tohandle both fresh definitions and pre-registered confirmations.
extractConstructorInfoand helpers returnExcept Stringinsteadof silently returning empty arrays. Template expansion uses an atomic
buildAndDefinethat checks freshness and defines in one step.evalBindingSpec's.datatypebranch callsexpandFunctionTemplatesto catch constructor and template function name collisions early via
logError, rather than panicking inaddCommand. Constructor namesare no longer required to be globally unique — only the generated
template function names must be distinct.
elabSyntaxArg,resultContext,extractParamNames, andscopeSepFormatfrom the monolithicrunSyntaxElaborator. LiftedextractConstructorInfoout of theGlobalContextnamespace (it only depends onDialectMap).argElabIndex. A precomputedArray (Option Nat)maps eachargLevelto its position inargElaborators, replacing linear searches.translateMutualBlockallocatesplaceholder entries on the fly when a datatype isn't already
pre-registered, rather than requiring forward declarations.
Test changes
StrataTest/DDM/MutualDatatypes.lean(new): DDM-level testdialect with function templates (
perConstructortester,perFieldaccessor). Tests mutual block visibility, three-wayrecursion, empty blocks, multi-constructor template expansion, and
negative cases (undefined type, duplicate type, type clash, duplicate
constructor name collisions, reused constructor name collisions).
StrataTest/Languages/Core/Examples/MutualDatatypes.lean(new):Core-level integration tests verifying tester functions, accessor
functions, equality, polymorphic havoc, and complex Stmt/StmtList
patterns on mutually recursive types.
StrataTest/Languages/Core/TestASTtoCST.lean(new):Round-trip tests for AST-to-CST translation.
forward typedeclarations from existing test files.By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.