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:

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

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

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:

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

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:

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

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:

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:

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:

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:

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

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

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:

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:

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