Zulip Chat Archive

Stream: metaprogramming / tactics

Topic: Making a KeyedDeclsAttribute


Max Nowak ๐Ÿ‰ (Jan 28 2026 at 21:12):

Can someone give me a "hello world" example of a custom KeyedDeclsAttribute? So far I have managed to piece together the following, but it still doesn't let me use the attribute.

import Lean
open Lean

syntax (name := greeterAttr) "greeter" : attr

abbrev Greeter := Syntax -> CoreM Bool

unsafe builtin_initialize greeters : KeyedDeclsAttribute Greeter <-
  mkElabAttribute Greeter `builtin_greeter `greeter  .anonymous ``Greeter "greeter" decl_name%

And then in another file:

@[greeter] -- unknown attribute `greeterAttr`
def hello : Greeter := ...

Aaron Liu (Jan 28 2026 at 21:24):

maybe don't use builtin initialize?

Max Nowak ๐Ÿ‰ (Jan 28 2026 at 21:27):

Using initialize instead doesn't help unfortunately.

Aaron Liu (Jan 28 2026 at 21:28):

I'll try take a look at the code

Aaron Liu (Jan 28 2026 at 21:30):

wow I can't tell if this is supposed to be used

Aaron Liu (Jan 28 2026 at 21:31):

reading the docs really gives the impression it's only for builtin attributes

Robin Arnez (Jan 28 2026 at 21:41):

your `greeter might need to be a ``greeterAttr

Robin Arnez (Jan 28 2026 at 21:41):

and yes, definitely not builtin_initialize

Robin Arnez (Jan 28 2026 at 21:41):

that's for bootstrapping stuff

Patrick Massot (Jan 28 2026 at 21:43):

KeyedDeclsAttribute does not appear anywhere in Mathlib. This is a very bad omen.

Max Nowak ๐Ÿ‰ (Jan 28 2026 at 21:55):

With ''greeterAttr I now get "Unexpected attribute argument" when using the attribute. At least that's a different error message, so, progress!

Max Nowak ๐Ÿ‰ (Jan 28 2026 at 22:08):

How does the @[macro] attribute work? In the Lean compiler source, there is:

unsafe def mkMacroAttributeUnsafe (ref : Name) : IO (KeyedDeclsAttribute Macro) :=
  mkElabAttribute Macro `builtin_macro `macro Name.anonymous `Lean.Macro "macro" ref

builtin_initialize macroAttribute : KeyedDeclsAttribute Macro โ† mkMacroAttribute decl_name%

Which seems almost exactly like what I'm doing, and yet my version doesn't work :(.

Robin Arnez (Jan 28 2026 at 22:08):

Well you need to specify an argument. I'll give you a rough idea of how to use elab attributes:

import Lean

open Lean Elab

abbrev Greeter := Syntax โ†’ CoreM Bool

declare_syntax_cat greeting

unsafe initialize greeters : KeyedDeclsAttribute Greeter <-
  mkElabAttribute Greeter `builtin_greeter `greeter  .anonymous ``Greeter "greeter" decl_name%

-- idk why core doesn't have this
instance : MonadMacroAdapter CoreM where
  getNextMacroScope := return (โ† getThe Core.State).nextMacroScope
  setNextMacroScope next := modifyThe Core.State fun s => { s with nextMacroScope := next }

partial def runGreeterFor (stx : Syntax) : CoreM Bool := withRef stx do
  match โ† liftMacroM (expandMacroImpl? (โ† getEnv) stx) with
  | some (_decl, stxNew?) =>
    let stxNew โ† liftMacroM <| liftExcept stxNew?
    runGreeterFor stxNew
  | _ =>
    let kind := stx.getKind
    let candidates := greeters.getEntries (โ† getEnv) kind
    let state โ† Core.saveState
    for candidate in candidates do
      try
        return โ† candidate.value stx
      catch ex => match ex with
        | .internal id _ =>
          if id == unsupportedSyntaxExceptionId then
            state.restore
          else
            throw ex
        | _ => throw ex
    return false

Other file:

syntax (name := hiThere) "hi!" : greeting

@[greeter hiThere]
public meta def greetHiThere : Greeter
  | `(greeting| hi!) => do
    Lean.logInfo "Hi, there!"
    return true
  | _ => Lean.Elab.throwUnsupportedSyntax

run_meta
  runGreeterFor (โ† `(greeting| hi!))

Robin Arnez (Jan 28 2026 at 22:09):

(code adjusted from the definition of elabTerm)

Max Nowak ๐Ÿ‰ (Jan 28 2026 at 22:14):

Is that also using syntax (name := greeterAttr) "greeter" : attr ?

Robin Arnez (Jan 28 2026 at 22:21):

No, not at all

Robin Arnez (Jan 28 2026 at 22:21):

We don't define syntax for the attribute

Robin Arnez (Jan 28 2026 at 22:21):

I think if we did, it would be confused because it already expects a certain builtin attribute syntax

Robin Arnez (Jan 28 2026 at 22:23):

Now, you probably also want

Basically, code copied from Lean.Elab.ElabRules

so you can do

elab "hi!" : greeting => do
  Lean.logInfo "Hi, there!"
  return true

run_meta
  runGreeterFor (โ† `(greeting| hi!))

Max Nowak ๐Ÿ‰ (Jan 28 2026 at 22:32):

Oh huh, I was not aware these attributes take an argument. But this actually solves a second issue I was about to have, so that's great!

Max Nowak ๐Ÿ‰ (Jan 28 2026 at 22:34):

So, if I understand it correctly, for example macro_rules generates a @[macro theParser] def ... for every match arm, where theParser is the top level parser of the match arm?

Max Nowak ๐Ÿ‰ (Jan 28 2026 at 22:35):

(or elab_rules, either way)

Max Nowak ๐Ÿ‰ (Jan 28 2026 at 22:36):

All right I think I get it! This is cool! Thanks a lot for the help!


Last updated: Feb 28 2026 at 14:05 UTC