Zulip Chat Archive

Stream: metaprogramming / tactics

Topic: Creating and registering structures without `elabCommand`


Ghilain Bergeron (Dec 18 2025 at 12:45):

Is there any way, ideally without resorting to implementing the compiler logic from https://github.com/leanprover/lean4/blob/2fcce7258eeb6e324366bc25f9058293b04b7547/src/Lean/Elab/Structure.lean#L1506-L1570 myself, of generating structures (with parents/subobjects) and all their associated functions like projections (including to the field of its transitive parents) from an array of fields of type Array (Name × Expr)?
I am not sure if I simply missed a couple of functions which would do all that or not.
Calling elabCommand on raw syntax is not really doable in my case (at least I don't think) since I am already managing Exprs, not Terms.

Robin Arnez (Dec 18 2025 at 23:30):

At the end this uses publicly accessible meta functions so here's kinda what calling them directly looks like (no guarantee that it's complete though):

import Lean
import Qq

open Lean Meta Qq

run_meta
  let u : Level := .param `u
  withLocalDeclQ  .implicit q(Type u) fun α => do
  withLocalDeclDQ `thing q($α) fun thing => do
  withLocalDeclDQ `foo q(Nat) fun foo => do
  withLocalDeclDQ `bar q(Fin $foo) fun bar => do

  let typeName := `Test
  let self : Q(Type u  Type u) := .const typeName [u]
  let projNames := #[`thing, `foo, `bar]
  let vars := #[α, thing, foo, bar]

  let ctor : Constructor := {
    name := typeName ++ `mk
    type :=  mkForallFVars vars q($self $α)
  }
  let indType : InductiveType := {
    name := typeName
    type :=  mkForallFVars #[α] q(Type u)
    ctors := [ctor]
  }
  addDecl (.inductDecl [`u] 1 [indType] false)

  -- see mkAuxConstructions in Lean.Elab.MutualInductive
  mkRecOn typeName
  mkCasesOn typeName
  mkCtorElim typeName
  mkNoConfusion typeName

  mkProjections typeName (projNames.map fun n => { ref := .missing, projName := typeName ++ n })
    (instImplicit := false) -- true for classes

  -- this is "simple" here because we don't have extends
  let flatCtorType := ctor.type
  let flatCtorValue  mkLambdaFVars vars (mkAppN (.const ctor.name [u]) vars)
  addDecl <| Declaration.defnDecl {
    name := mkFlatCtorOfStructCtorName ctor.name
    levelParams := [`u]
    type := flatCtorType
    value := flatCtorValue
    hints := .abbrev
    safety := .safe
  }

  modifyEnv fun env => registerStructure env {
    structName := typeName
    fields := projNames.map fun n => {
      fieldName := n
      projFn := typeName ++ n
      subobject? := none -- would be some for parent projections
      binderInfo := .default
    }
  }

#print Test

This is certainly not ideal though, it'd be nice if there were helper functions to make this process easier (and also make it rely less on implementation details).

Ghilain Bergeron (Dec 19 2025 at 09:16):

This is what I kind of already have yes, although in my case I have to handle parent structures as well (not as subobjects though).
The logic used in Lean.Elab.Command.Structure.elabStructureCommand unfortunately is too coupled with the syntax itself (although with a lot less checks), and I am just reimplementing the logic itself, which as you said is less than ideal and I believe barely maintainable.


Last updated: Dec 20 2025 at 21:32 UTC