diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/irUtils.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/irUtils.kt index 2177a8faf68..f1b38b69863 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/irUtils.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/irUtils.kt @@ -59,7 +59,11 @@ fun IrBuilderWithScope.parcelerCreate(parceler: IrClass, parcel: IrValueDeclarat // object P: Parceler { fun newArray(size: Int): Array } fun IrBuilderWithScope.parcelerNewArray(parceler: IrClass?, size: IrValueDeclaration): IrExpression? = - parceler?.parcelerSymbolByName("newArray")?.let { newArraySymbol -> + parceler?.parcelerSymbolByName("newArray")?.takeIf { + // The `newArray` method in `kotlinx.parcelize.Parceler` is stubbed out and we + // have to produce a new implementation, unless the user overrides it. + !it.owner.isFakeOverride || it.owner.resolveFakeOverride()?.parentClassOrNull?.fqNameWhenAvailable != PARCELER_FQNAME + }?.let { newArraySymbol -> irCall(newArraySymbol).apply { dispatchReceiver = irGetObject(parceler.symbol) putValueArgument(0, irGet(size)) @@ -101,7 +105,7 @@ fun IrBuilderWithScope.parcelableCreatorCreateFromParcel(creator: IrExpression, // has already done the work. private fun IrClass.parcelerSymbolByName(name: String): IrSimpleFunctionSymbol? = functions.firstOrNull { function -> - !function.isFakeOverride && function.name.asString() == name && function.overridesFunctionIn(PARCELER_FQNAME) + function.name.asString() == name && function.overridesFunctionIn(PARCELER_FQNAME) }?.symbol fun IrSimpleFunction.overridesFunctionIn(fqName: FqName): Boolean = diff --git a/plugins/android-extensions/android-extensions-compiler/test/org/jetbrains/kotlin/android/parcel/ParcelBoxTestGenerated.java b/plugins/android-extensions/android-extensions-compiler/test/org/jetbrains/kotlin/android/parcel/ParcelBoxTestGenerated.java index 07cac6661ef..c6fb8facd1c 100644 --- a/plugins/android-extensions/android-extensions-compiler/test/org/jetbrains/kotlin/android/parcel/ParcelBoxTestGenerated.java +++ b/plugins/android-extensions/android-extensions-compiler/test/org/jetbrains/kotlin/android/parcel/ParcelBoxTestGenerated.java @@ -190,6 +190,11 @@ public class ParcelBoxTestGenerated extends AbstractParcelBoxTest { runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/kt41553_2.kt"); } + @TestMetadata("kt46567.kt") + public void testKt46567() throws Exception { + runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/kt46567.kt"); + } + @TestMetadata("listKinds.kt") public void testListKinds() throws Exception { runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/listKinds.kt"); @@ -250,6 +255,11 @@ public class ParcelBoxTestGenerated extends AbstractParcelBoxTest { runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/newArray.kt"); } + @TestMetadata("newArrayParceler.kt") + public void testNewArrayParceler() throws Exception { + runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/newArrayParceler.kt"); + } + @TestMetadata("nullableTypes.kt") public void testNullableTypes() throws Exception { runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nullableTypes.kt"); diff --git a/plugins/android-extensions/android-extensions-compiler/test/org/jetbrains/kotlin/android/parcel/ParcelIrBoxTestGenerated.java b/plugins/android-extensions/android-extensions-compiler/test/org/jetbrains/kotlin/android/parcel/ParcelIrBoxTestGenerated.java index 29361d089b5..3ab23c76157 100644 --- a/plugins/android-extensions/android-extensions-compiler/test/org/jetbrains/kotlin/android/parcel/ParcelIrBoxTestGenerated.java +++ b/plugins/android-extensions/android-extensions-compiler/test/org/jetbrains/kotlin/android/parcel/ParcelIrBoxTestGenerated.java @@ -190,6 +190,11 @@ public class ParcelIrBoxTestGenerated extends AbstractParcelIrBoxTest { runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/kt41553_2.kt"); } + @TestMetadata("kt46567.kt") + public void testKt46567() throws Exception { + runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/kt46567.kt"); + } + @TestMetadata("listKinds.kt") public void testListKinds() throws Exception { runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/listKinds.kt"); @@ -250,6 +255,11 @@ public class ParcelIrBoxTestGenerated extends AbstractParcelIrBoxTest { runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/newArray.kt"); } + @TestMetadata("newArrayParceler.kt") + public void testNewArrayParceler() throws Exception { + runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/newArrayParceler.kt"); + } + @TestMetadata("nullableTypes.kt") public void testNullableTypes() throws Exception { runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nullableTypes.kt"); diff --git a/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/kt46567.kt b/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/kt46567.kt new file mode 100644 index 00000000000..b7b84975bf5 --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/kt46567.kt @@ -0,0 +1,64 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.android.parcel.* +import android.os.Parcel +import android.os.Parcelable +import java.util.Arrays + +/** + * Generic pair parceler + * Create concrete object to use (see below) + */ +open class PairParceler(private val firstParceler: Parceler, private val secondParceler: Parceler): Parceler> { + /** + * Reads the [T] instance state from the [parcel], constructs the new [T] instance and returns it. + */ + override fun create(parcel: Parcel): Pair = + firstParceler.create(parcel) to secondParceler.create(parcel) + + /** + * Writes the [T] instance state to the [parcel]. + */ + override fun Pair.write(parcel: Parcel, flags: Int) { + with(firstParceler) { this@write.first.write(parcel, 0) } + with(secondParceler) { this@write.second.write(parcel, 0) } + } +} + +object IntParceler: Parceler { + /** + * Reads the [T] instance state from the [parcel], constructs the new [T] instance and returns it. + */ + override fun create(parcel: Parcel): Int = parcel.readInt() + + /** + * Writes the [T] instance state to the [parcel]. + */ + override fun Int.write(parcel: Parcel, flags: Int) { + parcel.writeInt(this) + } +} + +/** + * [Int] to [Int] pair parceler + */ +object IntToIntParceler: PairParceler(IntParceler, IntParceler) + +@Parcelize +@TypeParceler, IntToIntParceler> +class A(val pair: Pair): Parcelable + +fun box() = parcelTest { parcel -> + val a1 = A(1 to 2) + a1.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val a2 = readFromParcel(parcel) + assert(a1.pair == a2.pair) +} diff --git a/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/newArrayParceler.kt b/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/newArrayParceler.kt new file mode 100644 index 00000000000..c0f5d3c65e9 --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/newArrayParceler.kt @@ -0,0 +1,45 @@ +// WITH_RUNTIME +// IGNORE_BACKEND: JVM + +@file:JvmName("TestKt") +package test + +import kotlinx.android.parcel.* +import android.os.Parcel +import android.os.Parcelable + +abstract class UserParceler : Parceler { + override fun User.write(parcel: Parcel, flags: Int) { + parcel.writeString(name) + } + + override fun newArray(size: Int): Array { + return Array(size + 1) { User(null) } + } +} + +@Parcelize +class User(val name: String?) : Parcelable { + companion object : UserParceler() { + override fun create(parcel: Parcel) = User(parcel.readString()) + } +} + +fun box() = parcelTest { parcel -> + val user = User("John") + val user2 = User("Joe") + val array = arrayOf(user, user2) + parcel.writeTypedArray(array, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val creator = User::class.java.getDeclaredField("CREATOR").get(null) as Parcelable.Creator + val result = parcel.createTypedArray(creator) + + assert(result.size == 3) + assert(result[0].name == user.name) + assert(result[1].name == user2.name) + assert(result[2].name == null) +} diff --git a/plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/customParcelablesSameModule.ir.txt b/plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/customParcelablesSameModule.ir.txt index f2cdcbe60f9..c55101de60d 100644 --- a/plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/customParcelablesSameModule.ir.txt +++ b/plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/customParcelablesSameModule.ir.txt @@ -167,4 +167,4 @@ public final class test/Foo : java/lang/Object, android/os/Parcelable { RETURN LABEL (L1) } -} +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/irUtils.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/irUtils.kt index facd7bbef72..29316059a21 100644 --- a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/irUtils.kt +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/irUtils.kt @@ -42,34 +42,36 @@ val IrClass.hasCreatorField: Boolean // object P : Parceler { fun T.write(parcel: Parcel, flags: Int) ...} fun IrBuilderWithScope.parcelerWrite( - parceler: IrClass, parcel: IrValueDeclaration, - flags: IrValueDeclaration, value: IrExpression -): IrCall { - return irCall(parceler.parcelerSymbolByName("write")!!).apply { - dispatchReceiver = irGetObject(parceler.symbol) - extensionReceiver = value - putValueArgument(0, irGet(parcel)) - putValueArgument(1, irGet(flags)) - } + parceler: IrClass, + parcel: IrValueDeclaration, + flags: IrValueDeclaration, + value: IrExpression, +) = irCall(parceler.parcelerSymbolByName("write")!!).apply { + dispatchReceiver = irGetObject(parceler.symbol) + extensionReceiver = value + putValueArgument(0, irGet(parcel)) + putValueArgument(1, irGet(flags)) } // object P : Parceler { fun create(parcel: Parcel): T } -fun IrBuilderWithScope.parcelerCreate(parceler: IrClass, parcel: IrValueDeclaration): IrExpression { - return irCall(parceler.parcelerSymbolByName("create")!!).apply { +fun IrBuilderWithScope.parcelerCreate(parceler: IrClass, parcel: IrValueDeclaration): IrExpression = + irCall(parceler.parcelerSymbolByName("create")!!).apply { dispatchReceiver = irGetObject(parceler.symbol) putValueArgument(0, irGet(parcel)) } -} // object P: Parceler { fun newArray(size: Int): Array } -fun IrBuilderWithScope.parcelerNewArray(parceler: IrClass?, size: IrValueDeclaration): IrExpression? { - return parceler?.parcelerSymbolByName("newArray")?.let { newArraySymbol -> +fun IrBuilderWithScope.parcelerNewArray(parceler: IrClass?, size: IrValueDeclaration): IrExpression? = + parceler?.parcelerSymbolByName("newArray")?.takeIf { + // The `newArray` method in `kotlinx.parcelize.Parceler` is stubbed out and we + // have to produce a new implementation, unless the user overrides it. + !it.owner.isFakeOverride || it.owner.resolveFakeOverride()?.parentClassOrNull?.fqNameWhenAvailable != PARCELER_FQNAME + }?.let { newArraySymbol -> irCall(newArraySymbol).apply { dispatchReceiver = irGetObject(parceler.symbol) putValueArgument(0, irGet(size)) } } -} // class Parcelable { fun writeToParcel(parcel: Parcel, flags: Int) ...} fun IrBuilderWithScope.parcelableWriteToParcel( @@ -104,34 +106,29 @@ fun IrBuilderWithScope.parcelableCreatorCreateFromParcel(creator: IrExpression, // Find a named function declaration which overrides the corresponding function in [Parceler]. // This is more reliable than trying to match the functions signature ourselves, since the frontend // has already done the work. -private fun IrClass.parcelerSymbolByName(name: String): IrSimpleFunctionSymbol? { - return functions.firstOrNull { function -> - !function.isFakeOverride && function.name.asString() == name && function.overridesFunctionIn(PARCELER_FQNAME) +private fun IrClass.parcelerSymbolByName(name: String): IrSimpleFunctionSymbol? = + functions.firstOrNull { function -> + function.name.asString() == name && function.overridesFunctionIn(PARCELER_FQNAME) }?.symbol -} -fun IrSimpleFunction.overridesFunctionIn(fqName: FqName): Boolean { - return parentClassOrNull?.fqNameWhenAvailable == fqName || allOverridden().any { it.parentClassOrNull?.fqNameWhenAvailable == fqName } -} +fun IrSimpleFunction.overridesFunctionIn(fqName: FqName): Boolean = + parentClassOrNull?.fqNameWhenAvailable == fqName || allOverridden().any { it.parentClassOrNull?.fqNameWhenAvailable == fqName } -private fun IrBuilderWithScope.kClassReference(classType: IrType): IrClassReferenceImpl { - return IrClassReferenceImpl( +private fun IrBuilderWithScope.kClassReference(classType: IrType): IrClassReferenceImpl = + IrClassReferenceImpl( startOffset, endOffset, context.irBuiltIns.kClassClass.starProjectedType, context.irBuiltIns.kClassClass, classType ) -} -private fun AndroidIrBuilder.kClassToJavaClass(kClassReference: IrExpression): IrCall { - return irGet(androidSymbols.javaLangClass.starProjectedType, null, androidSymbols.kotlinKClassJava.owner.getter!!.symbol).apply { +private fun AndroidIrBuilder.kClassToJavaClass(kClassReference: IrExpression): IrCall = + irGet(androidSymbols.javaLangClass.starProjectedType, null, androidSymbols.kotlinKClassJava.owner.getter!!.symbol).apply { extensionReceiver = kClassReference } -} // Produce a static reference to the java class of the given type. fun AndroidIrBuilder.javaClassReference(classType: IrType): IrCall = kClassToJavaClass(kClassReference(classType)) -fun IrClass.isSubclassOfFqName(fqName: String): Boolean { - return fqNameWhenAvailable?.asString() == fqName || superTypes.any { it.erasedUpperBound.isSubclassOfFqName(fqName) } -} +fun IrClass.isSubclassOfFqName(fqName: String): Boolean = + fqNameWhenAvailable?.asString() == fqName || superTypes.any { it.erasedUpperBound.isSubclassOfFqName(fqName) } inline fun IrBlockBuilder.forUntil(upperBound: IrExpression, loopBody: IrBlockBuilder.(IrValueDeclaration) -> Unit) { val indexTemporary = irTemporary(irInt(0), isMutable = true) diff --git a/plugins/parcelize/parcelize-compiler/testData/box/kt46567.kt b/plugins/parcelize/parcelize-compiler/testData/box/kt46567.kt new file mode 100644 index 00000000000..2b0bb4f2636 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/kt46567.kt @@ -0,0 +1,64 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import java.util.Arrays + +/** + * Generic pair parceler + * Create concrete object to use (see below) + */ +open class PairParceler(private val firstParceler: Parceler, private val secondParceler: Parceler): Parceler> { + /** + * Reads the [T] instance state from the [parcel], constructs the new [T] instance and returns it. + */ + override fun create(parcel: Parcel): Pair = + firstParceler.create(parcel) to secondParceler.create(parcel) + + /** + * Writes the [T] instance state to the [parcel]. + */ + override fun Pair.write(parcel: Parcel, flags: Int) { + with(firstParceler) { this@write.first.write(parcel, 0) } + with(secondParceler) { this@write.second.write(parcel, 0) } + } +} + +object IntParceler: Parceler { + /** + * Reads the [T] instance state from the [parcel], constructs the new [T] instance and returns it. + */ + override fun create(parcel: Parcel): Int = parcel.readInt() + + /** + * Writes the [T] instance state to the [parcel]. + */ + override fun Int.write(parcel: Parcel, flags: Int) { + parcel.writeInt(this) + } +} + +/** + * [Int] to [Int] pair parceler + */ +object IntToIntParceler: PairParceler(IntParceler, IntParceler) + +@Parcelize +@TypeParceler, IntToIntParceler> +class A(val pair: Pair): Parcelable + +fun box() = parcelTest { parcel -> + val a1 = A(1 to 2) + a1.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val a2 = readFromParcel(parcel) + assert(a1.pair == a2.pair) +} diff --git a/plugins/parcelize/parcelize-compiler/testData/box/newArrayParceler.kt b/plugins/parcelize/parcelize-compiler/testData/box/newArrayParceler.kt new file mode 100644 index 00000000000..e45a357534e --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/newArrayParceler.kt @@ -0,0 +1,45 @@ +// WITH_RUNTIME +// IGNORE_BACKEND: JVM + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +abstract class UserParceler : Parceler { + override fun User.write(parcel: Parcel, flags: Int) { + parcel.writeString(name) + } + + override fun newArray(size: Int): Array { + return Array(size + 1) { User(null) } + } +} + +@Parcelize +class User(val name: String?) : Parcelable { + companion object : UserParceler() { + override fun create(parcel: Parcel) = User(parcel.readString()) + } +} + +fun box() = parcelTest { parcel -> + val user = User("John") + val user2 = User("Joe") + val array = arrayOf(user, user2) + parcel.writeTypedArray(array, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val creator = User::class.java.getDeclaredField("CREATOR").get(null) as Parcelable.Creator + val result = parcel.createTypedArray(creator) + + assert(result.size == 3) + assert(result[0].name == user.name) + assert(result[1].name == user2.name) + assert(result[2].name == null) +} diff --git a/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeBoxTestGenerated.java b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeBoxTestGenerated.java index d925874e8c4..4b807963ae9 100644 --- a/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeBoxTestGenerated.java +++ b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeBoxTestGenerated.java @@ -200,6 +200,11 @@ public class ParcelizeBoxTestGenerated extends AbstractParcelizeBoxTest { runTest("plugins/parcelize/parcelize-compiler/testData/box/kt41553_2.kt"); } + @TestMetadata("kt46567.kt") + public void testKt46567() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt46567.kt"); + } + @TestMetadata("listKinds.kt") public void testListKinds() throws Exception { runTest("plugins/parcelize/parcelize-compiler/testData/box/listKinds.kt"); @@ -260,6 +265,11 @@ public class ParcelizeBoxTestGenerated extends AbstractParcelizeBoxTest { runTest("plugins/parcelize/parcelize-compiler/testData/box/newArray.kt"); } + @TestMetadata("newArrayParceler.kt") + public void testNewArrayParceler() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/newArrayParceler.kt"); + } + @TestMetadata("nullableTypes.kt") public void testNullableTypes() throws Exception { runTest("plugins/parcelize/parcelize-compiler/testData/box/nullableTypes.kt"); diff --git a/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeIrBoxTestGenerated.java b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeIrBoxTestGenerated.java index 6a2385d51e8..c9c5ab69848 100644 --- a/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeIrBoxTestGenerated.java +++ b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeIrBoxTestGenerated.java @@ -200,6 +200,11 @@ public class ParcelizeIrBoxTestGenerated extends AbstractParcelizeIrBoxTest { runTest("plugins/parcelize/parcelize-compiler/testData/box/kt41553_2.kt"); } + @TestMetadata("kt46567.kt") + public void testKt46567() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt46567.kt"); + } + @TestMetadata("listKinds.kt") public void testListKinds() throws Exception { runTest("plugins/parcelize/parcelize-compiler/testData/box/listKinds.kt"); @@ -260,6 +265,11 @@ public class ParcelizeIrBoxTestGenerated extends AbstractParcelizeIrBoxTest { runTest("plugins/parcelize/parcelize-compiler/testData/box/newArray.kt"); } + @TestMetadata("newArrayParceler.kt") + public void testNewArrayParceler() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/newArrayParceler.kt"); + } + @TestMetadata("nullableTypes.kt") public void testNullableTypes() throws Exception { runTest("plugins/parcelize/parcelize-compiler/testData/box/nullableTypes.kt");