diff --git a/plugins/parcelize/parcelize-compiler/build.gradle.kts b/plugins/parcelize/parcelize-compiler/build.gradle.kts index e2707729691..0a1ec1b4e06 100644 --- a/plugins/parcelize/parcelize-compiler/build.gradle.kts +++ b/plugins/parcelize/parcelize-compiler/build.gradle.kts @@ -65,6 +65,7 @@ dependencies { parcelizeRuntimeForTests(project(":plugins:parcelize:parcelize-runtime")) { isTransitive = false } parcelizeRuntimeForTests(project(":kotlin-android-extensions-runtime")) { isTransitive = false } + parcelizeRuntimeForTests(commonDependency("org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm")) { isTransitive = false } layoutLib("org.jetbrains.intellij.deps.android.tools:layoutlib:26.5.0") { isTransitive = false } layoutLibApi("com.android.tools.layoutlib:layoutlib-api:26.5.0") { isTransitive = false } diff --git a/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/AndroidSymbols.kt b/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/AndroidSymbols.kt index d2a03cdbec8..4c850b9c4f0 100644 --- a/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/AndroidSymbols.kt +++ b/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/AndroidSymbols.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.parcelize +import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext import org.jetbrains.kotlin.backend.common.ir.addExtensionReceiver import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.InlineClassRepresentation @@ -22,6 +23,7 @@ import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.createImplicitParameterDeclarationWithWrappedDescriptor import org.jetbrains.kotlin.ir.util.defaultType +import org.jetbrains.kotlin.name.CallableId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATE_FROM_PARCEL_NAME @@ -32,10 +34,11 @@ import org.jetbrains.kotlin.parcelize.ParcelizeNames.WRITE_TO_PARCEL_NAME // hence contain just enough information to produce correct JVM bytecode for *calls*. In particular, we omit generic types and // supertypes, which are not needed to produce correct bytecode. class AndroidSymbols( - val irBuiltIns: IrBuiltIns, - private val moduleFragment: IrModuleFragment + private val pluginContext: IrPluginContext, + private val moduleFragment: IrModuleFragment, ) { private val irFactory: IrFactory = IrFactoryImpl + val irBuiltIns: IrBuiltIns = pluginContext.irBuiltIns private val javaIo: IrPackageFragment = createPackage("java.io") private val javaLang: IrPackageFragment = createPackage("java.lang") @@ -499,6 +502,36 @@ class AndroidSymbols( isStatic = true }.symbol + private val kotlinxCollectionsImmutable = FqName(kotlinxImmutable()) + private val kotlinCollections = FqName("kotlin.collections") + private val kotlinIterable: FqName = kotlinCollections.child(Name.identifier("Iterable")) + private val kotlinMap: FqName = kotlinCollections.child(Name.identifier("Map")) + + private fun findKotlinxImmutableCollectionExtensionFunction( + receiver: FqName, + functionName: String, + ): IrSimpleFunctionSymbol { + val callableId = CallableId(kotlinxCollectionsImmutable, Name.identifier(functionName)) + return pluginContext.referenceFunctions(callableId) + .firstOrNull { + it.owner.extensionReceiverParameter?.type?.classFqName == receiver && + it.owner.valueParameters.isEmpty() + } + ?: error("Function from kotlinx.collections.immutable is not found on classpath: $callableId") + } + + val kotlinIterableToPersistentListExtension: IrSimpleFunctionSymbol by lazy { + findKotlinxImmutableCollectionExtensionFunction(kotlinIterable, "toPersistentList") + } + + val kotlinIterableToPersistentSetExtension: IrSimpleFunctionSymbol by lazy { + findKotlinxImmutableCollectionExtensionFunction(kotlinIterable, "toPersistentSet") + } + + val kotlinMapToPersistentMapExtension: IrSimpleFunctionSymbol by lazy { + findKotlinxImmutableCollectionExtensionFunction(kotlinMap, "toPersistentMap") + } + val unsafeCoerceIntrinsic: IrSimpleFunctionSymbol = irFactory.buildFun { name = Name.special("") diff --git a/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/IrParcelSerializerFactory.kt b/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/IrParcelSerializerFactory.kt index 1035a7bc3c3..728b2f35afd 100644 --- a/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/IrParcelSerializerFactory.kt +++ b/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/IrParcelSerializerFactory.kt @@ -15,6 +15,35 @@ import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.parcelize.ParcelizeNames.RAW_VALUE_ANNOTATION_FQ_NAMES class IrParcelSerializerFactory(private val symbols: AndroidSymbols) { + private val supportedBySimpleListSerializer = setOf( + "kotlin.collections.List", "kotlin.collections.MutableList", "kotlin.collections.ArrayList", + "java.util.List", "java.util.ArrayList", + *BuiltinParcelableTypes.IMMUTABLE_LIST_FQNAMES.toTypedArray() + ) + + // TODO: More java collections? + // TODO: Add tests for all of these types, not just some common ones... + private val supportedByListSerializer = setOf( + "kotlin.collections.MutableList", "kotlin.collections.List", "java.util.List", + "kotlin.collections.ArrayList", "java.util.ArrayList", + "kotlin.collections.ArrayDeque", "java.util.ArrayDeque", + "kotlin.collections.MutableSet", "kotlin.collections.Set", "java.util.Set", + "kotlin.collections.HashSet", "java.util.HashSet", + "kotlin.collections.LinkedHashSet", "java.util.LinkedHashSet", + "java.util.NavigableSet", "java.util.SortedSet", + *BuiltinParcelableTypes.IMMUTABLE_LIST_FQNAMES.toTypedArray(), + *BuiltinParcelableTypes.IMMUTABLE_SET_FQNAMES.toTypedArray(), + ) + + private val supportedByMapSerializer = setOf( + "kotlin.collections.MutableMap", "kotlin.collections.Map", "java.util.Map", + "kotlin.collections.HashMap", "java.util.HashMap", + "kotlin.collections.LinkedHashMap", "java.util.LinkedHashMap", + "java.util.SortedMap", "java.util.NavigableMap", "java.util.TreeMap", + "java.util.concurrent.ConcurrentHashMap", + *BuiltinParcelableTypes.IMMUTABLE_MAP_FQNAMES.toTypedArray(), + ) + /** * Resolve the given [irType] to a corresponding [IrParcelSerializer]. This depends on the TypeParcelers which * are currently in [scope], as well as the type of the enclosing Parceleable class [parcelizeType], which is needed @@ -192,43 +221,56 @@ class IrParcelSerializerFactory(private val symbols: AndroidSymbols) { ) } - // TODO: More java collections? - // TODO: Add tests for all of these types, not just some common ones... - // FIXME: Is the support for ArrayDeque missing in the old BE? - "kotlin.collections.MutableList", "kotlin.collections.List", "java.util.List", - "kotlin.collections.ArrayList", "java.util.ArrayList", - "kotlin.collections.ArrayDeque", "java.util.ArrayDeque", - "kotlin.collections.MutableSet", "kotlin.collections.Set", "java.util.Set", - "kotlin.collections.HashSet", "java.util.HashSet", - "kotlin.collections.LinkedHashSet", "java.util.LinkedHashSet", - "java.util.NavigableSet", "java.util.SortedSet" -> { + in supportedByListSerializer -> { val elementType = (irType as IrSimpleType).arguments.single().upperBound(irBuiltIns) - if (!scope.hasCustomSerializer(elementType) && classifierFqName in setOf( - "kotlin.collections.List", "kotlin.collections.MutableList", "kotlin.collections.ArrayList", - "java.util.List", "java.util.ArrayList" - ) + if (!scope.hasCustomSerializer(elementType) && + classifierFqName in supportedBySimpleListSerializer ) { - when (elementType.erasedUpperBound.fqNameWhenAvailable?.asString()) { - "android.os.IBinder" -> - return iBinderListSerializer - "kotlin.String", "java.lang.String" -> - return stringListSerializer + val elementTypeAsString = elementType.erasedUpperBound.fqNameWhenAvailable?.asString() + val simpleSerializer = + if (classifierFqName in BuiltinParcelableTypes.IMMUTABLE_LIST_FQNAMES) { + when (elementTypeAsString) { + "android.os.IBinder" -> iBinderPersistentListSerializer + "kotlin.String", "java.lang.String" -> stringPersistentListSerializer + else -> null + } + } else { + when (elementTypeAsString) { + "android.os.IBinder" -> iBinderListSerializer + "kotlin.String", "java.lang.String" -> stringListSerializer + else -> null + } + } + + if (simpleSerializer != null) { + return simpleSerializer } } + + val listSerializer = IrListParcelSerializer(classifier, elementType, get(elementType, scope, parcelizeType, strict())) + val actualSerializer = + when (classifierFqName) { + in BuiltinParcelableTypes.IMMUTABLE_LIST_FQNAMES -> IrExtensionFunctionOnReadCallingSerializer( + delegated = listSerializer, + converterExtensionFunction = symbols.kotlinIterableToPersistentListExtension + ) + in BuiltinParcelableTypes.IMMUTABLE_SET_FQNAMES -> IrExtensionFunctionOnReadCallingSerializer( + delegated = listSerializer, + converterExtensionFunction = symbols.kotlinIterableToPersistentSetExtension + ) + else -> listSerializer + } + return wrapNullableSerializerIfNeeded( irType, - IrListParcelSerializer(classifier, elementType, get(elementType, scope, parcelizeType, strict())) + actualSerializer ) } - "kotlin.collections.MutableMap", "kotlin.collections.Map", "java.util.Map", - "kotlin.collections.HashMap", "java.util.HashMap", - "kotlin.collections.LinkedHashMap", "java.util.LinkedHashMap", - "java.util.SortedMap", "java.util.NavigableMap", "java.util.TreeMap", - "java.util.concurrent.ConcurrentHashMap" -> { + in supportedByMapSerializer -> { val keyType = (irType as IrSimpleType).arguments[0].upperBound(irBuiltIns) val valueType = irType.arguments[1].upperBound(irBuiltIns) - val parceler = + val mapSerializer = IrMapParcelSerializer( classifier, keyType, @@ -236,7 +278,17 @@ class IrParcelSerializerFactory(private val symbols: AndroidSymbols) { get(keyType, scope, parcelizeType, strict()), get(valueType, scope, parcelizeType, strict()) ) - return wrapNullableSerializerIfNeeded(irType, parceler) + + val actualSerializer = + if (classifierFqName in BuiltinParcelableTypes.IMMUTABLE_MAP_FQNAMES) { + IrExtensionFunctionOnReadCallingSerializer( + mapSerializer, + symbols.kotlinMapToPersistentMapExtension + ) + } else { + mapSerializer + } + return wrapNullableSerializerIfNeeded(irType, actualSerializer) } } @@ -297,9 +349,21 @@ class IrParcelSerializerFactory(private val symbols: AndroidSymbols) { private val stringArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateStringArray, symbols.parcelWriteStringArray) private val stringListSerializer = IrSimpleParcelSerializer(symbols.parcelCreateStringArrayList, symbols.parcelWriteStringList) + private val stringPersistentListSerializer by lazy { + IrExtensionFunctionOnReadCallingSerializer( + delegated = stringListSerializer, + converterExtensionFunction = symbols.kotlinIterableToPersistentListExtension, + ) + } private val iBinderSerializer = IrSimpleParcelSerializer(symbols.parcelReadStrongBinder, symbols.parcelWriteStrongBinder) private val iBinderArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateBinderArray, symbols.parcelWriteBinderArray) private val iBinderListSerializer = IrSimpleParcelSerializer(symbols.parcelCreateBinderArrayList, symbols.parcelWriteBinderList) + private val iBinderPersistentListSerializer by lazy { + IrExtensionFunctionOnReadCallingSerializer( + delegated = iBinderListSerializer, + converterExtensionFunction = symbols.kotlinIterableToPersistentListExtension, + ) + } private val serializableSerializer = IrSimpleParcelSerializer(symbols.parcelReadSerializable, symbols.parcelWriteSerializable) private val stringSerializer = IrSimpleParcelSerializer(symbols.parcelReadString, symbols.parcelWriteString) private val byteSerializer = IrSimpleParcelSerializer(symbols.parcelReadByte, symbols.parcelWriteByte) diff --git a/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/IrParcelSerializers.kt b/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/IrParcelSerializers.kt index 8a51199e870..14d4a02391a 100644 --- a/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/IrParcelSerializers.kt +++ b/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/IrParcelSerializers.kt @@ -35,6 +35,20 @@ fun AndroidIrBuilder.writeParcelWith( return with(serializer) { writeParcel(parcel, flags, value) } } +class IrExtensionFunctionOnReadCallingSerializer( + private val delegated: IrParcelSerializer, + private val converterExtensionFunction: IrSimpleFunctionSymbol +) : IrParcelSerializer by delegated { + override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression { + val delegatedResult = with(delegated) { + readParcel(parcel) + } + return irCall(converterExtensionFunction).apply { + extensionReceiver = delegatedResult + } + } +} + // Creates a serializer from a pair of parcel methods of the form reader()T and writer(T)V. class IrSimpleParcelSerializer(private val reader: IrSimpleFunctionSymbol, private val writer: IrSimpleFunctionSymbol) : IrParcelSerializer { @@ -412,7 +426,12 @@ class IrListParcelSerializer( } } - private fun listSymbols(symbols: AndroidSymbols): Pair { + data class ListSymbols( + val constructor: IrConstructorSymbol, + val function: IrSimpleFunctionSymbol, + ) + + private fun listSymbols(symbols: AndroidSymbols): ListSymbols { // If the IrClass refers to a concrete type, try to find a constructor with capacity or fall back // the the default constructor if none exist. if (!irClass.isJvmInterface) { @@ -424,16 +443,31 @@ class IrListParcelSerializer( function.name.asString() == "add" && function.valueParameters.size == 1 } - return constructor.symbol to add.symbol + return ListSymbols( + constructor = constructor.symbol, + function = add.symbol + ) } return when (irClass.fqNameWhenAvailable?.asString()) { - "kotlin.collections.MutableList", "kotlin.collections.List", "java.util.List" -> - symbols.arrayListConstructor to symbols.arrayListAdd - "kotlin.collections.MutableSet", "kotlin.collections.Set", "java.util.Set" -> - symbols.linkedHashSetConstructor to symbols.linkedHashSetAdd - "java.util.NavigableSet", "java.util.SortedSet" -> - symbols.treeSetConstructor to symbols.treeSetAdd + "kotlin.collections.MutableList", + "kotlin.collections.List", + "java.util.List", + in BuiltinParcelableTypes.IMMUTABLE_LIST_FQNAMES -> ListSymbols( + constructor = symbols.arrayListConstructor, + function = symbols.arrayListAdd + ) + "kotlin.collections.MutableSet", + "kotlin.collections.Set", + "java.util.Set", + in BuiltinParcelableTypes.IMMUTABLE_SET_FQNAMES -> ListSymbols( + constructor = symbols.linkedHashSetConstructor, + function = symbols.linkedHashSetAdd + ) + "java.util.NavigableSet", "java.util.SortedSet" -> ListSymbols( + constructor = symbols.treeSetConstructor, + function = symbols.treeSetAdd + ) else -> error("Unknown list interface type: ${irClass.render()}") } } @@ -506,7 +540,12 @@ class IrMapParcelSerializer( } } - private fun mapSymbols(symbols: AndroidSymbols): Pair { + data class MapSymbols( + val constructor: IrConstructorSymbol, + val function: IrSimpleFunctionSymbol, + ) + + private fun mapSymbols(symbols: AndroidSymbols): MapSymbols { // If the IrClass refers to a concrete type, try to find a constructor with capacity or fall back // the the default constructor if none exist. if (!irClass.isJvmInterface) { @@ -520,14 +559,25 @@ class IrMapParcelSerializer( function.name.asString() == "put" && function.valueParameters.size == 2 } - return constructor.symbol to put.symbol + return MapSymbols( + constructor = constructor.symbol, + function = put.symbol, + ) } return when (irClass.fqNameWhenAvailable?.asString()) { - "kotlin.collections.MutableMap", "kotlin.collections.Map", "java.util.Map" -> - symbols.linkedHashMapConstructor to symbols.linkedHashMapPut - "java.util.SortedMap", "java.util.NavigableMap" -> - symbols.treeMapConstructor to symbols.treeMapPut + "kotlin.collections.MutableMap", + "kotlin.collections.Map", + "java.util.Map", + in BuiltinParcelableTypes.IMMUTABLE_MAP_FQNAMES -> MapSymbols( + constructor = symbols.linkedHashMapConstructor, + function = symbols.linkedHashMapPut, + ) + "java.util.SortedMap", + "java.util.NavigableMap" -> MapSymbols( + constructor = symbols.treeMapConstructor, + function = symbols.treeMapPut + ) else -> error("Unknown map interface type: ${irClass.render()}") } } diff --git a/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/ParcelizeFirIrGeneratorExtension.kt b/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/ParcelizeFirIrGeneratorExtension.kt index 02f11e6a33b..3d80d896eed 100644 --- a/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/ParcelizeFirIrGeneratorExtension.kt +++ b/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/ParcelizeFirIrGeneratorExtension.kt @@ -11,7 +11,7 @@ import org.jetbrains.kotlin.ir.declarations.IrModuleFragment class ParcelizeFirIrGeneratorExtension : IrGenerationExtension { override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { - val androidSymbols = AndroidSymbols(pluginContext.irBuiltIns, moduleFragment) + val androidSymbols = AndroidSymbols(pluginContext, moduleFragment) ParcelizeFirIrTransformer(pluginContext, androidSymbols).transform(moduleFragment) } } diff --git a/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/ParcelizeIrGeneratorExtension.kt b/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/ParcelizeIrGeneratorExtension.kt index fda849cbbb9..2098b99c288 100644 --- a/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/ParcelizeIrGeneratorExtension.kt +++ b/plugins/parcelize/parcelize-compiler/parcelize.backend/src/org/jetbrains/kotlin/parcelize/ParcelizeIrGeneratorExtension.kt @@ -11,7 +11,7 @@ import org.jetbrains.kotlin.ir.declarations.IrModuleFragment class ParcelizeIrGeneratorExtension : IrGenerationExtension { override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { - val androidSymbols = AndroidSymbols(pluginContext.irBuiltIns, moduleFragment) + val androidSymbols = AndroidSymbols(pluginContext, moduleFragment) ParcelizeIrTransformer(pluginContext, androidSymbols).transform(moduleFragment) } } diff --git a/plugins/parcelize/parcelize-compiler/parcelize.common/src/org/jetbrains/kotlin/parcelize/BuiltinParcelableTypes.kt b/plugins/parcelize/parcelize-compiler/parcelize.common/src/org/jetbrains/kotlin/parcelize/BuiltinParcelableTypes.kt index 183a1ba9cb9..d4ff18b1985 100644 --- a/plugins/parcelize/parcelize-compiler/parcelize.common/src/org/jetbrains/kotlin/parcelize/BuiltinParcelableTypes.kt +++ b/plugins/parcelize/parcelize-compiler/parcelize.common/src/org/jetbrains/kotlin/parcelize/BuiltinParcelableTypes.kt @@ -15,6 +15,27 @@ package org.jetbrains.kotlin.parcelize * reflection, as well as all objects, enums, and function types (since they are implicitly serializable). */ object BuiltinParcelableTypes { + val IMMUTABLE_LIST_FQNAMES = setOf( + kotlinxImmutable("PersistentList"), + kotlinxImmutable("ImmutableList"), + ) + + val IMMUTABLE_SET_FQNAMES = setOf( + kotlinxImmutable("PersistentSet"), + kotlinxImmutable("ImmutableSet"), + ) + + val IMMUTABLE_MAP_FQNAMES = setOf( + kotlinxImmutable("PersistentMap"), + kotlinxImmutable("ImmutableMap"), + ) + + val IMMUTABLE_COLLECTIONS_FQNAMES = setOf( + *IMMUTABLE_LIST_FQNAMES.toTypedArray(), + *IMMUTABLE_SET_FQNAMES.toTypedArray(), + *IMMUTABLE_MAP_FQNAMES.toTypedArray() + ) + val PARCELABLE_SUPERTYPE_FQNAMES = setOf( "android.os.Parcelable", "android.os.IBinder", @@ -97,5 +118,6 @@ object BuiltinParcelableTypes { "kotlin.collections.MutableMap", "kotlin.collections.MutableSet", "kotlin.collections.Set", + *IMMUTABLE_COLLECTIONS_FQNAMES.toTypedArray(), ) } diff --git a/plugins/parcelize/parcelize-compiler/parcelize.common/src/org/jetbrains/kotlin/parcelize/kotlinxImmutable.kt b/plugins/parcelize/parcelize-compiler/parcelize.common/src/org/jetbrains/kotlin/parcelize/kotlinxImmutable.kt new file mode 100644 index 00000000000..df87e732949 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/parcelize.common/src/org/jetbrains/kotlin/parcelize/kotlinxImmutable.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.parcelize + +/** + * Required because :kotlin-compiler-embeddable performs package relocation + * If there's a "kotlinx.collections.immutable" string literal in bytecode + * it becomes "org.jetbrains.kotlin.kotlinx.collections.immutable" thus + * breaking target project class name matching + */ +fun kotlinxImmutable(name: String? = null): String { + return listOfNotNull("kotlinx", "collections", "immutable", name).joinToString(".") +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/parcelize.k1/src/org/jetbrains/kotlin/parcelize/serializers/ParcelSerializer.kt b/plugins/parcelize/parcelize-compiler/parcelize.k1/src/org/jetbrains/kotlin/parcelize/serializers/ParcelSerializer.kt index 822bac538a3..5fa84509fe5 100644 --- a/plugins/parcelize/parcelize-compiler/parcelize.k1/src/org/jetbrains/kotlin/parcelize/serializers/ParcelSerializer.kt +++ b/plugins/parcelize/parcelize-compiler/parcelize.k1/src/org/jetbrains/kotlin/parcelize/serializers/ParcelSerializer.kt @@ -44,7 +44,7 @@ interface ParcelSerializer { val frameMap: FrameMap ) { fun findParcelerClass(type: KotlinType): KotlinType? { - return typeParcelers.firstOrNull { it.first == type }?.second + return typeParcelers.firstOrNull { it.mappedType == type }?.parcelerType } } diff --git a/plugins/parcelize/parcelize-compiler/parcelize.k1/src/org/jetbrains/kotlin/parcelize/serializers/TypeUtils.kt b/plugins/parcelize/parcelize-compiler/parcelize.k1/src/org/jetbrains/kotlin/parcelize/serializers/TypeUtils.kt index bca35a2a25e..357815fd502 100644 --- a/plugins/parcelize/parcelize-compiler/parcelize.k1/src/org/jetbrains/kotlin/parcelize/serializers/TypeUtils.kt +++ b/plugins/parcelize/parcelize-compiler/parcelize.k1/src/org/jetbrains/kotlin/parcelize/serializers/TypeUtils.kt @@ -10,7 +10,10 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils -typealias TypeParcelerMapping = Pair +data class TypeParcelerMapping( + val mappedType: KotlinType, + val parcelerType: KotlinType, +) fun KotlinType.isParcelable() = matchesFqNameWithSupertypes(ParcelizeNames.PARCELABLE_FQN.asString()) diff --git a/plugins/parcelize/parcelize-compiler/testData/box/listKinds.kt b/plugins/parcelize/parcelize-compiler/testData/box/listKinds.kt index a818f55936e..c267479bc88 100644 --- a/plugins/parcelize/parcelize-compiler/testData/box/listKinds.kt +++ b/plugins/parcelize/parcelize-compiler/testData/box/listKinds.kt @@ -8,6 +8,7 @@ import kotlinx.parcelize.* import android.os.Parcel import android.os.Parcelable import java.util.* +import kotlinx.collections.immutable.* @Parcelize data class Test( @@ -21,7 +22,11 @@ data class Test( val h: HashSet, val i: LinkedHashSet, val j: NavigableSet, - val k: SortedSet + val k: SortedSet, + val l: PersistentList, + val m: PersistentSet, + val n: ImmutableList, + val o: ImmutableSet, ) : Parcelable fun box() = parcelTest { parcel -> @@ -36,7 +41,11 @@ fun box() = parcelTest { parcel -> h = HashSet().apply { this += "H" }, i = LinkedHashSet().apply { this += "I" }, j = TreeSet().apply { this += "J" }, - k = TreeSet().apply { this += "K" } + k = TreeSet().apply { this += "K" }, + l = persistentListOf("L"), + m = persistentSetOf("M"), + n = persistentListOf("N"), + o = persistentSetOf("O"), ) first.writeToParcel(parcel, 0) diff --git a/plugins/parcelize/parcelize-compiler/testData/box/listSimplePersistent.kt b/plugins/parcelize/parcelize-compiler/testData/box/listSimplePersistent.kt new file mode 100644 index 00000000000..b1b5883c914 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/listSimplePersistent.kt @@ -0,0 +1,27 @@ +// WITH_STDLIB + +@file:JvmName("TestKt") + +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import kotlinx.collections.immutable.* + +@Parcelize +data class Test(val a: PersistentList) : Parcelable + +fun box() = parcelTest { parcel -> + val first = Test(persistentListOf("A", "B")) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val first2 = parcelableCreator().createFromParcel(parcel) + + assert(first == first2) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/mapKinds.kt b/plugins/parcelize/parcelize-compiler/testData/box/mapKinds.kt index 474b9d2cb47..ef00022e198 100644 --- a/plugins/parcelize/parcelize-compiler/testData/box/mapKinds.kt +++ b/plugins/parcelize/parcelize-compiler/testData/box/mapKinds.kt @@ -8,6 +8,7 @@ import kotlinx.parcelize.* import android.os.Parcel import android.os.Parcelable import java.util.* +import kotlinx.collections.immutable.* @Parcelize data class Test( @@ -17,7 +18,9 @@ data class Test( val d: LinkedHashMap, val e: TreeMap, val f: SortedMap, - val g: NavigableMap + val g: NavigableMap, + val h: PersistentMap, + val i: ImmutableMap, ) : Parcelable fun box() = parcelTest { parcel -> @@ -28,7 +31,9 @@ fun box() = parcelTest { parcel -> d = LinkedHashMap().apply { put("A", "B") }, e = TreeMap().apply { put("A", "B") }, f = TreeMap().apply { put("A", "B") }, - g = TreeMap().apply { put("A", "B") } + g = TreeMap().apply { put("A", "B") }, + h = persistentMapOf("A" to "B"), + i = persistentMapOf("A" to "B"), ) first.writeToParcel(parcel, 0) diff --git a/plugins/parcelize/parcelize-compiler/testData/box/mapSimplePersistent.kt b/plugins/parcelize/parcelize-compiler/testData/box/mapSimplePersistent.kt new file mode 100644 index 00000000000..a387edf63cd --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/mapSimplePersistent.kt @@ -0,0 +1,27 @@ +// WITH_STDLIB + +@file:JvmName("TestKt") + +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import kotlinx.collections.immutable.* + +@Parcelize +data class Test(val a: PersistentMap) : Parcelable + +fun box() = parcelTest { parcel -> + val first = Test(persistentMapOf("A" to "B", "C" to "D")) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val first2 = parcelableCreator().createFromParcel(parcel) + + assert(first == first2) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/simplePersistentList.asm.ir.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/simplePersistentList.asm.ir.txt new file mode 100644 index 00000000000..74a885a2e33 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/simplePersistentList.asm.ir.txt @@ -0,0 +1,48 @@ +public final class Test$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final Test createFromParcel(android.os.Parcel parcel) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + NEW (Test) + DUP + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, createStringArrayList, ()Ljava/util/ArrayList;) + CHECKCAST (java/lang/Iterable) + INVOKESTATIC (kotlinx/collections/immutable/ExtensionsKt, toPersistentList, (Ljava/lang/Iterable;)Lkotlinx/collections/immutable/PersistentList;) + INVOKESPECIAL (Test, , (Lkotlinx/collections/immutable/PersistentList;)V) + ARETURN + LABEL (L1) + } + + public java.lang.Object createFromParcel(android.os.Parcel source) { + LABEL (L0) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (Test$Creator, createFromParcel, (Landroid/os/Parcel;)LTest;) + ARETURN + LABEL (L1) + } + + public final Test[] newArray(int size) + + public java.lang.Object[] newArray(int size) +} + +public final class Test : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final kotlinx.collections.immutable.PersistentList names + + static void () + + public void (kotlinx.collections.immutable.PersistentList names) + + public int describeContents() + + public final kotlinx.collections.immutable.PersistentList getNames() + + public void writeToParcel(android.os.Parcel out, int flags) +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/simplePersistentList.kt b/plugins/parcelize/parcelize-compiler/testData/codegen/simplePersistentList.kt new file mode 100644 index 00000000000..058617e33cc --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/simplePersistentList.kt @@ -0,0 +1,9 @@ +// CURIOUS_ABOUT: createFromParcel +// WITH_STDLIB + +import kotlinx.parcelize.* +import android.os.Parcelable +import kotlinx.collections.immutable.* + +@Parcelize +class Test(val names: PersistentList): Parcelable 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 da48f006c38..8b245aad7ab 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 @@ -283,6 +283,12 @@ public class ParcelizeFirLightTreeBoxTestGenerated extends AbstractParcelizeFirL runTest("plugins/parcelize/parcelize-compiler/testData/box/listSimple.kt"); } + @Test + @TestMetadata("listSimplePersistent.kt") + public void testListSimplePersistent() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/listSimplePersistent.kt"); + } + @Test @TestMetadata("lists.kt") public void testLists() throws Exception { @@ -301,6 +307,12 @@ public class ParcelizeFirLightTreeBoxTestGenerated extends AbstractParcelizeFirL runTest("plugins/parcelize/parcelize-compiler/testData/box/mapSimple.kt"); } + @Test + @TestMetadata("mapSimplePersistent.kt") + public void testMapSimplePersistent() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/mapSimplePersistent.kt"); + } + @Test @TestMetadata("maps.kt") public void testMaps() 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 4c38631b238..6191eddd807 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 @@ -283,6 +283,12 @@ public class ParcelizeIrBoxTestGenerated extends AbstractParcelizeIrBoxTest { runTest("plugins/parcelize/parcelize-compiler/testData/box/listSimple.kt"); } + @Test + @TestMetadata("listSimplePersistent.kt") + public void testListSimplePersistent() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/listSimplePersistent.kt"); + } + @Test @TestMetadata("lists.kt") public void testLists() throws Exception { @@ -301,6 +307,12 @@ public class ParcelizeIrBoxTestGenerated extends AbstractParcelizeIrBoxTest { runTest("plugins/parcelize/parcelize-compiler/testData/box/mapSimple.kt"); } + @Test + @TestMetadata("mapSimplePersistent.kt") + public void testMapSimplePersistent() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/mapSimplePersistent.kt"); + } + @Test @TestMetadata("maps.kt") public void testMaps() throws Exception { diff --git a/plugins/parcelize/parcelize-compiler/tests-gen/org/jetbrains/kotlin/parcelize/test/runners/ParcelizeIrBytecodeListingTestGenerated.java b/plugins/parcelize/parcelize-compiler/tests-gen/org/jetbrains/kotlin/parcelize/test/runners/ParcelizeIrBytecodeListingTestGenerated.java index 1558f7e17ce..20fd7acbdaf 100644 --- a/plugins/parcelize/parcelize-compiler/tests-gen/org/jetbrains/kotlin/parcelize/test/runners/ParcelizeIrBytecodeListingTestGenerated.java +++ b/plugins/parcelize/parcelize-compiler/tests-gen/org/jetbrains/kotlin/parcelize/test/runners/ParcelizeIrBytecodeListingTestGenerated.java @@ -151,6 +151,12 @@ public class ParcelizeIrBytecodeListingTestGenerated extends AbstractParcelizeIr runTest("plugins/parcelize/parcelize-compiler/testData/codegen/simpleList.kt"); } + @Test + @TestMetadata("simplePersistentList.kt") + public void testSimplePersistentList() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/simplePersistentList.kt"); + } + @Test @TestMetadata("size.kt") public void testSize() throws Exception { diff --git a/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/services/ParcelizeEnvironmentConfigurator.kt b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/services/ParcelizeEnvironmentConfigurator.kt index 7ccd3f35ee7..ee39cacb77a 100644 --- a/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/services/ParcelizeEnvironmentConfigurator.kt +++ b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/services/ParcelizeEnvironmentConfigurator.kt @@ -9,6 +9,7 @@ import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.parcelize.ParcelizeComponentRegistrar +import org.jetbrains.kotlin.parcelize.kotlinxImmutable import org.jetbrains.kotlin.test.model.FrontendKinds import org.jetbrains.kotlin.test.model.TestModule import org.jetbrains.kotlin.test.services.EnvironmentConfigurator @@ -17,12 +18,29 @@ import org.jetbrains.kotlin.test.util.KtTestUtil import org.jetbrains.kotlin.utils.PathUtil import java.io.File +private fun getLibraryJar(classToDetect: String): File? = try { + PathUtil.getResourcePathForClass(Class.forName(classToDetect)) +} catch (e: ClassNotFoundException) { + null +} + class ParcelizeEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigurator(testServices) { override fun configureCompilerConfiguration(configuration: CompilerConfiguration, module: TestModule) { - val runtimeLibrary = File(PathUtil.kotlinPathsForCompiler.libPath, PathUtil.PARCELIZE_RUNTIME_PLUGIN_JAR_NAME) - val androidExtensionsRuntimeLibrary = File(PathUtil.kotlinPathsForCompiler.libPath, PathUtil.ANDROID_EXTENSIONS_RUNTIME_PLUGIN_JAR_NAME) + val libPath = PathUtil.kotlinPathsForCompiler.libPath + val runtimeLibrary = File(libPath, PathUtil.PARCELIZE_RUNTIME_PLUGIN_JAR_NAME) + val androidExtensionsRuntimeLibrary = File(libPath, PathUtil.ANDROID_EXTENSIONS_RUNTIME_PLUGIN_JAR_NAME) val androidApiJar = KtTestUtil.findAndroidApiJar() - configuration.addJvmClasspathRoots(listOf(runtimeLibrary, androidExtensionsRuntimeLibrary, androidApiJar)) + val kotlinxCollectionsImmutable = getLibraryJar(kotlinxImmutable("ImmutableList")) + ?: error("kotlinx-collections-immutable is not found on classpath") + + configuration.addJvmClasspathRoots( + listOf( + runtimeLibrary, + androidExtensionsRuntimeLibrary, + androidApiJar, + kotlinxCollectionsImmutable + ) + ) } override fun CompilerPluginRegistrar.ExtensionStorage.registerCompilerExtensions(