Skip to content

How to add attributes

David Miller edited this page Mar 27, 2025 · 4 revisions

You can add attributes to classes, methods, constructors, and parameters, typically in the context of constructs such as deftype and gen-class. An attribute is added by specifying an appropriate key/value pair in the metadata of the construct in question. The key in the metadata will be the attribute class. Note that you have to use the full name, e.g., ObsoleteAttribute, and not the truncated name (Obsolete) as in possible in languages such as C#.

The value associated with the attribute type key can have several forms. We have to accommodate

  • positional arguments to pass to a constructor of the attribute type
  • property/value pairs to allow setting of properties
  • multiple values for an attribute

Allowing for multiple values complicates matters because we have only a single key in the metadata. Thus the associated value has to be able to specify multiple initializations. We provide a general form with maximum flexibility (and complexity) along with several simplified forms to make it easier to express the simpler (and more common) cases.

Attribute initializers

If the value for an attribute key is a set, we interpret each item in the set as an initializer. Thus, multiple items in the set indicates multiple attachments of attribute values.

AttributeType #{ init1 init2 ... }

If the value is not a set, we treat it as a single initializer. In other words

AttributeType somethingNotASet is equivalent to AttributeType #{ somethingNotASet }

The most general form for an initializer is a map. Keys represent names of properties to set to the associated values. The special key :__args can be used to provide a vector of arguments to pass to the constructor.

If the initializer is a vector, it will be treated arguments to pass to the constructor. Thus,

[arg1 arg2 ...] is equivalent to { :__args [arg1 arg2 ... ] }.

If the initializer is not a map or a vector, it is treated as a single argument to pass.

something is equivalent to { :__args [something] }.

Initializer examples

Assuming appropriate imports to use just the typename. What we call the normalized form is the attribute value expanded out to the general case (a set, each element (initializer) a map).

Attribute+Value Serializable {} Single initializer, no argument values
Normalized Serializable #{ {} }
Result new Serializable() Call the no-argument constructor
Attribute+Value FileIOPermission SecurityAction/Demand Single argument to constructor
Normalized FileIOPermission #{ {:__args [SecurityActionDemand]} }
Result new FileIOPermission(SecurityAction/Demand)
Attribute+Value FileIOPermission #{ SecurityAction/Demand SecurityAction/Deny } Two initializers, each single value to pass to the constructor
Normalized FileIOPermission #{
{:__args [SecurityAction/Demand]}
{:__args [SecurityAction/Deny]} }
Result new FileIOPermission(SecurityAction/Demand)
new FileIOPermission(SecurityAction/Deny)
multiple values for the attribute
Attribute+Value FileIOPermission #{
SecurityAction/Demand
{ :__args [SecurityAction/Deny] :Read "abc" } }
Two initializers.
One is single value to pass.
The other passes one argument to the constructor
and sets a property.
Normalized FileIOPermission #{
{:__args [SecurityAction/Demand]}
{:__args [SecurityAction/Deny] :Read "abc"} }
Result new FileIOPermission(SecurityAction/Demand)
new FileIOPermission(SecurityAction/Deny){ Read = "abc" }

Where attribute specifies can be used

deftype and definterface

And things defined using deftype such as defrecord.

(deftype 
  ; Attributes on the class
   ^{ ObsoleteAttribute "abc"
     FileDialogPermissionAttribute SecurityAction/Demand
     FileIOPermissionAttribute #{ SecurityAction/Demand 
                                  { :__args [SecurityAction/Deny] :Read "def" }}}
   Bar [^int a
        ; Attributes on the field
        ^{ :tag int
	   NonSerializedAttribute {}
           ObsoleteAttribute "abc"}
	b]

Foo (
     ; Attributes on the method
     ^{ ObsoleteAttribute "abc"
        FileDialogPermissionAttribute SecurityAction/Demand
	FileIOPermissionAttribute #{ SecurityAction/Demand 
                                     { :__args [SecurityAction/Deny] :Read "def" }}}
     foo [this] 42))

gen-class

You can specify attributes for a constructor by attaching the metadata to the argument vector of the contructor. You can specify attributes for the generated class itself using the :class-attributes key.

(gen-class :name foo.Bar
           :extends SomeType
           :constructors {^{ObsoleteAttribute "help"} [Object] [Object Object]}
           :init init
           :class-attributes {
               ObsoleteAttribute "abc"
               FileDialogPermissionAttribute SecurityAction/Demand
               FileIOPermissionAttribute #{ SecurityAction/Demand 
                                            { :__args [SecurityAction/Deny] :Read "def" }}}
           :prefix "foo")