-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Kotlin: implement default interface forwarding #9876
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Kotlin: implement default interface forwarding #9876
Conversation
735b421 to
b157920
Compare
9ae872d to
80079ac
Compare
|
@igfoo @tamasvajk this is now fixed up and ready for review |
80079ac to
8f58b11
Compare
|
Generally looks plausible to me, but the tests are failing. |
| } | ||
|
|
||
| private fun isKotlinDefinedInterface(cls: IrClass?) = | ||
| cls != null && cls.isInterface && cls.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to cover annotation classes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, because they cannot have non-Object supertypes or members, so the circumstance should never be able to arise.
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.
…ing class and the interface are Kotlin-defined. If the interface is Java-defined and it provides a default interface implementation then real class-file default methods are being used and kotlinc won't synthesise anything. If the loaded .class file wasn't made by Kotlin, then we see all the real methods and there is no need to synthesise anything either.
… their unspecialised containing function
68218ee to
50f99d8
Compare
|
This should now be in a mergeable state. |
| | file://<external>/KTypeProjection.class:0:0:0:0 | copy$default | Forwarder for Kotlin calls that need default arguments filling in | | ||
| | file://<external>/KTypeProjection.class:0:0:0:0 | covariant | Proxy static method for a @JvmStatic-annotated function or property | | ||
| | file://<external>/KTypeProjection.class:0:0:0:0 | invariant | Proxy static method for a @JvmStatic-annotated function or property | | ||
| | file://<external>/List.class:0:0:0:0 | forEach | Forwarder for a Kotlin class inheriting an interface default method | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this correct? List.forEach is not inherited from a default interface method. It's the Iterable<T>.forEach extension function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this is the Kotlin Iterable equivalent of https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html#forEach-java.util.function.Consumer- -- there's also the extension function defined in CollectionsKt, which takes a Function1<T, Unit> instead of a Consumer<T>. Not sure why they bothered to do that instead of just SAM converting the function to Consumer.
|
|
||
| } | ||
|
|
||
| class Real : Test { } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What should happen if Real was an interface?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point -- just pushed a commit that avoids synthesising forwarders in abstract types, and makes sure the super qualifier is a direct supertype like it's supposed to be.
…per accesses correctly Intermediate interfaces don't need interface forwarders, since the Kotlin compiler won't try to make them non-abstract by synthesising methods. Super references should always target an immediate superclass, not the ancestor containing the intended implementation.
b8bafe0 to
14b8892
Compare
Kotlin's implementation of defaults depends on the -Xjvm-default setting (or the
@JvmDefaultdeprecated annotation, not implemented here): by default, actual interface class files don't use default methods, and any class that would inherit one instead implements the interface calling a static method defined onTheInterface$DefaultImpls. With-Xjvm-default=allor=all-compatibility, real interface default methods are emitted, with the latter retaining theDefaultImplsmethods 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 seeMyClass.fin the emitted class file and try to dispatch directly to it. The only downside is that we emit a default interfacemethod body for a prototype that will appear to be
abstractto the Java extractor and which it will extract as such. I work around this by tolerating the combinationdefault abstractin QL. The alternative would be to fully mimic the DefaultImpls approach, giving 100% fidelity to kotlinc's strategy and therefore no clash with the Javaextractor's view of the world.