Home > Mobile >  Why is a Kotlin enum property not a compile time constant?
Why is a Kotlin enum property not a compile time constant?

Time:12-24

The primary target of this question is understanding the implementation and why it is like this. A solution or workaround for it would of course also be highly appreciated...

Given this example:

enum class SomeEnum(val customProp: String) {
  FOO("fooProp"),
  BAR("barProp");
}

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
annotation class TheAnnotation(
  val targetValue: String
)

@TheAnnotation(targetValue = SomeEnum.FOO.customProp)
fun testFun() {
  
}

The compilation results in the following error:

SomeEnum.kt: (14, 30): An annotation argument must be a compile-time constant

For obvious reasons, annotation values (along with others) must be compile-time constants, which makes sense in many different ways. What is unclear to me, is why customProp is not treated as a constant by the compiler.

If enums are defined as finite, closed sets of information, they should, in my understanding, only be mutable at compile-time a.k.a. "compile-time constant". For the unlikely case that enums somehow are modifiable at runtime in Kotlin, that would answer the question as well.

Addendum:

The enum value (e.g. SomeEnum.FOO) is actually treated as a compile-time constant. The proof is, that the following slightly changed snippet compiles:

enum class SomeEnum(val customProp: String) {
  FOO("fooProp"),
  BAR("barProp");
}

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class TheAnnotation(
  val targetValue: SomeEnum
)

@TheAnnotation(targetValue = SomeEnum.FOO)
fun testFun() {

}

CodePudding user response:

enums are defined as finite, closed sets of information, they should, in my understanding, only be mutable at compile-time

Actually, no. An enum class is just a special kind of class, that doesn't allow you to create any new instances other than the ones that you name in the declaration, plus a bunch more syntactic sugars. Therefore, like a regular class, it can have properties whose values are only known at runtime, and properties that are mutable (though this is usually a very bad idea).

For example:

enum class Foo {
    A, B;

    val foo = System.currentTimeMillis() // you can't know this at compile time!
}

This basically de-sugars into:

class Foo private constructor(){
    val foo = System.currentTimeMillis()
    companion object {
        val A = Foo()
        val B = Foo()
    }
}

(The actual generated code has a bit more things than this, but this is enough to illustrate my point)

A and B are just two (and the only two) instances of Foo. It should be obvious that Foo.A is not a compile time constant*, let alone Foo.A.foo. You could add an init block in Foo to run arbitrary code. You could even make foo a var, allowing you to do hideous things such as:

Foo.A.foo = 1
// now every other code that uses Foo.A.foo will see "1" as its value

You might also wonder why they didn't implement a more restricted enum that doesn't allow you to do these things, and is a compile time constant, but that is a different question.

See also: The language spec


* Though you can still pass Foo.A to an annotation. To an annotation, Foo.A is a compile time constant, because all the annotation has to do, is to store the name "Foo.A", not the object that it refers to, which has to be computed at runtime.

  • Related