Zulip Chat Archive

Stream: lean4

Topic: Attribute example


Patrick Massot (Feb 23 2024 at 20:30):

Does anyone has an example of creating an attribute can be local or global and take an optional priority parameter?

Patrick Massot (Feb 23 2024 at 20:32):

I know about register_label_attr from Std but it cannot be used with local and cannot take parameters. So I’m looking for a slightly more sophisticated gadget.

Patrick Massot (Feb 29 2024 at 16:44):

I am really confused by the attribute system. Is there a way to define an attribute with parameters that would be stored and modified in any file? It seems every variant has limitations like being added only in the file defining the declaration or modifications are not persisted across files.

Tomas Skrivan (Feb 29 2024 at 17:03):

There might be an easier way but you can do it with SimpleScopedEnvExtension

Test file:

import Doodle.MyScopedAttr

#eval hasMyAttr ``id -- false

-- global
attribute [myAttr] id

#eval hasMyAttr ``id -- true


namespace TestScope

-- local attribute
attribute [local myAttr] Nat

#eval hasMyAttr ``Nat -- true

end  TestScope

#eval hasMyAttr ``Nat -- false

MyScopedAttr.lean

import Lean

open Lean Meta

syntax (name:=my_attr) "myAttr" (prio)? : attr

initialize myScopedAttrExt : SimpleScopedEnvExtension Name NameSet 
  registerSimpleScopedEnvExtension {
    name := by exact decl_name%
    initial := {}
    addEntry := fun set name => set.insert name
  }

initialize myScopedAttr : Unit 
  registerBuiltinAttribute {
    name  := ``my_attr
    descr := ""
    applicationTime := AttributeApplicationTime.afterCompilation
    add   := fun declName stx attrKind => do
      myScopedAttrExt.add declName attrKind
    erase := fun _declqName =>
      pure ()
  }

def hasMyAttr (name : Name) : CoreM Bool := do
  let a := myScopedAttrExt.getState ( getEnv)
  return a.contains name

Tomas Skrivan (Feb 29 2024 at 17:09):

Then in the function add inside registerBuiltinAttribute you can extract the priority information from stx which gives you the syntax of the attribute.

You will of course also need to modify the SimpleScopedEnvExtension Name NameSet such that you can store the priority information e.g. SimpleScopedEnvExtension (Name×Nat) (NameMap Nat)

Patrick Massot (Feb 29 2024 at 17:11):

Thanks Tomas. Can you actually remove the attribute later with this example?

Tomas Skrivan (Feb 29 2024 at 17:11):

Patrick Massot said:

I am really confused by the attribute system. Is there a way to define an attribute with parameters that would be stored and modified in any file? It seems every variant has limitations like being added only in the file defining the declaration or modifications are not persisted across files.

Yeah I also do not understand the limitation that you can add the attribute only in the file where you declare the function. For that reason I just use the above setup.

Tomas Skrivan (Feb 29 2024 at 17:12):

Patrick Massot said:

Thanks Tomas. Can you actually remove the attribute later with this example?

Sure, you just need to implement the erase function.

Patrick Massot (Feb 29 2024 at 17:14):

I would have no idea how to do that. But I also realize that maybe my question doesn’t really make sense. It isn’t clear what would be the expected behavior if you import one file adding the attribute and one file removing it. I guess this is why the simp attribute does not allow this.

Tomas Skrivan (Feb 29 2024 at 17:17):

What is the syntax to remove attribute? attribute [-myAttr] Nat does not work.

Patrick Massot (Feb 29 2024 at 17:18):

This is meant to work.

Tomas Skrivan (Feb 29 2024 at 17:20):

Ohh attribute [-my_attr] Nat works. There is the syntax of an attribute syntax (name:=my_attr) "myAttr" (prio)? and its name my_attr. I guess the convention is to have myAttr=my_attr.

Tomas Skrivan (Feb 29 2024 at 17:21):

Revised version:

import Lean

open Lean Meta


syntax (name:=myAttr) "myAttr" (prio)? : attr

initialize myScopedAttrExt : SimpleScopedEnvExtension Name NameSet 
  registerSimpleScopedEnvExtension {
    name := by exact decl_name%
    initial := {}
    addEntry := fun set name => set.insert name
  }

def hasMyAttr (name : Name) : CoreM Bool := do
  let a := myScopedAttrExt.getState ( getEnv)
  return a.contains name


initialize myScopedAttr : Unit 
  registerBuiltinAttribute {
    name  := ``myAttr
    descr := ""
    applicationTime := AttributeApplicationTime.afterCompilation
    add   := fun declName stx attrKind => do
      myScopedAttrExt.add declName attrKind
    erase := fun declName => do
      unless  hasMyAttr declName do
        throwError s!"{declName} does not have `myAttr` attribute!"

      modifyEnv fun env =>
        myScopedAttrExt.modifyState env fun s =>
          s.erase declName
  }
import Doodle.MyScopedAttr

#eval hasMyAttr ``id -- false

-- global
attribute [myAttr] id

#eval hasMyAttr ``id -- true


namespace TestScope

attribute [-myAttr] id

#eval hasMyAttr ``id -- false

-- local attribute
attribute [local myAttr mid] Nat

#eval hasMyAttr ``Nat -- true

attribute [-myAttr] Nat

#eval hasMyAttr ``Nat -- false

end  TestScope

#eval hasMyAttr ``Nat -- false

Patrick Massot (Feb 29 2024 at 17:37):

Thanks a lot!

Jannis Limperg (Feb 29 2024 at 18:19):

Patrick Massot said:

It isn’t clear what would be the expected behavior if you import one file adding the attribute and one file removing it. I guess this is why the simp attribute does not allow this.

That's right. As a result, erasing only works locally, i.e. attribute [-attr] foo only removes attr from foo for the remainder of the current file.


Last updated: May 02 2025 at 03:31 UTC