Skip to content

Files

Latest commit

 

History

History
833 lines (636 loc) · 33.5 KB

NameResolution.adoc

File metadata and controls

833 lines (636 loc) · 33.5 KB

Kotlin name resolution

This article describes the process of overload resolution in the Kotlin programming language. It explains how the compiler chooses the function that should be called in each invocation.

For example, if you write a.foo(b) somewhere in your code, the compiler has to find the most appropriate function named foo in the context. This task is extremely easy if there is only one function with the given name. However, Kotlin allows you to overload functions: define several functions with the same name but different argument lists. In this case the task of choosing the best one might be rather tricky.

What’s more these functions may come from different sources: it may be a regular top-level function, a member of a class, an extension function, a local declaration (defined inside another function); it may be declared in the same package or imported, and so on. It might be even a variable of a function type. All these cases may complicate things, and the goal of this document is to clarify how the resolution process works.

Implicit and Explicit receivers

This section introduces basic concepts that will be used later. It describes explicit and implicit receivers and shows where the latter come from.

Consider a regular top-level non-extension function:

fun foo(i: Int) { ... }

Such function is called simply by its name, without specifying any receiver (it just doesn’t have one):

foo(42)

This is the most simple case.

If the function is a member or an extension, it can be called by specifying its receiver explicitly. In this case we say that this function is called via an explicit receiver:

class A {
  fun foo(s: String) { ... }
}

>>> a.foo("x")  (1)
  1. The function foo is called with the explicit receiver a

An explicit receiver is a receiver that is written directly. In the example above a is the explicit receiver for the function call foo("x"). Note that a function invoked with the explicit receiver a doesn’t have to be a member of the class A, it might be an extension to it as well (or to a supertype of A).

The same function might be called without an explicit receiver if a proper this reference is available in the context. For example, inside an extension function to the A class, we can call foo without specifying this explicitly:

fun A.bar() {
    this.foo("y")   (1)
    foo("z")        (2)
}
  1. this is an explicit receiver

  2. this is an implicit receiver

In the latter case when we omit this we say that the function foo is called via an implicit receiver.

We’ve just bumped into the first challenge the Kotlin compiler has to cope with: when it sees a simple function invocation foo() it doesn’t mean that foo is a top-level function. It might also be a member or extension function called via an implicit receiver.

Let’s look at more examples of code with implicit receivers.

Inside a class, you use this to access an instance of the class, and you can call members or extensions just by their names:

class A {
    fun foo() {}

    fun f() {
        foo() (1)
    }
}
  1. This member function is called via implicit receiver this

If the class has a companion object, you may call members of the companion object by names as well:

class A {
    fun g() {
        A.bar() (1)
        bar()   (2)
    }

    companion object {
        fun bar() {}
    }
}
  1. You may call the function from the companion object by the class name

  2. Or you can omit the explicit specification

In this case the class name A, the reference to its companion object, becomes an implicit receiver available in this context. That means that it’s convenient but not correct to think of an implicit receiver only as an available this: the companion object might be an implicit receiver as well.

Another common case with implicit receivers is lambdas with receiver. For example, the function with below takes such a lambda as a second argument:

fun f() {
    with("str") {
        toUpperCase()  (1)
    }
}
  1. Implicit receiver is this@with

Inside the lambda you normally call functions on this receiver without specifying it, so it becomes implicit.

We saw examples with different implicit receivers in the context. Note that inside a block of code (lambda, or function body) several implicit receivers might be available. And that’s a very frequent case. Imagine that the function from the example above is a member of the class (we name it g now):

class B {
    fun g(...) {
        with("str") {
            /* ... */  (1)
        }
    }
}
  1. Two receivers this@with and this@B are available

In this case you can call methods on either of the implicit receivers this@with and this@B. Making this function an extension and adding a companion object increases the number of implicit receivers even more.

These implicit receivers are actually ordered: the one that is closer goes first. For example, this@with goes before this@B. An implicit receiver corresponding to a class goes before the receiver corresponding to its companion object.

Note that several companion objects might become implicit receivers, for example if the class extends another class with a companion object:

class A {
    companion object { ... }
}
class B: A() {
    fun h() { ... }   (1)
    companion object { ... }
}
  1. Implicit receivers: this@B, B (companion object to the class B), A (companion object to the class A)

