[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.declarations.inlineClassRepresentation
import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.parcelize.ParcelizeNames.RAW_VALUE_ANNOTATION_FQ_NAMES 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( private val supportedBySimpleListSerializer = setOf(
"kotlin.collections.List", "kotlin.collections.MutableList", "kotlin.collections.ArrayList", "kotlin.collections.List", "kotlin.collections.MutableList", "kotlin.collections.ArrayList",
"java.util.List", "java.util.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 // @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. // 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 return if (classifier.modality == Modality.FINAL && classifier.psiElement != null
&& (classifier.isParcelize || classifier.hasCreatorField) && (classifier.isParcelize(parcelizeAnnotations) || classifier.hasCreatorField)
) { ) {
wrapNullableSerializerIfNeeded(irType, IrEfficientParcelableParcelSerializer(classifier)) wrapNullableSerializerIfNeeded(irType, IrEfficientParcelableParcelSerializer(classifier))
} else { } 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.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment 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) { override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
val androidSymbols = AndroidSymbols(pluginContext, moduleFragment) 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.companionObject
import org.jetbrains.kotlin.ir.util.functions import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.util.render 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.ParcelizeNames.PARCELER_FQN
import org.jetbrains.kotlin.parcelize.fir.ParcelizePluginKey import org.jetbrains.kotlin.parcelize.fir.ParcelizePluginKey
class ParcelizeFirIrTransformer( class ParcelizeFirIrTransformer(
context: IrPluginContext, context: IrPluginContext,
androidSymbols: AndroidSymbols androidSymbols: AndroidSymbols,
) : ParcelizeIrTransformerBase(context, androidSymbols) { parcelizeAnnotations: List<FqName>
) : ParcelizeIrTransformerBase(context, androidSymbols, parcelizeAnnotations) {
fun transform(moduleFragment: IrModuleFragment) { fun transform(moduleFragment: IrModuleFragment) {
moduleFragment.accept(this, null) moduleFragment.accept(this, null)
@@ -34,7 +36,7 @@ class ParcelizeFirIrTransformer(
// Sealed classes can be annotated with `@Parcelize`, but that only implies that we // Sealed classes can be annotated with `@Parcelize`, but that only implies that we
// should process their immediate subclasses. // should process their immediate subclasses.
if (!declaration.isParcelize || declaration.modality == Modality.SEALED) if (!declaration.isParcelize(parcelizeAnnotations) || declaration.modality == Modality.SEALED)
return return
val parcelableProperties = declaration.parcelableProperties 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.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment 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) { override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
val androidSymbols = AndroidSymbols(pluginContext, moduleFragment) 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) @OptIn(ObsoleteDescriptorBasedAPI::class)
class ParcelizeIrTransformer( class ParcelizeIrTransformer(
context: IrPluginContext, context: IrPluginContext,
androidSymbols: AndroidSymbols androidSymbols: AndroidSymbols,
) : ParcelizeIrTransformerBase(context, androidSymbols) { parcelizeAnnotations: List<FqName>
) : ParcelizeIrTransformerBase(context, androidSymbols, parcelizeAnnotations) {
private val symbolMap = mutableMapOf<IrSimpleFunctionSymbol, IrSimpleFunctionSymbol>() private val symbolMap = mutableMapOf<IrSimpleFunctionSymbol, IrSimpleFunctionSymbol>()
fun transform(moduleFragment: IrModuleFragment) { fun transform(moduleFragment: IrModuleFragment) {
@@ -101,7 +102,7 @@ class ParcelizeIrTransformer(
// Sealed classes can be annotated with `@Parcelize`, but that only implies that we // Sealed classes can be annotated with `@Parcelize`, but that only implies that we
// should process their immediate subclasses. // should process their immediate subclasses.
if (!declaration.isParcelize || declaration.modality == Modality.SEALED) if (!declaration.isParcelize(parcelizeAnnotations) || declaration.modality == Modality.SEALED)
return return
val parcelableProperties = declaration.parcelableProperties 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.types.*
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATE_FROM_PARCEL_NAME import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATE_FROM_PARCEL_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATOR_NAME import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATOR_NAME
@@ -27,7 +28,8 @@ import org.jetbrains.kotlin.parcelize.serializers.ParcelizeExtensionBase
abstract class ParcelizeIrTransformerBase( abstract class ParcelizeIrTransformerBase(
protected val context: IrPluginContext, protected val context: IrPluginContext,
protected val androidSymbols: AndroidSymbols protected val androidSymbols: AndroidSymbols,
protected val parcelizeAnnotations: List<FqName>
) : ParcelizeExtensionBase, IrElementVisitorVoid { ) : ParcelizeExtensionBase, IrElementVisitorVoid {
private val irFactory: IrFactory = IrFactoryImpl private val irFactory: IrFactory = IrFactoryImpl
@@ -173,7 +175,7 @@ abstract class ParcelizeIrTransformerBase(
val parceler by lazy(parcelerThunk) val parceler by lazy(parcelerThunk)
} }
private val serializerFactory = IrParcelSerializerFactory(androidSymbols) private val serializerFactory = IrParcelSerializerFactory(androidSymbols, parcelizeAnnotations)
protected val IrClass.parcelableProperties: List<ParcelableProperty?> protected val IrClass.parcelableProperties: List<ParcelableProperty?>
get() { 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.NEW_ARRAY_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELABLE_FQN import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELABLE_FQN
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELER_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.ParcelizeNames.WRITE_TO_PARCEL_NAME
import org.jetbrains.kotlin.parcelize.serializers.ParcelizeExtensionBase import org.jetbrains.kotlin.parcelize.serializers.ParcelizeExtensionBase
import org.jetbrains.kotlin.types.Variance import org.jetbrains.kotlin.types.Variance
// true if the class should be processed by the parcelize plugin // true if the class should be processed by the parcelize plugin
val IrClass.isParcelize: Boolean fun IrClass.isParcelize(parcelizeAnnotations: List<FqName>): Boolean =
get() = kind in ParcelizeExtensionBase.ALLOWED_CLASS_KINDS && kind in ParcelizeExtensionBase.ALLOWED_CLASS_KINDS &&
(hasAnyAnnotation(PARCELIZE_CLASS_FQ_NAMES) || superTypes.any { superType -> (hasAnyAnnotation(parcelizeAnnotations) || superTypes.any { superType ->
superType.classOrNull?.owner?.let { superType.classOrNull?.owner?.let {
it.modality == Modality.SEALED && it.hasAnyAnnotation(PARCELIZE_CLASS_FQ_NAMES) it.modality == Modality.SEALED && it.hasAnyAnnotation(parcelizeAnnotations)
} == true } == 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.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.parcelize.fir.FirParcelizeExtensionRegistrar import org.jetbrains.kotlin.parcelize.fir.FirParcelizeExtensionRegistrar
import org.jetbrains.kotlin.platform.TargetPlatform import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.jvm.isJvm import org.jetbrains.kotlin.platform.jvm.isJvm
@@ -21,35 +22,44 @@ import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
class ParcelizeComponentRegistrar : CompilerPluginRegistrar() { class ParcelizeComponentRegistrar : CompilerPluginRegistrar() {
companion object { 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) { if (useFir) {
IrGenerationExtension.registerExtension(ParcelizeFirIrGeneratorExtension()) IrGenerationExtension.registerExtension(ParcelizeFirIrGeneratorExtension(parcelizeAnnotations))
} else { } else {
IrGenerationExtension.registerExtension(ParcelizeIrGeneratorExtension()) IrGenerationExtension.registerExtension(ParcelizeIrGeneratorExtension(parcelizeAnnotations))
} }
SyntheticResolveExtension.registerExtension(ParcelizeResolveExtension()) SyntheticResolveExtension.registerExtension(ParcelizeResolveExtension(parcelizeAnnotations))
StorageComponentContainerContributor.registerExtension(ParcelizeDeclarationCheckerComponentContainerContributor()) StorageComponentContainerContributor.registerExtension(ParcelizeDeclarationCheckerComponentContainerContributor(parcelizeAnnotations))
FirExtensionRegistrarAdapter.registerExtension(FirParcelizeExtensionRegistrar()) FirExtensionRegistrarAdapter.registerExtension(FirParcelizeExtensionRegistrar(parcelizeAnnotations))
} }
} }
override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) { 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 override val supportsK2: Boolean
get() = true get() = true
} }
class ParcelizeDeclarationCheckerComponentContainerContributor : StorageComponentContainerContributor { class ParcelizeDeclarationCheckerComponentContainerContributor(
private val parcelizeAnnotations: List<FqName>
) : StorageComponentContainerContributor {
override fun registerModuleComponents( override fun registerModuleComponents(
container: StorageComponentContainer, container: StorageComponentContainer,
platform: TargetPlatform, platform: TargetPlatform,
moduleDescriptor: ModuleDescriptor, moduleDescriptor: ModuleDescriptor,
) { ) {
if (platform.isJvm()) { if (platform.isJvm()) {
container.useInstance(ParcelizeDeclarationChecker()) container.useInstance(ParcelizeDeclarationChecker(parcelizeAnnotations))
container.useInstance(ParcelizeAnnotationChecker()) 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 -------------------- // -------------------- 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 PARCEL_ID = ClassId(FqName("android.os"), Name.identifier("Parcel"))
val PARCELABLE_ID = ClassId(FqName("android.os"), Name.identifier("Parcelable")) val PARCELABLE_ID = ClassId(FqName("android.os"), Name.identifier("Parcelable"))
val CREATOR_ID = PARCELABLE_ID.createNestedClassId(Name.identifier("Creator")) val CREATOR_ID = PARCELABLE_ID.createNestedClassId(Name.identifier("Creator"))
@@ -38,8 +36,6 @@ object ParcelizeNames {
// -------------------- FQNs -------------------- // -------------------- FQNs --------------------
val PARCELIZE_FQN = PARCELIZE_ID.asSingleFqName()
val OLD_PARCELIZE_FQN = OLD_PARCELIZE_ID.asSingleFqName()
val PARCELABLE_FQN = PARCELABLE_ID.asSingleFqName() val PARCELABLE_FQN = PARCELABLE_ID.asSingleFqName()
val CREATOR_FQN = CREATOR_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.ClassKind
import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.containingPackage 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.DEPRECATED_RUNTIME_PACKAGE
import org.jetbrains.kotlin.parcelize.ParcelizeNames.IGNORED_ON_PARCEL_FQ_NAMES 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.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.RAW_VALUE_ANNOTATION_FQ_NAMES
import org.jetbrains.kotlin.parcelize.ParcelizeNames.TYPE_PARCELER_FQ_NAMES import org.jetbrains.kotlin.parcelize.ParcelizeNames.TYPE_PARCELER_FQ_NAMES
import org.jetbrains.kotlin.parcelize.ParcelizeNames.WRITE_WITH_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.replaceAnnotations
import org.jetbrains.kotlin.types.typeUtil.supertypes 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) { override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
val constructorDescriptor = resolvedCall.resultingDescriptor as? ClassConstructorDescriptor ?: return val constructorDescriptor = resolvedCall.resultingDescriptor as? ClassConstructorDescriptor ?: return
val annotationClass = constructorDescriptor.constructedClass.takeIf { it.kind == ClassKind.ANNOTATION_CLASS } ?: 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) 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) checkDeprecatedAnnotations(resolvedCall, annotationEntry, context, isForbidden = false)
} }
} }
@@ -177,7 +177,7 @@ open class ParcelizeAnnotationChecker : CallChecker {
) { ) {
if (containingClass != null) { if (containingClass != null) {
val containingClassDescriptor = context.trace[BindingContext.CLASS, containingClass] 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 val reportElement = (annotationEntry.typeReference?.typeElement as? KtUserType)?.referenceExpression ?: annotationEntry
context.trace.report(ErrorsParcelize.CLASS_SHOULD_BE_PARCELIZE.on(reportElement, containingClass)) 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.representativeUpperBound
import org.jetbrains.kotlin.types.typeUtil.supertypes import org.jetbrains.kotlin.types.typeUtil.supertypes
open class ParcelizeDeclarationChecker : DeclarationChecker { open class ParcelizeDeclarationChecker(val parcelizeAnnotations: List<FqName>) : DeclarationChecker {
private companion object { private companion object {
private val IGNORED_ON_PARCEL_FQ_NAMES = listOf( private val IGNORED_ON_PARCEL_FQ_NAMES = listOf(
FqName("kotlinx.parcelize.IgnoredOnParcel"), FqName("kotlinx.parcelize.IgnoredOnParcel"),
@@ -71,7 +71,7 @@ open class ParcelizeDeclarationChecker : DeclarationChecker {
declaration: KtFunction, declaration: KtFunction,
diagnosticHolder: DiagnosticSink diagnosticHolder: DiagnosticSink
) { ) {
if (!containingClass.isParcelize) { if (!containingClass.isParcelize(parcelizeAnnotations)) {
return return
} }
@@ -97,7 +97,7 @@ open class ParcelizeDeclarationChecker : DeclarationChecker {
return property.annotations.hasIgnoredOnParcel() || (property.getter?.annotations?.hasIgnoredOnParcel() ?: false) 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) && (declaration.hasDelegate() || bindingContext[BindingContext.BACKING_FIELD_REQUIRED, property] == true)
&& !hasIgnoredOnParcel() && !hasIgnoredOnParcel()
&& !containingClass.hasCustomParceler() && !containingClass.hasCustomParceler()
@@ -109,7 +109,7 @@ open class ParcelizeDeclarationChecker : DeclarationChecker {
// @JvmName is not applicable to property so we can check just the descriptor name // @JvmName is not applicable to property so we can check just the descriptor name
if (property.name.asString() == "CREATOR" && property.findJvmFieldAnnotation() != null && containingClass.isCompanionObject) { if (property.name.asString() == "CREATOR" && property.findJvmFieldAnnotation() != null && containingClass.isCompanionObject) {
val outerClass = containingClass.containingDeclaration as? ClassDescriptor val outerClass = containingClass.containingDeclaration as? ClassDescriptor
if (outerClass != null && outerClass.isParcelize) { if (outerClass != null && outerClass.isParcelize(parcelizeAnnotations)) {
val reportElement = declaration.nameIdentifier ?: declaration val reportElement = declaration.nameIdentifier ?: declaration
diagnosticHolder.report(ErrorsParcelize.CREATOR_DEFINITION_IS_NOT_ALLOWED.on(reportElement)) diagnosticHolder.report(ErrorsParcelize.CREATOR_DEFINITION_IS_NOT_ALLOWED.on(reportElement))
} }
@@ -141,7 +141,7 @@ open class ParcelizeDeclarationChecker : DeclarationChecker {
bindingContext: BindingContext, bindingContext: BindingContext,
languageVersionSettings: LanguageVersionSettings languageVersionSettings: LanguageVersionSettings
) { ) {
if (!descriptor.isParcelize) { if (!descriptor.isParcelize(parcelizeAnnotations)) {
return 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.DESCRIBE_CONTENTS_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.NEW_ARRAY_NAME import org.jetbrains.kotlin.parcelize.ParcelizeNames.NEW_ARRAY_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELER_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.PARCEL_ID import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCEL_ID
import org.jetbrains.kotlin.parcelize.ParcelizeNames.WRITE_TO_PARCEL_NAME import org.jetbrains.kotlin.parcelize.ParcelizeNames.WRITE_TO_PARCEL_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeSyntheticComponent.ComponentKind.DESCRIBE_CONTENTS 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.ErrorTypeKind
import org.jetbrains.kotlin.types.error.ErrorUtils import org.jetbrains.kotlin.types.error.ErrorUtils
open class ParcelizeResolveExtension : SyntheticResolveExtension { open class ParcelizeResolveExtension(private val parcelizeAnnotations: List<FqName>) : SyntheticResolveExtension {
companion object { companion object {
fun resolveParcelClassType(module: ModuleDescriptor): SimpleType? { fun resolveParcelClassType(module: ModuleDescriptor): SimpleType? {
return module.findClassAcrossModuleDependencies(PARCEL_ID)?.defaultType return module.findClassAcrossModuleDependencies(PARCEL_ID)?.defaultType
@@ -107,7 +106,7 @@ open class ParcelizeResolveExtension : SyntheticResolveExtension {
override fun getSyntheticCompanionObjectNameIfNeeded(thisDescriptor: ClassDescriptor): Name? = null override fun getSyntheticCompanionObjectNameIfNeeded(thisDescriptor: ClassDescriptor): Name? = null
override fun getSyntheticFunctionNames(thisDescriptor: ClassDescriptor): List<Name> { 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( override fun generateSyntheticMethods(
@@ -123,7 +122,7 @@ open class ParcelizeResolveExtension : SyntheticResolveExtension {
} }
if (name.asString() == DESCRIBE_CONTENTS.methodName if (name.asString() == DESCRIBE_CONTENTS.methodName
&& thisDescriptor.isParcelize && thisDescriptor.isParcelize(parcelizeAnnotations)
&& !thisDescriptor.isSealed() && !thisDescriptor.isSealed()
&& isParcelizePluginEnabled() && isParcelizePluginEnabled()
&& result.none { it.isDescribeContents() } && result.none { it.isDescribeContents() }
@@ -131,7 +130,7 @@ open class ParcelizeResolveExtension : SyntheticResolveExtension {
) { ) {
result += createMethod(thisDescriptor, DESCRIBE_CONTENTS, Modality.OPEN, thisDescriptor.builtIns.intType) result += createMethod(thisDescriptor, DESCRIBE_CONTENTS, Modality.OPEN, thisDescriptor.builtIns.intType)
} else if (name.asString() == WRITE_TO_PARCEL.methodName } else if (name.asString() == WRITE_TO_PARCEL.methodName
&& thisDescriptor.isParcelize && thisDescriptor.isParcelize(parcelizeAnnotations)
&& !thisDescriptor.isSealed() && !thisDescriptor.isSealed()
&& isParcelizePluginEnabled() && isParcelizePluginEnabled()
&& result.none { it.isWriteToParcel() } && result.none { it.isWriteToParcel() }
@@ -173,13 +172,13 @@ interface ParcelizeSyntheticComponent {
} }
} }
val ClassDescriptor.hasParcelizeAnnotation: Boolean fun ClassDescriptor.hasParcelizeAnnotation(parcelizeAnnotations: List<FqName>): Boolean =
get() = PARCELIZE_CLASS_FQ_NAMES.any(annotations::hasAnnotation) parcelizeAnnotations.any(annotations::hasAnnotation)
val ClassDescriptor.isParcelize: Boolean fun ClassDescriptor.isParcelize(parcelizeAnnotations: List<FqName>): Boolean =
get() = hasParcelizeAnnotation hasParcelizeAnnotation(parcelizeAnnotations)
|| getSuperClassNotAny()?.takeIf(DescriptorUtils::isSealedClass)?.hasParcelizeAnnotation == true || getSuperClassNotAny()?.takeIf(DescriptorUtils::isSealedClass)?.hasParcelizeAnnotation(parcelizeAnnotations) == true
|| getSuperInterfaces().any { DescriptorUtils.isSealedClass(it) && it.hasParcelizeAnnotation } || getSuperInterfaces().any { DescriptorUtils.isSealedClass(it) && it.hasParcelizeAnnotation(parcelizeAnnotations) }
val KotlinType.isParceler: Boolean val KotlinType.isParceler: Boolean
get() = constructor.declarationDescriptor?.fqNameSafe == PARCELER_FQN get() = constructor.declarationDescriptor?.fqNameSafe == PARCELER_FQN
@@ -38,6 +38,7 @@ interface ParcelSerializer {
fun readValue(v: InstructionAdapter) fun readValue(v: InstructionAdapter)
data class ParcelSerializerContext( data class ParcelSerializerContext(
val parcelizeAnnotations: List<FqName>,
val typeMapper: KotlinTypeMapper, val typeMapper: KotlinTypeMapper,
val containerClassType: Type, val containerClassType: Type,
val typeParcelers: List<TypeParcelerMapping>, val typeParcelers: List<TypeParcelerMapping>,
@@ -344,7 +345,7 @@ interface ParcelSerializer {
val creatorAsmType = when { val creatorAsmType = when {
creatorVar != null -> typeMapper.mapTypeSafe(creatorVar.type, forceBoxed = true) 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 else -> null
} }
@@ -36,7 +36,8 @@ interface ParcelizeExtensionBase {
.isNotEmpty() .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) 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.ExpressionCheckers
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirAnnotationCallChecker import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirAnnotationCallChecker
import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.parcelize.fir.diagnostics.* 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 expressionCheckers: ExpressionCheckers = object : ExpressionCheckers() {
override val annotationCallCheckers: Set<FirAnnotationCallChecker> override val annotationCallCheckers: Set<FirAnnotationCallChecker>
get() = setOf(FirParcelizeAnnotationChecker) get() = setOf(FirParcelizeAnnotationChecker(parcelizeAnnotations))
} }
override val declarationCheckers: DeclarationCheckers = object : DeclarationCheckers() { override val declarationCheckers: DeclarationCheckers = object : DeclarationCheckers() {
override val classCheckers: Set<FirClassChecker> override val classCheckers: Set<FirClassChecker>
get() = setOf(FirParcelizeClassChecker) get() = setOf(FirParcelizeClassChecker(parcelizeAnnotations))
override val propertyCheckers: Set<FirPropertyChecker> override val propertyCheckers: Set<FirPropertyChecker>
get() = setOf(FirParcelizePropertyChecker) get() = setOf(FirParcelizePropertyChecker(parcelizeAnnotations))
override val simpleFunctionCheckers: Set<FirSimpleFunctionChecker> override val simpleFunctionCheckers: Set<FirSimpleFunctionChecker>
get() = setOf(FirParcelizeFunctionChecker) get() = setOf(FirParcelizeFunctionChecker(parcelizeAnnotations))
override val constructorCheckers: Set<FirConstructorChecker> 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.symbols.impl.*
import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.CallableId import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.parcelize.ParcelizeNames.DESCRIBE_CONTENTS_NAME import org.jetbrains.kotlin.parcelize.ParcelizeNames.DESCRIBE_CONTENTS_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.DEST_NAME import org.jetbrains.kotlin.parcelize.ParcelizeNames.DEST_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.FLAGS_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.PARCEL_ID
import org.jetbrains.kotlin.parcelize.ParcelizeNames.WRITE_TO_PARCEL_NAME import org.jetbrains.kotlin.parcelize.ParcelizeNames.WRITE_TO_PARCEL_NAME
import org.jetbrains.kotlin.parcelize.fir.diagnostics.checkParcelizeClassSymbols import org.jetbrains.kotlin.parcelize.fir.diagnostics.checkParcelizeClassSymbols
import org.jetbrains.kotlin.utils.addToStdlib.runIf 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 { 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 parcelizeMethodsNames = setOf(DESCRIBE_CONTENTS_NAME, WRITE_TO_PARCEL_NAME)
} }
private val predicate = LookupPredicate.create { annotated(annotations) }
private val matchedClasses by lazy { private val matchedClasses by lazy {
session.predicateBasedProvider.getSymbolsByPredicate(PREDICATE) session.predicateBasedProvider.getSymbolsByPredicate(predicate)
.filterIsInstance<FirRegularClassSymbol>() .filterIsInstance<FirRegularClassSymbol>()
} }
@@ -116,6 +119,6 @@ class FirParcelizeDeclarationGenerator(session: FirSession) : FirDeclarationGene
get() = ParcelizePluginKey get() = ParcelizePluginKey
override fun FirDeclarationPredicateRegistrar.registerPredicates() { override fun FirDeclarationPredicateRegistrar.registerPredicates() {
register(PREDICATE) register(predicate)
} }
} }
@@ -6,10 +6,12 @@
package org.jetbrains.kotlin.parcelize.fir package org.jetbrains.kotlin.parcelize.fir
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar 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() { override fun ExtensionRegistrarContext.configurePlugin() {
+::FirParcelizeDeclarationGenerator +::FirParcelizeDeclarationGenerator.bind(parcelizeAnnotationFqNames)
+::FirParcelizeCheckersExtension +::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
import org.jetbrains.kotlin.parcelize.ParcelizeNames.DEPRECATED_RUNTIME_PACKAGE 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.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.RAW_VALUE_ANNOTATION_CLASS_IDS
import org.jetbrains.kotlin.parcelize.ParcelizeNames.TYPE_PARCELER_CLASS_IDS import org.jetbrains.kotlin.parcelize.ParcelizeNames.TYPE_PARCELER_CLASS_IDS
import org.jetbrains.kotlin.parcelize.ParcelizeNames.WRITE_WITH_CLASS_IDS import org.jetbrains.kotlin.parcelize.ParcelizeNames.WRITE_WITH_CLASS_IDS
// TODO: extract common checker for expect interfaces // 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) { override fun check(expression: FirAnnotationCall, context: CheckerContext, reporter: DiagnosticReporter) {
val annotationType = expression.annotationTypeRef.coneType.fullyExpandedType(context.session) as? ConeClassLikeType ?: return val annotationType = expression.annotationTypeRef.coneType.fullyExpandedType(context.session) as? ConeClassLikeType ?: return
val resolvedAnnotationSymbol = annotationType.lookupTag.toFirRegularClassSymbol(context.session) ?: return val resolvedAnnotationSymbol = annotationType.lookupTag.toFirRegularClassSymbol(context.session) ?: return
@@ -47,7 +47,7 @@ object FirParcelizeAnnotationChecker : FirAnnotationCallChecker(MppCheckerKind.P
in IGNORED_ON_PARCEL_CLASS_IDS -> { in IGNORED_ON_PARCEL_CLASS_IDS -> {
checkDeprecatedAnnotations(expression, annotationClassId, context, reporter, isForbidden = false) 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) 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) { private fun checkIfTheContainingClassIsParcelize(annotationCall: FirAnnotationCall, context: CheckerContext, reporter: DiagnosticReporter) {
val enclosingClass = context.findClosestClassOrObject() ?: return 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 val reportElement = annotationCall.calleeReference.source ?: annotationCall.source
reporter.reportOn(reportElement, KtErrorsParcelize.CLASS_SHOULD_BE_PARCELIZE, enclosingClass.symbol, context) 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.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.SpecialNames import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATOR_NAME import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATOR_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.OLD_PARCELER_ID import org.jetbrains.kotlin.parcelize.ParcelizeNames.OLD_PARCELER_ID
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELABLE_ID import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELABLE_ID
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELER_CLASS_IDS 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.ExperimentalContracts
import kotlin.contracts.contract 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) { override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
checkParcelableClass(declaration, context, reporter) checkParcelableClass(declaration, context, reporter)
checkParcelerClass(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) { private fun checkParcelableClass(klass: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
val symbol = klass.symbol val symbol = klass.symbol
if (!symbol.isParcelize(context.session)) return if (!symbol.isParcelize(context.session, parcelizeAnnotations)) return
val source = klass.source ?: return val source = klass.source ?: return
val classKind = klass.classKind val classKind = klass.classKind
@@ -110,14 +110,14 @@ object FirParcelizeClassChecker : FirClassChecker(MppCheckerKind.Common) {
} }
@OptIn(ExperimentalContracts::class) @OptIn(ExperimentalContracts::class)
fun FirClassSymbol<*>?.isParcelize(session: FirSession): Boolean { fun FirClassSymbol<*>?.isParcelize(session: FirSession, parcelizeAnnotations: List<ClassId>): Boolean {
contract { contract {
returns(true) implies (this@isParcelize != null) returns(true) implies (this@isParcelize != null)
} }
if (this == null) return false if (this == null) return false
return checkParcelizeClassSymbols(this, session) { symbol -> 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.FirConstructor
import org.jetbrains.kotlin.fir.declarations.FirRegularClass import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.toAnnotationClassId import org.jetbrains.kotlin.fir.declarations.toAnnotationClassId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.parcelize.ParcelizeNames 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) { override fun check(declaration: FirConstructor, context: CheckerContext, reporter: DiagnosticReporter) {
if (!declaration.isPrimary) return if (!declaration.isPrimary) return
val source = declaration.source ?: return val source = declaration.source ?: return
if (source.kind == KtFakeSourceElementKind.ImplicitConstructor) return if (source.kind == KtFakeSourceElementKind.ImplicitConstructor) return
val containingClass = context.containingDeclarations.last() as? FirRegularClass ?: return val containingClass = context.containingDeclarations.last() as? FirRegularClass ?: return
val containingClassSymbol = containingClass.symbol 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()) { if (declaration.valueParameters.isEmpty()) {
reporter.reportOn(containingClass.source, KtErrorsParcelize.PARCELABLE_PRIMARY_CONSTRUCTOR_IS_EMPTY, context) 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.isInt
import org.jetbrains.kotlin.fir.types.isUnit import org.jetbrains.kotlin.fir.types.isUnit
import org.jetbrains.kotlin.fir.types.toRegularClassSymbol 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) { override fun check(declaration: FirSimpleFunction, context: CheckerContext, reporter: DiagnosticReporter) {
val containingClassSymbol = declaration.dispatchReceiverType?.toRegularClassSymbol(context.session) 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.origin != FirDeclarationOrigin.Source) return
if (declaration.isWriteToParcel() && declaration.isOverride) { if (declaration.isWriteToParcel() && declaration.isOverride) {
reporter.reportOn(declaration.source, KtErrorsParcelize.OVERRIDING_WRITE_TO_PARCEL_IS_NOT_ALLOWED, context) 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.resolve.lookupSuperTypes
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.parcelize.BuiltinParcelableTypes import org.jetbrains.kotlin.parcelize.BuiltinParcelableTypes
import org.jetbrains.kotlin.parcelize.ParcelizeNames import org.jetbrains.kotlin.parcelize.ParcelizeNames
import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATOR_NAME 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.IGNORED_ON_PARCEL_CLASS_IDS
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELER_ID 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) { override fun check(declaration: FirProperty, context: CheckerContext, reporter: DiagnosticReporter) {
val session = context.session val session = context.session
val containingClassSymbol = declaration.dispatchReceiverType?.toRegularClassSymbol(session) ?: return val containingClassSymbol = declaration.dispatchReceiverType?.toRegularClassSymbol(session) ?: return
if (containingClassSymbol.isParcelize(session)) { if (containingClassSymbol.isParcelize(session, parcelizeAnnotations)) {
val fromPrimaryConstructor = declaration.fromPrimaryConstructor ?: false val fromPrimaryConstructor = declaration.fromPrimaryConstructor ?: false
if ( if (
!fromPrimaryConstructor && !fromPrimaryConstructor &&
@@ -54,7 +55,7 @@ object FirParcelizePropertyChecker : FirPropertyChecker(MppCheckerKind.Common) {
if (declaration.name == CREATOR_NAME && containingClassSymbol.isCompanion && declaration.hasJvmFieldAnnotation(session)) { if (declaration.name == CREATOR_NAME && containingClassSymbol.isCompanion && declaration.hasJvmFieldAnnotation(session)) {
val outerClass = context.containingDeclarations.asReversed().getOrNull(1) as? FirRegularClass 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) 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.compiler.plugin.CompilerPluginRegistrar
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.parcelize.ParcelizeComponentRegistrar import org.jetbrains.kotlin.parcelize.ParcelizeComponentRegistrar
import org.jetbrains.kotlin.parcelize.ParcelizeConfigurationKeys
import org.jetbrains.kotlin.parcelize.kotlinxImmutable import org.jetbrains.kotlin.parcelize.kotlinxImmutable
import org.jetbrains.kotlin.test.model.FrontendKinds import org.jetbrains.kotlin.test.model.FrontendKinds
import org.jetbrains.kotlin.test.model.TestModule import org.jetbrains.kotlin.test.model.TestModule
@@ -47,6 +48,11 @@ class ParcelizeEnvironmentConfigurator(testServices: TestServices) : Environment
module: TestModule, module: TestModule,
configuration: CompilerConfiguration 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
)
} }
} }