JVM IR: Produce correct generic signatures for special bridge methods

This commit is contained in:
Steven Schäfer
2020-07-02 10:58:03 +02:00
committed by Alexander Udalov
parent c16b548dff
commit 9d63412b3e
12 changed files with 308 additions and 89 deletions
@@ -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. * 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 import org.jetbrains.kotlin.name.Name
data class SpecialMethodWithDefaultInfo( 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) { class SpecialBridgeMethods(val context: CommonBackendContext) {
@@ -54,36 +61,46 @@ class SpecialBridgeMethods(val context: CommonBackendContext) {
IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, bridge.valueParameters[1].symbol) IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, bridge.valueParameters[1].symbol)
private val SPECIAL_METHODS_WITH_DEFAULTS_MAP = mapOf( private val SPECIAL_METHODS_WITH_DEFAULTS_MAP = mapOf(
makeDescription(KotlinBuiltIns.FQ_NAMES.collection, "contains", 1) to SpecialMethodWithDefaultInfo(::constFalse, 1), makeDescription(KotlinBuiltIns.FQ_NAMES.collection, "contains", 1) to
makeDescription(KotlinBuiltIns.FQ_NAMES.mutableCollection, "remove", 1) to SpecialMethodWithDefaultInfo(::constFalse, 1, true), SpecialMethodWithDefaultInfo(::constFalse, 1),
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "containsKey", 1) to SpecialMethodWithDefaultInfo(::constFalse, 1), makeDescription(KotlinBuiltIns.FQ_NAMES.mutableCollection, "remove", 1) to
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "containsValue", 1) to SpecialMethodWithDefaultInfo(::constFalse, 1), SpecialMethodWithDefaultInfo(::constFalse, 1, needsArgumentBoxing = true),
makeDescription(KotlinBuiltIns.FQ_NAMES.mutableMap, "remove", 2) to SpecialMethodWithDefaultInfo(::constFalse, 2), makeDescription(KotlinBuiltIns.FQ_NAMES.map, "containsKey", 1) to
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "getOrDefault", 2) to SpecialMethodWithDefaultInfo(::getSecondArg, 1), SpecialMethodWithDefaultInfo(::constFalse, 1),
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "get", 1) to SpecialMethodWithDefaultInfo(::constNull, 1), makeDescription(KotlinBuiltIns.FQ_NAMES.map, "containsValue", 1) to
makeDescription(KotlinBuiltIns.FQ_NAMES.mutableMap, "remove", 1) to SpecialMethodWithDefaultInfo(::constNull, 1), SpecialMethodWithDefaultInfo(::constFalse, 1),
makeDescription(KotlinBuiltIns.FQ_NAMES.list, "indexOf", 1) to SpecialMethodWithDefaultInfo(::constMinusOne, 1), makeDescription(KotlinBuiltIns.FQ_NAMES.mutableMap, "remove", 2) to
makeDescription(KotlinBuiltIns.FQ_NAMES.list, "lastIndexOf", 1) to SpecialMethodWithDefaultInfo(::constMinusOne, 1) 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( private val SPECIAL_PROPERTIES_SET = mapOf(
makeDescription(KotlinBuiltIns.FQ_NAMES.collection, "size"), makeDescription(KotlinBuiltIns.FQ_NAMES.collection, "size") to BuiltInWithDifferentJvmName(),
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "size"), makeDescription(KotlinBuiltIns.FQ_NAMES.map, "size") to BuiltInWithDifferentJvmName(),
makeDescription(KotlinBuiltIns.FQ_NAMES.charSequence.toSafe(), "length"), makeDescription(KotlinBuiltIns.FQ_NAMES.charSequence.toSafe(), "length") to BuiltInWithDifferentJvmName(),
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "keys"), makeDescription(KotlinBuiltIns.FQ_NAMES.map, "keys") to BuiltInWithDifferentJvmName(needsGenericSignature = true),
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "values"), makeDescription(KotlinBuiltIns.FQ_NAMES.map, "values") to BuiltInWithDifferentJvmName(needsGenericSignature = true),
makeDescription(KotlinBuiltIns.FQ_NAMES.map, "entries") makeDescription(KotlinBuiltIns.FQ_NAMES.map, "entries") to BuiltInWithDifferentJvmName(needsGenericSignature = true)
) )
private val SPECIAL_METHODS_SETS = setOf( private val SPECIAL_METHODS_SETS = mapOf(
makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toByte"), makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toByte") to BuiltInWithDifferentJvmName(),
makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toShort"), makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toShort") to BuiltInWithDifferentJvmName(),
makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toInt"), makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toInt") to BuiltInWithDifferentJvmName(),
makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toLong"), makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toLong") to BuiltInWithDifferentJvmName(),
makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toFloat"), makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toFloat") to BuiltInWithDifferentJvmName(),
makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toDouble"), makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toDouble") to BuiltInWithDifferentJvmName(),
makeDescription(KotlinBuiltIns.FQ_NAMES.mutableList, "removeAt", 1), makeDescription(KotlinBuiltIns.FQ_NAMES.charSequence.toSafe(), "get", 1) to BuiltInWithDifferentJvmName(),
makeDescription(KotlinBuiltIns.FQ_NAMES.charSequence.toSafe(), "get", 1) makeDescription(KotlinBuiltIns.FQ_NAMES.mutableList, "removeAt", 1) to BuiltInWithDifferentJvmName(needsGenericSignature = true)
) )
fun findSpecialWithOverride(irFunction: IrSimpleFunction): Pair<IrSimpleFunction, SpecialMethodWithDefaultInfo>? { fun findSpecialWithOverride(irFunction: IrSimpleFunction): Pair<IrSimpleFunction, SpecialMethodWithDefaultInfo>? {
@@ -101,15 +118,15 @@ class SpecialBridgeMethods(val context: CommonBackendContext) {
return SPECIAL_METHODS_WITH_DEFAULTS_MAP[description] return SPECIAL_METHODS_WITH_DEFAULTS_MAP[description]
} }
fun isBuiltInWithDifferentJvmName(irFunction: IrSimpleFunction): Boolean { fun getBuiltInWithDifferentJvmName(irFunction: IrSimpleFunction): BuiltInWithDifferentJvmName? {
irFunction.correspondingPropertySymbol?.let { irFunction.correspondingPropertySymbol?.let {
val classFqName = irFunction.parentAsClass.fqNameWhenAvailable 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()]
} }
} }
@@ -24,9 +24,11 @@ import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.addFunction 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.declarations.*
import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol 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.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.*
@@ -127,19 +129,36 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass,
val overriddenSymbols: MutableList<IrSimpleFunctionSymbol> = mutableListOf() val overriddenSymbols: MutableList<IrSimpleFunctionSymbol> = mutableListOf()
) )
// Represents a special bridge to `overridden`. Special bridges are always public and non-synthetic. // Represents a special bridge to `overridden`. Special bridges are overrides for Java methods which are
// There are ten type-safe wrappers for different collection methods, which have an additional `methodInfo` // exposed to Kotlin with a different name or different types. Typically, the Java version of a method is
// field. There is only one special bridge method which uses generic types in Java (MutableList.removeAt) // non-generic, while Kotlin exposes a generic method. In this case, the bridge method needs to perform
// and needs a `specializedReturnType` for its overrides. Finally, we sometimes need to use INVOKESPECIAL // additional type checking at runtime. The behavior in case of type errors is configured in `methodInfo`.
// to invoke an existing special bridge implementation in a superclass, which is what `superQualifierSymbol` //
// is for. // 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( private data class SpecialBridge(
val overridden: IrSimpleFunction, val overridden: IrSimpleFunction,
val signature: Method, 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 methodInfo: SpecialMethodWithDefaultInfo? = null,
val superQualifierSymbol: IrClassSymbol? = null, val superQualifierSymbol: IrClassSymbol? = null,
val isFinal: Boolean = true, val isFinal: Boolean = true,
val isSynthetic: Boolean = false,
) )
override fun lower(irFile: IrFile) = irFile.transformChildrenVoid() override fun lower(irFile: IrFile) = irFile.transformChildrenVoid()
@@ -206,7 +225,9 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass,
blacklist += irFunction.overriddenFinalSpecialBridges() 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 // 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 // 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 // calls to the stub instead. Otherwise we'll end up calling the special method itself and get into an
// infinite loop. // infinite loop.
@@ -243,14 +264,26 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass,
blacklist += bridgeTarget.jvmMethod blacklist += bridgeTarget.jvmMethod
} }
irClass.addSpecialBridge(specialBridge, bridgeTarget)
blacklist += specialBridge.signature blacklist += specialBridge.signature
irClass.addSpecialBridge(specialBridge, bridgeTarget)
} else {
irFunction
} }
// Deal with existing function that override special bridge methods. // Deal with existing function that override special bridge methods.
if (!irFunction.isFakeOverride && specialBridge.methodInfo != null) { if (!irFunction.isFakeOverride && specialBridge.methodInfo != null) {
irFunction.rewriteSpecialMethodBody(targetMethod, specialBridge.signature, specialBridge.methodInfo) 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)) { } else if (irFunction.isJvmAbstract(context.state.jvmDefaultMode)) {
// Do not generate bridge methods for abstract methods which do not override a special bridge method. // 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 // 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 // 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 // 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. // 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. // Returns the special bridge overridden by the current methods if it exists.
private val IrSimpleFunction.specialBridgeOrNull: SpecialBridge? private val IrSimpleFunction.specialBridgeOrNull: SpecialBridge?
get() = specialBridgeCache.getOrPutNullable(symbol) { get() = specialBridgeCache.getOrPutNullable(symbol) {
val specialMethodInfo = specialBridgeMethods.getSpecialMethodInfo(this) specialBridgeMethods.getSpecialMethodInfo(this)?.let { specialMethodInfo ->
return when { SpecialBridge(this, jvmMethod, methodInfo = specialMethodInfo, needsGenericSignature = specialMethodInfo.needsGenericSignature)
specialMethodInfo != null -> } ?: specialBridgeMethods.getBuiltInWithDifferentJvmName(this)?.let { specialBuiltInInfo ->
// Note that there are type-safe special bridges with generic return types in Java (namely Map.getOrDefault, SpecialBridge(this, jvmMethod, needsGenericSignature = specialBuiltInInfo.needsGenericSignature)
// Map.get, and MutableMap.remove), but the JVM backend does not produce overrides with specialized return } ?: overriddenSymbols.asSequence().mapNotNull { it.owner.specialBridgeOrNull }.firstOrNull()?.let { specialBridge ->
// types for them. So for compatibility, neither do we. if (specialBridge.needsGenericSignature) {
SpecialBridge(this, jvmMethod, methodInfo = specialMethodInfo) // Compute the substituted signature.
val erasedParameterCount = specialBridge.methodInfo?.argumentsToCheck ?: 0
specialBridgeMethods.isBuiltInWithDifferentJvmName(this) -> val substitutedParameterTypes = valueParameters.mapIndexed { index, param ->
if (returnType.isTypeParameter()) if (index < erasedParameterCount) context.irBuiltIns.anyNType else param.type
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
} }
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 } 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) = private fun IrClass.addAbstractMethodStub(irFunction: IrSimpleFunction, needsArgumentBoxing: Boolean) =
addFunction { addFunction {
updateFrom(irFunction) 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 // 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. // be references to the original fake override stub.
copyCorrespondingPropertyFrom(irFunction) 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 = 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 = private fun IrClass.addSpecialBridge(specialBridge: SpecialBridge, target: IrSimpleFunction): IrSimpleFunction =
addFunction { addFunction {
modality = if (specialBridge.isFinal) Modality.FINAL else Modality.OPEN 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) name = Name.identifier(specialBridge.signature.name)
returnType = specialBridge.specializedReturnType ?: specialBridge.overridden.returnType.eraseTypeParameters() returnType = specialBridge.substitutedReturnType ?: specialBridge.overridden.returnType.eraseTypeParameters()
}.apply { }.apply {
copyParametersWithErasure( copyParametersWithErasure(
this@addSpecialBridge, this@addSpecialBridge,
specialBridge.overridden, specialBridge.overridden,
specialBridge.methodInfo?.needsArgumentBoxing == true specialBridge.methodInfo?.needsArgumentBoxing == true,
specialBridge.substitutedParameterTypes
) )
body = context.createIrBuilder(symbol).irBlockBody { body = context.createIrBuilder(symbol).irBlockBody {
specialBridge.methodInfo?.let { info -> specialBridge.methodInfo?.let { info ->
valueParameters.take(info.argumentsToCheck).forEach { valueParameters.take(info.argumentsToCheck).forEach {
@@ -396,7 +451,7 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass,
} }
+irReturn(delegatingCall(this@apply, target, specialBridge.superQualifierSymbol)) +irReturn(delegatingCall(this@apply, target, specialBridge.superQualifierSymbol))
} }
overriddenSymbols += specialBridge.overridden.symbol overriddenSymbols = listOf(specialBridge.overridden.symbol)
} }
private fun IrSimpleFunction.rewriteSpecialMethodBody( private fun IrSimpleFunction.rewriteSpecialMethodBody(
@@ -421,8 +476,8 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass,
argumentsToCheck.forEach { argumentsToCheck.forEach {
val parameterType = it.type val parameterType = it.type
if (!parameterType.isNullable()) { if (!parameterType.isNullable()) {
val newParameter = it.copyTo(this@rewriteSpecialMethodBody, type = context.irBuiltIns.anyNType) val newParameter = it.copyTo(this@rewriteSpecialMethodBody, type = parameterType.makeNullable())
variableMap.put(valueParameters[it.index], newParameter) variableMap[valueParameters[it.index]] = newParameter
newValueParameters[it.index] = newParameter newValueParameters[it.index] = newParameter
+parameterTypeCheck( +parameterTypeCheck(
newParameter, newParameter,
@@ -463,17 +518,33 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass,
private fun IrBuilderWithScope.parameterTypeCheck(parameter: IrValueParameter, type: IrType, defaultValue: IrExpression) = private fun IrBuilderWithScope.parameterTypeCheck(parameter: IrValueParameter, type: IrType, defaultValue: IrExpression) =
irIfThen(context.irBuiltIns.unitType, irNot(irIs(irGet(parameter), type)), irReturn(defaultValue)) 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 // 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. // with dispatch receivers pointing at a superclass instead of the current class.
dispatchReceiverParameter = irClass.thisReceiver?.copyTo(this, type = irClass.defaultType) dispatchReceiverParameter = irClass.thisReceiver?.copyTo(this, type = irClass.defaultType)
extensionReceiverParameter = from.extensionReceiverParameter?.copyWithTypeErasure(this, forceArgumentBoxing) 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, 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() varargElementType = varargElementType?.eraseTypeParameters()
) )
@@ -492,10 +563,10 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass,
private fun IrBuilderWithScope.irCastIfNeeded(expression: IrExpression, to: IrType): IrExpression = private fun IrBuilderWithScope.irCastIfNeeded(expression: IrExpression, to: IrType): IrExpression =
if (expression.type == to || to.isAny() || to.isNullableAny()) expression else irImplicitCast(expression, to) 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 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 private fun IrDeclaration.comesFromJava() = parentAsClass.origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB
+1
View File
@@ -1,4 +1,5 @@
// TARGET_BACKEND: JVM // TARGET_BACKEND: JVM
// IGNORE_BACKEND_FIR: JVM_IR
// java.lang.NoSuchMethodError: java.util.TreeMap.remove // java.lang.NoSuchMethodError: java.util.TreeMap.remove
// IGNORE_BACKEND: ANDROID // IGNORE_BACKEND: ANDROID
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
interface ImmutableCollection<out E> : Collection<E> { interface ImmutableCollection<out E> : Collection<E> {
fun add(element: @UnsafeVariance E): ImmutableCollection<E> fun add(element: @UnsafeVariance E): ImmutableCollection<E>
fun addAll(elements: Collection<@UnsafeVariance E>): ImmutableCollection<E> fun addAll(elements: Collection<@UnsafeVariance E>): ImmutableCollection<E>
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
open class A0<E> : MutableList<E> { open class A0<E> : MutableList<E> {
override fun add(element: E): Boolean { override fun add(element: E): Boolean {
throw UnsupportedOperationException() throw UnsupportedOperationException()
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// WITH_SIGNATURES // WITH_SIGNATURES
class GenericMap<K, V> : MutableMap<K, V> by HashMap<K, V>() class GenericMap<K, V> : MutableMap<K, V> by HashMap<K, V>()
@@ -1,5 +1,5 @@
// IGNORE_BACKEND: JVM_IR
// WITH_SIGNATURES // 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>() class StringStringMap : MutableMap<String, String> by HashMap<String, String>()
@@ -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
}
@@ -1,5 +1,5 @@
// IGNORE_BACKEND: JVM_IR
// WITH_SIGNATURES // 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>() class StringMap<V> : MutableMap<String, V> by HashMap<String, V>()
@@ -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 \(I\) -> call A9.removeAt
1 INVOKEINTERFACE A9\.remove \(Ljava/lang/Object;\) -> call A9.remove 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 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 \(I\)
// 1 INVOKEINTERFACE A9\.remove \(Ljava/lang/Object;\) // 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 \(I\)
// 2 INVOKEINTERFACE java\/util\/List.remove \(Ljava/lang/Object;\) // 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>() 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; // 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;