Implement Kotlin default interface method forwarding

Kotlin's implementation of defaults depends on the -Xjvm-default setting (or the @JvmDefault deprecated annotation, not implemented here): by default, actual interface class files don't use default method, and any class that would inherit one instead implements the interface calling a static method defined on TheInterface$DefaultImpls. With
-Xjvm-default=all or =all-compatibility, real interface default methods are emitted, with the latter retaining the DefaultImpls methods so that other Kotlin can use it.

Here I adopt a hybrid solution: create a real default method implementation, but also emit a forwarding method like `@override int f(int x) { return super.TheInterface.f(x); }`, because the Java extractor will see `MyClass.f` in the emitted class file and try to dispatch directly to it. The only downside is that we emit a default interface
method body for a prototype that will appear to be `abstract` to the Java extractor and which it will extract as such. I work around this by tolerating the combination `default abstract` in QL. The alternative would be to fully mimic the DefaultImpls approach, giving 100% fidelity to kotlinc's strategy and therefore no clash with the Java
extractor's view of the world.
This commit is contained in:
Chris Smowton
2022-07-20 10:01:16 +01:00
parent 040d72e7f1
commit e8a35983ee
9 changed files with 137 additions and 6 deletions

View File

@@ -67,6 +67,8 @@ class Element extends @element, Top {
i = 9 and result = "Forwarder for a @JvmOverloads-annotated function"
or
i = 10 and result = "Forwarder for Kotlin calls that need default arguments filling in"
or
i = 11 and result = "Forwarder for a Kotlin class inheriting an interface default method"
)
}
}

View File

@@ -471,7 +471,12 @@ class Method extends Callable, @method {
}
override predicate isAbstract() {
Callable.super.isAbstract()
// The combination `abstract default` isn't legal in Java,
// but it occurs when the Kotlin extractor records a default
// body, but the output class file in fact uses an abstract
// method and an associated static helper, which we don't
// extract as an implementation detail.
Callable.super.isAbstract() and not this.isDefault()
or
// JLS 9.4: An interface method lacking a `private`, `default`, or `static` modifier
// is implicitly abstract.