How does Kotlin dispatch the invoke operator?
First code snippet (simplified):
fun main(args: Array<String>) {
val test = object {
operator fun invoke() = println("test invocation")
}
test()
fun test() = println("test function")
test()
}
Decompiled from bytecode to Java looks as follows:
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
<undefinedtype> test = new Object() {
public final void invoke() {
String var1 = "test invocation";
System.out.println(var1);
}
};
((<undefinedtype>)test).invoke();
<undefinedtype> test$ = null.INSTANCE; // <---
test$.invoke(); // <---
}
Let's not make conclusions too soon, and let's reverse the order of declarations:
fun main(args: Array<String>) {
fun test() = println("test function")
test()
val test = object {
operator fun invoke() = println("test invocation")
}
test()
}
That decompiles to:
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
<undefinedtype> test$ = null.INSTANCE; // <---
test$.invoke();
Object var10000 = new Object() {
public final void invoke() {
String var1 = "test invocation";
System.out.println(var1);
}
};
test$.invoke(); // <---
}
So it seems that functions take precedence over objects with operator invoke
defined, when both of them are declared in the same scope (class scope, function scope). Let's then explicitly call invoke
operator instead of ()
syntax:
fun main(args: Array<String>) {
val test = object {
operator fun invoke() = println("test invocation")
}
fun test() = println("test function")
test.invoke() // calls object's invoke function
test() // calls local function
}
If you want do call object's invoke
function, just call it with x.invoke()
syntax. If you want to call the function, use ()
syntax.
Another thing is, when you define object / function / class in the inner scope, and there was already defined one with the same name in the outside scope, name shadowing happens. Thus in your example:
fun test() = println("test function")
fun main(args: Array<String>) {
val test = object {
operator fun invoke() = println("test invocation")
}
test() // Prints "test invocation"
}
Local variable test
shadows function test
defined in the class scope. This can be better seen if we just declare the same thing twice, one time in the outer scope, then in the inner scope:
val test = object {
operator fun invoke() = println("test invocation 1")
}
fun main(args: Array<String>) {
val test = object {
operator fun invoke() = println("test invocation 2")
}
test() // Prints "test invocation 2"
}
Here it's not different if it's function shadowed or property. If the outer scope is not the class scope, I don't know of a way to access outer one. But, if all of this happens inside a class, then it's quite simple:
class SomeClass {
fun test() = println("test function 1")
fun main(args: Array<String>) {
val test = object {
operator fun invoke() = println("test invocation 2")
}
test() // Prints "test invocation 2"
[email protected]() // Prints "test invocation 1"
}
}
The problem with class declaration, as shown in this (simplified example):
class test {} // Does not compile
fun test() = println("test function")
Is that you cannot differentiate function call from object construction. As with objects with a function invoke
declared, we could do that by using ()
or invoke()
syntaxes. I assume if there was something similar for classes (for example test.contruct()
), above would be allowed, but is not.
And at the last, the companion problem:
class test {
constructor() {
println("test constructor")
}
companion object {
operator fun invoke() = println("test companion invocation")
}
}
fun main(args: Array<String>) {
test() // Prints: "test constructor"
}
You need to remember that companion object
is just a syntax sugar. When you declare anything in companion, and then access it by SomeClass.propertyInCompanion
in fact you call SomeClass.Companion.propertyInCompanion
. Here, if there is a conflict, outer class always wins. If you need to call Companion
's invoke
function, then you have to specify it explicitly:
fun main(args: Array<String>) {
test() // Prints: "test constructor"
test.Companion() // Prints: "test companion invocation"
}
The 2 last of your code snippets are combination of all the above (name shadowing, outer class > companion) and local variable shadowing:
First snippet:
class test {
constructor() {
println("test constructor")
}
companion object {
operator fun invoke() = println("test companion invocation")
}
}
fun main(args: Array<String>) {
test() // class wins with companion, no local variable introducted
val test = object { // local variable test shadows outer scope "test"
operator fun invoke() = println("test invocation")
}
test() // calls local variable invoke function
fun test() = println("test function") // local function shadows local variable
test() // calls local function
}
Second snippet:
class test {
constructor() {
println("test constructor")
}
companion object {
operator fun invoke() = println("test companion invocation")
}
operator fun invoke() = println("test invocation overload")
}
fun main(args: Array<String>) {
val test = test() // class wins with companion. Also local variable shadows outer scope.
val test1 = test() // calls invoke function of local variable
}
Hope this answers your question.
From what I see in kotlin-spec.asc#order-of-evaluation there are three rules at play (unfortunately the text in incomplete in some points):
- expression with the best type match (does not occur in your question)
- local declaration takes precedence over non-local. This is also called shadowing.
A simple name is a single identifier. Its meaning depends on what symbol with that name are in scope. If only on symbols with that name is in scope, then the simple name refers to it. If there are multiple symbols with this name are in scope, then, informally, the symbol whose declaration is "closest" to the occurrence of the simple name is selected. For more precise rules, see TODO
if all symbols of same name are at the same level functions take precedence over properties with invoke
The actual order is
- function descriptor (
fun foo()
in the containing class) - dispatch receiver (see declaring-extensions-as-members)
In case of a name conflict between the members of the dispatch receiver and the extension receiver, the extension receiver takes precedence.
- extension receiver (
fun A.foo() defined outside of the class
) - Task Prioritizer (from what I understand finds the best match by type, or for example when there are some default parameters. I assume this is the category that
invoke
falls in)
- function descriptor (
If you apply this to your last example:
class test {
constructor() {
println("test constructor")
}
companion object {
operator fun invoke() = println("test companion invocation")
}
operator fun invoke() = println("test invocation overload")
}
fun main(args: Array<String>) {
val test = test() // Prints: "test constructor" //you create a local variable with invoke. Constructor is executed.
val test1 = test() // Prints: "test invocation overload" //invoke of the local variable is called.
test.Companion() //access the companions' invoke which is shadowed by the other invoke.
}