JVM IR: Produce correct generic signatures for special bridge methods
This commit is contained in:
committed by
Alexander Udalov
parent
c16b548dff
commit
9d63412b3e
+49
-32
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
@@ -19,7 +19,14 @@ import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
data class SpecialMethodWithDefaultInfo(
|
||||
val defaultValueGenerator: (IrSimpleFunction) -> IrExpression, val argumentsToCheck: Int, val needsArgumentBoxing: Boolean = false
|
||||
val defaultValueGenerator: (IrSimpleFunction) -> IrExpression,
|
||||
val argumentsToCheck: Int,
|
||||
val needsArgumentBoxing: Boolean = false,
|
||||
val needsGenericSignature: Boolean = false,
|
||||
)
|
||||
|
||||
class BuiltInWithDifferentJvmName(
|
||||
val needsGenericSignature: Boolean = false,
|
||||
)
|
||||
|
||||
class SpecialBridgeMethods(val context: CommonBackendContext) {
|
||||
@@ -54,36 +61,46 @@ class SpecialBridgeMethods(val context: CommonBackendContext) {
|
||||
IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, bridge.valueParameters[1].symbol)
|
||||
|
||||
private val SPECIAL_METHODS_WITH_DEFAULTS_MAP = mapOf(
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.collection, "contains", 1) to SpecialMethodWithDefaultInfo(::constFalse, 1),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.mutableCollection, "remove", 1) to SpecialMethodWithDefaultInfo(::constFalse, 1, true),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "containsKey", 1) to SpecialMethodWithDefaultInfo(::constFalse, 1),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "containsValue", 1) to SpecialMethodWithDefaultInfo(::constFalse, 1),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.mutableMap, "remove", 2) to SpecialMethodWithDefaultInfo(::constFalse, 2),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "getOrDefault", 2) to SpecialMethodWithDefaultInfo(::getSecondArg, 1),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "get", 1) to SpecialMethodWithDefaultInfo(::constNull, 1),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.mutableMap, "remove", 1) to SpecialMethodWithDefaultInfo(::constNull, 1),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.list, "indexOf", 1) to SpecialMethodWithDefaultInfo(::constMinusOne, 1),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.list, "lastIndexOf", 1) to SpecialMethodWithDefaultInfo(::constMinusOne, 1)
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.collection, "contains", 1) to
|
||||
SpecialMethodWithDefaultInfo(::constFalse, 1),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.mutableCollection, "remove", 1) to
|
||||
SpecialMethodWithDefaultInfo(::constFalse, 1, needsArgumentBoxing = true),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "containsKey", 1) to
|
||||
SpecialMethodWithDefaultInfo(::constFalse, 1),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "containsValue", 1) to
|
||||
SpecialMethodWithDefaultInfo(::constFalse, 1),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.mutableMap, "remove", 2) to
|
||||
SpecialMethodWithDefaultInfo(::constFalse, 2),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.list, "indexOf", 1) to
|
||||
SpecialMethodWithDefaultInfo(::constMinusOne, 1),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.list, "lastIndexOf", 1) to
|
||||
SpecialMethodWithDefaultInfo(::constMinusOne, 1),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "getOrDefault", 2) to
|
||||
SpecialMethodWithDefaultInfo(::getSecondArg, 1, needsGenericSignature = true),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "get", 1) to
|
||||
SpecialMethodWithDefaultInfo(::constNull, 1, needsGenericSignature = true),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.mutableMap, "remove", 1) to
|
||||
SpecialMethodWithDefaultInfo(::constNull, 1, needsGenericSignature = true)
|
||||
)
|
||||
|
||||
private val SPECIAL_PROPERTIES_SET = setOf(
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.collection, "size"),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "size"),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.charSequence.toSafe(), "length"),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "keys"),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "values"),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "entries")
|
||||
private val SPECIAL_PROPERTIES_SET = mapOf(
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.collection, "size") to BuiltInWithDifferentJvmName(),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "size") to BuiltInWithDifferentJvmName(),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.charSequence.toSafe(), "length") to BuiltInWithDifferentJvmName(),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "keys") to BuiltInWithDifferentJvmName(needsGenericSignature = true),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "values") to BuiltInWithDifferentJvmName(needsGenericSignature = true),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "entries") to BuiltInWithDifferentJvmName(needsGenericSignature = true)
|
||||
)
|
||||
|
||||
private val SPECIAL_METHODS_SETS = setOf(
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toByte"),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toShort"),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toInt"),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toLong"),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toFloat"),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toDouble"),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.mutableList, "removeAt", 1),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.charSequence.toSafe(), "get", 1)
|
||||
private val SPECIAL_METHODS_SETS = mapOf(
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toByte") to BuiltInWithDifferentJvmName(),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toShort") to BuiltInWithDifferentJvmName(),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toInt") to BuiltInWithDifferentJvmName(),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toLong") to BuiltInWithDifferentJvmName(),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toFloat") to BuiltInWithDifferentJvmName(),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toDouble") to BuiltInWithDifferentJvmName(),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.charSequence.toSafe(), "get", 1) to BuiltInWithDifferentJvmName(),
|
||||
makeDescription(KotlinBuiltIns.FQ_NAMES.mutableList, "removeAt", 1) to BuiltInWithDifferentJvmName(needsGenericSignature = true)
|
||||
)
|
||||
|
||||
fun findSpecialWithOverride(irFunction: IrSimpleFunction): Pair<IrSimpleFunction, SpecialMethodWithDefaultInfo>? {
|
||||
@@ -101,15 +118,15 @@ class SpecialBridgeMethods(val context: CommonBackendContext) {
|
||||
return SPECIAL_METHODS_WITH_DEFAULTS_MAP[description]
|
||||
}
|
||||
|
||||
fun isBuiltInWithDifferentJvmName(irFunction: IrSimpleFunction): Boolean {
|
||||
fun getBuiltInWithDifferentJvmName(irFunction: IrSimpleFunction): BuiltInWithDifferentJvmName? {
|
||||
irFunction.correspondingPropertySymbol?.let {
|
||||
val classFqName = irFunction.parentAsClass.fqNameWhenAvailable
|
||||
?: return false
|
||||
?: return null
|
||||
|
||||
return makeDescription(classFqName, it.owner.name.asString()) in SPECIAL_PROPERTIES_SET
|
||||
return SPECIAL_PROPERTIES_SET[makeDescription(classFqName, it.owner.name.asString())]
|
||||
}
|
||||
|
||||
return irFunction.toDescription() in SPECIAL_METHODS_SETS
|
||||
return SPECIAL_METHODS_SETS[irFunction.toDescription()]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+118
-47
@@ -24,9 +24,11 @@ import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.ir.IrStatement
|
||||
import org.jetbrains.kotlin.ir.builders.*
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.addFunction
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||
import org.jetbrains.kotlin.ir.types.*
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
@@ -127,19 +129,36 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass,
|
||||
val overriddenSymbols: MutableList<IrSimpleFunctionSymbol> = mutableListOf()
|
||||
)
|
||||
|
||||
// Represents a special bridge to `overridden`. Special bridges are always public and non-synthetic.
|
||||
// There are ten type-safe wrappers for different collection methods, which have an additional `methodInfo`
|
||||
// field. There is only one special bridge method which uses generic types in Java (MutableList.removeAt)
|
||||
// and needs a `specializedReturnType` for its overrides. Finally, we sometimes need to use INVOKESPECIAL
|
||||
// to invoke an existing special bridge implementation in a superclass, which is what `superQualifierSymbol`
|
||||
// is for.
|
||||
// Represents a special bridge to `overridden`. Special bridges are overrides for Java methods which are
|
||||
// exposed to Kotlin with a different name or different types. Typically, the Java version of a method is
|
||||
// non-generic, while Kotlin exposes a generic method. In this case, the bridge method needs to perform
|
||||
// additional type checking at runtime. The behavior in case of type errors is configured in `methodInfo`.
|
||||
//
|
||||
// Since special bridges may be exposed to Java as non-synthetic methods, we need correct generic signatures.
|
||||
// There are a total of seven generic special bridge methods (Map.getOrDefault, Map.get, MutableMap.remove with
|
||||
// only one argument, Map.keys, Map.values, Map.entries, and MutableList.removeAt). Of these seven there is only
|
||||
// one which the JVM backend currently handles correctly (MutableList.removeAt). For the rest, it's impossible
|
||||
// to reproduce the behavior of the JVM backend in this lowering.
|
||||
//
|
||||
// Finally, we sometimes need to use INVOKESPECIAL to invoke an existing special bridge implementation in a
|
||||
// superclass, which is what `superQualifierSymbol` is for.
|
||||
private data class SpecialBridge(
|
||||
val overridden: IrSimpleFunction,
|
||||
val signature: Method,
|
||||
val specializedReturnType: IrType? = null,
|
||||
// We need to produce a generic signature if the underlying Java method contains type parameters.
|
||||
// E.g., the `java.util.Map<K, V>.keySet` method has a return type of `Set<K>`, and hence overrides
|
||||
// need to generate a generic signature.
|
||||
val needsGenericSignature: Boolean = false,
|
||||
// The result of substituting type parameters in the overridden Java method. This is different from
|
||||
// substituting into the overridden Kotlin method. For example, Map.getOrDefault has two arguments
|
||||
// with generic types in Kotlin, but only the second parameter is generic in Java.
|
||||
// May be null if the underlying Java method does not contain generic types.
|
||||
val substitutedParameterTypes: List<IrType>? = null,
|
||||
val substitutedReturnType: IrType? = null,
|
||||
val methodInfo: SpecialMethodWithDefaultInfo? = null,
|
||||
val superQualifierSymbol: IrClassSymbol? = null,
|
||||
val isFinal: Boolean = true,
|
||||
val isSynthetic: Boolean = false,
|
||||
)
|
||||
|
||||
override fun lower(irFile: IrFile) = irFile.transformChildrenVoid()
|
||||
@@ -206,7 +225,9 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass,
|
||||
blacklist += irFunction.overriddenFinalSpecialBridges()
|
||||
|
||||
// We only generate a special bridge method if it does not clash with a final method in a superclass or the current method
|
||||
if (specialBridge.signature !in blacklist && (!irFunction.isFakeOverride || irFunction.jvmMethod != specialBridge.signature)) {
|
||||
val specialBridgeTarget = if (
|
||||
specialBridge.signature !in blacklist && (!irFunction.isFakeOverride || irFunction.jvmMethod != specialBridge.signature)
|
||||
) {
|
||||
// If irFunction is a fake override, we replace it with a stub and redirect all calls to irFunction with
|
||||
// calls to the stub instead. Otherwise we'll end up calling the special method itself and get into an
|
||||
// infinite loop.
|
||||
@@ -243,14 +264,26 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass,
|
||||
blacklist += bridgeTarget.jvmMethod
|
||||
}
|
||||
|
||||
irClass.addSpecialBridge(specialBridge, bridgeTarget)
|
||||
blacklist += specialBridge.signature
|
||||
irClass.addSpecialBridge(specialBridge, bridgeTarget)
|
||||
} else {
|
||||
irFunction
|
||||
}
|
||||
|
||||
// Deal with existing function that override special bridge methods.
|
||||
if (!irFunction.isFakeOverride && specialBridge.methodInfo != null) {
|
||||
irFunction.rewriteSpecialMethodBody(targetMethod, specialBridge.signature, specialBridge.methodInfo)
|
||||
}
|
||||
|
||||
// For generic special bridge methods we need to generate bridges for generic overrides coming from Java or Kotlin interfaces.
|
||||
if (specialBridge.substitutedReturnType != null) {
|
||||
for (overriddenSpecialBridge in irFunction.overriddenSpecialBridges()) {
|
||||
if (overriddenSpecialBridge.signature !in blacklist) {
|
||||
irClass.addSpecialBridge(overriddenSpecialBridge, specialBridgeTarget)
|
||||
blacklist += overriddenSpecialBridge.signature
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (irFunction.isJvmAbstract(context.state.jvmDefaultMode)) {
|
||||
// Do not generate bridge methods for abstract methods which do not override a special bridge method.
|
||||
// This matches the behavior of the JVM backend, but it does mean that we generate superfluous bridges
|
||||
@@ -271,6 +304,9 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass,
|
||||
}
|
||||
}
|
||||
|
||||
if (generated.isEmpty())
|
||||
return
|
||||
|
||||
// For concrete fake overrides, some of the bridges may be inherited from the super-classes. Specifically, bridges for all
|
||||
// declarations that are reachable from all concrete immediate super-functions of the given function. Note that all such bridges are
|
||||
// guaranteed to delegate to the same implementation as bridges for the given function, that's why it's safe to inherit them.
|
||||
@@ -289,32 +325,38 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass,
|
||||
// Returns the special bridge overridden by the current methods if it exists.
|
||||
private val IrSimpleFunction.specialBridgeOrNull: SpecialBridge?
|
||||
get() = specialBridgeCache.getOrPutNullable(symbol) {
|
||||
val specialMethodInfo = specialBridgeMethods.getSpecialMethodInfo(this)
|
||||
return when {
|
||||
specialMethodInfo != null ->
|
||||
// Note that there are type-safe special bridges with generic return types in Java (namely Map.getOrDefault,
|
||||
// Map.get, and MutableMap.remove), but the JVM backend does not produce overrides with specialized return
|
||||
// types for them. So for compatibility, neither do we.
|
||||
SpecialBridge(this, jvmMethod, methodInfo = specialMethodInfo)
|
||||
|
||||
specialBridgeMethods.isBuiltInWithDifferentJvmName(this) ->
|
||||
if (returnType.isTypeParameter())
|
||||
SpecialBridge(this, jvmMethod, specializedReturnType = returnType)
|
||||
else
|
||||
SpecialBridge(this, jvmMethod)
|
||||
|
||||
else -> {
|
||||
val overriddenSpecialBridge = overriddenSymbols.asSequence().mapNotNull { it.owner.specialBridgeOrNull }.firstOrNull()
|
||||
if (overriddenSpecialBridge?.specializedReturnType != null) {
|
||||
val specializedSignature = Method(
|
||||
overriddenSpecialBridge.signature.name,
|
||||
context.methodSignatureMapper.mapReturnType(this),
|
||||
overriddenSpecialBridge.signature.argumentTypes
|
||||
)
|
||||
overriddenSpecialBridge.copy(signature = specializedSignature, specializedReturnType = returnType)
|
||||
} else {
|
||||
overriddenSpecialBridge
|
||||
specialBridgeMethods.getSpecialMethodInfo(this)?.let { specialMethodInfo ->
|
||||
SpecialBridge(this, jvmMethod, methodInfo = specialMethodInfo, needsGenericSignature = specialMethodInfo.needsGenericSignature)
|
||||
} ?: specialBridgeMethods.getBuiltInWithDifferentJvmName(this)?.let { specialBuiltInInfo ->
|
||||
SpecialBridge(this, jvmMethod, needsGenericSignature = specialBuiltInInfo.needsGenericSignature)
|
||||
} ?: overriddenSymbols.asSequence().mapNotNull { it.owner.specialBridgeOrNull }.firstOrNull()?.let { specialBridge ->
|
||||
if (specialBridge.needsGenericSignature) {
|
||||
// Compute the substituted signature.
|
||||
val erasedParameterCount = specialBridge.methodInfo?.argumentsToCheck ?: 0
|
||||
val substitutedParameterTypes = valueParameters.mapIndexed { index, param ->
|
||||
if (index < erasedParameterCount) context.irBuiltIns.anyNType else param.type
|
||||
}
|
||||
|
||||
val substitutedOverride = buildFun {
|
||||
updateFrom(specialBridge.overridden)
|
||||
name = Name.identifier(specialBridge.signature.name)
|
||||
returnType = this@specialBridgeOrNull.returnType
|
||||
}.apply {
|
||||
// All existing special bridges only have value parameter types.
|
||||
valueParameters = this@specialBridgeOrNull.valueParameters.zip(substitutedParameterTypes).map { (param, type) ->
|
||||
param.copyTo(this, IrDeclarationOrigin.BRIDGE, type = type)
|
||||
}
|
||||
overriddenSymbols = listOf(specialBridge.overridden.symbol)
|
||||
parent = this@specialBridgeOrNull.parent
|
||||
}
|
||||
|
||||
specialBridge.copy(
|
||||
signature = substitutedOverride.jvmMethod,
|
||||
substitutedParameterTypes = substitutedParameterTypes,
|
||||
substitutedReturnType = returnType
|
||||
)
|
||||
} else {
|
||||
specialBridge
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,6 +376,14 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass,
|
||||
it.specialBridgeOrNull?.signature?.takeIf { bridgeSignature -> bridgeSignature != it.jvmMethod }
|
||||
}
|
||||
|
||||
// Sequence of special bridge methods which were not implemented in Kotlin superclasses.
|
||||
private fun IrSimpleFunction.overriddenSpecialBridges(): Sequence<SpecialBridge> = allOverridden().mapNotNull {
|
||||
if (it.parentAsClass.isInterface || it.comesFromJava())
|
||||
it.specialBridgeOrNull?.takeIf { bridge -> bridge.signature != jvmMethod }
|
||||
?.copy(isFinal = false, isSynthetic = true, methodInfo = null)
|
||||
else null
|
||||
}
|
||||
|
||||
private fun IrClass.addAbstractMethodStub(irFunction: IrSimpleFunction, needsArgumentBoxing: Boolean) =
|
||||
addFunction {
|
||||
updateFrom(irFunction)
|
||||
@@ -347,7 +397,10 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass,
|
||||
// However, we cannot link in the new function as the new accessor for the property, since there might still
|
||||
// be references to the original fake override stub.
|
||||
copyCorrespondingPropertyFrom(irFunction)
|
||||
copyParametersWithErasure(this@addAbstractMethodStub, irFunction, needsArgumentBoxing)
|
||||
dispatchReceiverParameter = thisReceiver?.copyTo(this, type = defaultType)
|
||||
valueParameters = irFunction.valueParameters.map { param ->
|
||||
param.copyTo(this, type = if (needsArgumentBoxing) param.type.makeNullable() else param.type)
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrClass.addBridge(bridge: Bridge, target: IrSimpleFunction): IrSimpleFunction =
|
||||
@@ -379,15 +432,17 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass,
|
||||
private fun IrClass.addSpecialBridge(specialBridge: SpecialBridge, target: IrSimpleFunction): IrSimpleFunction =
|
||||
addFunction {
|
||||
modality = if (specialBridge.isFinal) Modality.FINAL else Modality.OPEN
|
||||
origin = IrDeclarationOrigin.BRIDGE_SPECIAL
|
||||
origin = if (specialBridge.isSynthetic) IrDeclarationOrigin.BRIDGE else IrDeclarationOrigin.BRIDGE_SPECIAL
|
||||
name = Name.identifier(specialBridge.signature.name)
|
||||
returnType = specialBridge.specializedReturnType ?: specialBridge.overridden.returnType.eraseTypeParameters()
|
||||
returnType = specialBridge.substitutedReturnType ?: specialBridge.overridden.returnType.eraseTypeParameters()
|
||||
}.apply {
|
||||
copyParametersWithErasure(
|
||||
this@addSpecialBridge,
|
||||
specialBridge.overridden,
|
||||
specialBridge.methodInfo?.needsArgumentBoxing == true
|
||||
specialBridge.methodInfo?.needsArgumentBoxing == true,
|
||||
specialBridge.substitutedParameterTypes
|
||||
)
|
||||
|
||||
body = context.createIrBuilder(symbol).irBlockBody {
|
||||
specialBridge.methodInfo?.let { info ->
|
||||
valueParameters.take(info.argumentsToCheck).forEach {
|
||||
@@ -396,7 +451,7 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass,
|
||||
}
|
||||
+irReturn(delegatingCall(this@apply, target, specialBridge.superQualifierSymbol))
|
||||
}
|
||||
overriddenSymbols += specialBridge.overridden.symbol
|
||||
overriddenSymbols = listOf(specialBridge.overridden.symbol)
|
||||
}
|
||||
|
||||
private fun IrSimpleFunction.rewriteSpecialMethodBody(
|
||||
@@ -421,8 +476,8 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass,
|
||||
argumentsToCheck.forEach {
|
||||
val parameterType = it.type
|
||||
if (!parameterType.isNullable()) {
|
||||
val newParameter = it.copyTo(this@rewriteSpecialMethodBody, type = context.irBuiltIns.anyNType)
|
||||
variableMap.put(valueParameters[it.index], newParameter)
|
||||
val newParameter = it.copyTo(this@rewriteSpecialMethodBody, type = parameterType.makeNullable())
|
||||
variableMap[valueParameters[it.index]] = newParameter
|
||||
newValueParameters[it.index] = newParameter
|
||||
+parameterTypeCheck(
|
||||
newParameter,
|
||||
@@ -463,17 +518,33 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass,
|
||||
private fun IrBuilderWithScope.parameterTypeCheck(parameter: IrValueParameter, type: IrType, defaultValue: IrExpression) =
|
||||
irIfThen(context.irBuiltIns.unitType, irNot(irIs(irGet(parameter), type)), irReturn(defaultValue))
|
||||
|
||||
private fun IrSimpleFunction.copyParametersWithErasure(irClass: IrClass, from: IrSimpleFunction, forceArgumentBoxing: Boolean = false) {
|
||||
private fun IrSimpleFunction.copyParametersWithErasure(
|
||||
irClass: IrClass,
|
||||
from: IrSimpleFunction,
|
||||
forceArgumentBoxing: Boolean = false,
|
||||
substitutedParameterTypes: List<IrType>? = null
|
||||
) {
|
||||
// This is a workaround for a bug affecting fake overrides. Sometimes we encounter fake overrides
|
||||
// with dispatch receivers pointing at a superclass instead of the current class.
|
||||
dispatchReceiverParameter = irClass.thisReceiver?.copyTo(this, type = irClass.defaultType)
|
||||
extensionReceiverParameter = from.extensionReceiverParameter?.copyWithTypeErasure(this, forceArgumentBoxing)
|
||||
valueParameters = from.valueParameters.map { it.copyWithTypeErasure(this, forceArgumentBoxing) }
|
||||
valueParameters = if (substitutedParameterTypes != null) {
|
||||
from.valueParameters.zip(substitutedParameterTypes).map { (param, type) ->
|
||||
param.copyWithTypeErasure(this, forceArgumentBoxing, type)
|
||||
}
|
||||
} else {
|
||||
from.valueParameters.map { it.copyWithTypeErasure(this, forceArgumentBoxing) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrValueParameter.copyWithTypeErasure(target: IrSimpleFunction, forceBoxing: Boolean = false): IrValueParameter = copyTo(
|
||||
private fun IrValueParameter.copyWithTypeErasure(
|
||||
target: IrSimpleFunction,
|
||||
forceArgumentBoxing: Boolean = false,
|
||||
substitutedType: IrType? = null
|
||||
): IrValueParameter = copyTo(
|
||||
target, IrDeclarationOrigin.BRIDGE,
|
||||
type = type.eraseTypeParameters().let { if (forceBoxing) it.makeNullable() else it },
|
||||
type = (substitutedType ?: type.eraseTypeParameters()).let { if (forceArgumentBoxing) it.makeNullable() else it },
|
||||
// Currently there are no special bridge methods with vararg parameters, so we don't track substituted vararg element types.
|
||||
varargElementType = varargElementType?.eraseTypeParameters()
|
||||
)
|
||||
|
||||
@@ -492,10 +563,10 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass,
|
||||
private fun IrBuilderWithScope.irCastIfNeeded(expression: IrExpression, to: IrType): IrExpression =
|
||||
if (expression.type == to || to.isAny() || to.isNullableAny()) expression else irImplicitCast(expression, to)
|
||||
|
||||
private val signatureCache = mutableMapOf<IrFunction, Method>()
|
||||
private val signatureCache = mutableMapOf<IrFunctionSymbol, Method>()
|
||||
|
||||
private val IrFunction.jvmMethod: Method
|
||||
get() = signatureCache.getOrPut(this) { context.methodSignatureMapper.mapAsmMethod(this) }
|
||||
get() = signatureCache.getOrPut(symbol) { context.methodSignatureMapper.mapAsmMethod(this) }
|
||||
}
|
||||
|
||||
private fun IrDeclaration.comesFromJava() = parentAsClass.origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// TARGET_BACKEND: JVM
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
|
||||
// java.lang.NoSuchMethodError: java.util.TreeMap.remove
|
||||
// IGNORE_BACKEND: ANDROID
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
interface ImmutableCollection<out E> : Collection<E> {
|
||||
fun add(element: @UnsafeVariance E): ImmutableCollection<E>
|
||||
fun addAll(elements: Collection<@UnsafeVariance E>): ImmutableCollection<E>
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
open class A0<E> : MutableList<E> {
|
||||
override fun add(element: E): Boolean {
|
||||
throw UnsupportedOperationException()
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// WITH_SIGNATURES
|
||||
|
||||
class GenericMap<K, V> : MutableMap<K, V> by HashMap<K, V>()
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// WITH_SIGNATURES
|
||||
// Test expectations differ between JVM and JVM IR backends, because of KT-40277. This should be revisited once KT-40277 is resolved.
|
||||
|
||||
class StringStringMap : MutableMap<String, String> by HashMap<String, String>()
|
||||
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
@kotlin.Metadata
|
||||
public abstract class<Ljava/lang/Object;Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;Lkotlin/jvm/internal/markers/KMutableMap;> AbstractStringStringMap {
|
||||
public @org.jetbrains.annotations.NotNull <()Ljava/util/Collection<Ljava/lang/String;>;> method getValues(): java.util.Collection
|
||||
public bridge final <()Ljava/util/Collection<Ljava/lang/String;>;> method values(): java.util.Collection
|
||||
public @org.jetbrains.annotations.NotNull <()Ljava/util/Set<Ljava/lang/String;>;> method getKeys(): java.util.Set
|
||||
public bridge final <()Ljava/util/Set<Ljava/lang/String;>;> method keySet(): java.util.Set
|
||||
public bridge final <()Ljava/util/Set<Ljava/util/Map$Entry<Ljava/lang/String;Ljava/lang/String;>;>;> method entrySet(): java.util.Set
|
||||
public @org.jetbrains.annotations.NotNull <()Ljava/util/Set<Ljava/util/Map$Entry<Ljava/lang/String;Ljava/lang/String;>;>;> method getEntries(): java.util.Set
|
||||
public <(Ljava/util/Map<+Ljava/lang/String;+Ljava/lang/String;>;)V> method putAll(@org.jetbrains.annotations.NotNull p0: java.util.Map): void
|
||||
public <null> method <init>(): void
|
||||
public <null> method clear(): void
|
||||
public <null> method containsKey(@org.jetbrains.annotations.NotNull p0: java.lang.String): boolean
|
||||
public bridge final <null> method containsKey(p0: java.lang.Object): boolean
|
||||
public <null> method containsValue(@org.jetbrains.annotations.NotNull p0: java.lang.String): boolean
|
||||
public bridge final <null> method containsValue(p0: java.lang.Object): boolean
|
||||
public @org.jetbrains.annotations.Nullable <null> method get(@org.jetbrains.annotations.NotNull p0: java.lang.String): java.lang.String
|
||||
public synthetic bridge <null> method get(p0: java.lang.Object): java.lang.Object
|
||||
public bridge final <null> method get(p0: java.lang.Object): java.lang.String
|
||||
public <null> method getSize(): int
|
||||
public <null> method isEmpty(): boolean
|
||||
public @org.jetbrains.annotations.Nullable <null> method put(@org.jetbrains.annotations.NotNull p0: java.lang.String, @org.jetbrains.annotations.NotNull p1: java.lang.String): java.lang.String
|
||||
public synthetic bridge <null> method put(p0: java.lang.Object, p1: java.lang.Object): java.lang.Object
|
||||
public @org.jetbrains.annotations.Nullable <null> method remove(@org.jetbrains.annotations.NotNull p0: java.lang.String): java.lang.String
|
||||
public synthetic bridge <null> method remove(p0: java.lang.Object): java.lang.Object
|
||||
public bridge final <null> method remove(p0: java.lang.Object): java.lang.String
|
||||
public bridge final <null> method size(): int
|
||||
private synthetic final field <Ljava/util/HashMap<Ljava/lang/String;Ljava/lang/String;>;> $$delegate_0: java.util.HashMap
|
||||
}
|
||||
|
||||
@kotlin.Metadata
|
||||
public final class<Ljava/lang/Object;Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;Lkotlin/jvm/internal/markers/KMutableMap;> StringStringMap {
|
||||
public @org.jetbrains.annotations.NotNull <()Ljava/util/Collection<Ljava/lang/String;>;> method getValues(): java.util.Collection
|
||||
public bridge final <()Ljava/util/Collection<Ljava/lang/String;>;> method values(): java.util.Collection
|
||||
public @org.jetbrains.annotations.NotNull <()Ljava/util/Set<Ljava/lang/String;>;> method getKeys(): java.util.Set
|
||||
public bridge final <()Ljava/util/Set<Ljava/lang/String;>;> method keySet(): java.util.Set
|
||||
public bridge final <()Ljava/util/Set<Ljava/util/Map$Entry<Ljava/lang/String;Ljava/lang/String;>;>;> method entrySet(): java.util.Set
|
||||
public @org.jetbrains.annotations.NotNull <()Ljava/util/Set<Ljava/util/Map$Entry<Ljava/lang/String;Ljava/lang/String;>;>;> method getEntries(): java.util.Set
|
||||
public <(Ljava/util/Map<+Ljava/lang/String;+Ljava/lang/String;>;)V> method putAll(@org.jetbrains.annotations.NotNull p0: java.util.Map): void
|
||||
public <null> method <init>(): void
|
||||
public <null> method clear(): void
|
||||
public <null> method containsKey(@org.jetbrains.annotations.NotNull p0: java.lang.String): boolean
|
||||
public bridge final <null> method containsKey(p0: java.lang.Object): boolean
|
||||
public <null> method containsValue(@org.jetbrains.annotations.NotNull p0: java.lang.String): boolean
|
||||
public bridge final <null> method containsValue(p0: java.lang.Object): boolean
|
||||
public @org.jetbrains.annotations.Nullable <null> method get(@org.jetbrains.annotations.NotNull p0: java.lang.String): java.lang.String
|
||||
public synthetic bridge <null> method get(p0: java.lang.Object): java.lang.Object
|
||||
public bridge final <null> method get(p0: java.lang.Object): java.lang.String
|
||||
public <null> method getSize(): int
|
||||
public <null> method isEmpty(): boolean
|
||||
public @org.jetbrains.annotations.Nullable <null> method put(@org.jetbrains.annotations.NotNull p0: java.lang.String, @org.jetbrains.annotations.NotNull p1: java.lang.String): java.lang.String
|
||||
public synthetic bridge <null> method put(p0: java.lang.Object, p1: java.lang.Object): java.lang.Object
|
||||
public @org.jetbrains.annotations.Nullable <null> method remove(@org.jetbrains.annotations.NotNull p0: java.lang.String): java.lang.String
|
||||
public synthetic bridge <null> method remove(p0: java.lang.Object): java.lang.Object
|
||||
public bridge final <null> method remove(p0: java.lang.Object): java.lang.String
|
||||
public bridge final <null> method size(): int
|
||||
private synthetic final field <Ljava/util/HashMap<Ljava/lang/String;Ljava/lang/String;>;> $$delegate_0: java.util.HashMap
|
||||
}
|
||||
Vendored
+1
-1
@@ -1,5 +1,5 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// WITH_SIGNATURES
|
||||
// Test expectations differ between JVM and JVM IR backends, because of KT-40277. This should be revisited once KT-40277 is resolved.
|
||||
|
||||
class StringMap<V> : MutableMap<String, V> by HashMap<String, V>()
|
||||
|
||||
|
||||
compiler/testData/codegen/bytecodeListing/specialBridges/signatures/partiallySpecializedClass_ir.txt
Vendored
+51
@@ -0,0 +1,51 @@
|
||||
@kotlin.Metadata
|
||||
public abstract class<<V:Ljava/lang/Object;>Ljava/lang/Object;Ljava/util/Map<Ljava/lang/String;TV;>;Lkotlin/jvm/internal/markers/KMutableMap;> AbstractStringMap {
|
||||
public @org.jetbrains.annotations.NotNull <()Ljava/util/Collection<TV;>;> method getValues(): java.util.Collection
|
||||
public bridge final <()Ljava/util/Collection<TV;>;> method values(): java.util.Collection
|
||||
public @org.jetbrains.annotations.NotNull <()Ljava/util/Set<Ljava/lang/String;>;> method getKeys(): java.util.Set
|
||||
public bridge final <()Ljava/util/Set<Ljava/lang/String;>;> method keySet(): java.util.Set
|
||||
public bridge final <()Ljava/util/Set<Ljava/util/Map$Entry<Ljava/lang/String;TV;>;>;> method entrySet(): java.util.Set
|
||||
public @org.jetbrains.annotations.NotNull <()Ljava/util/Set<Ljava/util/Map$Entry<Ljava/lang/String;TV;>;>;> method getEntries(): java.util.Set
|
||||
public @org.jetbrains.annotations.Nullable <(Ljava/lang/Object;)TV;> method get(@org.jetbrains.annotations.NotNull p0: java.lang.String): java.lang.Object
|
||||
public bridge final <(Ljava/lang/Object;)TV;> method get(p0: java.lang.Object): java.lang.Object
|
||||
public @org.jetbrains.annotations.Nullable <(Ljava/lang/Object;)TV;> method remove(@org.jetbrains.annotations.NotNull p0: java.lang.String): java.lang.Object
|
||||
public bridge final <(Ljava/lang/Object;)TV;> method remove(p0: java.lang.Object): java.lang.Object
|
||||
public @org.jetbrains.annotations.Nullable <(Ljava/lang/String;TV;)TV;> method put(@org.jetbrains.annotations.NotNull p0: java.lang.String, p1: java.lang.Object): java.lang.Object
|
||||
public <(Ljava/util/Map<+Ljava/lang/String;+TV;>;)V> method putAll(@org.jetbrains.annotations.NotNull p0: java.util.Map): void
|
||||
public <null> method <init>(): void
|
||||
public <null> method clear(): void
|
||||
public <null> method containsKey(@org.jetbrains.annotations.NotNull p0: java.lang.String): boolean
|
||||
public bridge final <null> method containsKey(p0: java.lang.Object): boolean
|
||||
public <null> method containsValue(p0: java.lang.Object): boolean
|
||||
public <null> method getSize(): int
|
||||
public <null> method isEmpty(): boolean
|
||||
public synthetic bridge <null> method put(p0: java.lang.Object, p1: java.lang.Object): java.lang.Object
|
||||
public bridge final <null> method size(): int
|
||||
private synthetic final field <Ljava/util/HashMap<Ljava/lang/String;TV;>;> $$delegate_0: java.util.HashMap
|
||||
}
|
||||
|
||||
@kotlin.Metadata
|
||||
public final class<<V:Ljava/lang/Object;>Ljava/lang/Object;Ljava/util/Map<Ljava/lang/String;TV;>;Lkotlin/jvm/internal/markers/KMutableMap;> StringMap {
|
||||
public @org.jetbrains.annotations.NotNull <()Ljava/util/Collection<TV;>;> method getValues(): java.util.Collection
|
||||
public bridge final <()Ljava/util/Collection<TV;>;> method values(): java.util.Collection
|
||||
public @org.jetbrains.annotations.NotNull <()Ljava/util/Set<Ljava/lang/String;>;> method getKeys(): java.util.Set
|
||||
public bridge final <()Ljava/util/Set<Ljava/lang/String;>;> method keySet(): java.util.Set
|
||||
public bridge final <()Ljava/util/Set<Ljava/util/Map$Entry<Ljava/lang/String;TV;>;>;> method entrySet(): java.util.Set
|
||||
public @org.jetbrains.annotations.NotNull <()Ljava/util/Set<Ljava/util/Map$Entry<Ljava/lang/String;TV;>;>;> method getEntries(): java.util.Set
|
||||
public @org.jetbrains.annotations.Nullable <(Ljava/lang/Object;)TV;> method get(@org.jetbrains.annotations.NotNull p0: java.lang.String): java.lang.Object
|
||||
public bridge final <(Ljava/lang/Object;)TV;> method get(p0: java.lang.Object): java.lang.Object
|
||||
public @org.jetbrains.annotations.Nullable <(Ljava/lang/Object;)TV;> method remove(@org.jetbrains.annotations.NotNull p0: java.lang.String): java.lang.Object
|
||||
public bridge final <(Ljava/lang/Object;)TV;> method remove(p0: java.lang.Object): java.lang.Object
|
||||
public @org.jetbrains.annotations.Nullable <(Ljava/lang/String;TV;)TV;> method put(@org.jetbrains.annotations.NotNull p0: java.lang.String, p1: java.lang.Object): java.lang.Object
|
||||
public <(Ljava/util/Map<+Ljava/lang/String;+TV;>;)V> method putAll(@org.jetbrains.annotations.NotNull p0: java.util.Map): void
|
||||
public <null> method <init>(): void
|
||||
public <null> method clear(): void
|
||||
public <null> method containsKey(@org.jetbrains.annotations.NotNull p0: java.lang.String): boolean
|
||||
public bridge final <null> method containsKey(p0: java.lang.Object): boolean
|
||||
public <null> method containsValue(p0: java.lang.Object): boolean
|
||||
public <null> method getSize(): int
|
||||
public <null> method isEmpty(): boolean
|
||||
public synthetic bridge <null> method put(p0: java.lang.Object, p1: java.lang.Object): java.lang.Object
|
||||
public bridge final <null> method size(): int
|
||||
private synthetic final field <Ljava/util/HashMap<Ljava/lang/String;TV;>;> $$delegate_0: java.util.HashMap
|
||||
}
|
||||
@@ -90,18 +90,33 @@ fun box(
|
||||
}
|
||||
|
||||
/*
|
||||
16 INVOKEVIRTUAL A[0-9]+.removeAt \(I\) -> calls in bridges with signature `public final bridge remove\(I\)` + 7 calls from `public synthetic bridge remove\(I\)Ljava/lang/Object;`
|
||||
9 INVOKEVIRTUAL A[0-9]+\.remove \(I\) -> calls to A1-A9.removeAt
|
||||
1 INVOKEINTERFACE A9\.remove \(I\) -> call A9.removeAt
|
||||
1 INVOKEINTERFACE A9\.remove \(Ljava/lang/Object;\) -> call A9.remove
|
||||
|
||||
On the JVM backend we have:
|
||||
16 INVOKEVIRTUAL A[0-9]+.removeAt \(I\) -> calls in bridges with signature `public final bridge remove\(I\)` + 7 calls from `public synthetic bridge remove\(I\)Ljava/lang/Object;`
|
||||
9 INVOKEVIRTUAL A[0-9]+\.remove \(I\) -> calls to A1-A9.removeAt
|
||||
1 INVOKEVIRTUAL A10\.remove \(I\) -> one call in 'box' function
|
||||
|
||||
On the JVM IR backend we have:
|
||||
9 INVOKEVIRTUAL A[0-9]+.removeAt \(I\) -> calls in non-synthetic bridges with signature `public final bridge remove(I)`
|
||||
17 INVOKEVIRTUAL A[0-9]+\.remove \(I\) -> calls to A1-A9.removeAt + calls in synthetic bridges with signature `public synthetic bridge remove(I)Ljava/lang/Object;`
|
||||
2 INVOKEVIRTUAL A10\.remove \(I\) -> one call in 'box' function + call from synthetic `remove(I)` bridge
|
||||
|
||||
This currently differs because of KT-40277, and the test expectations should be revised once KT-40277 is resolved.
|
||||
*/
|
||||
|
||||
// 16 INVOKEVIRTUAL A[0-9]+.removeAt \(I\)
|
||||
// 9 INVOKEVIRTUAL A[0-9]+\.remove \(I\)
|
||||
// 1 INVOKEINTERFACE A9\.remove \(I\)
|
||||
// 1 INVOKEINTERFACE A9\.remove \(Ljava/lang/Object;\)
|
||||
// 1 INVOKEVIRTUAL A10\.remove \(I\)
|
||||
// 2 INVOKEINTERFACE java\/util\/List.remove \(I\)
|
||||
// 2 INVOKEINTERFACE java\/util\/List.remove \(Ljava/lang/Object;\)
|
||||
|
||||
// JVM_TEMPLATES:
|
||||
// 16 INVOKEVIRTUAL A[0-9]+.removeAt \(I\)
|
||||
// 9 INVOKEVIRTUAL A[0-9]+\.remove \(I\)
|
||||
// 1 INVOKEVIRTUAL A10\.remove \(I\)
|
||||
|
||||
// JVM_IR_TEMPLATES:
|
||||
// 9 INVOKEVIRTUAL A[0-9]+.removeAt \(I\)
|
||||
// 17 INVOKEVIRTUAL A[0-9]+\.remove \(I\)
|
||||
// 2 INVOKEVIRTUAL A10\.remove \(I\)
|
||||
|
||||
@@ -27,4 +27,14 @@ public class TestMap<K, V> implements Map<K, V> {
|
||||
|
||||
class MyMap: TestMap<String, String>()
|
||||
|
||||
// The Kotlin version of getOrDefault, which redirects to the default implementation in java.util.Map
|
||||
// 1 public bridge getOrDefault\(Ljava/lang/String;Ljava/lang/String;\)Ljava/lang/String;
|
||||
|
||||
// Test expectations differ between JVM and JVM IR backends, because of KT-40277. This should be revisited once KT-40277 is resolved.
|
||||
|
||||
// JVM_TEMPLATES:
|
||||
// 1 public final bridge getOrDefault\(Ljava/lang/Object;Ljava/lang/Object;\)Ljava/lang/Object;
|
||||
|
||||
// JVM_IR_TEMPLATES:
|
||||
// 1 public final bridge getOrDefault\(Ljava/lang/Object;Ljava/lang/String;\)Ljava/lang/String;
|
||||
// 1 public synthetic bridge getOrDefault\(Ljava/lang/Object;Ljava/lang/Object;\)Ljava/lang/Object;
|
||||
Reference in New Issue
Block a user