Support kotlinx.collections.immutable in kotlin-parcelize plugin
#KT-57685 Fixed Co-authored-by: Ilya Gulya <ilyagulya@gmail.com>
This commit is contained in:
+35
-2
@@ -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>")
|
||||
|
||||
+91
-27
@@ -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)
|
||||
|
||||
+64
-14
@@ -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()}")
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user