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.