diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/IrParcelSerializerFactory.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/IrParcelSerializerFactory.kt index 78576cc4b32..b59b63ba1fe 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/IrParcelSerializerFactory.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/IrParcelSerializerFactory.kt @@ -171,7 +171,7 @@ class IrParcelSerializerFactory(symbols: AndroidSymbols) { } return wrapNullableSerializerIfNeeded( irType, - IrListParcelSerializer(classifier, get(elementType, scope, parcelizeType, strict())) + IrListParcelSerializer(classifier, elementType, get(elementType, scope, parcelizeType, strict())) ) } @@ -185,6 +185,8 @@ class IrParcelSerializerFactory(symbols: AndroidSymbols) { val parceler = IrMapParcelSerializer( classifier, + keyType, + valueType, get(keyType, scope, parcelizeType, strict()), get(valueType, scope, parcelizeType, strict()) ) @@ -265,9 +267,9 @@ class IrParcelSerializerFactory(symbols: AndroidSymbols) { private val longArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateLongArray, symbols.parcelWriteLongArray) // Primitive types without dedicated read/write methods need an additional cast. - private val booleanSerializer = IrWrappedPrimitiveParcelSerializer(irBuiltIns.booleanType, intSerializer) - private val shortSerializer = IrWrappedPrimitiveParcelSerializer(irBuiltIns.shortType, intSerializer) - private val charSerializer = IrWrappedPrimitiveParcelSerializer(irBuiltIns.charType, intSerializer) + private val booleanSerializer = IrWrappedIntParcelSerializer(irBuiltIns.booleanType) + private val shortSerializer = IrWrappedIntParcelSerializer(irBuiltIns.shortType) + private val charSerializer = IrWrappedIntParcelSerializer(irBuiltIns.charType) private val charSequenceSerializer = IrCharSequenceParcelSerializer() diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/IrParcelSerializers.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/IrParcelSerializers.kt index 7f85035c8ce..cce03c515b1 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/IrParcelSerializers.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/IrParcelSerializers.kt @@ -47,32 +47,33 @@ class IrSimpleParcelSerializer(private val reader: IrSimpleFunctionSymbol, priva } } -// Serialize a value of the primitive type [parcelType] by wrapping the given [serializer] with casts. -class IrWrappedPrimitiveParcelSerializer(private val parcelType: IrType, private val serializer: IrParcelSerializer) : IrParcelSerializer { +// Serialize a value of the primitive [parcelType] by coercion to int. +class IrWrappedIntParcelSerializer(private val parcelType: IrType) : IrParcelSerializer { override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression { - val deserializedPrimitive = readParcelWith(serializer, parcel) return if (parcelType.isBoolean()) { - irIfThenElse( - parcelType, - irNotEquals(deserializedPrimitive, irInt(0)), - irInt(1), - irInt(0) - ) + irNotEquals(parcelReadInt(irGet(parcel)), irInt(0)) } else { - val conversion = deserializedPrimitive.type.getClass()!!.functions.first { function -> - function.name.asString() == "to${parcelType.getClass()!!.name}" + val conversion = context.irBuiltIns.intClass.functions.first { function -> + function.owner.name.asString() == "to${parcelType.getClass()!!.name}" } irCall(conversion).apply { - dispatchReceiver = deserializedPrimitive + dispatchReceiver = parcelReadInt(irGet(parcel)) } } } - private fun IrBuilderWithScope.irCastIfNeeded(expression: IrExpression, irType: IrType): IrExpression = - if (expression.type == irType) expression else irAs(expression, irType) - override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression = - writeParcelWith(serializer, parcel, flags, irCastIfNeeded(value, parcelType)) + parcelWriteInt( + irGet(parcel), + if (parcelType.isBoolean()) { + irIfThenElse(context.irBuiltIns.intType, value, irInt(1), irInt(0)) + } else { + val conversion = parcelType.classOrNull!!.functions.first { function -> + function.owner.name.asString() == "toInt" + } + irCall(conversion).apply { dispatchReceiver = value } + } + ) } // Wraps a non-null aware parceler to handle nullable types. @@ -253,7 +254,7 @@ class IrArrayParcelSerializer( forUntil(irGet(arraySize)) { index -> val getter = context.irBuiltIns.arrayClass.getSimpleFunction("get")!! - val element = irCall(getter).apply { + val element = irCall(getter, elementType).apply { dispatchReceiver = irGet(arrayTemporary) putValueArgument(0, irGet(index)) } @@ -332,7 +333,7 @@ class IrSparseArrayParcelSerializer( putValueArgument(0, irGet(index)) }) - +writeParcelWith(elementSerializer, parcel, flags, irCall(valueAtFunction).apply { + +writeParcelWith(elementSerializer, parcel, flags, irCall(valueAtFunction.symbol, elementType).apply { dispatchReceiver = irGet(arrayTemporary) putValueArgument(0, irGet(index)) }) @@ -342,7 +343,11 @@ class IrSparseArrayParcelSerializer( // Parcel serializer for all lists supported by Parcelize. List interfaces use hard-coded default implementations for deserialization. // List maps to ArrayList, Set maps to LinkedHashSet, NavigableSet and SortedSet map to TreeSet. -class IrListParcelSerializer(private val irClass: IrClass, private val elementSerializer: IrParcelSerializer) : IrParcelSerializer { +class IrListParcelSerializer( + private val irClass: IrClass, + private val elementType: IrType, + private val elementSerializer: IrParcelSerializer +) : IrParcelSerializer { override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression { val sizeFunction = irClass.getPropertyGetter("size")!! val iteratorFunction = irClass.getMethodWithoutArguments("iterator") @@ -360,7 +365,7 @@ class IrListParcelSerializer(private val irClass: IrClass, private val elementSe }) +irWhile().apply { condition = irCall(iteratorHasNext).apply { dispatchReceiver = irGet(iterator) } - body = writeParcelWith(elementSerializer, parcel, flags, irCall(iteratorNext).apply { + body = writeParcelWith(elementSerializer, parcel, flags, irCall(iteratorNext.symbol, elementType).apply { dispatchReceiver = irGet(iterator) }) } @@ -415,6 +420,8 @@ class IrListParcelSerializer(private val irClass: IrClass, private val elementSe // Map uses LinkedHashMap, while NavigableMap and SortedMap use to TreeMap. class IrMapParcelSerializer( private val irClass: IrClass, + private val keyType: IrType, + private val valueType: IrType, private val keySerializer: IrParcelSerializer, private val valueSerializer: IrParcelSerializer ) : IrParcelSerializer { @@ -447,10 +454,10 @@ class IrMapParcelSerializer( val element = irTemporary(irCall(iteratorNext).apply { dispatchReceiver = irGet(iterator) }) - +writeParcelWith(keySerializer, parcel, flags, irCall(elementKey).apply { + +writeParcelWith(keySerializer, parcel, flags, irCall(elementKey, keyType).apply { dispatchReceiver = irGet(element) }) - +writeParcelWith(valueSerializer, parcel, flags, irCall(elementValue).apply { + +writeParcelWith(valueSerializer, parcel, flags, irCall(elementValue, valueType).apply { dispatchReceiver = irGet(element) }) } 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 6c42ae238c6..3cdd492e98e 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 @@ -194,11 +194,31 @@ public class ParcelBoxTestGenerated extends AbstractParcelBoxTest { runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/maps.kt"); } + @TestMetadata("nestedArrays.kt") + public void testNestedArrays() throws Exception { + runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedArrays.kt"); + } + + @TestMetadata("nestedLists.kt") + public void testNestedLists() throws Exception { + runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedLists.kt"); + } + + @TestMetadata("nestedMaps.kt") + public void testNestedMaps() throws Exception { + runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedMaps.kt"); + } + @TestMetadata("nestedParcelable.kt") public void testNestedParcelable() throws Exception { runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedParcelable.kt"); } + @TestMetadata("nestedSparseArrays.kt") + public void testNestedSparseArrays() throws Exception { + runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedSparseArrays.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 ea4b741c8cb..bc3e274353a 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 @@ -194,11 +194,31 @@ public class ParcelIrBoxTestGenerated extends AbstractParcelIrBoxTest { runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/maps.kt"); } + @TestMetadata("nestedArrays.kt") + public void testNestedArrays() throws Exception { + runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedArrays.kt"); + } + + @TestMetadata("nestedLists.kt") + public void testNestedLists() throws Exception { + runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedLists.kt"); + } + + @TestMetadata("nestedMaps.kt") + public void testNestedMaps() throws Exception { + runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedMaps.kt"); + } + @TestMetadata("nestedParcelable.kt") public void testNestedParcelable() throws Exception { runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedParcelable.kt"); } + @TestMetadata("nestedSparseArrays.kt") + public void testNestedSparseArrays() throws Exception { + runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedSparseArrays.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/nestedArrays.kt b/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedArrays.kt new file mode 100644 index 00000000000..e067cc1ac77 --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedArrays.kt @@ -0,0 +1,26 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.android.parcel.* +import android.os.Parcel +import android.os.Parcelable +import java.util.Arrays + +@Parcelize +class Data(val data: Array>) : Parcelable + +fun box() = parcelTest { parcel -> + val first = Data(arrayOf(arrayOf(0, 1))) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val second = readFromParcel(parcel) + assert(second.data.size == 1) + assert(Arrays.equals(second.data[0], arrayOf(0, 1))) +} diff --git a/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedLists.kt b/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedLists.kt new file mode 100644 index 00000000000..d80bc46427c --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedLists.kt @@ -0,0 +1,26 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.android.parcel.* +import android.os.Parcel +import android.os.Parcelable +import java.util.Arrays + +@Parcelize +class Data(val data: List>) : Parcelable + +fun box() = parcelTest { parcel -> + val first = Data(listOf(arrayOf(0, 1))) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val second = readFromParcel(parcel) + assert(second.data.size == 1) + assert(Arrays.equals(second.data[0], arrayOf(0, 1))) +} diff --git a/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedMaps.kt b/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedMaps.kt new file mode 100644 index 00000000000..2e8e9d128fe --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedMaps.kt @@ -0,0 +1,28 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.android.parcel.* +import android.os.Parcel +import android.os.Parcelable +import java.util.Arrays + +@Parcelize +class Data(val data: Map, Array>) : Parcelable + +fun box() = parcelTest { parcel -> + val first = Data(mapOf(arrayOf(0) to arrayOf(1))) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val second = readFromParcel(parcel) + assert(second.data.size == 1) + val entry = second.data.entries.single() + assert(Arrays.equals(entry.key, arrayOf(0))) + assert(Arrays.equals(entry.value, arrayOf(1))) +} diff --git a/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedSparseArrays.kt b/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedSparseArrays.kt new file mode 100644 index 00000000000..dbad6765e81 --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/nestedSparseArrays.kt @@ -0,0 +1,30 @@ +// WITH_RUNTIME +// IGNORE_BACKEND: JVM + +@file:JvmName("TestKt") +package test + +import kotlinx.android.parcel.* +import android.os.Parcel +import android.os.Parcelable +import android.util.SparseArray +import java.util.Arrays + +@Parcelize +class Data(val data: SparseArray>) : Parcelable + +fun box() = parcelTest { parcel -> + var array = SparseArray>() + array.append(0, arrayOf(0, 1)) + val first = Data(array) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val second = readFromParcel(parcel) + assert(second.data.size() == 1) + assert(Arrays.equals(second.data.get(0), arrayOf(0, 1))) +} 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 10d9662e5b7..4c8c7378be2 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 @@ -112,8 +112,6 @@ public final class test/Foo$Creator : java/lang/Object, android/os/Parcelable$Cr INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) NEW DUP - GETSTATIC (Companion, Lk/KotlinParcelable$Companion;) - POP GETSTATIC (CREATOR, Lk/KotlinParcelable$Creator;) ALOAD (1) INVOKEVIRTUAL (k/KotlinParcelable$Creator, createFromParcel, (Landroid/os/Parcel;)Lk/KotlinParcelable;) diff --git a/plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/listInsideList.ir.txt b/plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/listInsideList.ir.txt index 3d85188cabf..4541abfd5f6 100644 --- a/plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/listInsideList.ir.txt +++ b/plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/listInsideList.ir.txt @@ -44,14 +44,13 @@ public final class Test : java/lang/Object, android/os/Parcelable { IFEQ (L2) ALOAD (4) INVOKEINTERFACE (java/util/Iterator, next, ()Ljava/lang/Object;) + CHECKCAST ASTORE (5) ALOAD (1) ALOAD (5) - CHECKCAST INVOKEINTERFACE (java/util/List, size, ()I) INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) ALOAD (5) - CHECKCAST INVOKEINTERFACE (java/util/List, iterator, ()Ljava/util/Iterator;) ASTORE (6) LABEL (L3) diff --git a/plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/serializeValue.txt b/plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/serializeValue.txt index a20601f170a..a7cda06e23b 100644 --- a/plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/serializeValue.txt +++ b/plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/serializeValue.txt @@ -1,7 +1,7 @@ public final class Test$Creator : java/lang/Object, android/os/Parcelable$Creator { public void () - public final java.lang.Object createFromParcel(android.os.Parcel in) { + public final Test createFromParcel(android.os.Parcel in) { LABEL (L0) ALOAD (1) LDC (in) @@ -18,7 +18,18 @@ public final class Test$Creator : java/lang/Object, android/os/Parcelable$Creato LABEL (L1) } - public final java.lang.Object[] newArray(int size) + public java.lang.Object createFromParcel(android.os.Parcel p0) { + LABEL (L0) + LINENUMBER (10) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (Test$Creator, createFromParcel, (Landroid/os/Parcel;)LTest;) + ARETURN + } + + public final Test[] newArray(int size) + + public java.lang.Object[] newArray(int p0) } public final class Test : java/lang/Object, android/os/Parcelable { diff --git a/plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/simple.ir.txt b/plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/simple.ir.txt index 55fbbfbcbfb..54fb592573c 100644 --- a/plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/simple.ir.txt +++ b/plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/simple.ir.txt @@ -145,8 +145,14 @@ public final class User : java/lang/Object, android/os/Parcelable { ALOAD (1) ALOAD (0) GETFIELD (isProUser, Z) + IFEQ (L1) + ICONST_1 + GOTO (L2) + LABEL (L1) + ICONST_0 + LABEL (L2) INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) RETURN - LABEL (L1) + LABEL (L3) } }