[Parcelize] Allow parcelize to work on common code in multiplatform.

Since parcelize is Android specific, it should only be enabled for
Android compilation. In order to allow parcelize to generate code
for declarations in common code, we make checkers platform checkers
and we allow the registration of an additional annotations to
trigger parcelize processing.

That way, code such as:

```
package my.package

annotation class Parcelize

expect interface MyParcelable

@Parcelize
data class User(name: String): MyParcelable
```

Will work with an Android platform actual of the form:

```
actual typealias MyParcelable = android.os.Parcelable
```

And telling the plugin to trigger parcelize processing with the
additional annotation `my.package.Parcelize`.

Fixes https://issuetracker.google.com/315775835.
This commit is contained in:
Mads Ager
2024-02-26 14:29:55 +01:00
committed by Space Cloud
parent 5f971d6d9d
commit fa0d456850
26 changed files with 177 additions and 87 deletions
@@ -12,9 +12,10 @@ import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.declarations.inlineClassRepresentation
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.parcelize.ParcelizeNames.RAW_VALUE_ANNOTATION_FQ_NAMES
class IrParcelSerializerFactory(private val symbols: AndroidSymbols) {
class IrParcelSerializerFactory(private val symbols: AndroidSymbols, private val parcelizeAnnotations: List<FqName>) {
private val supportedBySimpleListSerializer = setOf(
"kotlin.collections.List", "kotlin.collections.MutableList", "kotlin.collections.ArrayList",
"java.util.List", "java.util.ArrayList",
@@ -313,7 +314,7 @@ class IrParcelSerializerFactory(private val symbols: AndroidSymbols) {
// @Parcelize), we'll have a field in the class itself. Finally, with Parcelable instances which were
// manually implemented in Kotlin, we'll instead have an @JvmField property getter in the companion object.
return if (classifier.modality == Modality.FINAL && classifier.psiElement != null
&& (classifier.isParcelize || classifier.hasCreatorField)
&& (classifier.isParcelize(parcelizeAnnotations) || classifier.hasCreatorField)
) {
wrapNullableSerializerIfNeeded(irType, IrEfficientParcelableParcelSerializer(classifier))
} else {
@@ -8,10 +8,11 @@ package org.jetbrains.kotlin.parcelize
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.name.FqName
class ParcelizeFirIrGeneratorExtension : IrGenerationExtension {
class ParcelizeFirIrGeneratorExtension(private val parcelizeAnnotations: List<FqName>) : IrGenerationExtension {
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
val androidSymbols = AndroidSymbols(pluginContext, moduleFragment)
ParcelizeFirIrTransformer(pluginContext, androidSymbols).transform(moduleFragment)
ParcelizeFirIrTransformer(pluginContext, androidSymbols, parcelizeAnnotations).transform(moduleFragment)
}
}
@@ -14,13 +14,15 @@ import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.util.companionObject
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELER_FQN
import org.jetbrains.kotlin.parcelize.fir.ParcelizePluginKey
class ParcelizeFirIrTransformer(
context: IrPluginContext,
androidSymbols: AndroidSymbols
) : ParcelizeIrTransformerBase(context, androidSymbols) {
androidSymbols: AndroidSymbols,
parcelizeAnnotations: List<FqName>
) : ParcelizeIrTransformerBase(context, androidSymbols, parcelizeAnnotations) {
fun transform(moduleFragment: IrModuleFragment) {
moduleFragment.accept(this, null)
@@ -34,7 +36,7 @@ class ParcelizeFirIrTransformer(
// Sealed classes can be annotated with `@Parcelize`, but that only implies that we
// should process their immediate subclasses.
if (!declaration.isParcelize || declaration.modality == Modality.SEALED)
if (!declaration.isParcelize(parcelizeAnnotations) || declaration.modality == Modality.SEALED)
return
val parcelableProperties = declaration.parcelableProperties
@@ -8,10 +8,12 @@ package org.jetbrains.kotlin.parcelize
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.name.FqName
class ParcelizeIrGeneratorExtension : IrGenerationExtension {
// This class is open so that the IDE integration can create a subclass with a fixed set of annotations.
open class ParcelizeIrGeneratorExtension(private val parcelizeAnnotations: List<FqName>) : IrGenerationExtension {
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
val androidSymbols = AndroidSymbols(pluginContext, moduleFragment)
ParcelizeIrTransformer(pluginContext, androidSymbols).transform(moduleFragment)
ParcelizeIrTransformer(pluginContext, androidSymbols, parcelizeAnnotations).transform(moduleFragment)
}
}
@@ -36,8 +36,9 @@ import org.jetbrains.kotlin.parcelize.ParcelizeNames.WRITE_TO_PARCEL_NAME
@OptIn(ObsoleteDescriptorBasedAPI::class)
class ParcelizeIrTransformer(
context: IrPluginContext,
androidSymbols: AndroidSymbols
) : ParcelizeIrTransformerBase(context, androidSymbols) {
androidSymbols: AndroidSymbols,
parcelizeAnnotations: List<FqName>
) : ParcelizeIrTransformerBase(context, androidSymbols, parcelizeAnnotations) {
private val symbolMap = mutableMapOf<IrSimpleFunctionSymbol, IrSimpleFunctionSymbol>()
fun transform(moduleFragment: IrModuleFragment) {
@@ -101,7 +102,7 @@ class ParcelizeIrTransformer(
// Sealed classes can be annotated with `@Parcelize`, but that only implies that we
// should process their immediate subclasses.
if (!declaration.isParcelize || declaration.modality == Modality.SEALED)
if (!declaration.isParcelize(parcelizeAnnotations) || declaration.modality == Modality.SEALED)
return
val parcelableProperties = declaration.parcelableProperties
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATE_FROM_PARCEL_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATOR_NAME
@@ -27,7 +28,8 @@ import org.jetbrains.kotlin.parcelize.serializers.ParcelizeExtensionBase
abstract class ParcelizeIrTransformerBase(
protected val context: IrPluginContext,
protected val androidSymbols: AndroidSymbols
protected val androidSymbols: AndroidSymbols,
protected val parcelizeAnnotations: List<FqName>
) : ParcelizeExtensionBase, IrElementVisitorVoid {
private val irFactory: IrFactory = IrFactoryImpl
@@ -173,7 +175,7 @@ abstract class ParcelizeIrTransformerBase(
val parceler by lazy(parcelerThunk)
}
private val serializerFactory = IrParcelSerializerFactory(androidSymbols)
private val serializerFactory = IrParcelSerializerFactory(androidSymbols, parcelizeAnnotations)
protected val IrClass.parcelableProperties: List<ParcelableProperty?>
get() {
@@ -27,17 +27,16 @@ import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATOR_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.NEW_ARRAY_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELABLE_FQN
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELER_FQN
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELIZE_CLASS_FQ_NAMES
import org.jetbrains.kotlin.parcelize.ParcelizeNames.WRITE_TO_PARCEL_NAME
import org.jetbrains.kotlin.parcelize.serializers.ParcelizeExtensionBase
import org.jetbrains.kotlin.types.Variance
// true if the class should be processed by the parcelize plugin
val IrClass.isParcelize: Boolean
get() = kind in ParcelizeExtensionBase.ALLOWED_CLASS_KINDS &&
(hasAnyAnnotation(PARCELIZE_CLASS_FQ_NAMES) || superTypes.any { superType ->
fun IrClass.isParcelize(parcelizeAnnotations: List<FqName>): Boolean =
kind in ParcelizeExtensionBase.ALLOWED_CLASS_KINDS &&
(hasAnyAnnotation(parcelizeAnnotations) || superTypes.any { superType ->
superType.classOrNull?.owner?.let {
it.modality == Modality.SEALED && it.hasAnyAnnotation(PARCELIZE_CLASS_FQ_NAMES)
it.modality == Modality.SEALED && it.hasAnyAnnotation(parcelizeAnnotations)
} == true
})