[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
})
@@ -0,0 +1 @@
org.jetbrains.kotlin.parcelize.ParcelizeCommandLineProcessor
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2024 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
import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption
import org.jetbrains.kotlin.compiler.plugin.CliOption
import org.jetbrains.kotlin.compiler.plugin.CliOptionProcessingException
import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
import org.jetbrains.kotlin.config.CompilerConfiguration
class ParcelizeCommandLineProcessor : CommandLineProcessor {
companion object {
const val COMPILER_PLUGIN_ID: String = "org.jetbrains.kotlin.parcelize"
val ADDITIONAL_ANNOTATION_OPTION: CliOption =
CliOption(
"additionalAnnotation",
"<fully qualified name>",
"Additional annotation to trigger parcelize processing.",
required = false,
allowMultipleOccurrences = true
)
}
override val pluginId: String
get() = COMPILER_PLUGIN_ID
override val pluginOptions: Collection<AbstractCliOption>
get() = listOf(ADDITIONAL_ANNOTATION_OPTION)
override fun processOption(option: AbstractCliOption, value: String, configuration: CompilerConfiguration) {
when (option) {
ADDITIONAL_ANNOTATION_OPTION -> configuration.appendList(ParcelizeConfigurationKeys.ADDITIONAL_ANNOTATION, value)
else -> throw CliOptionProcessingException("Unknown option: ${option.optionName}")
}
}
}
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.container.useInstance
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.parcelize.fir.FirParcelizeExtensionRegistrar
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.jvm.isJvm
@@ -21,35 +22,44 @@ import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
class ParcelizeComponentRegistrar : CompilerPluginRegistrar() {
companion object {
fun registerParcelizeComponents(extensionStorage: ExtensionStorage, useFir: Boolean) = with(extensionStorage) {
fun registerParcelizeComponents(
extensionStorage: ExtensionStorage,
additionalAnnotation: List<String>,
useFir: Boolean
) = with(extensionStorage) {
val parcelizeAnnotations = ParcelizeNames.PARCELIZE_CLASS_FQ_NAMES.toMutableList()
additionalAnnotation.mapTo(parcelizeAnnotations) { FqName(it) }
if (useFir) {
IrGenerationExtension.registerExtension(ParcelizeFirIrGeneratorExtension())
IrGenerationExtension.registerExtension(ParcelizeFirIrGeneratorExtension(parcelizeAnnotations))
} else {
IrGenerationExtension.registerExtension(ParcelizeIrGeneratorExtension())
IrGenerationExtension.registerExtension(ParcelizeIrGeneratorExtension(parcelizeAnnotations))
}
SyntheticResolveExtension.registerExtension(ParcelizeResolveExtension())
StorageComponentContainerContributor.registerExtension(ParcelizeDeclarationCheckerComponentContainerContributor())
FirExtensionRegistrarAdapter.registerExtension(FirParcelizeExtensionRegistrar())
SyntheticResolveExtension.registerExtension(ParcelizeResolveExtension(parcelizeAnnotations))
StorageComponentContainerContributor.registerExtension(ParcelizeDeclarationCheckerComponentContainerContributor(parcelizeAnnotations))
FirExtensionRegistrarAdapter.registerExtension(FirParcelizeExtensionRegistrar(parcelizeAnnotations))
}
}
override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) {
registerParcelizeComponents(this, configuration.getBoolean(CommonConfigurationKeys.USE_FIR))
val additionalAnnotation = configuration.get(ParcelizeConfigurationKeys.ADDITIONAL_ANNOTATION) ?: emptyList()
registerParcelizeComponents(this, additionalAnnotation, configuration.getBoolean(CommonConfigurationKeys.USE_FIR))
}
override val supportsK2: Boolean
get() = true
}
class ParcelizeDeclarationCheckerComponentContainerContributor : StorageComponentContainerContributor {
class ParcelizeDeclarationCheckerComponentContainerContributor(
private val parcelizeAnnotations: List<FqName>
) : StorageComponentContainerContributor {
override fun registerModuleComponents(
container: StorageComponentContainer,
platform: TargetPlatform,
moduleDescriptor: ModuleDescriptor,
) {
if (platform.isJvm()) {
container.useInstance(ParcelizeDeclarationChecker())
container.useInstance(ParcelizeAnnotationChecker())
container.useInstance(ParcelizeDeclarationChecker(parcelizeAnnotations))
container.useInstance(ParcelizeAnnotationChecker(parcelizeAnnotations))
}
}
}
@@ -0,0 +1,13 @@
/*
* Copyright 2010-2024 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
import org.jetbrains.kotlin.config.CompilerConfigurationKey
object ParcelizeConfigurationKeys {
val ADDITIONAL_ANNOTATION: CompilerConfigurationKey<List<String>> =
CompilerConfigurationKey.create(ParcelizeCommandLineProcessor.ADDITIONAL_ANNOTATION_OPTION.description)
}
@@ -21,8 +21,6 @@ object ParcelizeNames {
// -------------------- Class ids --------------------
val PARCELIZE_ID = ClassId(FqName("kotlinx.parcelize"), Name.identifier("Parcelize"))
val OLD_PARCELIZE_ID = ClassId(FqName("kotlinx.android.parcel"), Name.identifier("Parcelize"))
val PARCEL_ID = ClassId(FqName("android.os"), Name.identifier("Parcel"))
val PARCELABLE_ID = ClassId(FqName("android.os"), Name.identifier("Parcelable"))
val CREATOR_ID = PARCELABLE_ID.createNestedClassId(Name.identifier("Creator"))
@@ -38,8 +36,6 @@ object ParcelizeNames {
// -------------------- FQNs --------------------
val PARCELIZE_FQN = PARCELIZE_ID.asSingleFqName()
val OLD_PARCELIZE_FQN = OLD_PARCELIZE_ID.asSingleFqName()
val PARCELABLE_FQN = PARCELABLE_ID.asSingleFqName()
val CREATOR_FQN = CREATOR_ID.asSingleFqName()
@@ -23,10 +23,10 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.containingPackage
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.parcelize.ParcelizeNames.DEPRECATED_RUNTIME_PACKAGE
import org.jetbrains.kotlin.parcelize.ParcelizeNames.IGNORED_ON_PARCEL_FQ_NAMES
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELER_FQN
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELIZE_CLASS_FQ_NAMES
import org.jetbrains.kotlin.parcelize.ParcelizeNames.RAW_VALUE_ANNOTATION_FQ_NAMES
import org.jetbrains.kotlin.parcelize.ParcelizeNames.TYPE_PARCELER_FQ_NAMES
import org.jetbrains.kotlin.parcelize.ParcelizeNames.WRITE_WITH_FQ_NAMES
@@ -43,7 +43,7 @@ import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
import org.jetbrains.kotlin.types.typeUtil.replaceAnnotations
import org.jetbrains.kotlin.types.typeUtil.supertypes
open class ParcelizeAnnotationChecker : CallChecker {
open class ParcelizeAnnotationChecker(val parcelizeAnnotations : List<FqName>) : CallChecker {
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
val constructorDescriptor = resolvedCall.resultingDescriptor as? ClassConstructorDescriptor ?: return
val annotationClass = constructorDescriptor.constructedClass.takeIf { it.kind == ClassKind.ANNOTATION_CLASS } ?: return
@@ -66,7 +66,7 @@ open class ParcelizeAnnotationChecker : CallChecker {
checkDeprecatedAnnotations(resolvedCall, annotationEntry, context, isForbidden = false)
}
if (annotationClass.fqNameSafe in PARCELIZE_CLASS_FQ_NAMES || annotationClass.fqNameSafe in RAW_VALUE_ANNOTATION_FQ_NAMES) {
if (annotationClass.fqNameSafe in parcelizeAnnotations || annotationClass.fqNameSafe in RAW_VALUE_ANNOTATION_FQ_NAMES) {
checkDeprecatedAnnotations(resolvedCall, annotationEntry, context, isForbidden = false)
}
}
@@ -177,7 +177,7 @@ open class ParcelizeAnnotationChecker : CallChecker {
) {
if (containingClass != null) {
val containingClassDescriptor = context.trace[BindingContext.CLASS, containingClass]
if (containingClassDescriptor != null && !containingClassDescriptor.isParcelize) {
if (containingClassDescriptor != null && !containingClassDescriptor.isParcelize(parcelizeAnnotations)) {
val reportElement = (annotationEntry.typeReference?.typeElement as? KtUserType)?.referenceExpression ?: annotationEntry
context.trace.report(ErrorsParcelize.CLASS_SHOULD_BE_PARCELIZE.on(reportElement, containingClass))
}
@@ -32,7 +32,7 @@ import org.jetbrains.kotlin.types.isError
import org.jetbrains.kotlin.types.typeUtil.representativeUpperBound
import org.jetbrains.kotlin.types.typeUtil.supertypes
open class ParcelizeDeclarationChecker : DeclarationChecker {
open class ParcelizeDeclarationChecker(val parcelizeAnnotations: List<FqName>) : DeclarationChecker {
private companion object {
private val IGNORED_ON_PARCEL_FQ_NAMES = listOf(
FqName("kotlinx.parcelize.IgnoredOnParcel"),
@@ -71,7 +71,7 @@ open class ParcelizeDeclarationChecker : DeclarationChecker {
declaration: KtFunction,
diagnosticHolder: DiagnosticSink
) {
if (!containingClass.isParcelize) {
if (!containingClass.isParcelize(parcelizeAnnotations)) {
return
}
@@ -97,7 +97,7 @@ open class ParcelizeDeclarationChecker : DeclarationChecker {
return property.annotations.hasIgnoredOnParcel() || (property.getter?.annotations?.hasIgnoredOnParcel() ?: false)
}
if (containingClass.isParcelize
if (containingClass.isParcelize(parcelizeAnnotations)
&& (declaration.hasDelegate() || bindingContext[BindingContext.BACKING_FIELD_REQUIRED, property] == true)
&& !hasIgnoredOnParcel()
&& !containingClass.hasCustomParceler()
@@ -109,7 +109,7 @@ open class ParcelizeDeclarationChecker : DeclarationChecker {
// @JvmName is not applicable to property so we can check just the descriptor name
if (property.name.asString() == "CREATOR" && property.findJvmFieldAnnotation() != null && containingClass.isCompanionObject) {
val outerClass = containingClass.containingDeclaration as? ClassDescriptor
if (outerClass != null && outerClass.isParcelize) {
if (outerClass != null && outerClass.isParcelize(parcelizeAnnotations)) {
val reportElement = declaration.nameIdentifier ?: declaration
diagnosticHolder.report(ErrorsParcelize.CREATOR_DEFINITION_IS_NOT_ALLOWED.on(reportElement))
}
@@ -141,7 +141,7 @@ open class ParcelizeDeclarationChecker : DeclarationChecker {
bindingContext: BindingContext,
languageVersionSettings: LanguageVersionSettings
) {
if (!descriptor.isParcelize) {
if (!descriptor.isParcelize(parcelizeAnnotations)) {
return
}
@@ -30,7 +30,6 @@ import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATE_FROM_PARCEL_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.DESCRIBE_CONTENTS_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.NEW_ARRAY_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELER_FQN
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELIZE_CLASS_FQ_NAMES
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCEL_ID
import org.jetbrains.kotlin.parcelize.ParcelizeNames.WRITE_TO_PARCEL_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeSyntheticComponent.ComponentKind.DESCRIBE_CONTENTS
@@ -46,7 +45,7 @@ import org.jetbrains.kotlin.types.SimpleType
import org.jetbrains.kotlin.types.error.ErrorTypeKind
import org.jetbrains.kotlin.types.error.ErrorUtils
open class ParcelizeResolveExtension : SyntheticResolveExtension {
open class ParcelizeResolveExtension(private val parcelizeAnnotations: List<FqName>) : SyntheticResolveExtension {
companion object {
fun resolveParcelClassType(module: ModuleDescriptor): SimpleType? {
return module.findClassAcrossModuleDependencies(PARCEL_ID)?.defaultType
@@ -107,7 +106,7 @@ open class ParcelizeResolveExtension : SyntheticResolveExtension {
override fun getSyntheticCompanionObjectNameIfNeeded(thisDescriptor: ClassDescriptor): Name? = null
override fun getSyntheticFunctionNames(thisDescriptor: ClassDescriptor): List<Name> {
return if (thisDescriptor.isParcelize) parcelizeMethodNames else emptyList()
return if (thisDescriptor.isParcelize(parcelizeAnnotations)) parcelizeMethodNames else emptyList()
}
override fun generateSyntheticMethods(
@@ -123,7 +122,7 @@ open class ParcelizeResolveExtension : SyntheticResolveExtension {
}
if (name.asString() == DESCRIBE_CONTENTS.methodName
&& thisDescriptor.isParcelize
&& thisDescriptor.isParcelize(parcelizeAnnotations)
&& !thisDescriptor.isSealed()
&& isParcelizePluginEnabled()
&& result.none { it.isDescribeContents() }
@@ -131,7 +130,7 @@ open class ParcelizeResolveExtension : SyntheticResolveExtension {
) {
result += createMethod(thisDescriptor, DESCRIBE_CONTENTS, Modality.OPEN, thisDescriptor.builtIns.intType)
} else if (name.asString() == WRITE_TO_PARCEL.methodName
&& thisDescriptor.isParcelize
&& thisDescriptor.isParcelize(parcelizeAnnotations)
&& !thisDescriptor.isSealed()
&& isParcelizePluginEnabled()
&& result.none { it.isWriteToParcel() }
@@ -173,13 +172,13 @@ interface ParcelizeSyntheticComponent {
}
}
val ClassDescriptor.hasParcelizeAnnotation: Boolean
get() = PARCELIZE_CLASS_FQ_NAMES.any(annotations::hasAnnotation)
fun ClassDescriptor.hasParcelizeAnnotation(parcelizeAnnotations: List<FqName>): Boolean =
parcelizeAnnotations.any(annotations::hasAnnotation)
val ClassDescriptor.isParcelize: Boolean
get() = hasParcelizeAnnotation
|| getSuperClassNotAny()?.takeIf(DescriptorUtils::isSealedClass)?.hasParcelizeAnnotation == true
|| getSuperInterfaces().any { DescriptorUtils.isSealedClass(it) && it.hasParcelizeAnnotation }
fun ClassDescriptor.isParcelize(parcelizeAnnotations: List<FqName>): Boolean =
hasParcelizeAnnotation(parcelizeAnnotations)
|| getSuperClassNotAny()?.takeIf(DescriptorUtils::isSealedClass)?.hasParcelizeAnnotation(parcelizeAnnotations) == true
|| getSuperInterfaces().any { DescriptorUtils.isSealedClass(it) && it.hasParcelizeAnnotation(parcelizeAnnotations) }
val KotlinType.isParceler: Boolean
get() = constructor.declarationDescriptor?.fqNameSafe == PARCELER_FQN
@@ -38,6 +38,7 @@ interface ParcelSerializer {
fun readValue(v: InstructionAdapter)
data class ParcelSerializerContext(
val parcelizeAnnotations: List<FqName>,
val typeMapper: KotlinTypeMapper,
val containerClassType: Type,
val typeParcelers: List<TypeParcelerMapping>,
@@ -344,7 +345,7 @@ interface ParcelSerializer {
val creatorAsmType = when {
creatorVar != null -> typeMapper.mapTypeSafe(creatorVar.type, forceBoxed = true)
clazz.isParcelize -> Type.getObjectType(asmType.internalName + "\$Creator")
clazz.isParcelize(context.parcelizeAnnotations) -> Type.getObjectType(asmType.internalName + "\$Creator")
else -> null
}
@@ -36,7 +36,8 @@ interface ParcelizeExtensionBase {
.isNotEmpty()
}
val ClassDescriptor.isParcelizeClassDescriptor get() = kind in ALLOWED_CLASS_KINDS && isParcelize
fun ClassDescriptor.isParcelizeClassDescriptor(parcelizeAnnotations: List<FqName>)
= kind in ALLOWED_CLASS_KINDS && isParcelize(parcelizeAnnotations)
fun ClassDescriptor.hasSyntheticDescribeContents() = hasParcelizeSyntheticMethod(DESCRIBE_CONTENTS)
@@ -10,25 +10,29 @@ import org.jetbrains.kotlin.fir.analysis.checkers.declaration.*
import org.jetbrains.kotlin.fir.analysis.checkers.expression.ExpressionCheckers
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirAnnotationCallChecker
import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.parcelize.fir.diagnostics.*
class FirParcelizeCheckersExtension(session: FirSession) : FirAdditionalCheckersExtension(session) {
class FirParcelizeCheckersExtension(
session: FirSession,
val parcelizeAnnotations: List<ClassId>
) : FirAdditionalCheckersExtension(session) {
override val expressionCheckers: ExpressionCheckers = object : ExpressionCheckers() {
override val annotationCallCheckers: Set<FirAnnotationCallChecker>
get() = setOf(FirParcelizeAnnotationChecker)
get() = setOf(FirParcelizeAnnotationChecker(parcelizeAnnotations))
}
override val declarationCheckers: DeclarationCheckers = object : DeclarationCheckers() {
override val classCheckers: Set<FirClassChecker>
get() = setOf(FirParcelizeClassChecker)
get() = setOf(FirParcelizeClassChecker(parcelizeAnnotations))
override val propertyCheckers: Set<FirPropertyChecker>
get() = setOf(FirParcelizePropertyChecker)
get() = setOf(FirParcelizePropertyChecker(parcelizeAnnotations))
override val simpleFunctionCheckers: Set<FirSimpleFunctionChecker>
get() = setOf(FirParcelizeFunctionChecker)
get() = setOf(FirParcelizeFunctionChecker(parcelizeAnnotations))
override val constructorCheckers: Set<FirConstructorChecker>
get() = setOf(FirParcelizeConstructorChecker)
get() = setOf(FirParcelizeConstructorChecker(parcelizeAnnotations))
}
}
@@ -23,25 +23,28 @@ import org.jetbrains.kotlin.fir.resolve.lookupSuperTypes
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.parcelize.ParcelizeNames.DESCRIBE_CONTENTS_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.DEST_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.FLAGS_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.OLD_PARCELIZE_FQN
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELIZE_FQN
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCEL_ID
import org.jetbrains.kotlin.parcelize.ParcelizeNames.WRITE_TO_PARCEL_NAME
import org.jetbrains.kotlin.parcelize.fir.diagnostics.checkParcelizeClassSymbols
import org.jetbrains.kotlin.utils.addToStdlib.runIf
class FirParcelizeDeclarationGenerator(session: FirSession) : FirDeclarationGenerationExtension(session) {
class FirParcelizeDeclarationGenerator(
session: FirSession,
private val annotations: List<FqName>
) : FirDeclarationGenerationExtension(session) {
companion object {
private val PREDICATE = LookupPredicate.create { annotated(PARCELIZE_FQN, OLD_PARCELIZE_FQN) }
private val parcelizeMethodsNames = setOf(DESCRIBE_CONTENTS_NAME, WRITE_TO_PARCEL_NAME)
}
private val predicate = LookupPredicate.create { annotated(annotations) }
private val matchedClasses by lazy {
session.predicateBasedProvider.getSymbolsByPredicate(PREDICATE)
session.predicateBasedProvider.getSymbolsByPredicate(predicate)
.filterIsInstance<FirRegularClassSymbol>()
}
@@ -116,6 +119,6 @@ class FirParcelizeDeclarationGenerator(session: FirSession) : FirDeclarationGene
get() = ParcelizePluginKey
override fun FirDeclarationPredicateRegistrar.registerPredicates() {
register(PREDICATE)
register(predicate)
}
}
@@ -6,10 +6,12 @@
package org.jetbrains.kotlin.parcelize.fir
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
class FirParcelizeExtensionRegistrar : FirExtensionRegistrar() {
class FirParcelizeExtensionRegistrar(val parcelizeAnnotationFqNames: List<FqName>) : FirExtensionRegistrar() {
override fun ExtensionRegistrarContext.configurePlugin() {
+::FirParcelizeDeclarationGenerator
+::FirParcelizeCheckersExtension
+::FirParcelizeDeclarationGenerator.bind(parcelizeAnnotationFqNames)
+::FirParcelizeCheckersExtension.bind(parcelizeAnnotationFqNames.map { ClassId.topLevel(it) })
}
}
@@ -23,13 +23,13 @@ import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.parcelize.ParcelizeNames
import org.jetbrains.kotlin.parcelize.ParcelizeNames.DEPRECATED_RUNTIME_PACKAGE
import org.jetbrains.kotlin.parcelize.ParcelizeNames.IGNORED_ON_PARCEL_CLASS_IDS
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELIZE_CLASS_CLASS_IDS
import org.jetbrains.kotlin.parcelize.ParcelizeNames.RAW_VALUE_ANNOTATION_CLASS_IDS
import org.jetbrains.kotlin.parcelize.ParcelizeNames.TYPE_PARCELER_CLASS_IDS
import org.jetbrains.kotlin.parcelize.ParcelizeNames.WRITE_WITH_CLASS_IDS
// TODO: extract common checker for expect interfaces
object FirParcelizeAnnotationChecker : FirAnnotationCallChecker(MppCheckerKind.Platform) {
class FirParcelizeAnnotationChecker(private val parcelizeAnnotationClassIds: List<ClassId>) :
FirAnnotationCallChecker(MppCheckerKind.Platform) {
override fun check(expression: FirAnnotationCall, context: CheckerContext, reporter: DiagnosticReporter) {
val annotationType = expression.annotationTypeRef.coneType.fullyExpandedType(context.session) as? ConeClassLikeType ?: return
val resolvedAnnotationSymbol = annotationType.lookupTag.toFirRegularClassSymbol(context.session) ?: return
@@ -47,7 +47,7 @@ object FirParcelizeAnnotationChecker : FirAnnotationCallChecker(MppCheckerKind.P
in IGNORED_ON_PARCEL_CLASS_IDS -> {
checkDeprecatedAnnotations(expression, annotationClassId, context, reporter, isForbidden = false)
}
in PARCELIZE_CLASS_CLASS_IDS, in RAW_VALUE_ANNOTATION_CLASS_IDS -> {
in parcelizeAnnotationClassIds, in RAW_VALUE_ANNOTATION_CLASS_IDS -> {
checkDeprecatedAnnotations(expression, annotationClassId, context, reporter, isForbidden = false)
}
}
@@ -148,7 +148,7 @@ object FirParcelizeAnnotationChecker : FirAnnotationCallChecker(MppCheckerKind.P
private fun checkIfTheContainingClassIsParcelize(annotationCall: FirAnnotationCall, context: CheckerContext, reporter: DiagnosticReporter) {
val enclosingClass = context.findClosestClassOrObject() ?: return
if (!enclosingClass.symbol.isParcelize(context.session)) {
if (!enclosingClass.symbol.isParcelize(context.session, parcelizeAnnotationClassIds)) {
val reportElement = annotationCall.calleeReference.source ?: annotationCall.source
reporter.reportOn(reportElement, KtErrorsParcelize.CLASS_SHOULD_BE_PARCELIZE, enclosingClass.symbol, context)
}
@@ -21,16 +21,16 @@ import org.jetbrains.kotlin.fir.resolve.lookupSuperTypes
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATOR_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.OLD_PARCELER_ID
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELABLE_ID
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELER_CLASS_IDS
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELIZE_CLASS_CLASS_IDS
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
object FirParcelizeClassChecker : FirClassChecker(MppCheckerKind.Common) {
class FirParcelizeClassChecker(private val parcelizeAnnotations: List<ClassId>) : FirClassChecker(MppCheckerKind.Platform) {
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
checkParcelableClass(declaration, context, reporter)
checkParcelerClass(declaration, context, reporter)
@@ -38,7 +38,7 @@ object FirParcelizeClassChecker : FirClassChecker(MppCheckerKind.Common) {
private fun checkParcelableClass(klass: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
val symbol = klass.symbol
if (!symbol.isParcelize(context.session)) return
if (!symbol.isParcelize(context.session, parcelizeAnnotations)) return
val source = klass.source ?: return
val classKind = klass.classKind
@@ -110,14 +110,14 @@ object FirParcelizeClassChecker : FirClassChecker(MppCheckerKind.Common) {
}
@OptIn(ExperimentalContracts::class)
fun FirClassSymbol<*>?.isParcelize(session: FirSession): Boolean {
fun FirClassSymbol<*>?.isParcelize(session: FirSession, parcelizeAnnotations: List<ClassId>): Boolean {
contract {
returns(true) implies (this@isParcelize != null)
}
if (this == null) return false
return checkParcelizeClassSymbols(this, session) { symbol ->
symbol.annotations.any { it.toAnnotationClassId(session) in PARCELIZE_CLASS_CLASS_IDS }
symbol.annotations.any { it.toAnnotationClassId(session) in parcelizeAnnotations }
}
}
@@ -16,16 +16,20 @@ import org.jetbrains.kotlin.fir.correspondingProperty
import org.jetbrains.kotlin.fir.declarations.FirConstructor
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.toAnnotationClassId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.parcelize.ParcelizeNames
object FirParcelizeConstructorChecker : FirConstructorChecker(MppCheckerKind.Common) {
class FirParcelizeConstructorChecker(private val parcelizeAnnotations: List<ClassId>) : FirConstructorChecker(MppCheckerKind.Platform) {
override fun check(declaration: FirConstructor, context: CheckerContext, reporter: DiagnosticReporter) {
if (!declaration.isPrimary) return
val source = declaration.source ?: return
if (source.kind == KtFakeSourceElementKind.ImplicitConstructor) return
val containingClass = context.containingDeclarations.last() as? FirRegularClass ?: return
val containingClassSymbol = containingClass.symbol
if (!containingClassSymbol.isParcelize(context.session) || containingClass.hasCustomParceler(context.session)) return
if (!containingClassSymbol.isParcelize(context.session, parcelizeAnnotations)
|| containingClass.hasCustomParceler(context.session)) {
return
}
if (declaration.valueParameters.isEmpty()) {
reporter.reportOn(containingClass.source, KtErrorsParcelize.PARCELABLE_PRIMARY_CONSTRUCTOR_IS_EMPTY, context)
@@ -17,11 +17,12 @@ import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.types.isInt
import org.jetbrains.kotlin.fir.types.isUnit
import org.jetbrains.kotlin.fir.types.toRegularClassSymbol
import org.jetbrains.kotlin.name.ClassId
object FirParcelizeFunctionChecker : FirSimpleFunctionChecker(MppCheckerKind.Common) {
class FirParcelizeFunctionChecker(private val parcelizeAnnotations: List<ClassId>) : FirSimpleFunctionChecker(MppCheckerKind.Platform) {
override fun check(declaration: FirSimpleFunction, context: CheckerContext, reporter: DiagnosticReporter) {
val containingClassSymbol = declaration.dispatchReceiverType?.toRegularClassSymbol(context.session)
if (!containingClassSymbol.isParcelize(context.session)) return
if (!containingClassSymbol.isParcelize(context.session, parcelizeAnnotations)) return
if (declaration.origin != FirDeclarationOrigin.Source) return
if (declaration.isWriteToParcel() && declaration.isOverride) {
reporter.reportOn(declaration.source, KtErrorsParcelize.OVERRIDING_WRITE_TO_PARCEL_IS_NOT_ALLOWED, context)
@@ -26,18 +26,19 @@ import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.lookupSuperTypes
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.parcelize.BuiltinParcelableTypes
import org.jetbrains.kotlin.parcelize.ParcelizeNames
import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATOR_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.IGNORED_ON_PARCEL_CLASS_IDS
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELER_ID
object FirParcelizePropertyChecker : FirPropertyChecker(MppCheckerKind.Common) {
class FirParcelizePropertyChecker(private val parcelizeAnnotations: List<ClassId>) : FirPropertyChecker(MppCheckerKind.Platform) {
override fun check(declaration: FirProperty, context: CheckerContext, reporter: DiagnosticReporter) {
val session = context.session
val containingClassSymbol = declaration.dispatchReceiverType?.toRegularClassSymbol(session) ?: return
if (containingClassSymbol.isParcelize(session)) {
if (containingClassSymbol.isParcelize(session, parcelizeAnnotations)) {
val fromPrimaryConstructor = declaration.fromPrimaryConstructor ?: false
if (
!fromPrimaryConstructor &&
@@ -54,7 +55,7 @@ object FirParcelizePropertyChecker : FirPropertyChecker(MppCheckerKind.Common) {
if (declaration.name == CREATOR_NAME && containingClassSymbol.isCompanion && declaration.hasJvmFieldAnnotation(session)) {
val outerClass = context.containingDeclarations.asReversed().getOrNull(1) as? FirRegularClass
if (outerClass != null && outerClass.symbol.isParcelize(session)) {
if (outerClass != null && outerClass.symbol.isParcelize(session, parcelizeAnnotations)) {
reporter.reportOn(declaration.source, KtErrorsParcelize.CREATOR_DEFINITION_IS_NOT_ALLOWED, context)
}
}
@@ -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.ParcelizeConfigurationKeys
import org.jetbrains.kotlin.parcelize.kotlinxImmutable
import org.jetbrains.kotlin.test.model.FrontendKinds
import org.jetbrains.kotlin.test.model.TestModule
@@ -47,6 +48,11 @@ class ParcelizeEnvironmentConfigurator(testServices: TestServices) : Environment
module: TestModule,
configuration: CompilerConfiguration
) {
ParcelizeComponentRegistrar.registerParcelizeComponents(this, useFir = module.frontendKind == FrontendKinds.FIR)
val additionalAnnotation = configuration.get(ParcelizeConfigurationKeys.ADDITIONAL_ANNOTATION)
ParcelizeComponentRegistrar.registerParcelizeComponents(
this,
additionalAnnotation,
useFir = module.frontendKind == FrontendKinds.FIR
)
}
}