Why can't a KCallable reference be annotated in Kotlin directly?

Today, I found a discrepancy in Android Studio IDE which led me to post this question. It looks like a corner case that the language interpreter does not support or resolve properly unless I am overlooking something.

In this code snippet, I am trying to annotate a reference to the callable of a property named task and the IDE does something odd. It doesn’t recognize (color-code) the annotation used in x2 term, but it does recognize it in x1 term. The compiler also doesn’t generate any errors.

annotation class Context

var ref = @Context ::task
val x1 = schedule(ref)
val x2 = schedule(@Context ::task)

This looks like a bug. Doesn’t it?

Android Studio Giraffe | 2022.3.1
Build #AI-223.8836.35.2231.10406996, built on June 28, 2023
Runtime version: 17.0.6+0-b2043.56-10027231 amd64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.

What if you put an extra parentheses in the second case? val x2 = schedule((@Context ::task))

Same. Try this.

fun schedule(t: () -> Unit) {}
fun task() {}
val ref = @Context ::task
val x1 = schedule(ref)
val x2 = schedule(@Context ::task)

They syntactically are equivalent or at least should be, but the interpreter doesn’t think so.

It’s an interesting find. I wonder if the language architecture disallows that syntax because of an ambiguity in interpreting ::task as lambda function vs. KFunction. The runtime type of ref is indeed KFunction0 which confirms that reasoning.

Even so, the question still remains, that why wouldn’t the interpreter resolve this by type conversion?

I think I answered myself. :slightly_smiling_face:

The correct way to go about this scenario is to always give the annotation to a named reference and not an anonymous reference or a function parameter.

If syntaxes such as @Context ::task were allowed to be passed as function parameters, the compiler wouldn’t be able to discern whether or not the annotation is targeting the actual task property or the functional reference to that property. If, for example, task had its own annotation declared in the code:

val task = {}

// or

val task = @Context {}

Then, the compiler will have to replace this annotation with the one that is stated in expression @Context ::task, creating a multiplicity complex where at runtime an anonymous copy of the ::task property would have to be created in order to stay correct to that syntax. It would be too much work for little benefit.

1 Like