title | date | author | tags | keywords | categories | reward | reward_title | reward_wechat | reward_alipay | source_url |
---|---|---|---|---|---|---|---|---|---|---|
Upcoming Change: Syntax For Annotations |
2015-04-03 06:13:00 -0700 |
Andrey Breslav |
官方动态 |
false |
Have a nice Kotlin! |
Kotlin’s syntax for annotations was inspired by C#, which surrounds them with square brackets:
{% raw %}
{% endraw %}[Inject]
fun setFoo(foo: Foo) { ... }
{% raw %}
{% endraw %}But brackets are precious for a language designer, and we would really like to use them later for something else, so we are considering changing the annotation syntax to the more Java-like @
-based one:
{% raw %}
{% endraw %}@Inject
fun setFoo(foo: Foo) { ... }
{% raw %}
{% endraw %}NOTE: the short syntax that does not require [...]
nor @
is going to be kept anyways, so you will still be able to say this:
{% raw %}
{% endraw %}data class Foo
volatile var bar: Bar = ...
{% raw %}
{% endraw %}This change has some implications, though.
First of all, the @
-syntax is already in use, for labels:
{% raw %}
{% endraw %}@loop
for (i in 1..20) {
if (enough(i)) break@loop
println(i)
}
{% raw %}
{% endraw %}Since expressions can be annotated as well as declarations, we need to change something here. The simplest option would be to move the @
to the end of a label declaration:
{% raw %}
{% endraw %}loop@
for (i in 1..20) {
if (enough(i)) break@loop
println(i)
}
{% raw %}
{% endraw %}Note that the use site (break@loop
) is not changed, and still looks pretty nice
We are also looking into how we could prescribe what an annotation should be attached to in the generated .class
-file:
{% raw %}
{% endraw %}class C(@Ann("arg") var foo: Int)
{% raw %}
{% endraw %}We have quite a few options here: the @Ann
annotation can be put on
- the field where foo is stored
- the property foo itself (not a Java declaration)
- getter of foo
- setter of foo
- parameter foo of the primary constructor of C
Some annotations are only applicable to, say, fields, and for those there’s no question, but some allow many possible targets. To express the intent, some additional syntax is needed. One option would be to prefix the annotation type name with the target(s):
{% raw %}
{% endraw %}class C(@field:Ann("arg") var foo: Int)
{% raw %}
{% endraw %}(many targets can be separated by a comma) Another option would be to do what Scala does:
{% raw %}
{% endraw %}class C(@(Ann@field)("arg") var foo: Int)
{% raw %}
{% endraw %}- Downside: too many parentheses
- Upside: @field is also an annotation (yes, Ann is an annotated annotation), which means more extensible syntax and fewer concepts in the language
Yet another option would be to have @field
annotation whose arguments are annotations for the field:
{% raw %}
{% endraw %}class C(@field(@Ann("arg")) var foo: Int)
{% raw %}
{% endraw %}- Upside: even fewer language changes than the previous case
- Downside: if the same annotation goes to two targets (e.g. getter and setter), it has to be duplicated
A modification of this approach:
{% raw %}
{% endraw %}class C(@at(FIELD, @Ann1("arg"), @Ann2) var foo: Int)
class C(@atMany(array(FIELD, PROPERTY), @Ann1("arg"), @Ann2) var foo: Int)
{% raw %}
{% endraw %}Then definitions are as follows:
{% raw %}
{% endraw %}annotation class at(val target: AnnotationTarget, vararg val annotations: Annotation)
annotation class atMany(val target: Array<AnnotationTarget>, vararg val annotations: Annotation)
{% raw %}
{% endraw %}And, for completeness, yet another approach involves adding an explicit (optional) syntax for declaring fields (inside properties):
{% raw %}
{% endraw %}@Ann1("arg") @Ann2
val foo: Int = 1
@Ann1("arg") @Ann2
field _foo
@GetterAnnotation
get
{% raw %}
{% endraw %}- Downside: There’s no way to mitigate duplication here
- Downside: It is likely to become an obscure piece of syntax (like $backingField) which is used rarely and supported poorly by tools
Our users seem to often write something like this:
{% raw %}
{% endraw %}fun example() {
data class Example(val foo: String, val bar: Int) // Error on this line
...
}
{% raw %}
{% endraw %}This does not parse correctly, because data
is not a keyword (neither is open
, btw), so we need to write it like this:
{% raw %}
{% endraw %}fun example() {
@data class Example(val foo: String, val bar: Int) // OK
...
}
{% raw %}
{% endraw %}Now, what if I want an open
local class, or abstract
? Those are modifiers, not annotations, and we can’t say @open
or @abstract
.
One option is to allow escaping modifiers with @
as well as annotations:
{% raw %}
{% endraw %}fun example() {
@open class Example(val foo: String, val bar: Int)
...
}
{% raw %}
{% endraw %}Other options include allowing modifiers on the same line with the class, but this does not straightforwardly extend to functions, which are expressions now . See more here ## Feedback Welcome
What do you think? P.S. BTW, we are working on a spec document draft here