Prototype of kotlinx.serialization FIR plugin:
Implement basic non-parameterized case
This commit is contained in:
@@ -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>)
|
||||
|
||||
+1
@@ -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>
|
||||
|
||||
+2
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+90
@@ -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
|
||||
}
|
||||
+15
@@ -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
|
||||
}
|
||||
}
|
||||
+339
@@ -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)))
|
||||
}
|
||||
}
|
||||
+75
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
+64
@@ -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)
|
||||
}
|
||||
+63
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -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()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
+33
@@ -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");
|
||||
}
|
||||
}
|
||||
+32
@@ -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");
|
||||
}
|
||||
}
|
||||
+8
@@ -20,6 +20,14 @@ fun main(args: Array<String>) {
|
||||
testClass<AbstractSerializationIrBoxTest> {
|
||||
model("boxIr")
|
||||
}
|
||||
|
||||
testClass<AbstractSerializationFirMembersTest> {
|
||||
model("firMembers")
|
||||
}
|
||||
|
||||
testClass<AbstractSerializationFirBlackBoxTest> {
|
||||
model("firMembers")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+31
@@ -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)
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import kotlinx.serialization.*
|
||||
|
||||
@Serializable
|
||||
class SomeClass
|
||||
|
||||
fun box(): String {
|
||||
// SomeClass.serializer()
|
||||
// SomeClass.Companion.serializer()
|
||||
return "OK"
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user