In the further discussion we’ll rely a lot on the concept of implicit receivers available in any given context. As a summary, you may think of them as a list of all available this references together with references to companion objects, e.g. [this@with, this@B, B].

Name resolution for a call with an explicit receiver

This section describes how the Kotlin compiler resolves a call when an explicit receiver is present.

Let’s consider the call a.foo() where the explicit receiver a has a type A. The function foo can be:

  • a member of the class A;

  • an extension function to A;

  • a member extension function.

Note
A member extension function

You can see an example of a member extension function call below:

interface Builder {
    operator fun String.unaryPlus()
}

fun Builder.addABC() {
    "ABC".unaryPlus()  (1)
    + "ABC"            (2)
}
  1. this call is resolved to a member extension unaryPlus

  2. the same call using the convention for the + sign

The unaryPlus function is a member extension: it’s an extension to String and a member of the class Builder at the same time. Such functions can be called only if both receivers are present in the context: in the example above "ABC" is an explicit receiver and this (referring to Builder) is an implicit one. Note that only an extension receiver (String in this example) may be explicit. The reference to Builder will always be an implicit receiver.

For now, we suppose that foo in the call a.foo() is a regular function and not a variable of a function type. The latter case will be covered in the section "Name resolution for the invoke convention".

Several foo functions might be available in the context: members, extensions and member extensions. As the Kotlin language supports function overloading, each of these categories can contain many functions with the same name. The task of the compiler is to choose the most appropriate function (or to report ambiguity if there’re many of them).

Functions from different categories have different priorities, e.g. a member always wins over an extension. If a member can be called, you can’t force the compiler to choose an extension:

operator fun String.plus(s: String) = "!"   (1)
class String {
    operator fun plus(other: Any?): String  (2)
}

>>> "a" + "b"                               (3)
  1. this extension method will never be called

  2. because a member always wins

  3. the member is chosen, not the extension function

Even though the extension function is more precise for the call (it takes String as a parameter, while the member takes Any?), it’s never chosen. If it was, it would be too easy to break existing code without noticing that by adding an extension.

You see now that members go before extensions, but what about member extensions? They have higher priority compared to top-level extensions, but lower than local extensions. Below we’ll cover the details.

Note
Local Declarations always Win

We call a variable or function local if it’s declared inside a function. In Kotlin you can declare functions inside other functions; it helps greatly to organize the code and to extract duplicated fragments without verbosity. You can even declare an extension function locally, it’s useful if such an extension makes code more readable and only makes sense inside another function.

To make the resolution rules clear, we stick to the rule "local declaration wins". That doesn’t mean that a local extension function might be prioritized over a member, but it has higher priority than all other extension functions (including member extensions).

Now let’s discuss the specific steps the compiler performs to resolve a call:

  • First the Kotlin compiler collects all possibly applicable functions in ordered groups. Members is an example of such a group - the one with the highest priority.

  • Then for each group the most specific function is chosen; if many are applicable and no one is the most specific, then an ambiguity error is reported.

We omit the description of the process of choosing the most specific function. It works very similar to how the most specific method is found in Java and in simple cases is rather straightforward.

Note that if any function from an earlier group is applicable, it’s chosen, in spite of the fact that the next group may contain a more precise function. This was shown in the example with members and extensions above: a member function is chosen, although an extension is available that is more precise.

Below we describe the groups of functions that are created to resolve a.foo():

  1. Members of a. All overloaded member functions with the name foo have the same priority and go in one group.

  2. Local extension functions. Several groups of local extensions are created. The functions are prioritized by scopes: more local functions have higher priority and go earlier. Two overloaded functions from the same scope will be in the same group. Note that local extensions have higher priority than other extensions, however, even they can’t go before members.

  3. Member extensions. Let’s say several implicit receivers are available in the context. Each of them adds a group of member extensions named foo: extensions that are declared as members of a class corresponding to the receiver. Such groups are prioritized by their implicit receivers. The closest receiver goes first (see "Implicit and Explicit receivers" for the details of implicit receivers order).

  4. Top-level extension functions. All other extension functions are declared at the package level. They can be either declared in the same package as the resolved call or imported (directly or via star-import). Several groups of functions are created here, their order will be described in the note "Top-level scope chain" below.

