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.
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)
-
The function
foo
is called with the explicit receivera
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)
}
-
this
is an explicit receiver -
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)
}
}
-
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() {}
}
}
-
You may call the function from the companion object by the class name
-
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)
}
}
-
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)
}
}
}
-
Two receivers
this@with
andthis@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 { ... }
}
-
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]
.
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)
}
The |
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)
-
this extension method will never be called
-
because a member always wins
-
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()
:
-
Members of
a
. All overloaded member functions with the namefoo
have the same priority and go in one group. -
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.
-
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). -
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)
}
}
-
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:
-
Members. The class
A
has no members namedfoo
, so no groups are created. -
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.
-
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 thewith
function). The second one of typeC
is an extension receiver of the functiontest
. 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 extensionsfoo
were declared in the classB
, they would all go in one group. -
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:
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 // 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)
}
However, if you replace the import directives with |
Now let’s discuss a case when the receiver is not present: it’s either absent or implicit.
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()
:
-
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. -
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()
likethis.foo()
wherethis
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 saythis@a
andthis@b
are implicit receivers, andthis@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 callthis@a.foo()
, then we repeat the whole process for the callthis@b.foo()
. Note that in this case an extension tothis@a
may be preferred to a member ofthis@b
: the order of implicit receivers is more important. -
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:
-
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.
-
The groups of functions built for the first implicit receiver
this@with
of the typeA
. We perform the whole process of building the groups of functions for the callthis@with.foo()
as if the implicit receiver was written explicitly. Only an extension function forA
exists (the function #1), so it goes next. -
The functions for the second implicit receiver
this@test
of the typeB
. The classB
has a member namedfoo
(the function #2), so this member composes the next group. Note that if both a member and an extension for the classB
were available, the member would go in a separate group with higher priority, exactly like for the callthis@test.foo()
. -
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 This Java class public class A {
public static int foo() {
return 0;
}
} If you declared the same class in Kotlin, you might have put the function 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
If |
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)
}
-
the call
f.invoke("a")
by convention is simplified tof("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)
-
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)
}
-
The member property of function type is called
In this case the Kotlin compiler constructs the following groups:
-
The first group contains a property
foo
with theinvoke
function. Note that both the property and theinvoke
function are members. The property is a member of theA
class. Theinvoke
function is a member of theFunction0
interface from the standard library, which is similar toFunction1
interface shown above. -
No local extensions or member extensions with the name
foo
are declared, so the top-level extensionfoo
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 propertyfoo
of function type. -
The priority of both the property and the
invoke
function matters. Thus if theinvoke
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()
:
-
The parameter
foo
of the functiontest
that has the typeA.() → Int
can be called as extension function and goes in the first group. This parameter will actually be called in the example above. -
The top-level extension function #1 goes next.
-
We have two top-level extension properties named
foo
in the context, for each of them theinvoke
function is available. Two groups are created. The first one contains the member propertyval foo: CallableFoo
together with the functionfun CallableFoo.invoke()
. The second group contains the propertyval A.foo: () → Int
together with the member functioninvoke
of the classFunction0<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, propertyfoo
of the typeCallableFoo
goes after regular extension functions, because only the extension functioninvoke
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 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)
}
In this example While resolving the call |
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 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.