diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CollectionStubMethodLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CollectionStubMethodLowering.kt index 34fb828fa4b..cdb9f49e073 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CollectionStubMethodLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CollectionStubMethodLowering.kt @@ -172,17 +172,10 @@ private class CollectionStubMethodLowering(val context: JvmBackendContext) : Cla // Compute stubs that should be generated, compare based on signature private fun generateRelevantStubMethods(irClass: IrClass): Set { - val ourStubsForCollectionClasses = preComputedStubs.filter { (readOnlyClass, mutableClass) -> - irClass.superTypes.any { supertypeSymbol -> - val supertype = supertypeSymbol.classOrNull?.owner - // We need to generate stub methods for following 2 cases: - // current class's direct super type is a java class or kotlin interface, and is an subtype of an immutable collection - supertype != null - && (supertype.comesFromJava() || supertype.isInterface) - && supertypeSymbol.isSubtypeOfClass(readOnlyClass) - && !irClass.symbol.isSubtypeOfClass(mutableClass) - } - } + val ourStubsForCollectionClasses = stubsForCollectionClasses(irClass) + val superStubClasses = irClass.superClass?.superClassChain?.map { superClass -> + stubsForCollectionClasses(superClass).map { it.readOnlyClass } + }?.fold(emptySet(), { a, b -> a union b }) ?: emptySet() // do a second filtering to ensure only most relevant classes are included. val redundantClasses = ourStubsForCollectionClasses.filter { (readOnlyClass) -> @@ -191,7 +184,7 @@ private class CollectionStubMethodLowering(val context: JvmBackendContext) : Cla // perform type substitution and type erasure here return ourStubsForCollectionClasses.filter { (readOnlyClass) -> - readOnlyClass !in redundantClasses + readOnlyClass !in redundantClasses && readOnlyClass !in superStubClasses }.flatMap { (readOnlyClass, mutableClass, mutableOnlyMethods) -> val substitutionMap = computeSubstitutionMap(readOnlyClass.owner, mutableClass.owner, irClass) mutableOnlyMethods.map { function -> @@ -211,4 +204,28 @@ private class CollectionStubMethodLowering(val context: JvmBackendContext) : Cla types.all { other -> type.isSubtypeOfClass(other.classOrNull!!) } } } + + private val stubsCache = mutableMapOf>() + + private fun stubsForCollectionClasses(irClass: IrClass): Collection = + stubsCache.getOrPut(irClass) { + if (irClass.comesFromJava()) emptySet() + else preComputedStubs.filter { (readOnlyClass, mutableClass) -> + irClass.superTypes.any { supertypeSymbol -> + val supertype = supertypeSymbol.classOrNull?.owner + // We need to generate stub methods for following 2 cases: + // current class's direct super type is a java class or kotlin interface, and is an subtype of an immutable collection + supertype != null + && (supertype.comesFromJava() || supertype.isInterface) + && supertypeSymbol.isSubtypeOfClass(readOnlyClass) + && !irClass.symbol.isSubtypeOfClass(mutableClass) + } + } + } + + private val IrClass.superClass: IrClass? + get() = superTypes.mapNotNull { (it as? IrSimpleType)?.classifier?.owner as? IrClass }.singleOrNull { !it.isInterface } + + private val IrClass.superClassChain: Sequence + get() = generateSequence(this) { it.superClass } } \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/collectionStubs.kt b/compiler/testData/codegen/bytecodeText/collectionStubs.kt new file mode 100644 index 00000000000..66d8f8bf910 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/collectionStubs.kt @@ -0,0 +1,27 @@ +open class BaseEmptyList : List { + override val size: Int = 0 + override fun contains(element: T): Boolean = false + override fun containsAll(elements: Collection): Boolean = false + override fun get(index: Int): T = error("Do not call") + override fun indexOf(element: T): Int = -1 + override fun isEmpty(): Boolean = true + override fun iterator(): Iterator = emptyIterator() + override fun lastIndexOf(element: T): Int = -1 + override fun listIterator(): ListIterator = emptyIterator() + override fun listIterator(index: Int): ListIterator = emptyIterator() + override fun subList(fromIndex: Int, toIndex: Int): List = this + + private fun emptyIterator() = object : ListIterator { + override fun hasNext(): Boolean = false + override fun next(): T = error("Do not call") + override fun hasPrevious(): Boolean = false + override fun nextIndex(): Int = 0 + override fun previous(): T = error("Do not call") + override fun previousIndex(): Int = 0 + } +} + +class DerivedEmptyList : BaseEmptyList(), List + +// add() stub should be generated for BaseEmptyList, but not for DerivedEmptyList +// 1 public add\(ILjava/lang/Object;\)V diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index 5883a621fec..d4a88a8b4ed 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -84,6 +84,11 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { runTest("compiler/testData/codegen/bytecodeText/charConstant.kt"); } + @TestMetadata("collectionStubs.kt") + public void testCollectionStubs() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/collectionStubs.kt"); + } + @TestMetadata("componentEvaluatesOnlyOnce.kt") public void testComponentEvaluatesOnlyOnce() throws Exception { runTest("compiler/testData/codegen/bytecodeText/componentEvaluatesOnlyOnce.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java index eb2ae03ca57..ac5786a7efb 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java @@ -84,6 +84,11 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest { runTest("compiler/testData/codegen/bytecodeText/charConstant.kt"); } + @TestMetadata("collectionStubs.kt") + public void testCollectionStubs() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/collectionStubs.kt"); + } + @TestMetadata("componentEvaluatesOnlyOnce.kt") public void testComponentEvaluatesOnlyOnce() throws Exception { runTest("compiler/testData/codegen/bytecodeText/componentEvaluatesOnlyOnce.kt");