12
votes

How does Kotlin disambiguate function calls, constructors, companion objects and invocation overloads? In Kotlin 1.3.11, I can declare two homonymous members in the same scope:

fun main(args: Array<String>) {
    val test = object {
        operator fun invoke() = println("test invocation")
    }

    test() // Prints: "test invocation"

    // I think this should fail to compile, but it works
    fun test() = println("test function")

    test() // Prints: "test function"
}

You might think it uses the most recent declaration, but not so!

fun main(args: Array<String>) {
    fun test() = println("test function")

    val test = object {
        operator fun invoke() = println("test invocation")
    }

    test() // Prints: "test function"
}

But there is also some weird interaction with scope. If I move the function declaration outside:

fun test() = println("test function")

fun main(args: Array<String>) {
    val test = object {
        operator fun invoke() = println("test invocation")
    }

    test() // Prints "test invocation"
}

Similarly, if I move the object outside, this also compiles:

val test = object {
    operator fun invoke() = println("test invocation")
}

fun main(args: Array<String>) {
    fun test() = println("test function")
    test() // Prints: "test function"
}

I can also move them both outside:

val test = object {
    operator fun invoke() = println("test invocation")
}

fun test() = println("test function")

fun main(args: Array<String>) {
    test() // Prints: "test function"
}

But if I overload test using a class name, it does not compile:

class test {} // Does not compile

fun test() = println("test function")

val test = object {
    operator fun invoke() = println("test invocation")
}

Attempting to compile this program results in the following error:

Error:(1, 6) Conflicting overloads: public fun test(): Unit defined in root package in file Simplest version.kt, public constructor test() defined in test, public val test: Any defined in root package in file Simplest version.kt, public final class test defined in root package in file Simplest version.kt
Error:(1, 6) Conflicting declarations: public fun test(): Unit, public constructor test(), public val test: Any, public final class test
Error:(2, 0) Conflicting overloads: public fun test(): Unit defined in root package in file Simplest version.kt, public constructor test() defined in test, public val test: Any defined in root package in file Simplest version.kt, public final class test defined in root package in file Simplest version.kt
Error:(3, 4) Conflicting declarations: public fun test(): Unit, public constructor test(), public val test: Any, public final class test

However it does compile when using a nested scope:

class test {
    constructor() {
        println("test constructor")
    }
}

fun main(args: Array<String>) {
    fun test() = println("test function")

    val test = object {
        operator fun invoke() = println("test invocation")
    }

    test() // Prints: "test function"
}

There is also some ambiguity between companion objects and constructors:

class test {
    constructor() {
        println("test constructor")
    }

    companion object {
        operator fun invoke() = println("test companion invocation")
    }
}

fun main(args: Array<String>) {
    test() // Prints: "test constructor"
}

Somehow, the following example also compiles:

class test {
    constructor() {
        println("test constructor")
    }

    companion object {
        operator fun invoke() = println("test companion invocation")
    }
}

fun main(args: Array<String>) {
    test() // Prints: "test constructor"

    val test = object {
        operator fun invoke() = println("test invocation")
    }

    test() // Prints: "test invocation"

    fun test() = println("test function")

    test() // Prints: "test function"
}

This is even less intuitive:

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"

    val test1 = test() // Prints: "test invocation overload"
}

What are the rules for overloading named members and why will the Kotlin compiler accept invocable variables and homonymous functions in the same scope, but not in the presence of a homonymous class (in certain cases, but not others)? Also, how does invoke work in the presence of a scoped constructor or companion object with the same call site syntax?

2
I don't know Kotlin overloading rules, but that looks horrible...xinaiz
Created a ticket for better compiler warnings in these scenarios: youtrack.jetbrains.com/issue/KT-29125breandan

2 Answers

4
votes

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

  1. expression with the best type match (does not occur in your question)
  2. 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

  3. 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)

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.
}
3
votes

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.