JVM IR: move out bridge/collection lowering caches to separate files

Both BridgeLoweringCache and CollectionStubComputer are not tecnically
part of the lowerings, so they shouldn't be in the "lower" package.
This commit is contained in:
Alexander Udalov
2021-09-10 02:48:22 +02:00
parent 08cc789123
commit b4c84aa2a6
6 changed files with 257 additions and 209 deletions
@@ -11,13 +11,13 @@ import org.jetbrains.kotlin.backend.common.Mapping
import org.jetbrains.kotlin.backend.common.ir.Ir
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.common.psi.PsiErrorBuilder
import org.jetbrains.kotlin.backend.jvm.caches.BridgeLoweringCache
import org.jetbrains.kotlin.backend.jvm.caches.CollectionStubComputer
import org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen
import org.jetbrains.kotlin.backend.jvm.codegen.IrTypeMapper
import org.jetbrains.kotlin.backend.jvm.codegen.MethodSignatureMapper
import org.jetbrains.kotlin.backend.jvm.codegen.createFakeContinuation
import org.jetbrains.kotlin.backend.jvm.intrinsics.IrIntrinsicMethods
import org.jetbrains.kotlin.backend.jvm.lower.BridgeLowering
import org.jetbrains.kotlin.backend.jvm.lower.CollectionStubComputer
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
@@ -113,7 +113,7 @@ class JvmBackendContext(
fun getOverridesWithoutStubs(function: IrSimpleFunction): List<IrSimpleFunctionSymbol> =
overridesWithoutStubs.getOrElse(function) { function.overriddenSymbols }
internal val bridgeLoweringCache = BridgeLowering.BridgeLoweringCache(this)
internal val bridgeLoweringCache = BridgeLoweringCache(this)
internal val functionsWithSpecialBridges: MutableSet<IrFunction> = ConcurrentHashMap.newKeySet()
override var inVerbosePhase: Boolean = false // TODO: needs parallelizing
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2021 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.
*/
package org.jetbrains.kotlin.backend.jvm
import org.jetbrains.kotlin.backend.common.lower.SpecialMethodWithDefaultInfo
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.org.objectweb.asm.commons.Method
// 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.
data class SpecialBridge(
val overridden: IrSimpleFunction,
val signature: Method,
// 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,
val isOverriding: Boolean = true,
)
@@ -0,0 +1,99 @@
/*
* Copyright 2010-2021 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.
*/
package org.jetbrains.kotlin.backend.jvm.caches
import org.jetbrains.kotlin.backend.common.ir.copyTo
import org.jetbrains.kotlin.backend.common.lower.SpecialBridgeMethods
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.SpecialBridge
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.name.Name
import org.jetbrains.org.objectweb.asm.commons.Method
import java.util.concurrent.ConcurrentHashMap
internal class BridgeLoweringCache(private val context: JvmBackendContext) {
private val specialBridgeMethods = SpecialBridgeMethods(context)
// TODO: consider moving this cache out to the backend context and using it everywhere throughout the codegen.
// It might benefit performance, but can lead to confusing behavior if some declarations are changed along the way.
// For example, adding an override for a declaration whose signature is already cached can result in incorrect signature
// if its return type is a primitive type, and the new override's return type is an object type.
private val signatureCache = ConcurrentHashMap<IrFunctionSymbol, Method>()
fun computeJvmMethod(function: IrFunction): Method =
signatureCache.getOrPut(function.symbol) { context.methodSignatureMapper.mapAsmMethod(function) }
private fun canHaveSpecialBridge(function: IrSimpleFunction): Boolean {
if (function.name in specialBridgeMethods.specialMethodNames)
return true
// Function name could be mangled by inline class rules
val functionName = function.name.asString()
if (specialBridgeMethods.specialMethodNames.any { functionName.startsWith(it.asString() + "-") })
return true
return false
}
fun computeSpecialBridge(function: IrSimpleFunction): SpecialBridge? {
// Optimization: do not try to compute special bridge for irrelevant methods.
val correspondingProperty = function.correspondingPropertySymbol
if (correspondingProperty != null) {
if (correspondingProperty.owner.name !in specialBridgeMethods.specialPropertyNames) return null
} else {
if (!canHaveSpecialBridge(function)) {
return null
}
}
val specialMethodInfo = specialBridgeMethods.getSpecialMethodInfo(function)
if (specialMethodInfo != null)
return SpecialBridge(
function, computeJvmMethod(function), specialMethodInfo.needsGenericSignature, methodInfo = specialMethodInfo
)
val specialBuiltInInfo = specialBridgeMethods.getBuiltInWithDifferentJvmName(function)
if (specialBuiltInInfo != null)
return SpecialBridge(
function, computeJvmMethod(function), specialBuiltInInfo.needsGenericSignature,
isOverriding = specialBuiltInInfo.isOverriding
)
for (overridden in function.overriddenSymbols) {
val specialBridge = computeSpecialBridge(overridden.owner) ?: continue
if (!specialBridge.needsGenericSignature) return specialBridge
// Compute the substituted signature.
val erasedParameterCount = specialBridge.methodInfo?.argumentsToCheck ?: 0
val substitutedParameterTypes = function.valueParameters.mapIndexed { index, param ->
if (index < erasedParameterCount) context.irBuiltIns.anyNType else param.type
}
val substitutedOverride = context.irFactory.buildFun {
updateFrom(specialBridge.overridden)
name = Name.identifier(specialBridge.signature.name)
returnType = function.returnType
}.apply {
// All existing special bridges only have value parameter types.
valueParameters = function.valueParameters.zip(substitutedParameterTypes).map { (param, type) ->
param.copyTo(this, IrDeclarationOrigin.BRIDGE, type = type)
}
overriddenSymbols = listOf(specialBridge.overridden.symbol)
parent = function.parent
}
return specialBridge.copy(
signature = computeJvmMethod(substitutedOverride),
substitutedParameterTypes = substitutedParameterTypes,
substitutedReturnType = function.returnType
)
}
return null
}
}
@@ -0,0 +1,104 @@
/*
* Copyright 2010-2021 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.
*/
package org.jetbrains.kotlin.backend.jvm.caches
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.types.isStrictSubtypeOfClass
import org.jetbrains.kotlin.ir.types.isSubtypeOfClass
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.util.isFromJava
import org.jetbrains.kotlin.ir.util.parentAsClass
import java.util.concurrent.ConcurrentHashMap
internal class CollectionStubComputer(val context: JvmBackendContext) {
private class LazyStubsForCollectionClass(
override val readOnlyClass: IrClassSymbol,
override val mutableClass: IrClassSymbol
) : StubsForCollectionClass {
override val candidatesForStubs: Collection<IrSimpleFunction> by lazy {
// Old back-end generates stubs for 'class A : C', where
// 'C' is some "read-only collection" interface from kotlin.collections,
// 'MC' is a corresponding "mutable collection" interface from kotlin.collections,
// by building fake overrides for a special 'class X : A(), MC'
// and taking fake overrides that override members from 'MC'.
//
// Here we are looking at this problem from a slightly different angle:
// we select suitable member functions 'f' in 'MC' that might potentially require stubs (we are here!),
// and then we generate stubs for functions 'f' that are not effectively overridden by members of 'A'
// (this happens in the lowering itself).
//
// In order for this to be equivalent to the old back-end approach,
// we should take 'f' in 'MC' such that any of the following conditions is true:
// - 'f' is declared in 'MC' - that is, 'f' itself is not a fake override;
// - 'f' is abstract and doesn't override anything from 'C'.
//
// NB1 it also covers default methods from JDK collection classes case
// (since that's the only way a member function in 'MC' might be non-abstract).
//
// NB2 the scheme of stub method generation in the old back-end depends too much on
// which particular declarations are present in 'MC'.
// Some of these declarations are redundant from the stub generation point of view -
// for example, 'kotlin.collections.MutableListIterator' contains the following (redundant) declarations:
// override fun next(): T
// override fun hasNext(): Boolean
// which cause stubs for 'next' and 'hasNext' to be generated in a 'abstract class A<T> : ListIterator<T>'.
// See https://youtrack.jetbrains.com/issue/KT-36724.
// In the ideal world, it should be enough to check that
// the given member function 'f' from 'MC' doesn't override anything from 'C'.
mutableClass.owner.functions
.filter { memberFun ->
!memberFun.isFakeOverride ||
(memberFun.modality == Modality.ABSTRACT && memberFun.overriddenSymbols.none { overriddenFun ->
overriddenFun.owner.parentAsClass.symbol == readOnlyClass
})
}
.toList()
}
}
private val preComputedStubs: Collection<StubsForCollectionClass> by lazy {
with(context.ir.symbols) {
listOf(
LazyStubsForCollectionClass(collection, mutableCollection),
LazyStubsForCollectionClass(set, mutableSet),
LazyStubsForCollectionClass(list, mutableList),
LazyStubsForCollectionClass(map, mutableMap),
LazyStubsForCollectionClass(mapEntry, mutableMapEntry),
LazyStubsForCollectionClass(iterable, mutableIterable),
LazyStubsForCollectionClass(iterator, mutableIterator),
LazyStubsForCollectionClass(listIterator, mutableListIterator)
)
}
}
private val stubsCache = ConcurrentHashMap<IrClass, List<StubsForCollectionClass>>()
fun stubsForCollectionClasses(irClass: IrClass): List<StubsForCollectionClass> =
stubsCache.getOrPut(irClass) {
computeStubsForCollectionClasses(irClass)
}
private fun computeStubsForCollectionClasses(irClass: IrClass): List<StubsForCollectionClass> {
if (irClass.isFromJava()) return emptyList()
val stubs = preComputedStubs.filter {
irClass.symbol.isStrictSubtypeOfClass(it.readOnlyClass) && !irClass.symbol.isSubtypeOfClass(it.mutableClass)
}
return stubs.filter {
stubs.none { other -> it.readOnlyClass != other.readOnlyClass && other.readOnlyClass.isSubtypeOfClass(it.readOnlyClass) }
}
}
}
interface StubsForCollectionClass {
val readOnlyClass: IrClassSymbol
val mutableClass: IrClassSymbol
val candidatesForStubs: Collection<IrSimpleFunction>
}
@@ -10,10 +10,14 @@ import org.jetbrains.kotlin.backend.common.ir.allOverridden
import org.jetbrains.kotlin.backend.common.ir.copyTo
import org.jetbrains.kotlin.backend.common.ir.isMethodOfAny
import org.jetbrains.kotlin.backend.common.ir.isStatic
import org.jetbrains.kotlin.backend.common.lower.*
import org.jetbrains.kotlin.backend.common.lower.SpecialMethodWithDefaultInfo
import org.jetbrains.kotlin.backend.common.lower.VariableRemapper
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irNot
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.SpecialBridge
import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface
import org.jetbrains.kotlin.backend.jvm.ir.*
import org.jetbrains.kotlin.codegen.AsmUtil
@@ -22,11 +26,9 @@ import org.jetbrains.kotlin.descriptors.Modality
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.*
@@ -35,7 +37,6 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.SmartList
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.Method
import java.util.concurrent.ConcurrentHashMap
/*
* Generate bridge methods to fix virtual dispatch after type erasure and to adapt Kotlin collections to
@@ -127,39 +128,6 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass
val overriddenSymbols: MutableList<IrSimpleFunctionSymbol> = mutableListOf()
)
// 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.
data class SpecialBridge(
val overridden: IrSimpleFunction,
val signature: Method,
// 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,
val isOverriding: Boolean = true,
)
override fun lower(irFile: IrFile) {
irFile.transformChildrenVoid()
}
@@ -601,86 +569,6 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass
private val IrFunction.jvmMethod: Method
get() = context.bridgeLoweringCache.computeJvmMethod(this)
internal class BridgeLoweringCache(private val context: JvmBackendContext) {
private val specialBridgeMethods = SpecialBridgeMethods(context)
// TODO: consider moving this cache out to the backend context and using it everywhere throughout the codegen.
// It might benefit performance, but can lead to confusing behavior if some declarations are changed along the way.
// For example, adding an override for a declaration whose signature is already cached can result in incorrect signature
// if its return type is a primitive type, and the new override's return type is an object type.
private val signatureCache = ConcurrentHashMap<IrFunctionSymbol, Method>()
fun computeJvmMethod(function: IrFunction): Method =
signatureCache.getOrPut(function.symbol) { context.methodSignatureMapper.mapAsmMethod(function) }
private fun canHaveSpecialBridge(function: IrSimpleFunction): Boolean {
if (function.name in specialBridgeMethods.specialMethodNames)
return true
// Function name could be mangled by inline class rules
val functionName = function.name.asString()
if (specialBridgeMethods.specialMethodNames.any { functionName.startsWith(it.asString() + "-") })
return true
return false
}
fun computeSpecialBridge(function: IrSimpleFunction): SpecialBridge? {
// Optimization: do not try to compute special bridge for irrelevant methods.
val correspondingProperty = function.correspondingPropertySymbol
if (correspondingProperty != null) {
if (correspondingProperty.owner.name !in specialBridgeMethods.specialPropertyNames) return null
} else {
if (!canHaveSpecialBridge(function)) {
return null
}
}
val specialMethodInfo = specialBridgeMethods.getSpecialMethodInfo(function)
if (specialMethodInfo != null)
return SpecialBridge(
function, computeJvmMethod(function), specialMethodInfo.needsGenericSignature, methodInfo = specialMethodInfo
)
val specialBuiltInInfo = specialBridgeMethods.getBuiltInWithDifferentJvmName(function)
if (specialBuiltInInfo != null)
return SpecialBridge(
function, computeJvmMethod(function), specialBuiltInInfo.needsGenericSignature,
isOverriding = specialBuiltInInfo.isOverriding
)
for (overridden in function.overriddenSymbols) {
val specialBridge = computeSpecialBridge(overridden.owner) ?: continue
if (!specialBridge.needsGenericSignature) return specialBridge
// Compute the substituted signature.
val erasedParameterCount = specialBridge.methodInfo?.argumentsToCheck ?: 0
val substitutedParameterTypes = function.valueParameters.mapIndexed { index, param ->
if (index < erasedParameterCount) context.irBuiltIns.anyNType else param.type
}
val substitutedOverride = context.irFactory.buildFun {
updateFrom(specialBridge.overridden)
name = Name.identifier(specialBridge.signature.name)
returnType = function.returnType
}.apply {
// All existing special bridges only have value parameter types.
valueParameters = function.valueParameters.zip(substitutedParameterTypes).map { (param, type) ->
param.copyTo(this, IrDeclarationOrigin.BRIDGE, type = type)
}
overriddenSymbols = listOf(specialBridge.overridden.symbol)
parent = function.parent
}
return specialBridge.copy(
signature = computeJvmMethod(substitutedOverride),
substitutedParameterTypes = substitutedParameterTypes,
substitutedReturnType = function.returnType
)
}
return null
}
}
}
// Check whether a fake override will resolve to an implementation in class, not an interface.
@@ -691,4 +579,3 @@ private fun IrSimpleFunction.resolvesToClass(): Boolean {
private fun IrSimpleFunction.overriddenFromClass(): IrSimpleFunction? =
overriddenSymbols.singleOrNull { !it.owner.parentAsClass.isJvmInterface }?.owner
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.backend.common.ClassLoweringPass
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.caches.StubsForCollectionClass
import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
@@ -28,7 +29,6 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.TypeCheckerState
import org.jetbrains.kotlin.utils.addToStdlib.cast
import java.util.concurrent.ConcurrentHashMap
internal val collectionStubMethodLowering = makeIrFilePhase(
::CollectionStubMethodLowering,
@@ -390,90 +390,3 @@ internal class CollectionStubMethodLowering(val context: JvmBackendContext) : Cl
private val IrClass.superClassChain: Sequence<IrClass>
get() = generateSequence(this) { it.superClass }
}
internal interface StubsForCollectionClass {
val readOnlyClass: IrClassSymbol
val mutableClass: IrClassSymbol
val candidatesForStubs: Collection<IrSimpleFunction>
}
internal class CollectionStubComputer(val context: JvmBackendContext) {
private class LazyStubsForCollectionClass(
override val readOnlyClass: IrClassSymbol,
override val mutableClass: IrClassSymbol
) : StubsForCollectionClass {
override val candidatesForStubs: Collection<IrSimpleFunction> by lazy {
// Old back-end generates stubs for 'class A : C', where
// 'C' is some "read-only collection" interface from kotlin.collections,
// 'MC' is a corresponding "mutable collection" interface from kotlin.collections,
// by building fake overrides for a special 'class X : A(), MC'
// and taking fake overrides that override members from 'MC'.
//
// Here we are looking at this problem from a slightly different angle:
// we select suitable member functions 'f' in 'MC' that might potentially require stubs (we are here!),
// and then we generate stubs for functions 'f' that are not effectively overridden by members of 'A'
// (this happens in the lowering itself).
//
// In order for this to be equivalent to the old back-end approach,
// we should take 'f' in 'MC' such that any of the following conditions is true:
// - 'f' is declared in 'MC' - that is, 'f' itself is not a fake override;
// - 'f' is abstract and doesn't override anything from 'C'.
//
// NB1 it also covers default methods from JDK collection classes case
// (since that's the only way a member function in 'MC' might be non-abstract).
//
// NB2 the scheme of stub method generation in the old back-end depends too much on
// which particular declarations are present in 'MC'.
// Some of these declarations are redundant from the stub generation point of view -
// for example, 'kotlin.collections.MutableListIterator' contains the following (redundant) declarations:
// override fun next(): T
// override fun hasNext(): Boolean
// which cause stubs for 'next' and 'hasNext' to be generated in a 'abstract class A<T> : ListIterator<T>'.
// See https://youtrack.jetbrains.com/issue/KT-36724.
// In the ideal world, it should be enough to check that
// the given member function 'f' from 'MC' doesn't override anything from 'C'.
mutableClass.owner.functions
.filter { memberFun ->
!memberFun.isFakeOverride ||
(memberFun.modality == Modality.ABSTRACT && memberFun.overriddenSymbols.none { overriddenFun ->
overriddenFun.owner.parentAsClass.symbol == readOnlyClass
})
}
.toList()
}
}
private val preComputedStubs: Collection<StubsForCollectionClass> by lazy {
with(context.ir.symbols) {
listOf(
LazyStubsForCollectionClass(collection, mutableCollection),
LazyStubsForCollectionClass(set, mutableSet),
LazyStubsForCollectionClass(list, mutableList),
LazyStubsForCollectionClass(map, mutableMap),
LazyStubsForCollectionClass(mapEntry, mutableMapEntry),
LazyStubsForCollectionClass(iterable, mutableIterable),
LazyStubsForCollectionClass(iterator, mutableIterator),
LazyStubsForCollectionClass(listIterator, mutableListIterator)
)
}
}
private val stubsCache = ConcurrentHashMap<IrClass, List<StubsForCollectionClass>>()
fun stubsForCollectionClasses(irClass: IrClass): List<StubsForCollectionClass> =
stubsCache.getOrPut(irClass) {
computeStubsForCollectionClasses(irClass)
}
private fun computeStubsForCollectionClasses(irClass: IrClass): List<StubsForCollectionClass> {
if (irClass.isFromJava()) return emptyList()
val stubs = preComputedStubs.filter {
irClass.symbol.isStrictSubtypeOfClass(it.readOnlyClass) && !irClass.symbol.isSubtypeOfClass(it.mutableClass)
}
return stubs.filter {
stubs.none { other -> it.readOnlyClass != other.readOnlyClass && other.readOnlyClass.isSubtypeOfClass(it.readOnlyClass) }
}
}
}