[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:
+10
-6
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
+9
-6
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -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) })
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -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)
|
||||
}
|
||||
|
||||
+5
-5
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-2
@@ -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)
|
||||
|
||||
+3
-2
@@ -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)
|
||||
|
||||
+4
-3
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user