From 8ce2e4654b4c9d3eb8042b03722774a469be54f1 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Wed, 2 Dec 2020 16:36:46 +0100 Subject: [PATCH] JVM IR: allow custom toArray to have any array type To avoid breaking Java source compatibility. This problem can be fixed later once JVM IR is stabilized. #KT-43111 Fixed --- .../jvm/lower/SyntheticAccessorLowering.kt | 2 +- .../backend/jvm/lower/ToArrayLowering.kt | 15 +++-- .../codegen/box/toArray/toArrayFromJava.kt | 11 ++-- .../toArray/customNonGenericToArray.kt | 11 ++++ .../toArray/customNonGenericToArray.txt | 62 +++++++++++++++++++ .../toArray/customNonGenericToArray_ir.txt | 62 +++++++++++++++++++ .../toArray}/noToArrayInJava.kt | 0 .../toArray}/noToArrayInJava.txt | 0 .../toArray}/noToArrayInJava_ir.txt | 0 .../codegen/BytecodeListingTestGenerated.java | 28 +++++++-- .../ir/IrBytecodeListingTestGenerated.java | 28 +++++++-- 11 files changed, 199 insertions(+), 20 deletions(-) create mode 100644 compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/customNonGenericToArray.kt create mode 100644 compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/customNonGenericToArray.txt create mode 100644 compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/customNonGenericToArray_ir.txt rename compiler/testData/codegen/bytecodeListing/{ => collectionStubs/toArray}/noToArrayInJava.kt (100%) rename compiler/testData/codegen/bytecodeListing/{ => collectionStubs/toArray}/noToArrayInJava.txt (100%) rename compiler/testData/codegen/bytecodeListing/{ => collectionStubs/toArray}/noToArrayInJava_ir.txt (100%) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/SyntheticAccessorLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/SyntheticAccessorLowering.kt index 30265076126..d7f3367875d 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/SyntheticAccessorLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/SyntheticAccessorLowering.kt @@ -588,7 +588,7 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle if (!withSuper && !declaration.visibility.isPrivate && !declaration.visibility.isProtected) return true // `toArray` is always accessible cause mapped to public functions - if (symbolOwner is IrSimpleFunction && (symbolOwner.isNonGenericToArray(context) || symbolOwner.isGenericToArray(context))) { + if (symbolOwner is IrSimpleFunction && (symbolOwner.isNonGenericToArray() || symbolOwner.isGenericToArray(context))) { if (symbolOwner.parentAsClass.isCollectionSubClass) { return true } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/ToArrayLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/ToArrayLowering.kt index 2651c849424..4a1da573b3b 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/ToArrayLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/ToArrayLowering.kt @@ -11,8 +11,8 @@ 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.codegen.isJvmInterface -import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.ir.builders.declarations.addDispatchReceiver import org.jetbrains.kotlin.ir.builders.declarations.addFunction import org.jetbrains.kotlin.ir.builders.declarations.addTypeParameter @@ -75,7 +75,7 @@ private class ToArrayLowering(private val context: JvmBackendContext) : ClassLow } } - irClass.findOrCreate(indirectCollectionSubClass, { it.isNonGenericToArray(context) }) { + irClass.findOrCreate(indirectCollectionSubClass, IrSimpleFunction::isNonGenericToArray) { irClass.addFunction { name = Name.identifier("toArray") origin = JvmLoweredDeclarationOrigin.TO_ARRAY @@ -132,7 +132,12 @@ internal fun IrSimpleFunction.isGenericToArray(context: JvmBackendContext): Bool returnType.isArrayOrNullableArrayOf(context, typeParameters[0].symbol) && valueParameters[0].type.isArrayOrNullableArrayOf(context, typeParameters[0].symbol) -// Match `fun toArray(): Array` -internal fun IrSimpleFunction.isNonGenericToArray(context: JvmBackendContext): Boolean = +// Match `fun toArray(): Array<...>`. +// It would be more correct to check that the return type is erased to `Object[]`, however the old backend doesn't do that +// (see `FunctionDescriptor.isNonGenericToArray` and KT-43111). +internal fun IrSimpleFunction.isNonGenericToArray(): Boolean = name.asString() == "toArray" && typeParameters.isEmpty() && valueParameters.isEmpty() && - extensionReceiverParameter == null && returnType.isArrayOrNullableArrayOf(context, context.irBuiltIns.anyClass) + extensionReceiverParameter == null && returnType.isArrayOrNullableArray() + +private fun IrType.isArrayOrNullableArray(): Boolean = + this is IrSimpleType && (isArray() || isNullableArray()) diff --git a/compiler/testData/codegen/box/toArray/toArrayFromJava.kt b/compiler/testData/codegen/box/toArray/toArrayFromJava.kt index 4ee49bb44ef..1992aed47cf 100644 --- a/compiler/testData/codegen/box/toArray/toArrayFromJava.kt +++ b/compiler/testData/codegen/box/toArray/toArrayFromJava.kt @@ -1,6 +1,4 @@ // TARGET_BACKEND: JVM -// The old backend thinks `toArray(): Array` is the same as `toArray(): Array` -// IGNORE_BACKEND: JVM // WITH_RUNTIME // FILE: MyListWithCustomToArray.java @@ -29,7 +27,7 @@ class MyListSubclass(val list: List): MyListWithCustomToArray() { get() = list.size } -class MyCollection(val list: List) : Collection by list { +class MyCollectionWithCustomIntToArray(val list: List) : Collection by list { fun toArray(): Array = arrayOfNulls(0) } @@ -43,8 +41,13 @@ fun box(): String { list2.toArray().contentToString().let { if (it != "[null]") return "fail 3: $it" } list2.toArray(arrayOfNulls(1)).contentToString().let { if (it != "[null]") return "fail 4: $it" } - val list3 = MyCollection(listOf(2, 3, 9)) as java.util.Collection<*> + val list3 = MyCollectionWithCustomIntToArray(listOf(2, 3, 9)) as java.util.Collection<*> + /* + // This fails with AbstractMethodError at the moment because of a bug where the backend doesn't check the array element type + // of the return type when looking for an implementation of the non-generic parameterless `toArray`. + // See `FunctionDescriptor.isNonGenericToArray`, `IrSimpleFunction.isNonGenericToArray` and KT-43111. list3.toArray().contentToString().let { if (it != "[2, 3, 9]") return "fail 5: $it" } list3.toArray(arrayOfNulls(0)).contentToString().let { if (it != "[2, 3, 9]") return "fail 6: $it" } + */ return "OK" } diff --git a/compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/customNonGenericToArray.kt b/compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/customNonGenericToArray.kt new file mode 100644 index 00000000000..5d8a3ceea98 --- /dev/null +++ b/compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/customNonGenericToArray.kt @@ -0,0 +1,11 @@ +class InternalToArray(d: Collection): Collection by d { + internal fun toArray(): Array = null!! +} + +class PrivateToArray(d: Collection): Collection by d { + private fun toArray(): Array = null!! +} + +class PublicToArray(d: Collection): Collection by d { + public fun toArray(): Array = null!! +} diff --git a/compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/customNonGenericToArray.txt b/compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/customNonGenericToArray.txt new file mode 100644 index 00000000000..f520132fd70 --- /dev/null +++ b/compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/customNonGenericToArray.txt @@ -0,0 +1,62 @@ +@kotlin.Metadata +public final class InternalToArray { + // source: 'customNonGenericToArray.kt' + private synthetic final field $$delegate_0: java.util.Collection + public method (@org.jetbrains.annotations.NotNull p0: java.util.Collection): void + public method add(p0: java.lang.Object): boolean + public method addAll(p0: java.util.Collection): boolean + public method clear(): void + public method contains(@org.jetbrains.annotations.NotNull 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 method remove(p0: java.lang.Object): boolean + public method removeAll(p0: java.util.Collection): boolean + public method retainAll(p0: java.util.Collection): boolean + public bridge final method size(): int + public final @org.jetbrains.annotations.NotNull method toArray$test_module(): java.lang.Integer[] + public method toArray(p0: java.lang.Object[]): java.lang.Object[] +} + +@kotlin.Metadata +public final class PrivateToArray { + // source: 'customNonGenericToArray.kt' + private synthetic final field $$delegate_0: java.util.Collection + public method (@org.jetbrains.annotations.NotNull p0: java.util.Collection): void + public method add(p0: java.lang.Object): boolean + public method addAll(p0: java.util.Collection): boolean + public method clear(): void + public method contains(@org.jetbrains.annotations.NotNull 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 method remove(p0: java.lang.Object): boolean + public method removeAll(p0: java.util.Collection): boolean + public method retainAll(p0: java.util.Collection): boolean + public bridge final method size(): int + public final @org.jetbrains.annotations.NotNull method toArray(): java.lang.Integer[] + public method toArray(p0: java.lang.Object[]): java.lang.Object[] +} + +@kotlin.Metadata +public final class PublicToArray { + // source: 'customNonGenericToArray.kt' + private synthetic final field $$delegate_0: java.util.Collection + public method (@org.jetbrains.annotations.NotNull p0: java.util.Collection): void + public method add(p0: java.lang.Object): boolean + public method addAll(p0: java.util.Collection): boolean + public method clear(): void + public method contains(@org.jetbrains.annotations.NotNull 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 method remove(p0: java.lang.Object): boolean + public method removeAll(p0: java.util.Collection): boolean + public method retainAll(p0: java.util.Collection): boolean + public bridge final method size(): int + public final @org.jetbrains.annotations.NotNull method toArray(): java.lang.Integer[] + public method toArray(p0: java.lang.Object[]): java.lang.Object[] +} diff --git a/compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/customNonGenericToArray_ir.txt b/compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/customNonGenericToArray_ir.txt new file mode 100644 index 00000000000..5643251b8e4 --- /dev/null +++ b/compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/customNonGenericToArray_ir.txt @@ -0,0 +1,62 @@ +@kotlin.Metadata +public final class InternalToArray { + // source: 'customNonGenericToArray.kt' + private synthetic final field $$delegate_0: java.util.Collection + public method (@org.jetbrains.annotations.NotNull p0: java.util.Collection): void + public method add(p0: java.lang.Object): boolean + public method addAll(p0: java.util.Collection): boolean + public method clear(): void + public method contains(@org.jetbrains.annotations.Nullable 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 method remove(p0: java.lang.Object): boolean + public method removeAll(p0: java.util.Collection): boolean + public method retainAll(p0: java.util.Collection): boolean + public bridge final method size(): int + public final @org.jetbrains.annotations.NotNull method toArray(): java.lang.Integer[] + public method toArray(p0: java.lang.Object[]): java.lang.Object[] +} + +@kotlin.Metadata +public final class PrivateToArray { + // source: 'customNonGenericToArray.kt' + private synthetic final field $$delegate_0: java.util.Collection + public method (@org.jetbrains.annotations.NotNull p0: java.util.Collection): void + public method add(p0: java.lang.Object): boolean + public method addAll(p0: java.util.Collection): boolean + public method clear(): void + public method contains(@org.jetbrains.annotations.Nullable 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 method remove(p0: java.lang.Object): boolean + public method removeAll(p0: java.util.Collection): boolean + public method retainAll(p0: java.util.Collection): boolean + public bridge final method size(): int + public final @org.jetbrains.annotations.NotNull method toArray(): java.lang.Integer[] + public method toArray(p0: java.lang.Object[]): java.lang.Object[] +} + +@kotlin.Metadata +public final class PublicToArray { + // source: 'customNonGenericToArray.kt' + private synthetic final field $$delegate_0: java.util.Collection + public method (@org.jetbrains.annotations.NotNull p0: java.util.Collection): void + public method add(p0: java.lang.Object): boolean + public method addAll(p0: java.util.Collection): boolean + public method clear(): void + public method contains(@org.jetbrains.annotations.Nullable 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 method remove(p0: java.lang.Object): boolean + public method removeAll(p0: java.util.Collection): boolean + public method retainAll(p0: java.util.Collection): boolean + public bridge final method size(): int + public final @org.jetbrains.annotations.NotNull method toArray(): java.lang.Integer[] + public method toArray(p0: java.lang.Object[]): java.lang.Object[] +} diff --git a/compiler/testData/codegen/bytecodeListing/noToArrayInJava.kt b/compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/noToArrayInJava.kt similarity index 100% rename from compiler/testData/codegen/bytecodeListing/noToArrayInJava.kt rename to compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/noToArrayInJava.kt diff --git a/compiler/testData/codegen/bytecodeListing/noToArrayInJava.txt b/compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/noToArrayInJava.txt similarity index 100% rename from compiler/testData/codegen/bytecodeListing/noToArrayInJava.txt rename to compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/noToArrayInJava.txt diff --git a/compiler/testData/codegen/bytecodeListing/noToArrayInJava_ir.txt b/compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/noToArrayInJava_ir.txt similarity index 100% rename from compiler/testData/codegen/bytecodeListing/noToArrayInJava_ir.txt rename to compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/noToArrayInJava_ir.txt diff --git a/compiler/tests-gen/org/jetbrains/kotlin/codegen/BytecodeListingTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/codegen/BytecodeListingTestGenerated.java index a730a1eb31d..da780c90a5c 100644 --- a/compiler/tests-gen/org/jetbrains/kotlin/codegen/BytecodeListingTestGenerated.java +++ b/compiler/tests-gen/org/jetbrains/kotlin/codegen/BytecodeListingTestGenerated.java @@ -174,11 +174,6 @@ public class BytecodeListingTestGenerated extends AbstractBytecodeListingTest { runTest("compiler/testData/codegen/bytecodeListing/noRemoveAtInReadOnly.kt"); } - @TestMetadata("noToArrayInJava.kt") - public void testNoToArrayInJava() throws Exception { - runTest("compiler/testData/codegen/bytecodeListing/noToArrayInJava.kt"); - } - @TestMetadata("privateCompanionFields.kt") public void testPrivateCompanionFields() throws Exception { runTest("compiler/testData/codegen/bytecodeListing/privateCompanionFields.kt"); @@ -616,6 +611,29 @@ public class BytecodeListingTestGenerated extends AbstractBytecodeListingTest { runTest("compiler/testData/codegen/bytecodeListing/collectionStubs/abstractStubSignatures/stringGenericMutableMap.kt"); } } + + @TestMetadata("compiler/testData/codegen/bytecodeListing/collectionStubs/toArray") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ToArray extends AbstractBytecodeListingTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath); + } + + public void testAllFilesPresentInToArray() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/bytecodeListing/collectionStubs/toArray"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); + } + + @TestMetadata("customNonGenericToArray.kt") + public void testCustomNonGenericToArray() throws Exception { + runTest("compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/customNonGenericToArray.kt"); + } + + @TestMetadata("noToArrayInJava.kt") + public void testNoToArrayInJava() throws Exception { + runTest("compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/noToArrayInJava.kt"); + } + } } @TestMetadata("compiler/testData/codegen/bytecodeListing/coroutines") diff --git a/compiler/tests-gen/org/jetbrains/kotlin/codegen/ir/IrBytecodeListingTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/codegen/ir/IrBytecodeListingTestGenerated.java index 74eab19f719..5eda0b8c230 100644 --- a/compiler/tests-gen/org/jetbrains/kotlin/codegen/ir/IrBytecodeListingTestGenerated.java +++ b/compiler/tests-gen/org/jetbrains/kotlin/codegen/ir/IrBytecodeListingTestGenerated.java @@ -174,11 +174,6 @@ public class IrBytecodeListingTestGenerated extends AbstractIrBytecodeListingTes runTest("compiler/testData/codegen/bytecodeListing/noRemoveAtInReadOnly.kt"); } - @TestMetadata("noToArrayInJava.kt") - public void testNoToArrayInJava() throws Exception { - runTest("compiler/testData/codegen/bytecodeListing/noToArrayInJava.kt"); - } - @TestMetadata("privateCompanionFields.kt") public void testPrivateCompanionFields() throws Exception { runTest("compiler/testData/codegen/bytecodeListing/privateCompanionFields.kt"); @@ -616,6 +611,29 @@ public class IrBytecodeListingTestGenerated extends AbstractIrBytecodeListingTes runTest("compiler/testData/codegen/bytecodeListing/collectionStubs/abstractStubSignatures/stringGenericMutableMap.kt"); } } + + @TestMetadata("compiler/testData/codegen/bytecodeListing/collectionStubs/toArray") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ToArray extends AbstractIrBytecodeListingTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath); + } + + public void testAllFilesPresentInToArray() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/bytecodeListing/collectionStubs/toArray"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @TestMetadata("customNonGenericToArray.kt") + public void testCustomNonGenericToArray() throws Exception { + runTest("compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/customNonGenericToArray.kt"); + } + + @TestMetadata("noToArrayInJava.kt") + public void testNoToArrayInJava() throws Exception { + runTest("compiler/testData/codegen/bytecodeListing/collectionStubs/toArray/noToArrayInJava.kt"); + } + } } @TestMetadata("compiler/testData/codegen/bytecodeListing/coroutines")