Prototype of kotlinx.serialization FIR plugin:

Implement basic non-parameterized case
This commit is contained in:
Leonid Startsev
2022-01-19 15:09:08 +03:00
committed by Space
parent ed92cc43c7
commit 6f6342dafc
16 changed files with 779 additions and 1 deletions
@@ -36,6 +36,7 @@ data class FirExtensionPointName(val name: Name) {
constructor(name: String) : this(Name.identifier(name))
}
// todo: KDOC
abstract class FirDeclarationPredicateRegistrar {
abstract fun register(vararg predicates: DeclarationPredicate)
abstract fun register(predicates: Collection<DeclarationPredicate>)
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.fir.extensions.AnnotationFqn
// -------------------------------------------- Predicates --------------------------------------------
// todo: Missing KDOC
sealed class DeclarationPredicate {
abstract val annotations: Set<AnnotationFqn>
abstract val metaAnnotations: Set<AnnotationFqn>
@@ -61,6 +61,7 @@ fun FirDeclarationGenerationExtension.buildMaterializeFunction(
}
}
// FIXME: this has to be shared
@OptIn(SymbolInternals::class)
fun FirDeclarationGenerationExtension.buildConstructor(classId: ClassId, isInner: Boolean, key: GeneratedDeclarationKey): FirConstructor {
val lookupTag = ConeClassLikeLookupTagImpl(classId)
@@ -92,6 +93,7 @@ fun FirDeclarationGenerationExtension.buildConstructor(classId: ClassId, isInner
}
}
// FIXME: this has to be shared
fun ClassId.toSimpleConeType(typeArguments: Array<ConeKotlinTypeProjection> = emptyArray()): ConeClassLikeType {
return ConeClassLikeTypeImpl(
ConeClassLikeLookupTagImpl(this),
@@ -19,6 +19,16 @@ dependencies {
compileOnly(project(":js:js.translator"))
compileOnly(project(":kotlin-util-klib-metadata"))
// FIR dependencies
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:fir:fir2ir"))
// compileOnly(project(":compiler:ir.tree.impl"))
compileOnly(project(":compiler:fir:entrypoint"))
runtimeOnly(kotlinStdlib())
testApi(projectTests(":compiler:tests-common"))
@@ -44,6 +54,7 @@ sourceSets {
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>> {
kotlinOptions {
freeCompilerArgs += "-opt-in=org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI"
freeCompilerArgs += "-opt-in=org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi"
}
}
@@ -0,0 +1,90 @@
/*
* Copyright 2010-2022 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.kotlinx.serialization.compiler.fir
import org.jetbrains.kotlin.GeneratedDeclarationKey
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.containingClassForStaticMemberAttr
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirConstructor
import org.jetbrains.kotlin.fir.declarations.builder.FirSimpleFunctionBuilder
import org.jetbrains.kotlin.fir.declarations.builder.buildPrimaryConstructor
import org.jetbrains.kotlin.fir.declarations.builder.buildSimpleFunction
import org.jetbrains.kotlin.fir.declarations.impl.FirResolvedDeclarationStatusImpl
import org.jetbrains.kotlin.fir.declarations.origin
import org.jetbrains.kotlin.fir.declarations.utils.modality
import org.jetbrains.kotlin.fir.extensions.FirDeclarationGenerationExtension
import org.jetbrains.kotlin.fir.moduleData
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
// FIXME: this has to be shared (copied from plugin example)
@OptIn(SymbolInternals::class)
fun FirDeclarationGenerationExtension.buildConstructor(classId: ClassId, isInner: Boolean, key: GeneratedDeclarationKey): FirConstructor {
val lookupTag = ConeClassLikeLookupTagImpl(classId)
return buildPrimaryConstructor {
moduleData = session.moduleData
origin = key.origin
returnTypeRef = buildResolvedTypeRef {
type = ConeClassLikeTypeImpl(
lookupTag,
emptyArray(),
isNullable = false
)
}
status = FirResolvedDeclarationStatusImpl(
Visibilities.Public,
Modality.FINAL,
EffectiveVisibility.Public
)
symbol = FirConstructorSymbol(classId)
if (isInner && classId.isNestedClass) {
dispatchReceiverType = classId.parentClassId?.let {
val firClass = session.symbolProvider.getClassLikeSymbolByClassId(it)?.fir as? FirClass
firClass?.defaultType()
}
}
}.also {
it.containingClassForStaticMemberAttr = lookupTag
}
}
// FIXME: copied from Parcelize plugin
inline fun FirSession.createFunction(
owner: FirRegularClassSymbol,
callableId: CallableId,
init: FirSimpleFunctionBuilder.() -> Unit
): FirNamedFunctionSymbol {
val function = buildSimpleFunction {
moduleData = this@createFunction.moduleData
origin = SerializationPluginKey.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
}
@@ -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.kotlinx.serialization.compiler.fir
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar
class FirSerializationExtensionRegistrar: FirExtensionRegistrar() {
override fun ExtensionRegistrarContext.configurePlugin() {
+::SerializationFirResolveExtension
+::SerializationFirSupertypesExtension
}
}
@@ -0,0 +1,339 @@
/*
* 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.kotlinx.serialization.compiler.fir
import org.jetbrains.kotlin.GeneratedDeclarationKey
import org.jetbrains.kotlin.descriptors.ClassKind
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.analysis.checkers.getContainingDeclarationSymbol
import org.jetbrains.kotlin.fir.copy
import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.declarations.builder.*
import org.jetbrains.kotlin.fir.declarations.impl.FirResolvedDeclarationStatusImpl
import org.jetbrains.kotlin.fir.declarations.origin
import org.jetbrains.kotlin.fir.declarations.utils.addDefaultBoundIfNecessary
import org.jetbrains.kotlin.fir.declarations.utils.superConeTypes
import org.jetbrains.kotlin.fir.extensions.FirDeclarationGenerationExtension
import org.jetbrains.kotlin.fir.extensions.FirDeclarationPredicateRegistrar
import org.jetbrains.kotlin.fir.extensions.MemberGenerationContext
import org.jetbrains.kotlin.fir.extensions.predicate.AnnotatedWith
import org.jetbrains.kotlin.fir.extensions.predicateBasedProvider
import org.jetbrains.kotlin.fir.moduleData
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.lookupSuperTypes
import org.jetbrains.kotlin.fir.scopes.*
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.constructClassLikeType
import org.jetbrains.kotlin.fir.types.toTypeProjection
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationPackages
object SerializationPluginKey : GeneratedDeclarationKey() {
override fun toString(): String {
return "KotlinxSerializationPlugin"
}
}
val generatedSerializerClassId = ClassId(SerializationPackages.internalPackageFqName, SerialEntityNames.GENERATED_SERIALIZER_CLASS)
val kSerializerClassId = ClassId(SerializationPackages.packageFqName, SerialEntityNames.KSERIALIZER_NAME)
class SerializationFirResolveExtension(session: FirSession) : FirDeclarationGenerationExtension(session) {
// FIXME: I can't check status here
override fun getNestedClassifiersNames(classSymbol: FirClassSymbol<*>): Set<Name> {
val result = mutableSetOf<Name>()
if (classSymbol.shouldHaveGeneratedMethodsInCompanion && !classSymbol.isSerializableObject)
result += SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT
if (classSymbol.shouldHaveGeneratedSerializer /* TODO && !classSymbol.hasCompanionObjectAsSerializer*/)
result += SerialEntityNames.SERIALIZER_CLASS_NAME
return result
}
/**
* It's not documented how these classes relate with getCallableNamesForClass
* Document says:
* 1.> If you generate some class using generateClassLikeDeclaration then you don't need to fill it's declarations.
* Q: Don't need or MUSTN'T?
*
* 2. > Instead of that you need to generate members via generateProperties/Functions/Constructors methods
* of same generation extension (this is important note if you have multiple generation extensions in your plugin)
*
* Q: Does this mean that for e.g. generated companion `generateFunctions` will be called regardless of provided predicate?
*
* 3. This method is stated to be called on SUPERTYPES, but getNestedClassifiersNames stated to be called after SUPERTYPES. How is this possible?
*
* 4. It is should be mentioned that supertypes for generated class should be added here,
* while supertypes for exisiting classes — in separate extension
*/
override fun generateClassLikeDeclaration(classId: ClassId): FirClassLikeSymbol<*>? {
return when (classId.shortClassName) {
SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT -> {
generateCompanionDeclaration(classId)
}
SerialEntityNames.SERIALIZER_CLASS_NAME -> {
addSerializerImplClass(classId)
}
else -> error("Can't generate class ${classId.asSingleFqName()}")
}
}
/**
* 1. It is unclear that for `generateConstructors` to work, one need to return SpecialNames.INIT from here
* 1a. Also it is unclear that objects need constructors too.
*
* 2. If it is a user-written companion object, will I get here? Why?
*
* 3. Is it supposed to check that classSymbol does not already have same Name? How to add a generated overload?
*
* 4. It is unclear what to do with properties
*
*/
override fun getCallableNamesForClass(classSymbol: FirClassSymbol<*>): Set<Name> {
val classId = classSymbol.classId
val result = mutableSetOf<Name>()
when (classId.shortClassName) {
SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT -> {
val origin = classSymbol.origin as? FirDeclarationOrigin.Plugin
if (origin?.key == SerializationPluginKey) {
result += SpecialNames.INIT
result += SerialEntityNames.SERIALIZER_PROVIDER_NAME
} else {
// TODO: handle user-written companions & named companions
}
}
SerialEntityNames.SERIALIZER_CLASS_NAME -> {
// TODO: check classSymbol for already added functions
// TODO: support user-defined serializers?
result += setOf(
SpecialNames.INIT,
SerialEntityNames.SAVE_NAME,
SerialEntityNames.LOAD_NAME,
// FIXME: correctly create property?
SerialEntityNames.SERIAL_DESC_FIELD_NAME
)
if (classSymbol.superConeTypes.any {
it.classId == ClassId(
SerializationPackages.internalPackageFqName,
SerialEntityNames.GENERATED_SERIALIZER_CLASS
)
}) {
result += SerialEntityNames.CHILD_SERIALIZERS_GETTER
if (classSymbol.typeParameterSymbols.isNotEmpty()) {
result += SerialEntityNames.TYPE_PARAMS_SERIALIZERS_GETTER
}
}
}
else -> if (classSymbol.isSerializableObject) result += SerialEntityNames.SERIALIZER_PROVIDER_NAME
}
return result
}
@OptIn(SymbolInternals::class)
private fun <T> getFromSupertype(callableId: CallableId, owner: FirClassSymbol<*>, extractor: (FirTypeScope) -> List<T>): T {
val scopeSession = ScopeSession() // ????????
val scopes = lookupSuperTypes(
owner, lookupInterfaces = true, deep = false, useSiteSession = session
).mapNotNull { useSiteSuperType ->
useSiteSuperType.scopeForSupertype(session, scopeSession, owner.fir)
}
val targets = scopes.flatMap { extractor(it) }
val target = targets.singleOrNull() ?: error("Multiple overrides found for ${callableId.callableName}")
return target
}
// TODO: support @Serializer(for)
@OptIn(SymbolInternals::class)
override fun generateFunctions(callableId: CallableId, context: MemberGenerationContext?): List<FirNamedFunctionSymbol> {
val owner = context?.owner ?: return emptyList()
if (owner.name == SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT && callableId.callableName == SerialEntityNames.SERIALIZER_PROVIDER_NAME)
return listOf(generateSerializerGetterInCompanion(owner, callableId))
if (owner.name != SerialEntityNames.SERIALIZER_CLASS_NAME) return emptyList()
if (callableId.callableName !in setOf(
SpecialNames.INIT,
SerialEntityNames.SAVE_NAME,
SerialEntityNames.LOAD_NAME,
SerialEntityNames.CHILD_SERIALIZERS_GETTER,
SerialEntityNames.TYPE_PARAMS_SERIALIZERS_GETTER
)
) return emptyList()
val target = getFromSupertype(callableId, owner) { it.getFunctions(callableId.callableName) }
val original = target.fir
val copy = buildSimpleFunctionCopy(original) {
symbol = FirNamedFunctionSymbol(callableId)
origin = SerializationPluginKey.origin
status = original.status.copy(modality = Modality.FINAL)
}
return listOf(copy.symbol)
}
private fun generateSerializerGetterInCompanion(owner: FirClassSymbol<*>, callableId: CallableId): FirNamedFunctionSymbol {
val f = buildSimpleFunction {
moduleData = session.moduleData
symbol = FirNamedFunctionSymbol(callableId)
origin = SerializationPluginKey.origin
status = FirResolvedDeclarationStatusImpl(
Visibilities.Public,
Modality.FINAL,
EffectiveVisibility.Public
)
name = callableId.callableName
dispatchReceiverType = owner.defaultType()
// TODO: handle serializable objects
val serializableType =
(owner.getContainingDeclarationSymbol(session) as? FirClassSymbol<*>) ?: error("Can't get outer class for $owner")
// TODO: Add value parameters & type parameters for parameterized classes
returnTypeRef = buildResolvedTypeRef {
type = kSerializerClassId.constructClassLikeType(
arrayOf(serializableType.defaultType().toTypeProjection(Variance.INVARIANT)),
isNullable = false
)
}
}
return f.symbol
}
@OptIn(SymbolInternals::class)
override fun generateProperties(callableId: CallableId, context: MemberGenerationContext?): List<FirPropertySymbol> {
val owner = context?.owner ?: return emptyList()
if (owner.name != SerialEntityNames.SERIALIZER_CLASS_NAME) return emptyList()
if (callableId.callableName != SerialEntityNames.SERIAL_DESC_FIELD_NAME) return emptyList()
val target = getFromSupertype(callableId, owner) { it.getProperties(callableId.callableName).filterIsInstance<FirPropertySymbol>() }
val original = target.fir
val copy = buildPropertyCopy(original) {
symbol = FirPropertySymbol(callableId)
origin = SerializationPluginKey.origin
status = original.status.copy(modality = Modality.FINAL)
getter = buildPropertyAccessor {
status = original.status.copy(modality = Modality.FINAL)
symbol = FirPropertyAccessorSymbol()
origin = SerializationPluginKey.origin
moduleData = session.moduleData
isGetter = true
returnTypeRef = original.returnTypeRef
dispatchReceiverType = owner.defaultType()
propertySymbol = this@buildPropertyCopy.symbol
}
}
return listOf(copy.symbol)
}
// FIXME: it seems that this list will always be used, why not provide it automatically?
private val matchedClasses by lazy {
session.predicateBasedProvider.getSymbolsByPredicate(AnnotatedWith(setOf(SerializationAnnotations.serializableAnnotationFqName)))
.filterIsInstance<FirRegularClassSymbol>()
}
override fun generateConstructors(context: MemberGenerationContext): List<FirConstructorSymbol> {
val constructor = buildConstructor(context.owner.classId, isInner = false, SerializationPluginKey)
return listOf(constructor.symbol)
}
fun addSerializerImplClass(
classId: ClassId
): FirClassLikeSymbol<*>? {
val owner = matchedClasses.firstOrNull { it.classId == classId.outerClassId } ?: return null
val hasTypeParams = owner.typeParameterSymbols.isNotEmpty()
val serializerKind = if (hasTypeParams) ClassKind.CLASS else ClassKind.OBJECT
val serializerFirClass = buildRegularClass {
moduleData = session.moduleData
origin = SerializationPluginKey.origin
classKind = serializerKind
scopeProvider = session.kotlinScopeProvider
name = SerialEntityNames.SERIALIZER_CLASS_NAME
symbol = FirRegularClassSymbol(classId)
status = FirResolvedDeclarationStatusImpl(
Visibilities.Public,
Modality.FINAL,
EffectiveVisibility.Public
)
// FIXME: how to create annotations?
// annotations = listOf(Annotations.create(listOf(KSerializerDescriptorResolver.createDeprecatedHiddenAnnotation(thisDescriptor.module))))
typeParameters.addAll(owner.typeParameterSymbols.map { param ->
buildTypeParameter {
moduleData = session.moduleData
origin = SerializationPluginKey.origin
resolvePhase = FirResolvePhase.BODY_RESOLVE
variance = Variance.INVARIANT
name = param.name
symbol = FirTypeParameterSymbol()
containingDeclarationSymbol = this@buildRegularClass.symbol
isReified = false
addDefaultBoundIfNecessary() // there should be KSerializer but whatever
}
})
superTypeRefs += buildResolvedTypeRef {
// FIXME: document how one gets type ref from FirClass
// is this a correct one or should I go with buildTypeProjectionWithVariance ?
// is this a correct place to add supertypes to synthetic declarations? Looks like FirSupertypesExtension does not see class.
// It seems that this code generates KSerializer<Box<[declared type param of Box]>> instead of KSerializer<Box<*>>, but it didn't matter for old FE
type = generatedSerializerClassId.constructClassLikeType(arrayOf(owner.defaultType().toTypeProjection(Variance.INVARIANT)), isNullable = false)
}
}
// TODO: add typed constructor
// val secondaryCtors =
// if (!hasTypeParams)
// emptyList()
// else
// listOf(
// KSerializerDescriptorResolver.createTypedSerializerConstructorDescriptor(
// serializerFirClass,
// thisDescriptor,
// typeParameters
// )
// )
// serializerFirClass.secondaryConstructors = secondaryCtors
return serializerFirClass.symbol
}
fun generateCompanionDeclaration(classId: ClassId): FirClassLikeSymbol<*>? {
if (classId.shortClassName != SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT) return null
val owner = matchedClasses.firstOrNull { it.classId == classId.outerClassId } ?: return null
if (owner.companionObjectSymbol != null) return null
val regularClass = buildRegularClass {
moduleData = session.moduleData
origin = SerializationPluginKey.origin
classKind = ClassKind.OBJECT
scopeProvider = session.kotlinScopeProvider
status = FirResolvedDeclarationStatusImpl(
Visibilities.Public,
Modality.FINAL,
EffectiveVisibility.Public
).apply {
isCompanion = true
}
name = SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT
symbol = FirRegularClassSymbol(classId)
superTypeRefs += session.builtinTypes.anyType
}
return regularClass.symbol
}
override fun FirDeclarationPredicateRegistrar.registerPredicates() {
// No predicate on final/open etc
register(AnnotatedWith(setOf(SerializationAnnotations.serializableAnnotationFqName)))
}
}
@@ -0,0 +1,75 @@
/*
* Copyright 2010-2022 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.kotlinx.serialization.compiler.fir
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirClassLikeDeclaration
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.extensions.FirDeclarationPredicateRegistrar
import org.jetbrains.kotlin.fir.extensions.FirSupertypeGenerationExtension
import org.jetbrains.kotlin.fir.extensions.predicate.*
import org.jetbrains.kotlin.fir.extensions.predicateBasedProvider
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.getContainingDeclaration
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef
import org.jetbrains.kotlin.fir.types.builder.buildTypeProjectionWithVariance
import org.jetbrains.kotlin.fir.types.classId
import org.jetbrains.kotlin.fir.types.constructClassLikeType
import org.jetbrains.kotlin.fir.types.toTypeProjection
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationPackages
class SerializationFirSupertypesExtension(session: FirSession) : FirSupertypeGenerationExtension(session) {
companion object {
private val serializerFor: DeclarationPredicate =
AnnotatedWith(setOf(SerializationAnnotations.serializerAnnotationFqName)) // @Serializer(for=...)
private val generatedSerializer: DeclarationPredicate =
ancestorAnnotated(SerializationAnnotations.serializableAnnotationFqName) // @Serializable X.$serializer
private val PREDICATE = serializerFor or generatedSerializer
}
override fun needTransformSupertypes(declaration: FirClassLikeDeclaration): Boolean =
session.predicateBasedProvider.matches(serializerFor, declaration)
|| ( // TODO: this part is not working
session.predicateBasedProvider.matches(generatedSerializer, declaration)
&& declaration is FirRegularClass
&& declaration.name == SerialEntityNames.SERIALIZER_CLASS_NAME
)
override fun FirDeclarationPredicateRegistrar.registerPredicates() {
register(PREDICATE)
}
override fun computeAdditionalSupertypes(
classLikeDeclaration: FirClassLikeDeclaration,
resolvedSupertypes: List<FirResolvedTypeRef>
): List<FirResolvedTypeRef> {
val kSerializerClassId = ClassId(SerializationPackages.packageFqName, SerialEntityNames.KSERIALIZER_NAME)
val generatedSerializerClassId = ClassId(SerializationPackages.internalPackageFqName, SerialEntityNames.GENERATED_SERIALIZER_CLASS)
if (resolvedSupertypes.any { it.type.classId == kSerializerClassId || it.type.classId == generatedSerializerClassId }) return emptyList()
if (session.predicateBasedProvider.matches(serializerFor, classLikeDeclaration)) {
TODO("Support @Serializer(for=...) supertype generation")
} else {
// TODO: Remove this section as it is not working because one should add supertype for generated class where this class is created.
// @Serializable X.$serializer
val parent = (classLikeDeclaration.getContainingDeclaration(session) as? FirRegularClass)
?: error("Generated serializer $classLikeDeclaration is not contained in any class")
return listOf(buildResolvedTypeRef {
// FIXME: document how one gets type ref from FirClass
// FIXME: is this a correct one or should I go with buildTypeProjectionWithVariance ?
// It seems that this code generates KSerializer<Box<[declared type param]>> instead of KSerializer<Box<*>>, but it didn't matter for old FE
type = generatedSerializerClassId.constructClassLikeType(arrayOf(parent.defaultType().toTypeProjection(Variance.INVARIANT)), isNullable = false)
})
}
}
}
@@ -0,0 +1,64 @@
/*
* Copyright 2010-2022 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.kotlinx.serialization.compiler.fir
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.declarations.findArgumentByName
import org.jetbrains.kotlin.fir.declarations.utils.modality
import org.jetbrains.kotlin.fir.expressions.classId
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations
// FIXME: why FirAnnotation.classId.asSingleFqName is available, but FirAnnotation.fqName() requires FirSession?
internal val FirClassSymbol<*>.hasSerializableAnnotation
get() = this.annotations.any {
it.classId?.asSingleFqName()?.asString() == SerializationAnnotations.serializableAnnotationFqName.asString()
}
internal val FirClassSymbol<*>.hasSerializableAnnotationWithoutArgs: Boolean
get() {
if (!hasSerializableAnnotation) return false
val target = this.resolvedAnnotationsWithArguments.find {
it.classId?.asSingleFqName()?.asString() == SerializationAnnotations.serializableAnnotationFqName.asString()
}!!
return target.findArgumentByName(Name.identifier("with")) == null
}
internal val FirClassSymbol<*>.shouldHaveGeneratedMethodsInCompanion: Boolean
get() = this.isSerializableObject || this.isSerializableEnum() || this.classKind == ClassKind.CLASS && hasSerializableAnnotation || this.isSealedSerializableInterface
internal val FirClassSymbol<*>.isSerializableObject: Boolean
get() = classKind == ClassKind.OBJECT && hasSerializableAnnotation
internal val FirClassSymbol<*>.isInternallySerializableObject: Boolean
get() = classKind == ClassKind.OBJECT && hasSerializableAnnotationWithoutArgs
internal val FirClassSymbol<*>.isSealedSerializableInterface: Boolean
get() = classKind == ClassKind.INTERFACE && modality == Modality.SEALED && hasSerializableAnnotation
internal val FirClassSymbol<*>.isInternalSerializable: Boolean
get() {
if (classKind != ClassKind.CLASS) return false
return hasSerializableAnnotationWithoutArgs
}
internal fun FirClassSymbol<*>.isSerializableEnum(): Boolean = classKind == ClassKind.ENUM_CLASS && hasSerializableAnnotation
internal fun FirClassSymbol<*>.isInternallySerializableEnum(): Boolean =
classKind == ClassKind.ENUM_CLASS && hasSerializableAnnotationWithoutArgs
internal val FirClassSymbol<*>.shouldHaveGeneratedSerializer: Boolean
get() = (isInternalSerializable && isFinalOrOpen()) || isInternallySerializableEnum()
@OptIn(SymbolInternals::class)
private fun FirClassSymbol<*>.isFinalOrOpen(): Boolean {
val modality = fir.status.modality
// null means default modality, final
return (modality == null || modality == Modality.FINAL || modality == Modality.OPEN)
}
@@ -0,0 +1,63 @@
/*
* Copyright 2010-2022 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.kotlinx.serialization
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.test.backend.BlackBoxCodegenSuppressor
import org.jetbrains.kotlin.test.backend.handlers.*
import org.jetbrains.kotlin.test.backend.ir.JvmIrBackendFacade
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.builders.fir2IrStep
import org.jetbrains.kotlin.test.builders.irHandlersStep
import org.jetbrains.kotlin.test.builders.jvmArtifactsHandlersStep
import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives
import org.jetbrains.kotlin.test.runners.AbstractKotlinCompilerTest
import org.jetbrains.kotlin.test.runners.AbstractKotlinCompilerWithTargetBackendTest
import org.jetbrains.kotlin.test.runners.baseFirDiagnosticTestConfiguration
import org.jetbrains.kotlinx.serialization.compiler.fir.FirSerializationExtensionRegistrar
abstract class AbstractSerializationFirMembersTest: AbstractKotlinCompilerTest() {
override fun TestConfigurationBuilder.configuration() {
baseFirDiagnosticTestConfiguration()
defaultDirectives {
+FirDiagnosticsDirectives.ENABLE_PLUGIN_PHASES
+FirDiagnosticsDirectives.FIR_DUMP
}
configureForKotlinxSerialization(listOf(getSerializationCoreLibraryJar()!!)) {
FirExtensionRegistrarAdapter.registerExtension(FirSerializationExtensionRegistrar())
}
}
}
open class AbstractSerializationFirBlackBoxTest : AbstractKotlinCompilerWithTargetBackendTest(TargetBackend.JVM_IR) {
override fun TestConfigurationBuilder.configuration() {
baseFirDiagnosticTestConfiguration()
defaultDirectives {
+FirDiagnosticsDirectives.ENABLE_PLUGIN_PHASES
}
fir2IrStep()
irHandlersStep {
useHandlers(
::IrTextDumpHandler,
::IrTreeVerifierHandler,
)
}
facadeStep(::JvmIrBackendFacade)
jvmArtifactsHandlersStep {
useHandlers(::JvmBoxRunner)
}
useAfterAnalysisCheckers(::BlackBoxCodegenSuppressor)
configureForKotlinxSerialization(listOf(getSerializationCoreLibraryJar()!!)) {
FirExtensionRegistrarAdapter.registerExtension(FirSerializationExtensionRegistrar())
}
}
}
@@ -47,7 +47,7 @@ internal fun getSerializationLibraryJar(classToDetect: String): File? = try {
null
}
internal fun TestConfigurationBuilder.configureForKotlinxSerialization(librariesPaths: List<File>) {
internal fun TestConfigurationBuilder.configureForKotlinxSerialization(librariesPaths: List<File>, registerAdditionalExtensions: ExtensionStorage.() -> Unit = {}) {
useConfigurators(
{ services ->
object : EnvironmentConfigurator(services) {
@@ -60,6 +60,7 @@ internal fun TestConfigurationBuilder.configureForKotlinxSerialization(libraries
override fun ExtensionStorage.registerCompilerExtensions(module: TestModule, configuration: CompilerConfiguration) {
SerializationComponentRegistrar.registerExtensions(this)
registerAdditionalExtensions()
}
}
})
@@ -0,0 +1,33 @@
/*
* 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.kotlinx.serialization;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.util.KtTestUtil;
import org.jetbrains.kotlin.test.TargetBackend;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link GenerateNewCompilerTests.kt}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers")
@TestDataPath("$PROJECT_ROOT")
public class SerializationFirBlackBoxTestGenerated extends AbstractSerializationFirBlackBoxTest {
@Test
public void testAllFilesPresentInFirMembers() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
}
@Test
@TestMetadata("classWithCompanionObject.kt")
public void testClassWithCompanionObject() throws Exception {
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers/classWithCompanionObject.kt");
}
}
@@ -0,0 +1,32 @@
/*
* 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.kotlinx.serialization;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.util.KtTestUtil;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link GenerateNewCompilerTests.kt}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers")
@TestDataPath("$PROJECT_ROOT")
public class SerializationFirMembersTestGenerated extends AbstractSerializationFirMembersTest {
@Test
public void testAllFilesPresentInFirMembers() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@Test
@TestMetadata("classWithCompanionObject.kt")
public void testClassWithCompanionObject() throws Exception {
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers/classWithCompanionObject.kt");
}
}
@@ -20,6 +20,14 @@ fun main(args: Array<String>) {
testClass<AbstractSerializationIrBoxTest> {
model("boxIr")
}
testClass<AbstractSerializationFirMembersTest> {
model("firMembers")
}
testClass<AbstractSerializationFirBlackBoxTest> {
model("firMembers")
}
}
}
}
@@ -0,0 +1,31 @@
FILE: classWithCompanionObject.kt
@R|kotlinx/serialization/Serializable|() public final class SomeClass : R|kotlin/Any| {
public constructor(): R|SomeClass| {
super<R|kotlin/Any|>()
}
public final companion object Companion : R|kotlin/Any| {
public final fun serializer(): R|kotlinx/serialization/KSerializer<SomeClass>|
public constructor(): R|SomeClass.Companion|
}
public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer<SomeClass>| {
public final fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|SomeClass|): R|kotlin/Unit|
public final fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|SomeClass|
public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor|
public get(): R|kotlinx/serialization/descriptors/SerialDescriptor|
public final fun childSerializers(): R|kotlin/Array<kotlinx/serialization/KSerializer<*>>|
public constructor(): R|SomeClass.$serializer|
}
}
public final fun box(): R|kotlin/String| {
^box String(OK)
}
@@ -0,0 +1,12 @@
import kotlinx.serialization.*
@Serializable
class SomeClass
fun box(): String {
// SomeClass.serializer()
// SomeClass.Companion.serializer()
return "OK"
}