[Parcelize] Reorganize module structure of Parcelize plugin

Also mark parcelize as compatible with K2
This commit is contained in:
Dmitriy Novozhilov
2022-05-17 15:44:02 +03:00
committed by teamcity
parent 2a7dc1cc0c
commit 259862d294
45 changed files with 193 additions and 127 deletions
@@ -0,0 +1,30 @@
description = "Parcelize compiler plugin (Backend)"
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
implementation(project(":plugins:parcelize:parcelize-compiler:parcelize.common"))
compileOnly(intellijCore())
compileOnly(project(":compiler:fir:cones"))
compileOnly(project(":compiler:fir:tree"))
compileOnly(project(":compiler:fir:resolve"))
compileOnly(project(":compiler:fir:checkers"))
compileOnly(project(":compiler:fir:checkers:checkers.jvm"))
compileOnly(project(":compiler:ir.backend.common"))
compileOnly(project(":compiler:ir.tree.impl"))
compileOnly(project(":compiler:fir:entrypoint"))
compileOnly(project(":kotlin-reflect-api"))
}
sourceSets {
"main" { projectDefault() }
"test" { none() }
}
runtimeJar()
javadocJar()
sourcesJar()
@@ -0,0 +1,34 @@
/*
* Copyright 2010-2021 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.fir
import org.jetbrains.kotlin.fir.FirSession
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.parcelize.fir.diagnostics.*
class FirParcelizeCheckersExtension(session: FirSession) : FirAdditionalCheckersExtension(session) {
override val expressionCheckers: ExpressionCheckers = object : ExpressionCheckers() {
override val annotationCallCheckers: Set<FirAnnotationCallChecker>
get() = setOf(FirParcelizeAnnotationChecker)
}
override val declarationCheckers: DeclarationCheckers = object : DeclarationCheckers() {
override val classCheckers: Set<FirClassChecker>
get() = setOf(FirParcelizeClassChecker)
override val propertyCheckers: Set<FirPropertyChecker>
get() = setOf(FirParcelizePropertyChecker)
override val simpleFunctionCheckers: Set<FirSimpleFunctionChecker>
get() = setOf(FirParcelizeFunctionChecker)
override val constructorCheckers: Set<FirConstructorChecker>
get() = setOf(FirParcelizeConstructorChecker)
}
}
@@ -0,0 +1,185 @@
/*
* Copyright 2010-2021 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.fir
import org.jetbrains.kotlin.descriptors.EffectiveVisibility
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirPluginKey
import org.jetbrains.kotlin.fir.declarations.builder.FirSimpleFunctionBuilder
import org.jetbrains.kotlin.fir.declarations.builder.buildSimpleFunction
import org.jetbrains.kotlin.fir.declarations.builder.buildValueParameter
import org.jetbrains.kotlin.fir.declarations.impl.FirResolvedDeclarationStatusImpl
import org.jetbrains.kotlin.fir.declarations.utils.modality
import org.jetbrains.kotlin.fir.extensions.FirDeclarationGenerationExtension
import org.jetbrains.kotlin.fir.extensions.FirDeclarationPredicateRegistrar
import org.jetbrains.kotlin.fir.extensions.predicate.has
import org.jetbrains.kotlin.fir.extensions.predicateBasedProvider
import org.jetbrains.kotlin.fir.moduleData
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.lookupSuperTypes
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef
import org.jetbrains.kotlin.fir.types.classId
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.types.toRegularClassSymbol
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
import org.jetbrains.kotlin.fir.types.isInt
import org.jetbrains.kotlin.name.CallableId
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.utils.addToStdlib.runIf
class FirParcelizeDeclarationGenerator(session: FirSession) : FirDeclarationGenerationExtension(session) {
companion object {
private val PREDICATE = has(PARCELIZE_FQN, OLD_PARCELIZE_FQN)
private val parcelizeMethodsNames = setOf(DESCRIBE_CONTENTS_NAME, WRITE_TO_PARCEL_NAME)
}
private val matchedClasses by lazy {
session.predicateBasedProvider.getSymbolsByPredicate(PREDICATE)
.filterIsInstance<FirRegularClassSymbol>()
}
override fun generateFunctions(callableId: CallableId, owner: FirClassSymbol<*>?): List<FirNamedFunctionSymbol> {
if (owner == null) return emptyList()
require(owner is FirRegularClassSymbol)
val functionSymbol = when (callableId.callableName) {
DESCRIBE_CONTENTS_NAME -> {
val hasDescribeContentImplementation = owner.hasDescribeContentsImplementation() ||
lookupSuperTypes(owner, lookupInterfaces = false, deep = true, session).any {
it.fullyExpandedType(session).toRegularClassSymbol(session)?.hasDescribeContentsImplementation() ?: false
}
runIf(!hasDescribeContentImplementation) {
generateDescribeContents(owner, callableId)
}
}
WRITE_TO_PARCEL_NAME -> {
val declaredFunctions = owner.declarationSymbols.filterIsInstance<FirNamedFunctionSymbol>()
runIf(declaredFunctions.none { it.isWriteToParcel() }) { generateWriteToParcel(owner, callableId) }
}
else -> null
}
return listOfNotNull(functionSymbol)
}
private fun FirRegularClassSymbol.hasDescribeContentsImplementation(): Boolean {
return declarationSymbols.filterIsInstance<FirNamedFunctionSymbol>().any { it.isDescribeContentsImplementation() }
}
private fun FirNamedFunctionSymbol.isDescribeContentsImplementation(): Boolean {
if (name != DESCRIBE_CONTENTS_NAME) return false
return valueParameterSymbols.isEmpty()
}
private fun FirNamedFunctionSymbol.isWriteToParcel(): Boolean {
if (name != WRITE_TO_PARCEL_NAME) return false
val parameterSymbols = valueParameterSymbols
if (parameterSymbols.size != 2) return false
val (destSymbol, flagsSymbol) = parameterSymbols
if (destSymbol.resolvedReturnTypeRef.coneType.classId != PARCEL_ID) return false
if (!flagsSymbol.resolvedReturnTypeRef.type.isInt) return false
return true
}
private fun generateDescribeContents(owner: FirRegularClassSymbol, callableId: CallableId): FirNamedFunctionSymbol {
return createFunction(owner, callableId) {
returnTypeRef = session.builtinTypes.intType
}
}
private fun generateWriteToParcel(owner: FirRegularClassSymbol, callableId: CallableId): FirNamedFunctionSymbol {
return createFunction(owner, callableId) {
returnTypeRef = session.builtinTypes.unitType
valueParameters += buildValueParameter {
moduleData = session.moduleData
origin = key.origin
name = DEST_NAME
returnTypeRef = buildResolvedTypeRef {
type = ConeClassLikeTypeImpl(
ConeClassLikeLookupTagImpl(PARCEL_ID),
emptyArray(),
isNullable = false
)
}
symbol = FirValueParameterSymbol(name)
isCrossinline = false
isNoinline = false
isVararg = false
}
valueParameters += buildValueParameter {
moduleData = session.moduleData
origin = key.origin
name = FLAGS_NAME
returnTypeRef = session.builtinTypes.intType
symbol = FirValueParameterSymbol(name)
isCrossinline = false
isNoinline = false
isVararg = false
}
}
}
private inline fun createFunction(
owner: FirRegularClassSymbol,
callableId: CallableId,
init: FirSimpleFunctionBuilder.() -> Unit
): FirNamedFunctionSymbol {
val function = buildSimpleFunction {
moduleData = session.moduleData
origin = key.origin
status = FirResolvedDeclarationStatusImpl(
Visibilities.Public,
if (owner.modality == Modality.FINAL) Modality.FINAL else Modality.OPEN,
EffectiveVisibility.Public
).apply {
isOverride = true
}
name = callableId.callableName
symbol = FirNamedFunctionSymbol(callableId)
dispatchReceiverType = owner.defaultType()
init()
}
return function.symbol
}
@OptIn(SymbolInternals::class)
override fun getCallableNamesForClass(classSymbol: FirClassSymbol<*>): Set<Name> {
return when {
classSymbol.fir.modality == Modality.ABSTRACT -> emptySet()
classSymbol in matchedClasses && classSymbol.fir.modality != Modality.SEALED -> parcelizeMethodsNames
else -> {
val hasAnnotatedSealedSuperType = classSymbol.resolvedSuperTypeRefs.any {
val superSymbol = it.type.fullyExpandedType(session).toRegularClassSymbol(session) ?: return@any false
superSymbol.fir.modality == Modality.SEALED && superSymbol in matchedClasses
}
if (hasAnnotatedSealedSuperType) {
parcelizeMethodsNames
} else {
emptySet()
}
}
}
}
private val key: FirPluginKey
get() = FirParcelizePluginKey
override fun FirDeclarationPredicateRegistrar.registerPredicates() {
register(PREDICATE)
}
}
@@ -0,0 +1,15 @@
/*
* Copyright 2010-2021 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.fir
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar
class FirParcelizeExtensionRegistrar : FirExtensionRegistrar() {
override fun ExtensionRegistrarContext.configurePlugin() {
+::FirParcelizeDeclarationGenerator
+::FirParcelizeCheckersExtension
}
}
@@ -0,0 +1,14 @@
/*
* Copyright 2010-2021 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.fir
import org.jetbrains.kotlin.fir.declarations.FirPluginKey
object FirParcelizePluginKey : FirPluginKey() {
override fun toString(): String {
return "FirParcelize"
}
}
@@ -0,0 +1,69 @@
/*
* Copyright 2010-2021 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.fir.diagnostics
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirAnnotationCallChecker
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.name.ClassId
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
object FirParcelizeAnnotationChecker : FirAnnotationCallChecker() {
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
when (val annotationClassId = resolvedAnnotationSymbol.classId) {
in TYPE_PARCELER_CLASS_IDS -> {
checkTypeParcelerUsage(expression, context, reporter)
checkDeprecatedAnnotations(expression, annotationClassId, context, reporter, isForbidden = true)
}
in WRITE_WITH_CLASS_IDS -> {
checkWriteWithUsage(expression, context, reporter)
checkDeprecatedAnnotations(expression, annotationClassId, context, reporter, isForbidden = true)
}
in IGNORED_ON_PARCEL_CLASS_IDS -> {
checkDeprecatedAnnotations(expression, annotationClassId, context, reporter, isForbidden = false)
}
in PARCELIZE_CLASS_CLASS_IDS, in RAW_VALUE_ANNOTATION_CLASS_IDS -> {
checkDeprecatedAnnotations(expression, annotationClassId, context, reporter, isForbidden = false)
}
}
}
private fun checkDeprecatedAnnotations(
annotationCall: FirAnnotationCall,
annotationClassId: ClassId,
context: CheckerContext,
reporter: DiagnosticReporter,
isForbidden: Boolean
) {
if (annotationClassId.packageFqName == DEPRECATED_RUNTIME_PACKAGE) {
val factory = if (isForbidden) KtErrorsParcelize.FORBIDDEN_DEPRECATED_ANNOTATION else KtErrorsParcelize.DEPRECATED_ANNOTATION
reporter.reportOn(annotationCall.source, factory, context)
}
}
@Suppress("UNUSED_PARAMETER")
private fun checkTypeParcelerUsage(annotationCall: FirAnnotationCall, context: CheckerContext, reporter: DiagnosticReporter) {
// TODO: this check checks type arguments of annotation which are not supported in FIR
}
@Suppress("UNUSED_PARAMETER")
private fun checkWriteWithUsage(annotationCall: FirAnnotationCall, context: CheckerContext, reporter: DiagnosticReporter) {
// TODO: this check checks type arguments of annotation which are not supported in FIR
}
}
@@ -0,0 +1,141 @@
/*
* Copyright 2010-2021 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.fir.diagnostics
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.withSuppressedDiagnostics
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.constructors
import org.jetbrains.kotlin.fir.declarations.delegateFieldsMap
import org.jetbrains.kotlin.fir.declarations.utils.*
import org.jetbrains.kotlin.fir.expressions.classId
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.lookupSuperTypes
import org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.types.classId
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.types.toRegularClassSymbol
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
import org.jetbrains.kotlin.fir.types.isSubtypeOf
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() {
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
checkParcelableClass(declaration, context, reporter)
checkParcelerClass(declaration, context, reporter)
}
private fun checkParcelableClass(klass: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
val symbol = klass.symbol
if (!symbol.isParcelize(context.session)) return
val source = klass.source ?: return
if (klass !is FirRegularClass) {
reporter.reportOn(source, KtErrorsParcelize.PARCELABLE_SHOULD_BE_CLASS, context)
return
}
val classKind = klass.classKind
if (classKind == ClassKind.ANNOTATION_CLASS || classKind == ClassKind.INTERFACE && !klass.isSealed) {
reporter.reportOn(source, KtErrorsParcelize.PARCELABLE_SHOULD_BE_CLASS, context)
return
}
klass.companionObjectSymbol?.let { companionSymbol ->
if (companionSymbol.classId.shortClassName == CREATOR_NAME) {
reporter.reportOn(companionSymbol.source, KtErrorsParcelize.CREATOR_DEFINITION_IS_NOT_ALLOWED, context)
}
}
if (classKind == ClassKind.CLASS && klass.isAbstract) {
reporter.reportOn(source, KtErrorsParcelize.PARCELABLE_SHOULD_BE_INSTANTIABLE, context)
}
if (klass.isInner) {
reporter.reportOn(source, KtErrorsParcelize.PARCELABLE_CANT_BE_INNER_CLASS, context)
}
if (klass.isLocal) {
reporter.reportOn(source, KtErrorsParcelize.PARCELABLE_CANT_BE_LOCAL_CLASS, context)
}
val supertypes = lookupSuperTypes(klass, lookupInterfaces = true, deep = true, context.session, substituteTypes = false)
if (supertypes.none { it.classId == PARCELABLE_ID }) {
reporter.reportOn(source, KtErrorsParcelize.NO_PARCELABLE_SUPERTYPE, context)
}
klass.delegateFieldsMap?.forEach { (index, _) ->
val superTypeRef = klass.superTypeRefs[index]
val superType = superTypeRef.coneType
val parcelableType = ConeClassLikeTypeImpl(
ConeClassLikeLookupTagImpl(PARCELABLE_ID),
emptyArray(),
isNullable = false
)
if (superType.isSubtypeOf(parcelableType, context.session)) {
reporter.reportOn(superTypeRef.source, KtErrorsParcelize.PARCELABLE_DELEGATE_IS_NOT_ALLOWED, context)
}
}
val constructorSymbols = klass.constructors(context.session)
val primaryConstructorSymbol = constructorSymbols.find { it.isPrimary }
val secondaryConstructorSymbols = constructorSymbols.filterNot { it.isPrimary }
if (primaryConstructorSymbol == null && secondaryConstructorSymbols.isNotEmpty()) {
reporter.reportOn(source, KtErrorsParcelize.PARCELABLE_SHOULD_HAVE_PRIMARY_CONSTRUCTOR, context)
}
}
private fun checkParcelerClass(klass: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
if (klass !is FirRegularClass || klass.isCompanion) return
for (superTypeRef in klass.superTypeRefs) {
withSuppressedDiagnostics(superTypeRef, context) {
if (superTypeRef.coneType.classId == OLD_PARCELER_ID) {
val strategy = if (klass.name == SpecialNames.NO_NAME_PROVIDED) {
SourceElementPositioningStrategies.OBJECT_KEYWORD
} else {
SourceElementPositioningStrategies.NAME_IDENTIFIER
}
reporter.reportOn(klass.source, KtErrorsParcelize.DEPRECATED_PARCELER, it, positioningStrategy = strategy)
}
}
}
}
}
@OptIn(ExperimentalContracts::class)
fun FirClassSymbol<*>?.isParcelize(session: FirSession): Boolean {
contract {
returns(true) implies (this@isParcelize != null)
}
if (this == null) return false
if (this.annotations.any { it.classId in PARCELIZE_CLASS_CLASS_IDS }) return true
return resolvedSuperTypeRefs.any {
val symbol = it.type.fullyExpandedType(session).toRegularClassSymbol(session) ?: return@any false
symbol.annotations.any { it.classId in PARCELIZE_CLASS_CLASS_IDS }
}
}
fun FirRegularClass.hasCustomParceler(session: FirSession): Boolean {
val companion = companionObjectSymbol ?: return false
return lookupSuperTypes(companion, lookupInterfaces = true, deep = true, useSiteSession = session).any {
it.classId in PARCELER_CLASS_IDS
}
}
@@ -0,0 +1,54 @@
/*
* Copyright 2010-2021 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.fir.diagnostics
import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.hasValOrVar
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirConstructorChecker
import org.jetbrains.kotlin.fir.declarations.FirConstructor
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.expressions.classId
import org.jetbrains.kotlin.parcelize.ParcelizeNames
object FirParcelizeConstructorChecker : FirConstructorChecker() {
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 (declaration.valueParameters.isEmpty()) {
reporter.reportOn(containingClass.source, KtErrorsParcelize.PARCELABLE_PRIMARY_CONSTRUCTOR_IS_EMPTY, context)
} else {
for (valueParameter in declaration.valueParameters) {
if (valueParameter.source?.hasValOrVar() != true) {
reporter.reportOn(
valueParameter.source,
KtErrorsParcelize.PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR,
context
)
}
if (valueParameter.defaultValue == null) {
val illegalAnnotation = valueParameter.annotations.firstOrNull {
it.classId in ParcelizeNames.IGNORED_ON_PARCEL_CLASS_IDS
}
if (illegalAnnotation != null) {
reporter.reportOn(
illegalAnnotation.source,
KtErrorsParcelize.INAPPLICABLE_IGNORED_ON_PARCEL_CONSTRUCTOR_PROPERTY,
context
)
}
}
}
}
}
}
@@ -0,0 +1,36 @@
/*
* Copyright 2010-2021 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.fir.diagnostics
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirSimpleFunctionChecker
import org.jetbrains.kotlin.fir.types.toRegularClassSymbol
import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.fir.declarations.utils.isOverride
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.types.isInt
import org.jetbrains.kotlin.fir.types.isUnit
object FirParcelizeFunctionChecker : FirSimpleFunctionChecker() {
override fun check(declaration: FirSimpleFunction, context: CheckerContext, reporter: DiagnosticReporter) {
val containingClassSymbol = declaration.dispatchReceiverType?.toRegularClassSymbol(context.session)
if (!containingClassSymbol.isParcelize(context.session)) 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)
}
}
private fun FirSimpleFunction.isWriteToParcel(): Boolean {
return typeParameters.isEmpty() &&
valueParameters.size == 2 &&
valueParameters[1].returnTypeRef.coneType.isInt &&
returnTypeRef.coneType.isUnit
}
}
@@ -0,0 +1,92 @@
/*
* Copyright 2010-2021 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.fir.diagnostics
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirPropertyChecker
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.hasJvmFieldAnnotation
import org.jetbrains.kotlin.fir.declarations.utils.fromPrimaryConstructor
import org.jetbrains.kotlin.fir.declarations.utils.hasBackingField
import org.jetbrains.kotlin.fir.declarations.utils.isCompanion
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.resolve.lookupSuperTypes
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.ConeErrorType
import org.jetbrains.kotlin.fir.types.classId
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.types.toRegularClassSymbol
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() {
override fun check(declaration: FirProperty, context: CheckerContext, reporter: DiagnosticReporter) {
val containingClassSymbol = declaration.dispatchReceiverType?.toRegularClassSymbol(context.session) ?: return
if (containingClassSymbol.isParcelize(context.session)) {
val fromPrimaryConstructor = declaration.fromPrimaryConstructor ?: false
if (
!fromPrimaryConstructor &&
(declaration.hasBackingField || declaration.delegate != null) &&
!declaration.hasIgnoredOnParcel() &&
!containingClassSymbol.hasCustomParceler(context.session)
) {
reporter.reportOn(declaration.source, KtErrorsParcelize.PROPERTY_WONT_BE_SERIALIZED, context)
}
if (fromPrimaryConstructor) {
checkParcelableClassProperty(declaration, containingClassSymbol, context, reporter)
}
}
if (declaration.name == CREATOR_NAME && containingClassSymbol.isCompanion && declaration.hasJvmFieldAnnotation) {
val outerClass = context.containingDeclarations.asReversed().getOrNull(1) as? FirRegularClass
if (outerClass != null && outerClass.symbol.isParcelize(context.session)) {
reporter.reportOn(declaration.source, KtErrorsParcelize.CREATOR_DEFINITION_IS_NOT_ALLOWED, context)
}
}
}
@Suppress("UNUSED_PARAMETER")
private fun checkParcelableClassProperty(
property: FirProperty,
containingClassSymbol: FirRegularClassSymbol,
context: CheckerContext,
reporter: DiagnosticReporter
) {
val type = property.returnTypeRef.coneType
if (type is ConeErrorType || containingClassSymbol.hasCustomParceler(context.session)) return
/*
* TODO: abstract code from ParcelSerializer or IrParcelSerializerFactory to avoid duplication
* of allowed types checking
*/
}
private fun FirProperty.hasIgnoredOnParcel(): Boolean {
return annotations.hasIgnoredOnParcel() || (getter?.annotations?.hasIgnoredOnParcel() ?: false)
}
private fun List<FirAnnotation>.hasIgnoredOnParcel(): Boolean {
return this.any {
if (it.annotationTypeRef.coneType.classId !in IGNORED_ON_PARCEL_CLASS_IDS) return@any false
val target = it.useSiteTarget
target == null || target == AnnotationUseSiteTarget.PROPERTY || target == AnnotationUseSiteTarget.PROPERTY_GETTER
}
}
private fun FirRegularClassSymbol.hasCustomParceler(session: FirSession): Boolean {
val companionObjectSymbol = this.companionObjectSymbol ?: return false
return lookupSuperTypes(companionObjectSymbol, lookupInterfaces = true, deep = true, session).any {
it.classId == PARCELER_ID
}
}
}
@@ -0,0 +1,180 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.parcelize.fir.diagnostics
import org.jetbrains.kotlin.diagnostics.KtDiagnostic
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.diagnostics.KtDiagnosticRenderer
import org.jetbrains.kotlin.diagnostics.rendering.Renderers.RENDER_CLASS_OR_OBJECT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.RENDER_TYPE
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.CLASS_SHOULD_BE_PARCELIZE
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.CREATOR_DEFINITION_IS_NOT_ALLOWED
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.DEPRECATED_ANNOTATION
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.DEPRECATED_PARCELER
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.DUPLICATING_TYPE_PARCELERS
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.FORBIDDEN_DEPRECATED_ANNOTATION
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.INAPPLICABLE_IGNORED_ON_PARCEL
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.INAPPLICABLE_IGNORED_ON_PARCEL_CONSTRUCTOR_PROPERTY
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.NO_PARCELABLE_SUPERTYPE
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.OVERRIDING_WRITE_TO_PARCEL_IS_NOT_ALLOWED
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.PARCELABLE_CANT_BE_INNER_CLASS
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.PARCELABLE_CANT_BE_LOCAL_CLASS
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.PARCELABLE_DELEGATE_IS_NOT_ALLOWED
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.PARCELABLE_PRIMARY_CONSTRUCTOR_IS_EMPTY
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.PARCELABLE_SHOULD_BE_CLASS
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.PARCELABLE_SHOULD_BE_INSTANTIABLE
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.PARCELABLE_SHOULD_HAVE_PRIMARY_CONSTRUCTOR
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.PARCELABLE_SHOULD_NOT_BE_ENUM_CLASS
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.PARCELABLE_TYPE_NOT_SUPPORTED
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.PARCELER_SHOULD_BE_OBJECT
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.PARCELER_TYPE_INCOMPATIBLE
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.PROPERTY_WONT_BE_SERIALIZED
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.REDUNDANT_TYPE_PARCELER
object KtDefaultErrorMessagesParcelize {
fun getRendererForDiagnostic(diagnostic: KtDiagnostic): KtDiagnosticRenderer {
val factory = diagnostic.factory
return MAP[factory] ?: factory.ktRenderer
}
val MAP = KtDiagnosticFactoryToRendererMap("Parcelize").also { map ->
map.put(
PARCELABLE_SHOULD_BE_CLASS,
"'Parcelable' should be a class"
)
map.put(
PARCELABLE_DELEGATE_IS_NOT_ALLOWED,
"Delegating 'Parcelable' is not allowed"
)
map.put(
PARCELABLE_SHOULD_NOT_BE_ENUM_CLASS,
"'Parcelable' should not be a 'enum class'"
)
map.put(
PARCELABLE_SHOULD_BE_INSTANTIABLE,
"'Parcelable' should not be an 'abstract' class"
)
map.put(
PARCELABLE_CANT_BE_INNER_CLASS,
"'Parcelable' can't be an inner class"
)
map.put(
PARCELABLE_CANT_BE_LOCAL_CLASS,
"'Parcelable' can't be a local class"
)
map.put(
NO_PARCELABLE_SUPERTYPE,
"No 'Parcelable' supertype"
)
map.put(
PARCELABLE_SHOULD_HAVE_PRIMARY_CONSTRUCTOR,
"'Parcelable' should have a primary constructor"
)
map.put(
PARCELABLE_PRIMARY_CONSTRUCTOR_IS_EMPTY,
"The primary constructor is empty, no data will be serialized to 'Parcel'"
)
map.put(
PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR,
"'Parcelable' constructor parameter should be 'val' or 'var'"
)
map.put(
PROPERTY_WONT_BE_SERIALIZED,
"Property would not be serialized into a 'Parcel'. Add '@IgnoredOnParcel' annotation to remove the warning"
)
map.put(
OVERRIDING_WRITE_TO_PARCEL_IS_NOT_ALLOWED,
"Overriding 'writeToParcel' is not allowed. Use 'Parceler' companion object instead"
)
map.put(
CREATOR_DEFINITION_IS_NOT_ALLOWED,
"'CREATOR' definition is not allowed. Use 'Parceler' companion object instead"
)
map.put(
PARCELABLE_TYPE_NOT_SUPPORTED,
"Type is not directly supported by 'Parcelize'. " +
"Annotate the parameter type with '@RawValue' if you want it to be serialized using 'writeValue()'"
)
map.put(
PARCELER_SHOULD_BE_OBJECT,
"Parceler should be an object"
)
map.put(
PARCELER_TYPE_INCOMPATIBLE,
"Parceler type {0} is incompatible with {1}",
RENDER_TYPE, RENDER_TYPE
)
map.put(
DUPLICATING_TYPE_PARCELERS,
"Duplicating ''TypeParceler'' annotations"
)
map.put(
REDUNDANT_TYPE_PARCELER,
"This ''TypeParceler'' is already provided for {0}",
RENDER_CLASS_OR_OBJECT
)
map.put(
CLASS_SHOULD_BE_PARCELIZE,
"{0} should be annotated with ''@Parcelize''",
RENDER_CLASS_OR_OBJECT
)
map.put(
INAPPLICABLE_IGNORED_ON_PARCEL,
"'@IgnoredOnParcel' is only applicable to class properties"
)
map.put(
INAPPLICABLE_IGNORED_ON_PARCEL_CONSTRUCTOR_PROPERTY,
"'@IgnoredOnParcel' is inapplicable to properties without default value declared in the primary constructor"
)
map.put(
FORBIDDEN_DEPRECATED_ANNOTATION,
"Parceler-related annotations from package 'kotlinx.android.parcel' are forbidden. Change package to 'kotlinx.parcelize'"
)
map.put(
DEPRECATED_ANNOTATION,
"Parcelize annotations from package 'kotlinx.android.parcel' are deprecated. Change package to 'kotlinx.parcelize'"
)
map.put(
DEPRECATED_PARCELER,
"'kotlinx.android.parcel.Parceler' is deprecated. Use 'kotlinx.parcelize.Parceler' instead"
)
}
}
@@ -0,0 +1,53 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.parcelize.fir.diagnostics
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.diagnostics.*
import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies.ABSTRACT_MODIFIER
import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies.DELEGATED_SUPERTYPE_BY_KEYWORD
import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies.INNER_MODIFIER
import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies.NAME_IDENTIFIER
import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies.OVERRIDE_MODIFIER
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.psi.KtClassOrObject
object KtErrorsParcelize {
val PARCELABLE_SHOULD_BE_CLASS by error0<PsiElement>(NAME_IDENTIFIER)
val PARCELABLE_DELEGATE_IS_NOT_ALLOWED by error0<PsiElement>(DELEGATED_SUPERTYPE_BY_KEYWORD)
val PARCELABLE_SHOULD_NOT_BE_ENUM_CLASS by error0<PsiElement>()
val PARCELABLE_SHOULD_BE_INSTANTIABLE by error0<PsiElement>(ABSTRACT_MODIFIER)
val PARCELABLE_CANT_BE_INNER_CLASS by error0<PsiElement>(INNER_MODIFIER)
val PARCELABLE_CANT_BE_LOCAL_CLASS by error0<PsiElement>(NAME_IDENTIFIER)
val NO_PARCELABLE_SUPERTYPE by error0<PsiElement>(NAME_IDENTIFIER)
val PARCELABLE_SHOULD_HAVE_PRIMARY_CONSTRUCTOR by error0<PsiElement>(NAME_IDENTIFIER)
val PARCELABLE_PRIMARY_CONSTRUCTOR_IS_EMPTY by warning0<PsiElement>(NAME_IDENTIFIER)
val PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR by error0<PsiElement>(NAME_IDENTIFIER)
val PROPERTY_WONT_BE_SERIALIZED by warning0<PsiElement>(NAME_IDENTIFIER)
val OVERRIDING_WRITE_TO_PARCEL_IS_NOT_ALLOWED by error0<PsiElement>(OVERRIDE_MODIFIER)
val CREATOR_DEFINITION_IS_NOT_ALLOWED by error0<PsiElement>(NAME_IDENTIFIER)
val PARCELABLE_TYPE_NOT_SUPPORTED by error0<PsiElement>()
val PARCELER_SHOULD_BE_OBJECT by error0<PsiElement>()
val PARCELER_TYPE_INCOMPATIBLE by error2<PsiElement, ConeKotlinType, ConeKotlinType>()
val DUPLICATING_TYPE_PARCELERS by error0<PsiElement>()
val REDUNDANT_TYPE_PARCELER by warning1<PsiElement, KtClassOrObject>()
val CLASS_SHOULD_BE_PARCELIZE by error1<PsiElement, KtClassOrObject>()
val FORBIDDEN_DEPRECATED_ANNOTATION by error0<PsiElement>()
val DEPRECATED_ANNOTATION by warning0<PsiElement>()
val DEPRECATED_PARCELER by error0<PsiElement>()
val INAPPLICABLE_IGNORED_ON_PARCEL by warning0<PsiElement>()
val INAPPLICABLE_IGNORED_ON_PARCEL_CONSTRUCTOR_PROPERTY by warning0<PsiElement>()
}