Let’s consider the following artificial example to see the groups of functions that are created to resolve the call a.foo():

class A
class B { fun A.foo() = 2 }
class C { fun A.foo() = 3 }
fun A.foo() = 4

fun C.test(a: A) {
    fun A.foo() = 1
    with (B()) {
        fun A.foo() = 0

        a.foo()    (1)
    }
}
  1. a call to resolve

Five functions with the name foo are available in the context: two local extensions, two member extensions and a top-level extension. Five groups of functions are created, each group contains one function. The value the function returns reveals an order of its group: the function #0 (a function that returns 0) has the highest priority and goes first. In this example it is simple to see which function is called: the closest local function #0. But the example illustrates well the different groups of functions:

  1. Members. The class A has no members named foo, so no groups are created.

  2. Local extensions. Two local extension functions are declared: #0 and #1. They are declared in different scopes, so two groups are created. The group containing the function #0 (the closest one) has higher priority.

  3. Member extensions. Two implicit receivers are available. The first one of type B is a receiver in a lambda with receiver (lambda is the argument of the with function). The second one of type C is an extension receiver of the function test. Each receiver adds the corresponding group (a group containing function #2 goes first, then a group with function #3). Note that if several overloaded member extensions foo were declared in the class B, they would all go in one group.

  4. Top-level extensions. One group containing an extension function #4 is created.

Note that all local functions go before all member extensions, so the function #1 has higher priority than the function #2, although one might think that second one is closer. Here you can see the Kotlin rule "local declarations win" in action.

We can slightly modify the example above to see when the function not from the first group is chosen:

class A
class B { fun A.foo(i: Int) = 0 }
class C { fun A.foo(a: Any) = 1 }
fun A.foo(i: Int?) = 2

fun C.test(a: A) {
    with (B()) {
        a.foo("abc") // the function #1 is called

        val i: Int? = ...
        a.foo(i) // the function #2 is called
    }
}

We removed local functions and added different parameters to remaining functions foo. For each of the calls a.foo("abc") and a.foo(i) three groups of functions are created. The function from the first group #0 is inapplicable in both cases, so the Kotlin compiler tries to find the first applicable function from the next groups. It’s function #1 for String argument and #2 for nullable argument (both functions #0 and #1 expect a non-null parameter).

Note
Top-level scope chain

In Kotlin you can declare a function at the top-level. Such a function might be declared at the same package as the usage or imported (explicitly or via star-import). Top-level functions are prioritized in the following order:

  1. Explicit imports. If you import the necessary function by name, it has the highest priority.

  2. Functions in the same package. Such function may be located in the same file as the usage or in the other files in the same package.

  3. Star-imports. By using * you import all contents of a package. Note that the functions imported in this way have lower priority then the functions imported directly.

  4. Functions from stdlib. The Kotlin standard library contains lots of extension functions. They are all implicitly imported in any Kotlin file. If you declare the function with the same name as in stdlib, then use it from the same package or import it, your function will have higher priority then the library function.

Note that explicit import has the highest priority. That means it might be unsafe to replace an explicit import with star import, because the resolution for the call of imported function might change. If a function with the same name is declared in the same package and is applicable, it might be chosen instead of the function that was explicitly imported. The action "Optimize imports" in IntelliJ IDEA does everything correctly, so prefer using it.

In the example below the function foo from another package a is chosen because it is imported explicitly. The function foo from the same package has lower priority:

// first file
package a

class A
fun A.foo() = 1

// second file
package b

import a.A
import a.foo

fun A.foo() = 2

fun test(a: A) {
    a.foo()          (1)
}
  1. the function #1 is called

However, if you replace the import directives with import a.*, the function foo declared in the package b will be chosen.

Now let’s discuss a case when the receiver is not present: it’s either absent or implicit.

Name resolution for a call without an explicit receiver

Let’s consider the call foo(). Depending on the context, it might be a call on an implicit receiver like in this.foo() or just calling a regular function that is neither a member nor an extension.

This case is resolved similarly to the case when an explicit receiver is present. The Kotlin compiler constructs ordered groups of functions, then tests the applicability of the functions from each group. If an applicable function is found, it’s the result.

We create the following groups of functions to resolve the call foo():

  1. Local functions. All local non-extension functions named foo are added here. We put them in several groups according to the scopes they are declared in. More local function wins, as before.

  2. Functions for each implicit receiver as if it was explicit. The previous section describes how to construct groups of functions for the case when explicit receiver is present. We repeat this process for each implicit receiver available in the context. In a sense, we try to resolve the call foo() like this.foo() where this is an implicit receiver. If two implicit receivers are available, all the functions for the first receiver go before all the functions for the second one. Let’s say this@a and this@b are implicit receivers, and this@a is closer in the scope and goes first. At first we add groups of members, local extensions, member extensions, top-level extensions (as was described before) constructed for the call this@a.foo(), then we repeat the whole process for the call this@b.foo(). Note that in this case an extension to this@a may be preferred to a member of this@b: the order of implicit receivers is more important.

  3. Top-level functions. All other non-extension functions constitute several groups in an order described in the note "Top-level scope chain" above.

Let’s look at an example:

class A
fun A.foo() = 1

class B { fun foo() = 2 }

fun foo() = 3

fun B.test(a: A) {
    fun foo() = 0
    with(a) {
        foo()
    }
}

The Kotlin compiler constructs the following groups:

  1. Local functions. The local function returning 0 (the function #0) is declared, and the first group contains only this function. Note that the function #0 is applicable, so it will be the result.

  2. The groups of functions built for the first implicit receiver this@with of the type A. We perform the whole process of building the groups of functions for the call this@with.foo() as if the implicit receiver was written explicitly. Only an extension function for A exists (the function #1), so it goes next.

  3. The functions for the second implicit receiver this@test of the type B. The class B has a member named foo (the function #2), so this member composes the next group. Note that if both a member and an extension for the class B were available, the member would go in a separate group with higher priority, exactly like for the call this@test.foo().

  4. Top-level functions with the name foo. The function #3 goes at last here after the functions for the implicit receivers.

We described how the Kotlin compiler prioritizes the functions for resolving a call without an explicit receiver.

The order of implicit receivers is important. Note how in the following example after you swap two arguments of with functions, another method is called:

class A { fun foo() = 1 }
class B { fun foo() = 2 }

fun test(a: A, b: B) {
    with (b) { with (a) { foo() } }
    with (a) { with (b) { foo() } }
}

These foo calls look very similar, they differ only in the order of implicit receivers available in the context. However, different functions are called: function #1 in the first case and function #2 in the second one.

Two implicit receivers are available for each foo invocation: for simplicity let’s say a and b; in the code we may reference the closest one by this@with. In the first case a is closer and has more priority than b, so a member of A is called (function #1). In the second case it’s a member of B (function #2).

Note
Static functions from Java code

A static function foo declared in a Java class A can be called simply as foo() in any Kotlin class that extends A (directly or indirectly). Static functions are handled in a special way, because they don’t correspond to any implicit receiver. However, they have exactly the same treatment as if they had been written in Kotlin and declared in the companion object to A.

This Java class A contains a static function foo:

public class A {
    public static int foo() {
        return 0;
    }
}

If you declared the same class in Kotlin, you might have put the function foo in the companion object:

open class A1 {
    companion object { fun foo() = 0 }
}

You may extend the class declared in Java in the Kotlin code:

class C {
    inner class B : A() {
        init { foo() }
    }
    fun foo() = 1
}

The list of implicit receivers for the context in which foo() is called will be:

  • this@B,

  • A (the companion object for A if it’s declared in Kotlin),

  • this@C.

If A is declared in Java, there’s no implicit receiver corresponding to its companion object, because there is no companion object. However, all static Java methods are added with the same order as if they were members of the companion object: they go right after the members of the class.

Name resolution for the invoke convention

This section describes how hidden invoke calls are resolved. First we’ll describe the invoke convention, and then go into details of the name resolution process.

This convention in Kotlin lets you call a value as a function if it has the appropriate invoke method, which can be a member or an extension:

interface MyFunction {
    operator fun invoke(s: String): Int
}
fun test(f: MyFunction) {
    val i: Int = f("a")     (1)
}
  1. the call f.invoke("a") by convention is simplified to f("a")

The invoke function should be applicable on the arguments passed.

When you call a value of function type as a regular function in Kotlin, the same convention takes place. The function type (Int) → Int is syntactic sugar for Function1<Int, Int>, which is a regular interface declared in the standard library:

interface Function1<in P1, out R> : Function<R> {
    operator fun invoke(p1: P1): R
}
>>> val f: (Int) -> Int = { it + 1 }
>>> f(11)                              (1)
  1. the short form of the call f.invoke(11)

If a value has extension function type, it can be called as extension function:

>>> val f : Int.() -> Int = { this }
>>> 1.f()

The resolution of such call works through the invoke convention as well, but in this case the receiver becomes the first argument of the invoke function. That means the call 1.f() might be rewritten as f(1), which is the short form for f.invoke(1).

The Kotlin compiler has to take this convention into account every time it resolves a call a.foo(), because foo might be either a regular function, or a value that is called via the invoke convention.

Earlier we described how the calls a.foo() and foo() are resolved. Just to remind you, the compiler builds several groups of possibly applicable functions according to different categories of functions. These groups are ordered: functions from different groups have different priorities. The applicable function with the highest priority is the result.

You can see now that this description ignores the invoke convention: only regular functions are considered. In fact, the algorithm doesn’t change at all for the invoke convention. Local variables and properties that can be called by this convention are divided into similar groups. Thus more groups are created, while all the rest stays the same.

The property is considered together with the invoke function. Groups of properties with invoke functions are mixed with groups of regular functions, in a sense that a group of properties can have higher priority than a group of functions and vice versa. However, functions and properties can’t go in one group: the function always surpasses the property of the same category. Both the property and the invoke function determine the priority of the group: we compare the priority of the property and of the invoke function and the "lowest" one becomes the group priority. The examples below will illustrate that.

A member property of function type has higher priority than an extension function with the same name:

class A {
    val foo: () -> Int = { 1 }
}

fun A.foo() = 2

fun test(a: A) {
    a.foo()        (1)
}
  1. The member property of function type is called

In this case the Kotlin compiler constructs the following groups:

  1. The first group contains a property foo with the invoke function. Note that both the property and the invoke function are members. The property is a member of the A class. The invoke function is a member of the Function0 interface from the standard library, which is similar to Function1 interface shown above.

  2. No local extensions or member extensions with the name foo are declared, so the top-level extension foo goes next.

Note that there is no member function named foo, but if it was present, it would be put into a separate group with the highest priority.

Let’s see an example of how group priorities are determined:

  • Functions go before properties of the same category, so a top-level extension named foo has higher priority then a top-level extension property foo of function type.

  • The priority of both the property and the invoke function matters. Thus if the invoke function is declared as an extension, the member property with this function goes after the group "extension functions".

class A {
    val foo: CallableFoo = CallableFoo()
}

fun A.foo() = 1

class CallableFoo
operator fun CallableFoo.invoke() = 2

val A.foo: () -> Int
    get() = { 3 }

fun test(a: A, foo: A.() -> Int) {
    a.foo()
}

The following groups are created to resolve the call a.foo():

  1. The parameter foo of the function test that has the type A.() → Int can be called as extension function and goes in the first group. This parameter will actually be called in the example above.

  2. The top-level extension function #1 goes next.

  3. We have two top-level extension properties named foo in the context, for each of them the invoke function is available. Two groups are created. The first one contains the member property val foo: CallableFoo together with the function fun CallableFoo.invoke(). The second group contains the property val A.foo: () → Int together with the member function invoke of the class Function0<Int>. These properties belong to different groups with different priorities, because the first property is a member, while the second one is an extension. Note that despite being a member, property foo of the type CallableFoo goes after regular extension functions, because only the extension function invoke is available in the context.

Note that to resolve a call with implicit receiver we still prioritize groups by their receivers. Thus the functions and properties of closer receiver have higher priority:

class A { fun foo() = 2 }

class B { val foo: () -> Int = { 1 } }

fun test(a: A, b: B) {
    with (a) { with (b) { foo() } }
}

The call foo() in the example above is resolved to a property of the class B, because the receiver of the type B is closer than the receiver of the type A.

Note
Resolving a call with three implicit receivers

When the compiler resolves a property by the invoke convention, it resolves separately the property and the invoke function. The property can be a member extension, and the invoke function might require an extension receiver as well. Thus we can construct a call with three implicit receivers:

class A {
    val B.foo: C.() -> Unit
        get() = { println("Implicit receivers: " +
                "$this; ${this@foo}; ${this@A}") }}
}
class B
class C

fun test(a: A, b: B, c: C) {
    with (a) { with (b) { with (c) { foo() } } }   (1)
}
  1. Three implicit receivers A, B and C are used to resolve foo

In this example foo is declared as an extension property to B that has type C.() → Unit. Its getter returns a lambda with receiver. Inside this lambda we can access its receiver of type C simply by this. Also, we can access property’s receiver of type B by specifying a label this@foo and the instance of outer class by writing this@A.

While resolving the call foo the compiler has to ensure that all necessary receivers are available: A and B to resolve a property foo, and C to call the hidden invoke function.

Name resolution and SAM conversion

Kotlin supports SAM conversions. If a Java method has a parameter of the SAM interface (an interface with a single abstract method), in Kotlin you can pass a lambda argument instead. This section describes how it’s supported in the name resolution algorithm.

For each Java method taking SAM interface as a parameter, the Kotlin compiler generates an additional syntactic method. This new method takes a parameter of the corresponding function type and delegates to the original method. Note that this syntactic method exists only during the name resolution process and isn’t represented in the bytecode.

If we wrote the generated syntactic member in Java directly, it could look like this:

public class J {
    /* original method */
    public int foo(Runnable r) { return 1; }

    /* syntactic method generated by the Kotlin compiler for name resolution */
    public int foo(final Function0<Unit> f) {
        return foo(new Runnable() {
            public void run() {
                f.invoke();
            }
        });
    }
}

Both of these methods (original and syntactic) participate in the regular name resolution process. Thus if you call foo and pass lambda as a parameter, the syntactic method will be chosen:

fun test(j: J) {
    println(j.foo { }) // 1
}

Note that as there is no real foo method taking lambda as a parameter, the necessary conversion happens on the call site. (Under the hood the bytecode is optimized, so an object of the required SAM interface is created straight away instead of an object of the Kotlin function type.)

Finding the most specific member for the group of methods slightly changes when syntactic methods appear. Imagine that the J1 class explicitly declares both the method taking Runnable and the method taking Function0. In this case the method taking Function0 should be chosen without any ambiguity:

public class J1 {
    public int foo(Runnable r) { return 1; }
    public int foo(Function0<Unit> f) { return 2; }
}
fun test(j1: J1) {
    println(j1.foo { })  // 2
}

To achieve such behaviour the syntactic foo (returning 1) should have less priority than the declared foo (returning 2).

Finding the most specific member is divided into two steps:

Step 1. members + syntactic members → most specific

The most specific candidate is found for the group consisting of both members and syntactic members. If such candidate exists, it’s the result. Otherwise, the result is determined in step 2.

Step 2. members → most specific

The most specific candidate is found only for members (without syntactic members).

If no appropriate member is found, the name resolution algorithm proceeds as described earlier in this document (tries to find the appropriate function among local extensions, member extensions, etc.).

Now we can see how this process works for J and J1 classes defined above. For j.foo() call the first step returns the result, because the syntactic member for foo is chosen as the most specific candidate. However, for j1.foo() the first step finishes with ambiguity, because two foo methods (1-syntactic and 2-declared explicitly) have the same signature. Then the second step produces the result, which is the foo-2 method, because only declared methods are considered.

Note
Finding the most specific member with SAM conversion in Kotlin 1.0

In Kotlin 1.0 syntactic methods were generated differently: they were generated as extensions. That was leading to unpleasant consequences if another suitable member (taking Object) was declared. The class J2 illustrates the problem:

public class J2 {
    public int foo(Runnable r) { return 1; }
    public int foo(Object o) { return 3; }
}
fun test(j2: J2) {
    // Kotlin 1.0 prints 3 - surprise!
    // Kotlin 1.1 prints 1 as expected
    println(j2.foo {})
}

In Kotlin 1.0 foo-3 is chosen, because it’s a member, while the syntactic method for foo-1 is an extension (and a member is always chosen over an extension). Thus in Kotlin 1.1 the algorithm was slightly changed, and the syntactic methods are generated as syntactic members as desribed above.

We discussed how the Kotlin compiler chooses the function that should be called in each invocation. Generally we expect that you write code without confusing overloaded functions. However, we wanted to clarify the compiler choice in the cases when it might be unclear.