Kotlin logo

Kotlin

A concise multiplatform language developed by JetBrains

Language design

Upcoming Change: Syntax For Annotations

Kotlin’s syntax for annotations was inspired by C#, which surrounds them with square brackets:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[Inject]
fun setFoo(foo: Foo) { ... }
[Inject] fun setFoo(foo: Foo) { ... }
[Inject]
fun setFoo(foo: Foo) { ... }

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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@Inject
fun setFoo(foo: Foo) { ... }
@Inject fun setFoo(foo: Foo) { ... }
@Inject
fun setFoo(foo: Foo) { ... }

NOTE: the short syntax that does not require [...] nor @ is going to be kept anyways, so you will still be able to say this:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
data class Foo
volatile var bar: Bar = ...
data class Foo volatile var bar: Bar = ...
data class Foo

volatile var bar: Bar = ...

This change has some implications, though.

Labels

First of all, the @-syntax is already in use, for labels:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@loop
for (i in 1..20) {
if (enough(i)) break@loop
println(i)
}
@loop for (i in 1..20) { if (enough(i)) break@loop println(i) }
@loop
for (i in 1..20) {
    if (enough(i)) break@loop
    println(i)
}

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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
loop@
for (i in 1..20) {
if (enough(i)) break@loop
println(i)
}
loop@ for (i in 1..20) { if (enough(i)) break@loop println(i) }
loop@
for (i in 1..20) {
    if (enough(i)) break@loop
    println(i)
}

Note that the use site (break@loop) is not changed, and still looks pretty nice :)

Targeting

We are also looking into how we could prescribe what an annotation should be attached to in the generated .class-file:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class C(@Ann("arg") var foo: Int)
class C(@Ann("arg") var foo: Int)
class C(@Ann("arg") var foo: Int)

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):

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class C(@field:Ann("arg") var foo: Int)
class C(@field:Ann("arg") var foo: Int)
class C(@field:Ann("arg") var foo: Int)

(many targets can be separated by a comma)

Another option would be to do what Scala does:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class C(@(Ann@field)("arg") var foo: Int)
class C(@(Ann@field)("arg") var foo: Int)
class C(@(Ann@field)("arg") var foo: Int)
  • 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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class C(@field(@Ann("arg")) var foo: Int)
class C(@field(@Ann("arg")) var foo: Int)
class C(@field(@Ann("arg")) var foo: Int)
  • 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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class C(@at(FIELD, @Ann1("arg"), @Ann2) var foo: Int)
class C(@atMany(array(FIELD, PROPERTY), @Ann1("arg"), @Ann2) var foo: Int)
class C(@at(FIELD, @Ann1("arg"), @Ann2) var foo: Int) class C(@atMany(array(FIELD, PROPERTY), @Ann1("arg"), @Ann2) var foo: Int)
class C(@at(FIELD, @Ann1("arg"), @Ann2) var foo: Int)
class C(@atMany(array(FIELD, PROPERTY), @Ann1("arg"), @Ann2) var foo: Int)

Then definitions are as follows:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
annotation class at(val target: AnnotationTarget, vararg val annotations: Annotation)
annotation class atMany(val target: Array<AnnotationTarget>, vararg val annotations: Annotation)
annotation class at(val target: AnnotationTarget, vararg val annotations: Annotation) annotation class atMany(val target: Array<AnnotationTarget>, vararg val annotations: Annotation)
annotation class at(val target: AnnotationTarget, vararg val annotations: Annotation)
annotation class atMany(val target: Array<AnnotationTarget>, vararg val annotations: Annotation)

And, for completeness, yet another approach involves adding an explicit (optional) syntax for declaring fields (inside properties):

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@Ann1("arg") @Ann2
val foo: Int = 1
@Ann1("arg") @Ann2
field _foo
@GetterAnnotation
get
@Ann1("arg") @Ann2 val foo: Int = 1 @Ann1("arg") @Ann2 field _foo @GetterAnnotation get
@Ann1("arg") @Ann2
val foo: Int = 1
    @Ann1("arg") @Ann2
    field _foo
    @GetterAnnotation
    get
  • 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

Annotations on Local Declarations

Our users seem to often write something like this:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
fun example() {
data class Example(val foo: String, val bar: Int) // Error on this line
...
}
fun example() { data class Example(val foo: String, val bar: Int) // Error on this line ... }
fun example() {
    data class Example(val foo: String, val bar: Int) // Error on this line
    ...
}

This does not parse correctly, because data is not a keyword (neither is open, btw), so we need to write it like this:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
fun example() {
@data class Example(val foo: String, val bar: Int) // OK
...
}
fun example() { @data class Example(val foo: String, val bar: Int) // OK ... }
fun example() {
    @data class Example(val foo: String, val bar: Int) // OK
    ...
}

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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
fun example() {
@open class Example(val foo: String, val bar: Int)
...
}
fun example() { @open class Example(val foo: String, val bar: Int) ... }
fun example() {
    @open class Example(val foo: String, val bar: Int)
    ...
}

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

image description