Added description for 'Name resolution and SAM conversion'
This commit is contained in:
@@ -715,6 +715,119 @@ Also we can access property's receiver of type `B` by specifying a label `this@f
|
||||
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:
|
||||
|
||||
[source, java]
|
||||
----
|
||||
public class J {
|
||||
/* original method */
|
||||
public int foo(Runnable r) { return 1; }
|
||||
|
||||
/* syntactic method generated by the Koltin compiler for name resolution */
|
||||
public int foo(final Function0<Unit> f) {
|
||||
return foo(new Runnable() {
|
||||
public void run() {
|
||||
f.invoke();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Both 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:
|
||||
|
||||
[source, kotlin]
|
||||
----
|
||||
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:
|
||||
|
||||
[source, java]
|
||||
----
|
||||
public class J1 {
|
||||
public int foo(Runnable r) { return 1; }
|
||||
public int foo(Function0<Unit> f) { return 2; }
|
||||
}
|
||||
----
|
||||
|
||||
[source, kotlin]
|
||||
----
|
||||
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 the step 2.
|
||||
|
||||
Step 2.
|
||||
`members -> most specific`
|
||||
|
||||
The most specific candidate is found for only 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 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:
|
||||
|
||||
[source, java]
|
||||
----
|
||||
public class J2 {
|
||||
public int foo(Runnable r) { return 1; }
|
||||
public int foo(Object o) { return 3; }
|
||||
}
|
||||
----
|
||||
|
||||
[source, java]
|
||||
----
|
||||
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.
|
||||
Reference in New Issue
Block a user