Added description for 'Name resolution and SAM conversion'

This commit is contained in:
Svetlana Isakova
2017-05-02 20:41:22 +03:00
parent b5d0de7c3f
commit 191e0802e9
+113
View File
@@ -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.