Support kotlinx.collections.immutable in kotlin-parcelize plugin

#KT-57685 Fixed

Co-authored-by: Ilya Gulya <ilyagulya@gmail.com>
This commit is contained in:
Ilya Gulya
2023-10-02 23:29:43 +00:00
committed by Space Team
parent e8853fd40b
commit f6b2c642c2
20 changed files with 416 additions and 54 deletions
@@ -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("<unsafe-coerce>")
@@ -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)
@@ -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<IrConstructorSymbol, IrSimpleFunctionSymbol> {
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<IrConstructorSymbol, IrSimpleFunctionSymbol> {
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()}")
}
}
@@ -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)
}
}
@@ -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)
}
}