From 669ac1ae30adb37736d9289a67ba2f0f380bea81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Sch=C3=A4fer?= Date: Fri, 24 Feb 2023 13:38:28 +0100 Subject: [PATCH] Parcelize: Handle nullable sparse arrays Fixes https://issuetracker.google.com/269956252 --- .../parcelize/ir/IrParcelSerializerFactory.kt | 42 ++++-- .../testData/box/nullableSparseArrays.kt | 128 ++++++++++++++++++ .../runners/ParcelizeBoxTestGenerated.java | 6 + ...ParcelizeFirLightTreeBoxTestGenerated.java | 6 + .../runners/ParcelizeIrBoxTestGenerated.java | 6 + 5 files changed, 175 insertions(+), 13 deletions(-) create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/nullableSparseArrays.kt diff --git a/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/ir/IrParcelSerializerFactory.kt b/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/ir/IrParcelSerializerFactory.kt index 652a96a01f4..04cebff0625 100644 --- a/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/ir/IrParcelSerializerFactory.kt +++ b/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/ir/IrParcelSerializerFactory.kt @@ -154,26 +154,42 @@ class IrParcelSerializerFactory(private val symbols: AndroidSymbols) { // This will only be hit if we have a custom serializer for booleans "android.util.SparseBooleanArray" -> - return IrSparseArrayParcelSerializer( - classifier, - irBuiltIns.booleanType, - get(irBuiltIns.booleanType, scope, parcelizeType, strict()) + return wrapNullableSerializerIfNeeded( + irType, + IrSparseArrayParcelSerializer( + classifier, + irBuiltIns.booleanType, + get(irBuiltIns.booleanType, scope, parcelizeType, strict()) + ) ) "android.util.SparseIntArray" -> - return IrSparseArrayParcelSerializer( - classifier, - irBuiltIns.intType, - get(irBuiltIns.intType, scope, parcelizeType, strict()) + return wrapNullableSerializerIfNeeded( + irType, + IrSparseArrayParcelSerializer( + classifier, + irBuiltIns.intType, + get(irBuiltIns.intType, scope, parcelizeType, strict()) + ) ) "android.util.SparseLongArray" -> - return IrSparseArrayParcelSerializer( - classifier, - irBuiltIns.longType, - get(irBuiltIns.longType, scope, parcelizeType, strict()) + return wrapNullableSerializerIfNeeded( + irType, + IrSparseArrayParcelSerializer( + classifier, + irBuiltIns.longType, + get(irBuiltIns.longType, scope, parcelizeType, strict()) + ) ) "android.util.SparseArray" -> { val elementType = (irType as IrSimpleType).arguments.single().upperBound(irBuiltIns) - return IrSparseArrayParcelSerializer(classifier, elementType, get(elementType, scope, parcelizeType, strict())) + return wrapNullableSerializerIfNeeded( + irType, + IrSparseArrayParcelSerializer( + classifier, + elementType, + get(elementType, scope, parcelizeType, strict()) + ) + ) } // TODO: More java collections? diff --git a/plugins/parcelize/parcelize-compiler/testData/box/nullableSparseArrays.kt b/plugins/parcelize/parcelize-compiler/testData/box/nullableSparseArrays.kt new file mode 100644 index 00000000000..58b62a28294 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/nullableSparseArrays.kt @@ -0,0 +1,128 @@ +// IGNORE_BACKEND: JVM +// WITH_STDLIB + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import android.util.* + +object TrueParceler: Parceler { + override fun create(parcel: Parcel) = true + override fun Boolean.write(parcel: Parcel, flags: Int) {} +} + +@Parcelize +data class Data(val a: String, val b: String) : Parcelable + +@Parcelize +class Box( + val a: SparseBooleanArray?, + val b: SparseIntArray?, + val c: SparseLongArray?, + val d: SparseArray?, + val e: SparseArray? +) : Parcelable + +@Parcelize +@TypeParceler +class TrueBox(val value: SparseBooleanArray?) : Parcelable + +fun box() = parcelTest { parcel -> + val boxNonNull = Box( + SparseBooleanArray().apply { put(1, true); put(100, false) }, + SparseIntArray().apply { put(1, 5); put(100, -1); put(1000, 0) }, + SparseLongArray().apply { put(3, 2); put(2, 3); put(10, 10) }, + SparseArray().apply { put(1, Data("A", "B")); put(10, Data("C", "D")); put(105, Data("E", "")) }, + SparseArray().apply { put(1, Data("A", "B")); put(10, null) } + ) + val boxNull = Box(null, null, null, null, null) + val trueBoxNonNull = TrueBox(SparseBooleanArray().apply { put(1, false) }) + val trueBoxNull = TrueBox(null) + + boxNonNull.writeToParcel(parcel, 0) + boxNull.writeToParcel(parcel, 0) + trueBoxNonNull.writeToParcel(parcel, 0) + trueBoxNull.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val boxNonNull2 = parcelableCreator().createFromParcel(parcel) + assert(compareBoxes(boxNonNull, boxNonNull2)) + + val boxNull2 = parcelableCreator().createFromParcel(parcel) + assert(compareBoxes(boxNull, boxNull2)) + + val trueSparseArray = parcelableCreator().createFromParcel(parcel).value + assert(trueSparseArray != null) + assert(trueSparseArray?.size() == 1) + assert(trueSparseArray?.keyAt(0) == 1) + assert(trueSparseArray?.valueAt(0) == true) + + val trueBoxNull2 = parcelableCreator().createFromParcel(parcel) + assert(trueBoxNull2.value == null) +} + +private fun compareBoxes(first: Box, second: Box): Boolean { + if (!compareSparseBooleanArrays(first.a, second.a)) return false + if (!compareSparseIntArrays(first.b, second.b)) return false + if (!compareSparseLongArrays(first.c, second.c)) return false + if (!compareSparseArrays(first.d, second.d)) return false + return compareSparseArrays(first.e, second.e) +} + +private fun compareSparseBooleanArrays(first: SparseBooleanArray?, second: SparseBooleanArray?): Boolean { + if (first === second) return true + if (first == null || second == null) return false + if (first.size() != second.size()) return false + + for (i in 0 until first.size()) { + if (first.keyAt(i) != second.keyAt(i)) return false + if (first.valueAt(i) != second.valueAt(i)) return false + } + + return true +} + +private fun compareSparseIntArrays(first: SparseIntArray?, second: SparseIntArray?): Boolean { + if (first === second) return true + if (first == null || second == null) return false + if (first.size() != second.size()) return false + + for (i in 0 until first.size()) { + if (first.keyAt(i) != second.keyAt(i)) return false + if (first.valueAt(i) != second.valueAt(i)) return false + } + + return true +} + +private fun compareSparseLongArrays(first: SparseLongArray?, second: SparseLongArray?): Boolean { + if (first === second) return true + if (first == null || second == null) return false + if (first.size() != second.size()) return false + + for (i in 0 until first.size()) { + if (first.keyAt(i) != second.keyAt(i)) return false + if (first.valueAt(i) != second.valueAt(i)) return false + } + + return true +} + +private fun compareSparseArrays(first: SparseArray<*>?, second: SparseArray<*>?): Boolean { + if (first === second) return true + if (first == null || second == null) return false + if (first.size() != second.size()) return false + + for (i in 0 until first.size()) { + if (first.keyAt(i) != second.keyAt(i)) return false + if (first.valueAt(i) != second.valueAt(i)) return false + } + + return true +} diff --git a/plugins/parcelize/parcelize-compiler/tests-gen/org/jetbrains/kotlin/parcelize/test/runners/ParcelizeBoxTestGenerated.java b/plugins/parcelize/parcelize-compiler/tests-gen/org/jetbrains/kotlin/parcelize/test/runners/ParcelizeBoxTestGenerated.java index 97d627f8b13..117cbabc073 100644 --- a/plugins/parcelize/parcelize-compiler/tests-gen/org/jetbrains/kotlin/parcelize/test/runners/ParcelizeBoxTestGenerated.java +++ b/plugins/parcelize/parcelize-compiler/tests-gen/org/jetbrains/kotlin/parcelize/test/runners/ParcelizeBoxTestGenerated.java @@ -355,6 +355,12 @@ public class ParcelizeBoxTestGenerated extends AbstractParcelizeBoxTest { runTest("plugins/parcelize/parcelize-compiler/testData/box/newArrayParceler.kt"); } + @Test + @TestMetadata("nullableSparseArrays.kt") + public void testNullableSparseArrays() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/nullableSparseArrays.kt"); + } + @Test @TestMetadata("nullableTypes.kt") public void testNullableTypes() throws Exception { diff --git a/plugins/parcelize/parcelize-compiler/tests-gen/org/jetbrains/kotlin/parcelize/test/runners/ParcelizeFirLightTreeBoxTestGenerated.java b/plugins/parcelize/parcelize-compiler/tests-gen/org/jetbrains/kotlin/parcelize/test/runners/ParcelizeFirLightTreeBoxTestGenerated.java index c5fd87c469f..da48f006c38 100644 --- a/plugins/parcelize/parcelize-compiler/tests-gen/org/jetbrains/kotlin/parcelize/test/runners/ParcelizeFirLightTreeBoxTestGenerated.java +++ b/plugins/parcelize/parcelize-compiler/tests-gen/org/jetbrains/kotlin/parcelize/test/runners/ParcelizeFirLightTreeBoxTestGenerated.java @@ -355,6 +355,12 @@ public class ParcelizeFirLightTreeBoxTestGenerated extends AbstractParcelizeFirL runTest("plugins/parcelize/parcelize-compiler/testData/box/newArrayParceler.kt"); } + @Test + @TestMetadata("nullableSparseArrays.kt") + public void testNullableSparseArrays() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/nullableSparseArrays.kt"); + } + @Test @TestMetadata("nullableTypes.kt") public void testNullableTypes() throws Exception { diff --git a/plugins/parcelize/parcelize-compiler/tests-gen/org/jetbrains/kotlin/parcelize/test/runners/ParcelizeIrBoxTestGenerated.java b/plugins/parcelize/parcelize-compiler/tests-gen/org/jetbrains/kotlin/parcelize/test/runners/ParcelizeIrBoxTestGenerated.java index 75e06d74ae2..4c38631b238 100644 --- a/plugins/parcelize/parcelize-compiler/tests-gen/org/jetbrains/kotlin/parcelize/test/runners/ParcelizeIrBoxTestGenerated.java +++ b/plugins/parcelize/parcelize-compiler/tests-gen/org/jetbrains/kotlin/parcelize/test/runners/ParcelizeIrBoxTestGenerated.java @@ -355,6 +355,12 @@ public class ParcelizeIrBoxTestGenerated extends AbstractParcelizeIrBoxTest { runTest("plugins/parcelize/parcelize-compiler/testData/box/newArrayParceler.kt"); } + @Test + @TestMetadata("nullableSparseArrays.kt") + public void testNullableSparseArrays() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/nullableSparseArrays.kt"); + } + @Test @TestMetadata("nullableTypes.kt") public void testNullableTypes() throws Exception {