-
Notifications
You must be signed in to change notification settings - Fork 159
How to add attributes
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.
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 toAttributeType #{ 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] }
.
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" }
|
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))
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")