Version 1.9-rfc+0.1
kotlin.Any
kotlin.Nothing
kotlin.Unit
kotlin.Boolean
kotlin.Char
kotlin.String
kotlin.Enum
kotlin.Throwable
kotlin.Comparable
kotlin.Function
kotlin.annotation.Retention
kotlin.annotation.Target
kotlin.annotation.Repeatable
kotlin.RequiresOptIn
/ kotlin.OptIn
kotlin.Deprecated
/ kotlin.ReplaceWith
kotlin.Suppress
kotlin.SinceKotlin
kotlin.UnsafeVariance
kotlin.DslMarker
kotlin.PublishedApi
kotlin.BuilderInference
kotlin.RestrictSuspension
kotlin.OverloadResolutionByLambdaReturnType
type(e)
e
Kotlin supports overloading for callables and properties, that is, the ability for several callables (functions or function-like properties) or properties with the same name to coexist in the same scope, with the compiler picking the most suitable one when such entity is referenced. This section describes overload resolution process in detail.
Note: most of this section explains the overload resolution process for callables, as the overload resolution process for properties uses the same framework. Important differences w.r.t. properties are covered in the corresponding section.
Unlike many object-oriented languages, Kotlin does not have only regular class methods, but also top-level functions, local functions, extension functions and function-like values, which complicate the overload resolution process quite a bit. Additionally, Kotlin has infix functions, operator and property overloading, which add their own specifics to this process.
Every function or property that is defined as a method or an extension has one or more special parameters called receiver parameters. When calling such a callable using navigation operators (.
or ?.
) the left hand side value is called an explicit receiver of this particular call. In addition to the explicit receiver, each call may indirectly access zero or more implicit receivers.
Implicit receivers are available in a syntactic scope according to the following rules:
this
;this
;this
parameter of the extension is also available inside the extension declaration;this
argument of the lambda expression is also available inside the lambda expression declaration.Important: a phantom static implicit
this
is a special receiver, which is included in the receiver chain for the purposes of handling static functions from enum classes. It may also be used on platforms to handle their static-like entities, e.g., static methods on JVM platform.
The available receivers are prioritized in the following way:
this
receiver has higher priority than phantom static implicit this
;this
receiver has higher priority than the current class companion object receiver;Important: these rules mean implicit receivers are always totally ordered w.r.t. their priority, as no two implicit receivers can have the same priority.
Important: DSL-specific annotations (marked with
kotlin.DslMarker
annotation) change the availability of implicit receivers in the following way: for all types marked with a particular DSL-specific annotation, only the highest priority implicit receiver is available in a given scope.
The implicit receiver having the highest priority is also called the default implicit receiver. The default implicit receiver is available in a scope as this
. Other available receivers may be accessed using labeled this-expressions.
If an implicit receiver is available in a given scope, it may be used to call callables implicitly in that scope without using the navigation operator.
For extension callables, the receiver used as the extension receiver parameter is called extension receiver, while the implicit receiver associated with the declaration scope the extension is declared in is called dispatch receiver. For a particular callable invocation, any or both receivers may be involved, but, if an extension receiver is involved, the dispatch receiver must be implicit.
Note: by definition, local extension callables do not have a dispatch receiver, as they are declared in a statement scope.
Note: there may be situations in which the same implicit receiver is used as both the dispatch receiver and the extension receiver for a particular callable invocation, for example:
interface Y class X : Y { fun Y.foo() {} // `foo` is an extension for Y, // needs extension receiver to be called fun bar() { // `this` reference is both foo() // the extension and the dispatch receiver } } fun <T> mk(): T = TODO() fun main() { val x: X = mk() val y: Y = mk() // y.foo() // Error, as there is no implicit receiver // of type X available { with (x) // OK! y.foo() } }
Any function in Kotlin may be called in several different ways:
package.foo()
;a.foo()
;a foo b
;a + b
;foo()
.Although syntactically similar, there is a difference between the first two kinds of calls: in the first case, package
is a name of a Kotlin package, while in the second case a
is a value or a type.
For each of these cases, a compiler should first pick a number of overload candidates, which form a set of possibly intended callables (overload candidate set, OCS), and then choose the most specific function to call based on the types of the function and the call arguments.
Important: the overload candidates are picked before the most specific function is chosen.
invoke
conventionA callable for the purpose of this section is one of the following:
invoke
available as a member or an extension in the current scope:
For property-like callables, a call
is an overloadable operator which is expanded to
. The call may contain type parameters, named parameters, variable argument parameter expansion and trailing lambda parameters, all of which are forwarded as-is to the corresponding invoke
function.
The set of implicit receivers itself (denoted by this
expression) may also be used as a property-like callable using this
as the left-hand side of the call expression. As with normal property-like callables,
is an overloadable operator which is expanded to
.
A member callable is one of the following:
invoke
.An extension callable is one of the following:
invoke
;invoke
;invoke
.Informally: the mnemonic rule to remember this order is “functions before properties, members before extensions”.
A local callable is any callable which is declared in a statement scope.
When calculating overload candidate sets, member callables produce the following sets, considered separately, ordered by higher priority first:
Extension callables produce the following sets, considered separately, ordered by higher priority first:
invoke
;invoke
;invoke
.Let us define this partition of callables to overload candidate sets as c-level partition (callable-level partition). As this partition is the most fine-grained of all other steps of partitioning resolution candidates into sets, it is always performed last, after all other applicable steps.
If a call is fully-qualified (that is, it contains a complete package path), then the overload candidate set simply contains all the top-level callables with the specified name in the specified package. As a package name can never clash with any other declared entity, after performing c-level partition on , the resulting sets are the only ones available for further processing.
Example:
package a.b.c fun foo(a: Int) {} fun foo(a: Double) {} fun foo(a: List<Char>) {} val foo = {} . . . a.b.c.foo()
Here the resulting overload candidate set contains all the callables named
foo
from the packagea.b.c
.
Important: a fully-qualified callable name has the form
P.n()
, wheren
is a simple callable name andP
is a complete package path referencing an existing package.
If a call is done via a navigation operator (.
or ?.
), but is not a fully-qualified call, then the left hand side value of the call is the explicit receiver of this call.
A call of callable f
with an explicit receiver e
is correct if at least one of the following holds:
f
is an accessible member callable of the classifier type type(e)
or any of its supertypes;f
is an accessible extension callable of the classifier type type(e)
or any of its supertypes, including top-level, local and imported extensions.f
is an accessible static member callable of the classifier type e
.Important: callables for case 2 include not only regular extension callables, but also extension callables from any of the available implicit receivers. For example, if class
P
contains a member extension functionf
for another classT
and an object of classP
is available as an implicit receiver, extension functionf
may be used for such call ifT
conforms to the typetype(e)
.
If a call is correct, for a callable f
with an explicit receiver e
of type T
the following sets are analyzed (in the given order):
f
of type T
;f
, whose receiver type U
conforms to type T
, in the current scope and its upwards-linked scopes, ordered by the size of the scope (smallest first), excluding the package scope;
f
(i.e., we analyze local extension callables only);f
in the order of the implicit receiver priority;f
, whose receiver type U
conforms to type T
;f
, whose receiver type U
conforms to type T
, declared in the package scope;f
, whose receiver type U
conforms to type T
;f
(either from the Kotlin standard library or platform-specific ones), whose receiver type U
conforms to type T
.Note: here type
U
conforms to typeT
, if .
There is a important special case here, however, as a callable may be a property-like callable with an operator function invoke
, and these may belong to different sets (e.g., the property itself may be star-imported, while the invoke
operator on it is a local extension). In this situation, such callable belongs to the lowest priority set of its parts (e.g., for the above case, priority 5 set).
Example: when trying to resolve between an explicitly imported extension property (priority 3) with a member
invoke
(priority 1) and a local property (priority 2) with a star-imported extensioninvoke
(priority 5), the first one wins (max(3, 1) < max(2, 5)
).
When analyzing these sets, the first set which contains any applicable callable is picked for c-level partition, which gives us the resulting overload candidate set.
Important: this means, among other things, that if the set constructed on step contains the overall most suitable candidate function, but the set constructed on step is not empty, the callables from set will be picked despite them being less suitable overload candidates.
After we have fixed the overload candidate set, we search this set for the most specific callable.
A call with an explicit receiver may be performed not only on a value receiver, but also on a type receiver.
Note: type receivers can appear when working with enum classes or interoperating with platform-dependent code.
They mostly follow the same rules as calls with an explicit value receiver. However, for a callable f
with an explicit type receiver T
the following sets are analyzed (in the given order):
f
of type T
;f
of type T
declared implicitly;T.f()
, where T
is a companion object of type T
.A call with an explicit receiver may be performed not only on a value receiver, but also on a super-form receiver.
They mostly follow the same rules as calls with an explicit value receiver. However, there are some differences which we outline below.
For a callable f
with an explicit basic super-form receiver super
in a classifier declaration with supertypes A1
, A2
, … , AN
the following sets are considered for non-emptiness:
f
of type A1
;f
of type A2
;f
of type AN
.If at least two of these sets are non-empty, this is a compile-time error. Otherwise, the non-empty set (if any) is analyzed as usual.
For a callable f
with an explicit extended super-form receiver super<A>
the following sets are analyzed (in the given order):
f
of type A
.Additionally, in either case, abstract callables are not considered valid candidates for the overload resolution process.
Infix function calls are a special case of function calls with explicit receiver in the left hand side position, i.e., a foo b
may be an infix form of a.foo(b)
.
However, there is an important difference: during the overload candidate set construction the only callables considered for inclusion are the ones with the infix
modifier. This means we consider only function-like callables with infix
modifier and property-like callables with an infix
operator function invoke
. All other callables are not considered for inclusion. Aside from this difference, candidates are selected using the same rules as for normal calls with explicit receiver.
Important: this filtering is done before we perform selection of the overload candidate set w.r.t. rules for calls with explicit receiver.
Different platform implementations may extend the set of functions considered as infix functions for the overload candidate set.
According to the operator overloading section, some operator expressions in Kotlin can be overloaded using definition-by-convention via specifically-named functions. This makes operator expressions semantically equivalent to function calls with explicit receiver, where the receiver expression is selected based on the operator used.
However, there is an important difference: during the overload candidate set construction the only functions considered for inclusion are the ones with the operator
modifier. All other functions (and any properties) are not considered for inclusion. Aside from this difference, candidates are selected using the same rules as for normal calls with explicit receiver.
Important: this filtering is done before we perform selection of the overload candidate set w.r.t. rules for calls with explicit receiver.
Note: this also means that all the properties available through the
invoke
convention are non-eligible for operator calls, as there is no way of specifying theoperator
modifier for them; even though theinvoke
callable is required to always have such modifier. Asinvoke
convention itself is an operator call, it is impossible to use more than oneinvoke
convention in a single call.
Different platform implementations may extend the set of functions considered as operator functions for the overload candidate set.
Note: these rules are valid not only for dedicated operator expressions, but also for other operator-based defined-by-convention calls, e.g.,
for
-loop iteration conventions, operator-form assignments or property delegation.
A call which is performed with a simple path is a call without an explicit receiver. As such, it may either have one or more implicit receivers or reference a top-level function.
Note: this case does not include calls using the
invoke
operator function where the left-land side of the call is not an identifier, but some other kind of expression (as this is not a simple path). These cases are handled the same way as operator calls and need no further special treatment.Example:
fun foo(a: Foo, b: Bar) { 42) (a + b)(// Such a call is handled as if it is // (a + b).invoke(42) }
As with calls with explicit receiver, we first pick an overload candidate set and then search this set for the most specific function to match the call.
For an identifier named f
the following sets are analyzed (in the given order):
f
in the current scope and its upwards-linked scopes, ordered by the size of the scope (smallest first), excluding the package scope;e
and d
available in the current scope, calculated as if e
is the explicit receiver, in order of the receiver priority;f
, in the order of:
Similarly to how it works for calls with explicit receiver, a property-like callable with an invoke function
belongs to the lowest priority set of its parts.
When analyzing these sets, the first set which contains any callable with the corresponding name and conforming types is picked for c-level partition, which gives us the resulting overload candidate set.
After we have fixed the overload candidate set, we search this set for the most specific callable.
Calls in Kotlin may use named parameters in call expressions, e.g., f(a = 2)
, where a
is a parameter specified in the declaration of f
. Such calls are treated the same way as normal calls, but the overload resolution sets are filtered to only contain callables which have matching formal parameter names for all named parameters from the call.
Important: this filtering is done before we perform selection of the overload candidate set w.r.t. rules for the respective type of call.
Note: for properties called via
invoke
convention, the named parameters must be present in the declaration of theinvoke
operator function.
Unlike positional arguments, named arguments are matched by name directly to their respective formal parameters; this matching is performed separately for each function candidate.
While the number of defaults does affect resolution process, the fact that some argument was or was not mapped as a named argument does not affect this process in any way.
A call expression may have a single lambda expression placed outside of the argument list or even completely replacing it (see this section for further details). This has no effect on the overload resolution process, aside from the argument reordering which may happen because of variable length parameters or parameters with defaults.
Example: this means that calls
f(1, 2) { g() }
andf(1, 2, body = { g() })
are completely equivalent w.r.t. the overload resolution, assumingbody
is the name of the last formal parameter off
.
A call expression may have a type argument list explicitly specified before the argument list (see this section for further details). Such calls are treated the same way as normal calls, but the overload resolution sets are filtered to only contain callables which contain exactly the same number of formal type parameters at declaration site. In case of a property-like callable with invoke
, type parameters must be present at the invoke
operator function declaration instead.
Important: this filtering is done before we perform selection of the overload candidate set w.r.t. rules for the respective type of call.
A function is applicable for a specific call if and only if the function parameters may be assigned the arguments values specified at the call site and all type constraints of the function type parameters hold w.r.t. supplied or inferred type arguments.
Determining function applicability for a specific call is a type constraint problem.
First, for every non-lambda argument of the function called, type inference is performed. Lambda arguments are excluded, as their type inference needs the results of overload resolution to finish.
Second, the following constraint system is built:
If this constraint system is sound, the function is applicable for the call. Only applicable functions are considered for the next step: choosing the most specific candidate from the overload candidate set.
Receiver parameters are handled in the same way as other parameters in this mechanism, with one important exception: any receiver of type is deemed not applicable for any member callables, regardless of other parameters. This is due to the fact that, as is the subtype of any other type in Kotlin type system, it would have allowed all member callables of all available types to participate in the overload resolution, which is theoretically possible, but very resource-consuming and does not make much sense from the practical point of view. Extension callables are still available, because they are limited to the declarations available or imported in the current scope.
Note: although it is impossible to create a value of type directly, there may be situations where performing overload resolution on such value is necessary; for example, it may occur when doing safe navigation on values of type .
The main rationale for choosing the most specific candidate from the overload candidate set is the following:
The most specific callable can forward itself to any other callable from the overload candidate set, while the opposite is not true.
If there are several functions with this property, none of them are the most specific and an overload resolution ambiguity error should be reported by the compiler.
Consider the following example.
fun f(arg: Int, arg2: String) {} // (1)
fun f(arg: Any?, arg2: CharSequence) {} // (2)
...2, "Hello") f(
Both functions (1) and (2) are applicable for the call, but function (1) could easily call function (2) by forwarding both arguments into it, and the reverse is impossible. As a result, function (1) is more specific of the two.
fun f1(arg: Int, arg2: String) {
// VALID: can forward both arguments
f2(arg, arg2) }
fun f2(arg: Any?, arg2: CharSequence) {
// INVALID: function f1 is not applicable
f1(arg, arg2) }
The rest of this section will describe how the Kotlin compiler checks for this property in more detail.
When an overload resolution set is selected and it contains more than one callable, we need to choose the most specific candidate from these callables. The selection process uses the type constraint facilities of Kotlin, in a way similar to the process of determining function applicability.
For every two distinct members of the candidate set and , the following constraint system is constructed and solved:
Note: this constraint system checks whether can forward itself to .
If the resulting constraint system is sound, it means that is equally or more applicable than as an overload candidate (aka applicability criteria). The check is then repeated with and swapped.
This check may result in one of the following outcomes:
In case 1, the more applicable candidate of the two is found and no additional steps are needed.
In case 2, an additional step is performed.
In case 3, several additional steps are performed in order.
Note: it may seem strange to process built-in integer types in a way different from other types, but it is needed for cases when the call argument is an integer literal with an integer literal type. In this particular case, several functions with different built-in integer types for the corresponding parameter may be applicable, and the
kotlin.Int
overload is selected to be the most specific.
Important: compiler implementations may extend these steps with additional checks, if they deem necessary to do so.
If after these additional steps there are still several candidates which are equally applicable for the call, we may attempt to use the lambda return type to refine function applicability. If there are still more than one most specific candidate afterwards, this is an overload ambiguity which must be reported as a compile-time error.
Note: unlike the applicability test, the candidate comparison constraint system is not based on the actual call, meaning that, when comparing two candidates, only constraints visible at declaration site apply.
If the callables in check are properties with available invoke
, the same process is applied in two steps:
invoke
overload is chosen.If the most specific candidate set
is ambiguous (has more than one callable) and contains at least one callable marked with kotlin.OverloadResolutionByLambdaReturnType
, several additional checks and steps are performed to reduce it, by attempting to infer a single lambda return type and use it to refine function applicability.
First, we perform the following checks.
We check if the function call contains exactly one lambda argument which requires type inference (which does not have an explicitly defined type).
For every function in we collect parameters corresponding to argument and check their function types to be structurally equal excluding return types (SEERT).
Informally: SEERT checks whether function types have the exactly same input parameters.
Examples: the following two function types are considered SEERT.
(Int, String) -> Int
(Int, String) -> Double
The following two function types are not considered SEERT.
Int.(String) -> Int
(Int, String) -> Double
If all checks succeed, we can perform the type inference for the lambda argument , as in all cases its parameter types are known (corollary from check 2 succeeding) and their corresponding constraints can be added to the constraint system. The constraint system solution gives us the inferred lambda return type , which may be used to refine function applicability, by removing overload candidates with incompatible lambda return types.
This is performed by repeating the function applicability test on the most specific candidate set , with the additional constraint added for the corresponding lambda argument . Candidates which remain applicable with this additional constraint are added to the refined set .
Note: If any of the checks described above fails, we continue with the set .
If set
contains more than one candidate, we attempt to prefer candidates without kotlin.OverloadResolutionByLambdaReturnType
annotation. If there are any, they are included in the resulting most specific candidate set
, with which we finish the MSC selection. Otherwise, we finish the MSC selection with the set
.
Example:
@OverloadResolutionByLambdaReturnType fun foo(cb: (Unit) -> String) = Unit // (1) @OverloadResolutionByLambdaReturnType fun foo(cb: (Unit) -> Int) = Unit // (2) fun testOk01() { { 42 } foo // Both (1) and (2) are applicable // (2) is preferred by the lambda return type }
Example:
@OverloadResolutionByLambdaReturnType fun foo(cb: Unit.() -> String) = Unit // (1) @OverloadResolutionByLambdaReturnType fun foo(cb: (Unit) -> Int) = Unit // (2) fun testError01() { val take = Unit // Overload ambiguity { 42 } foo // Both (1) and (2) are applicable // None is preferred by the lambda return type // as their parameters are not SEERT }
Example:
@OverloadResolutionByLambdaReturnType fun foo(cb: Unit.() -> String) = Unit // (1) @OverloadResolutionByLambdaReturnType fun foo(cb: (Unit) -> Int) = Unit // (2) fun testOk02() { val take = Unit { a -> 42 } foo // Only (2) is applicable // as its lambda takes one parameter }
Example:
@OverloadResolutionByLambdaReturnType fun foo(cb: (Unit) -> String) = Unit // (1) @OverloadResolutionByLambdaReturnType fun foo(cb: (Unit) -> CharSequence) = Unit // (2) fun testError02() { // Error: required String, found CharSequence { a -> foo val a: CharSequence = "42" a} // Both (1) and (2) are applicable // (1) is the only most specific candidate // We do not attempt refinement by the lambda return type }
As properties in Kotlin can have custom getters and setters, be extension or delegated, they are also subject to overload resolution. Overload resolution for property access works similarly to how it works for callables, i.e., it consists of two steps: building the overload candidate set of applicable candidates, and then choosing the most specific candidate from the overload candidate set.
Important: this section concerns only properties accessed using property access syntax
a.x
or justx
without call suffix. If a property is accessed with a call suffix, it is treated as any other callable and is required to have a suitableinvoke
overload available, see the rest of this part for details
There are two variants of property access syntax: read-only property access and property assignment.
Note: there is also safe navigation syntax for both assignment and read-only access, but that is expanded to non-safe navigation syntax covered by this section. Please refer to corresponding sections for details.
Read-only property access a.x
is resolved the same way as if the property access in question was a special function call a.x$get()
and each property val/var x: T
was replaced with corresponding function fun x$get(): T
having all the same extension receivers, context receivers, type parameters and scope as the original property and providing direct access to the property getter. For different flavors of property declarations and getters, refer to corresponding section. Please note that this excludes any possibility to employ invoke
-convention as these ephemeral functions cannot be properties themselves.
Example: one may consider property access in class
A
to be resolved as if it has been transformed to classAA
.class A { val a: Int = 5 // (1) val Double.a: Boolean // (2) get() = this != 42.0 fun test() { // Resolves to (1) println(a) 42.0) { with(this@A.a) // Resolves to (1) println(this.a) // Resolves to (2) println(// Resolves to (2) println(a) } } }
class AA { fun a$get(): Int = 5 // (1) fun Double.a$get(): Boolean // (2) this != 42.0 = fun test() { get()) // Resolves to (1) println(a$ with(42.0) { this@AA.a$get()) // Resolves to (1) println(println(this.a$get()) // Resolves to (2) println(a$get()) // Resolves to (2) } } }
Property assignment a.x = y
is resolved the same way as if it was replaced with a special function call a.x$set(y)
and each property var/val x: T
was replaced with a corresponding function fun x$set(value: T)
having all the same extension receiver parameters, context receiver parameters, type parameters and scope as the original property and providing direct access to the property setter. For different flavors of property declarations and setters, refer to corresponding section. Please note that, although a read-only property declaration (using the keyword val
) does not allow for assignment or having a setter, it still takes part in overload resolution for property assignment and may still be picked up as a candidate. Such a candidate (in case it is selected as the final candidate) will result in compiler error at later stages of compilation.
Note: informally, one may look at property assignment resolution as a sub-kind of read-only property resolution described above, first resolving the property as if it was accessed in a read-only fashion, and then using the setter. Read-only property access and property assignment syntax used in the same position never resolve to different property candidates
Example: one may consider property access in class
B
to be resolved as if it has been transformed to classBB
. Declaration bodies for ephemeral functions are omitted to avoid confusionclass B { var b: Int = 5 // (1) val Double.b: Int // (2) get() = this.toInt() fun test() { 5 // Resolves to (1) b = 42.0) { with(// Resolves to (1) this@B.b = 5 // Resolves to (2) and compiler error: cannot assign read-only property this.b = 5 // Resolves to (2) and compiler error: cannot assign read-only property 5 b = } } }
class BB { fun b$get(): Int // (1, getter) fun b$set(value: Int) // (1, setter) fun Double.b$get(): Int // (2, getter) fun Double.b$set(value: Int) // (2, setter) fun test() { set(5) // Resolves to (1) b$ with(42.0) { // Resolves to (1) this@B.b$set(5) // Resolves to (2) this.b$set(5) // Resolves to (2) this.b$set(5) } } }
The overload resolution for properties has the following features distinct from overload resolution for callables.
Callable references introduce a special case of overload resolution which is somewhat similar to how regular calls are resolved, but different in several important aspects.
First, property and function references are treated equally, as both kinds of references have a type which is a subtype of a function type. Second, the type information needed to perform the resolution steps is acquired from expected type of the reference itself, rather than the types of arguments and/or result. The invoke
operator convention does not apply to callable reference candidates. Third, and most important, is that, in the case of a call with a callable reference as a parameter, the resolution is bidirectional, meaning that both the callable being called and the callable being referenced are to be resolved simultaneously.
In a simple case when the callable reference is not used as an argument to an overloaded call, its resolution is performed as follows:
Note: this is different from the overload resolution for regular calls in that no most specific candidate selection process is performed inside the sets
Important: when the callable reference resolution for
T::f
requires building overload candidate sets for both type and value receiver candidates, they are considered in the following order.
- Static member callables named
f
of typeT
;- The overload candidate sets for call
t::f
, wheret
is a value of typeT
;- The overload candidate sets for call
T::f
, whereT
is a companion object of typeT
.Callable references to members of companion objects are deprioritized, as you could always use the
T.Companion::f
syntax to reference them.
Important: when building the OCS for a callable reference,
invoke
operator convention does not apply, and all property references are treated equally as function references, being placed in the same sets. For example, consider the following code:fun foo() = 1 val foo = 2 ...val y = ::foo
Here both function
foo
and propertyfoo
are valid candidates for the callable reference and are placed in the same candidate set, thus producing an overload ambiguity. It is not important whether there is a suitableinvoke
operator available for the type of propertyfoo
.
Example: consider the following two functions:
fun foo(i: Int): Int = 2 // (1) fun foo(d: Double): Double = 2.0 // (2)
In the following case:
val x: (Int) -> Int = ::foo
candidate (1) is picked, because (assuming is the type of the callable reference) the constraint is built and only candidate (1) is applicable w.r.t. this constraint.
In another case:
fun bar(f: (Double) -> Double) {} bar(::foo)
candidate (2) is picked, because (assuming is the type of the callable reference) the constraint is built and only candidate (2) is applicable w.r.t. this constraint.
Please note that no bidirectional resolution is performed here as there is only one candidate for
bar
. If there were more than one candidate, the bidirectional resolution process would apply, possibly resulting in an overload resolution failure.
If a callable reference (or several callable references) is itself an argument to an overloaded function call, the resolution process is performed for both callables simultaneously.
Assume we have a call f(::g, b, c)
.
f
, a separate overload resolution process is completed as described in other parts of this section, up to the point of picking the most specific candidate. During this process, the only constraint for the callable reference ::g
is that it is an argument of a function type;f
found during the previous step, the overload resolution process for ::g
is performed as described here and the most specific candidate for ::g
is selected.Note: this may result in selecting the most specific candidate for
f
which has no available candidates for::g
, meaning the bidirectional resolution process fails when resolving::g
.
When performing bidirectional resolution for calls with multiple callable reference arguments, the algorithm is exactly the same, with each callable reference resolved separately in step 2. This ensures the overload resolution process for every callable being called is performed only once.
Type inference in Kotlin is a very complicated process, and it is performed after overload resolution is done; meaning type inference may not affect the way overload resolution candidate is picked in any way.
Note: if we had allowed interdependence between type inference and overload resolution, we would have been able to create an infinitely oscillating behaviour, leading to an infinite compilation.
Important: an exception to this limitation is when a lambda return type is used to refine function applicability. By limiting the scope of interdependence between type inference and overload resolution to a single step, we avoid creating an oscillating behaviour.
In cases when it is known two callables are definitely interlinked in overload resolution (e.g., two member function-like callables declared in the same classifier), meaning they will always be considered together for overload resolution, Kotlin compiler performs conflicting overload detection for such callables.
Two callables f
and g
are definitely interlinked in overload resolution, if the following are true.
f
is not overriding g
(and vice versa);f
and g
belong to the same level of c-level partition;f
and g
are declared in the same scope.Different platform implementations may extend which callables are considered as definitely interlinked.
Two definitely interlinked callables f
and g
may create a overload conflict, if they could result in an overload ambiguity on most regular call sites.
To check whether such situation is possible, we compare f
and g
w.r.t. their applicability for a phantom call site with a fully specified argument list (i.e., with no used default arguments). If both f
and g
are equally or more specific to each other and neither of them is selected by the additional steps of MSC selection, we have an overload conflict.
Different platform implementations may extend which callables are considered as conflicting overloads.