diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/CollectionStubMethodGenerator.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/CollectionStubMethodGenerator.kt index 8777d4fe4de..b83a7b831d7 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/CollectionStubMethodGenerator.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/CollectionStubMethodGenerator.kt @@ -25,6 +25,7 @@ import org.jetbrains.kotlin.load.java.BuiltinMethodsWithSpecialGenericSignature. import org.jetbrains.kotlin.load.java.BuiltinMethodsWithSpecialGenericSignature.isBuiltinWithSpecialDescriptorInJvm import org.jetbrains.kotlin.load.java.isFromBuiltins import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.platform.JavaToKotlinClassMap import org.jetbrains.kotlin.resolve.NonReportingOverrideStrategy import org.jetbrains.kotlin.resolve.OverrideResolver import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns @@ -204,8 +205,37 @@ class CollectionStubMethodGenerator( OverrideResolver.generateOverridesInAClass(klass, listOf(), object : NonReportingOverrideStrategy() { override fun addFakeOverride(fakeOverride: CallableMemberDescriptor) { if (fakeOverride !is FunctionDescriptor) return - if (fakeOverride.findOverriddenFromDirectSuperClass(mutableCollectionClass)?.kind == DECLARATION) { - result.add(fakeOverride) + val foundOverriddenFromDirectSuperClass = fakeOverride.findOverriddenFromDirectSuperClass(mutableCollectionClass) ?: return + if (foundOverriddenFromDirectSuperClass.kind == DECLARATION) { + // For regular classes there should no be fake overrides having return types incompatible with return types of their + // overridden, while here it's possible to create declaration like `fun remove(e: E): ImmutableCollection` + // in read-only class that obviously conflicts with `fun remove(e: E): Boolean`. + // But overrides binding algorithm suppose there should be no conflicts like this, so it simply chooses a random + // representative for fake override, while we interested here in ones from mutable version. + // + // NB: READ_ONLY_ARE_EQUAL_TO_MUTABLE_TYPE_CHECKER is used here for cases like: + // `fun iterator(): CharIterator` defined in read-only collection + // The problem is that 'CharIterator' is not a subtype of 'MutableIterator' while from Java's point of view it is, + // so we must hack our subtyping a little bit + val newDescriptor = + if (READ_ONLY_ARE_EQUAL_TO_MUTABLE_TYPE_CHECKER.isSubtypeOf( + fakeOverride.returnType!!, foundOverriddenFromDirectSuperClass.returnType!!)) + fakeOverride + else + foundOverriddenFromDirectSuperClass.copy( + fakeOverride.containingDeclaration, + fakeOverride.modality, + fakeOverride.visibility, + fakeOverride.kind, false) + + newDescriptor.overriddenDescriptors = + fakeOverride.overriddenDescriptors.filter { + superDescriptor -> + // filter out incompatible descriptors, e.g. `fun remove(e: E): ImmutableCollection` for `fun remove(e: E): Boolean` + READ_ONLY_ARE_EQUAL_TO_MUTABLE_TYPE_CHECKER.isSubtypeOf(newDescriptor.returnType!!, superDescriptor.returnType!!) + } + + result.add(newDescriptor) } } @@ -266,3 +296,13 @@ class CollectionStubMethodGenerator( } } } + +private val READ_ONLY_ARE_EQUAL_TO_MUTABLE_TYPE_CHECKER = KotlinTypeChecker.withAxioms { x, y -> + val firstClass = x.declarationDescriptor as? ClassDescriptor ?: return@withAxioms x == y + val secondClass = y.declarationDescriptor as? ClassDescriptor ?: return@withAxioms x == y + + val j2k = JavaToKotlinClassMap.INSTANCE + val firstReadOnly = if (j2k.isMutable(firstClass)) j2k.convertMutableToReadOnly(firstClass) else firstClass + val secondReadOnly = if (j2k.isMutable(secondClass)) j2k.convertMutableToReadOnly(secondClass) else secondClass + firstReadOnly.typeConstructor == secondReadOnly.typeConstructor +} diff --git a/compiler/testData/codegen/box/builtinStubMethods/customReadOnlyIterator.kt b/compiler/testData/codegen/box/builtinStubMethods/customReadOnlyIterator.kt new file mode 100644 index 00000000000..2038e67cbb5 --- /dev/null +++ b/compiler/testData/codegen/box/builtinStubMethods/customReadOnlyIterator.kt @@ -0,0 +1,34 @@ +class A : Collection { + override val size: Int + get() = throw UnsupportedOperationException() + + override fun contains(element: Char): Boolean { + throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun containsAll(elements: Collection): Boolean { + throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun isEmpty(): Boolean { + throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun iterator() = MyIterator +} + +object MyIterator : Iterator { + override fun hasNext() = true + + override fun next() = 'a' +} + + +fun box(): String { + val it: MyIterator = A().iterator() + + if (!it.hasNext()) return "fail 1" + if (it.next() != 'a') return "fail 2" + + return "OK" +} diff --git a/compiler/testData/codegen/box/builtinStubMethods/immutableRemove.kt b/compiler/testData/codegen/box/builtinStubMethods/immutableRemove.kt new file mode 100644 index 00000000000..e55ae496483 --- /dev/null +++ b/compiler/testData/codegen/box/builtinStubMethods/immutableRemove.kt @@ -0,0 +1,50 @@ +// FULL_JDK +// WITH_RUNTIME +interface ImmutableCollection : Collection { + fun add(element: @UnsafeVariance E): ImmutableCollection + fun addAll(elements: Collection<@UnsafeVariance E>): ImmutableCollection + fun remove(element: @UnsafeVariance E): ImmutableCollection +} + +class ImmutableCollectionmpl : ImmutableCollection { + override val size: Int + get() = throw UnsupportedOperationException() + + override fun contains(element: E): Boolean { + throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun containsAll(elements: Collection): Boolean { + throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun isEmpty(): Boolean { + throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun iterator(): Iterator { + throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun add(element: E): ImmutableCollection = this + override fun addAll(elements: Collection): ImmutableCollection = this + override fun remove(element: E): ImmutableCollection = this +} + +fun box(): String { + val c = ImmutableCollectionmpl() + if (c.remove("") !== c) return "fail 1" + if (c.add("") !== c) return "fail 2" + if (c.addAll(java.util.ArrayList()) !== c) return "fail 3" + + val method = c.javaClass.methods.single { it.name == "remove" && it.returnType == Boolean::class.javaPrimitiveType } + + try { + method.invoke(c, "") + return "fail 4" + } catch (e: java.lang.reflect.InvocationTargetException) { + if (e.cause!!.message != "Mutating immutable collection") return "fail 5: ${e.cause!!.message}" + } + + return "OK" +} diff --git a/compiler/testData/codegen/bytecodeListing/immutableCollection.kt b/compiler/testData/codegen/bytecodeListing/immutableCollection.kt new file mode 100644 index 00000000000..827f0a50f45 --- /dev/null +++ b/compiler/testData/codegen/bytecodeListing/immutableCollection.kt @@ -0,0 +1,30 @@ +interface ImmutableCollection : Collection { + fun add(element: @UnsafeVariance E): ImmutableCollection + fun addAll(elements: Collection<@UnsafeVariance E>): ImmutableCollection + fun remove(element: @UnsafeVariance E): ImmutableCollection +} + +class ImmutableCollectionmpl : ImmutableCollection { + override val size: Int + get() = throw UnsupportedOperationException() + + override fun contains(element: E): Boolean { + throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun containsAll(elements: Collection): Boolean { + throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun isEmpty(): Boolean { + throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun iterator(): Iterator { + throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun add(element: E): ImmutableCollection = this + override fun addAll(elements: Collection): ImmutableCollection = this + override fun remove(element: E): ImmutableCollection = this +} diff --git a/compiler/testData/codegen/bytecodeListing/immutableCollection.txt b/compiler/testData/codegen/bytecodeListing/immutableCollection.txt new file mode 100644 index 00000000000..7d933fbc3f7 --- /dev/null +++ b/compiler/testData/codegen/bytecodeListing/immutableCollection.txt @@ -0,0 +1,35 @@ +@kotlin.Metadata +public interface ImmutableCollection { + public abstract @org.jetbrains.annotations.NotNull method add(p0: java.lang.Object): ImmutableCollection + public abstract method add(p0: java.lang.Object): boolean + public abstract @org.jetbrains.annotations.NotNull method addAll(@org.jetbrains.annotations.NotNull p0: java.util.Collection): ImmutableCollection + public abstract method addAll(p0: java.util.Collection): boolean + public abstract method clear(): void + public abstract method iterator(): java.util.Iterator + public abstract @org.jetbrains.annotations.NotNull method remove(p0: java.lang.Object): ImmutableCollection + public abstract method remove(p0: java.lang.Object): boolean + public abstract method removeAll(p0: java.util.Collection): boolean + public abstract method retainAll(p0: java.util.Collection): boolean +} + +@kotlin.Metadata +public final class ImmutableCollectionmpl { + public method (): void + public @org.jetbrains.annotations.NotNull method add(p0: java.lang.Object): ImmutableCollection + public method add(p0: java.lang.Object): boolean + public @org.jetbrains.annotations.NotNull method addAll(@org.jetbrains.annotations.NotNull p0: java.util.Collection): ImmutableCollection + public method addAll(p0: java.util.Collection): boolean + public method clear(): void + public method contains(p0: java.lang.Object): boolean + public method containsAll(@org.jetbrains.annotations.NotNull p0: java.util.Collection): boolean + public method getSize(): int + public method isEmpty(): boolean + public @org.jetbrains.annotations.NotNull method iterator(): java.util.Iterator + public @org.jetbrains.annotations.NotNull method remove(p0: java.lang.Object): ImmutableCollection + public method remove(p0: java.lang.Object): boolean + public method removeAll(p0: java.util.Collection): boolean + public method retainAll(p0: java.util.Collection): boolean + public final method size(): int + public method toArray(): java.lang.Object[] + public method toArray(p0: java.lang.Object[]): java.lang.Object[] +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index a8f0b6fd026..f9a358303e4 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -1276,12 +1276,24 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("customReadOnlyIterator.kt") + public void testCustomReadOnlyIterator() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/customReadOnlyIterator.kt"); + doTest(fileName); + } + @TestMetadata("delegationToArrayList.kt") public void testDelegationToArrayList() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/delegationToArrayList.kt"); doTest(fileName); } + @TestMetadata("immutableRemove.kt") + public void testImmutableRemove() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/immutableRemove.kt"); + doTest(fileName); + } + @TestMetadata("implementationInTrait.kt") public void testImplementationInTrait() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/implementationInTrait.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeListingTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeListingTestGenerated.java index 38fec7660ef..ac184d95cdf 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeListingTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeListingTestGenerated.java @@ -53,6 +53,12 @@ public class BytecodeListingTestGenerated extends AbstractBytecodeListingTest { doTest(fileName); } + @TestMetadata("immutableCollection.kt") + public void testImmutableCollection() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeListing/immutableCollection.kt"); + doTest(fileName); + } + @TestMetadata("inlineOnly.kt") public void testInlineOnly() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeListing/inlineOnly.kt");