Merge pull request #9876 from smowton/smowton/feature/interface-forwarding

Kotlin: implement default interface forwarding
This commit is contained in:
Chris Smowton
2022-10-20 10:17:47 +01:00
committed by GitHub
26 changed files with 474 additions and 35 deletions

View File

@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.backend.common.lower.parents
import org.jetbrains.kotlin.backend.common.pop
import org.jetbrains.kotlin.builtins.functions.BuiltInFunctionArity
import org.jetbrains.kotlin.config.JvmAnalysisFlags
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.java.JavaVisibilities
import org.jetbrains.kotlin.ir.IrElement
@@ -845,10 +846,68 @@ open class KotlinFileExtractor(
}
}
private fun isKotlinDefinedInterface(cls: IrClass?) =
cls != null && cls.isInterface && cls.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB
private fun needsInterfaceForwarder(f: IrFunction) =
// forAllMethodsWithBody means -Xjvm-default=all or all-compatibility, in which case real Java default interfaces are used, and we don't need to do anything.
// Otherwise, for a Kotlin-defined method inheriting a Kotlin-defined default, we need to create a synthetic method like
// `int f(int x) { return super.InterfaceWithDefault.f(x); }`, because kotlinc will generate a public method and Java callers may directly target it.
// (NB. kotlinc's actual implementation strategy is different -- it makes an inner class called InterfaceWithDefault$DefaultImpls and stores the default methods
// there to allow default method usage in Java < 8, but this is hopefully niche.
!pluginContext.languageVersionSettings.getFlag(JvmAnalysisFlags.jvmDefaultMode).forAllMethodsWithBody &&
f.parentClassOrNull.let { it != null && it.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB && it.modality != Modality.ABSTRACT } &&
f.realOverrideTarget.let { it != f && (it as? IrSimpleFunction)?.modality != Modality.ABSTRACT && isKotlinDefinedInterface(it.parentClassOrNull) }
private fun makeInterfaceForwarder(f: IrFunction, parentId: Label<out DbReftype>, extractBody: Boolean, extractMethodAndParameterTypeAccesses: Boolean, typeSubstitution: TypeSubstitution?, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?) =
forceExtractFunction(f, parentId, extractBody = false, extractMethodAndParameterTypeAccesses, typeSubstitution, classTypeArgsIncludingOuterClasses, overriddenAttributes = OverriddenFunctionAttributes(visibility = DescriptorVisibilities.PUBLIC)).also { functionId ->
tw.writeCompiler_generated(functionId, CompilerGeneratedKinds.INTERFACE_FORWARDER.kind)
if (extractBody) {
val realFunctionLocId = tw.getLocation(f)
val inheritedDefaultFunction = f.realOverrideTarget
val directlyInheritedSymbol =
when(f) {
is IrSimpleFunction ->
f.overriddenSymbols.find { it.owner === inheritedDefaultFunction }
?: f.overriddenSymbols.find { it.owner.realOverrideTarget === inheritedDefaultFunction }
?: inheritedDefaultFunction.symbol
else -> inheritedDefaultFunction.symbol // This is strictly invalid, since we shouldn't use A.super.f(...) where A may not be a direct supertype, but this path should also be unreachable.
}
val defaultDefiningInterfaceType = (directlyInheritedSymbol.owner.parentClassOrNull ?: return functionId).typeWith()
extractExpressionBody(functionId, realFunctionLocId).also { returnId ->
extractRawMethodAccess(
f,
realFunctionLocId,
f.returnType,
functionId,
returnId,
0,
returnId,
f.valueParameters.size,
{ argParentId, idxOffset ->
f.valueParameters.mapIndexed { idx, param ->
val syntheticParamId = useValueParameter(param, functionId)
extractVariableAccess(syntheticParamId, param.type, realFunctionLocId, argParentId, idxOffset + idx, functionId, returnId)
}
},
f.dispatchReceiverParameter?.type,
{ callId ->
extractSuperAccess(defaultDefiningInterfaceType, functionId, callId, -1, returnId, realFunctionLocId)
},
null
)
}
}
}
private fun extractFunction(f: IrFunction, parentId: Label<out DbReftype>, extractBody: Boolean, extractMethodAndParameterTypeAccesses: Boolean, typeSubstitution: TypeSubstitution?, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?) =
if (isFake(f))
null
else {
if (isFake(f)) {
if (needsInterfaceForwarder(f))
makeInterfaceForwarder(f, parentId, extractBody, extractMethodAndParameterTypeAccesses, typeSubstitution, classTypeArgsIncludingOuterClasses)
else
null
} else {
forceExtractFunction(f, parentId, extractBody, extractMethodAndParameterTypeAccesses, typeSubstitution, classTypeArgsIncludingOuterClasses).also {
// The defaults-forwarder function is a static utility, not a member, so we only need to extract this for the unspecialised instance of this class.
if (classTypeArgsIncludingOuterClasses.isNullOrEmpty())
@@ -1163,7 +1222,7 @@ open class KotlinFileExtractor(
extractBody(body, id)
}
extractVisibility(f, id, f.visibility)
extractVisibility(f, id, overriddenAttributes?.visibility ?: f.visibility)
if (f.isInline) {
addModifiers(id, "inline")
@@ -1227,7 +1286,9 @@ open class KotlinFileExtractor(
private fun extractProperty(p: IrProperty, parentId: Label<out DbReftype>, extractBackingField: Boolean, extractFunctionBodies: Boolean, extractPrivateMembers: Boolean, typeSubstitution: TypeSubstitution?, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?) {
with("property", p) {
if (isFake(p)) return
fun needsInterfaceForwarderQ(f: IrFunction?) = f?.let { needsInterfaceForwarder(f) } ?: false
if (isFake(p) && !needsInterfaceForwarderQ(p.getter) && !needsInterfaceForwarderQ(p.setter)) return
DeclarationStackAdjuster(p).use {
@@ -5375,7 +5436,9 @@ open class KotlinFileExtractor(
val sourceLoc: Label<DbLocation>? = null,
val valueParameters: List<IrValueParameter>? = null,
val typeParameters: List<IrTypeParameter>? = null,
val isStatic: Boolean? = null)
val isStatic: Boolean? = null,
val visibility: DescriptorVisibility? = null,
)
private fun peekDeclStackAsDeclarationParent(elementToReportOn: IrElement): IrDeclarationParent? {
val trapWriter = tw
@@ -5400,6 +5463,7 @@ open class KotlinFileExtractor(
DELEGATED_PROPERTY_SETTER(7),
JVMSTATIC_PROXY_METHOD(8),
JVMOVERLOADS_METHOD(9),
DEFAULT_ARGUMENTS_METHOD(10)
DEFAULT_ARGUMENTS_METHOD(10),
INTERFACE_FORWARDER(11),
}
}