Support serializable objects in FIR plugin

This commit is contained in:
Leonid Startsev
2022-08-18 13:09:06 +02:00
committed by Space
parent 4db579e128
commit 6b32ce76c2
6 changed files with 74 additions and 8 deletions
@@ -132,8 +132,10 @@ class SerializationFirResolveExtension(session: FirSession) : FirDeclarationGene
@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 (callableId.callableName == SerialEntityNames.SERIALIZER_PROVIDER_NAME) {
val serializableClass = session.getSerializableClassDescriptorByCompanion(owner) ?: return emptyList()
return listOf(generateSerializerGetterInCompanion(owner, serializableClass, callableId))
}
if (owner.name != SerialEntityNames.SERIALIZER_CLASS_NAME) return emptyList()
if (callableId.callableName !in setOf(
SpecialNames.INIT,
@@ -154,7 +156,7 @@ class SerializationFirResolveExtension(session: FirSession) : FirDeclarationGene
return listOf(copy.symbol)
}
private fun generateSerializerGetterInCompanion(owner: FirClassSymbol<*>, callableId: CallableId): FirNamedFunctionSymbol {
private fun generateSerializerGetterInCompanion(owner: FirClassSymbol<*>, serializableClassSymbol: FirClassSymbol<*>, callableId: CallableId): FirNamedFunctionSymbol {
val f = buildSimpleFunction {
moduleData = session.moduleData
symbol = FirNamedFunctionSymbol(callableId)
@@ -167,10 +169,6 @@ class SerializationFirResolveExtension(session: FirSession) : FirDeclarationGene
name = callableId.callableName
dispatchReceiverType = owner.defaultType()
// TODO: handle serializable objects
val serializableClassSymbol =
(owner.getContainingDeclarationSymbol(session) as? FirClassSymbol<*>) ?: error("Can't get outer class for $owner")
typeParameters.addAll(serializableClassSymbol.typeParameterSymbols.map { newSimpleTypeParameter(session, symbol, it.name) })
val parametersAsArguments = typeParameters.map { it.toConeType() }.toTypedArray<ConeTypeProjection>()
@@ -193,7 +191,7 @@ class SerializationFirResolveExtension(session: FirSession) : FirDeclarationGene
return f.symbol
}
@OptIn(SymbolInternals::class) // TODO: localSerializersFieldDescriptors
@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()
@@ -5,15 +5,22 @@
package org.jetbrains.kotlinx.serialization.compiler.fir
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingDeclarationSymbol
import org.jetbrains.kotlin.fir.analysis.checkers.outerClassSymbol
import org.jetbrains.kotlin.fir.declarations.findArgumentByName
import org.jetbrains.kotlin.fir.declarations.utils.isCompanion
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
import org.jetbrains.kotlinx.serialization.compiler.resolve.isSerializableObject
import org.jetbrains.kotlinx.serialization.compiler.resolve.shouldHaveGeneratedMethodsInCompanion
internal val FirClassSymbol<*>.hasSerializableAnnotation
get() = this.annotations.any {
@@ -59,4 +66,12 @@ private fun FirClassSymbol<*>.isFinalOrOpen(): Boolean {
val modality = rawStatus.modality
// null means default modality, final
return (modality == null || modality == Modality.FINAL || modality == Modality.OPEN)
}
internal fun FirSession.getSerializableClassDescriptorByCompanion(thisDescriptor: FirClassSymbol<*>): FirClassSymbol<*>? {
if (thisDescriptor.isSerializableObject) return thisDescriptor
if (!thisDescriptor.isCompanion) return null
val classDescriptor = (thisDescriptor.getContainingDeclarationSymbol(this) as? FirClassSymbol<*>) ?: return null
if (!classDescriptor.shouldHaveGeneratedMethodsInCompanion) return null
return classDescriptor
}
@@ -54,4 +54,10 @@ public class SerializationFirBlackBoxTestGenerated extends AbstractSerialization
public void testPrivatePropertiesSerialization() throws Exception {
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers/privatePropertiesSerialization.kt");
}
@Test
@TestMetadata("serializableObject.kt")
public void testSerializableObject() throws Exception {
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers/serializableObject.kt");
}
}
@@ -53,4 +53,10 @@ public class SerializationFirMembersTestGenerated extends AbstractSerializationF
public void testPrivatePropertiesSerialization() throws Exception {
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers/privatePropertiesSerialization.kt");
}
@Test
@TestMetadata("serializableObject.kt")
public void testSerializableObject() throws Exception {
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers/serializableObject.kt");
}
}
@@ -0,0 +1,26 @@
FILE: serializableObject.kt
@R|kotlinx/serialization/Serializable|() public final object FooBar : R|kotlin/Any| {
private constructor(): R|FooBar| {
super<R|kotlin/Any|>()
}
public final fun serializer(): R|kotlinx/serialization/KSerializer<FooBar>|
}
public final fun box(): R|kotlin/String| {
lval encoded: R|kotlin/String| = Q|kotlinx/serialization/json/Json|.R|kotlinx/serialization/json/Json.encodeToString|<R|FooBar|>(Q|FooBar|.R|/FooBar.serializer|(), Q|FooBar|)
when () {
!=(R|<local>/encoded|, String({})) -> {
^box R|<local>/encoded|
}
}
lval decoded: R|FooBar| = Q|kotlinx/serialization/json/Json|.R|kotlinx/serialization/json/Json.decodeFromString|<R|FooBar|>(Q|FooBar|.R|/FooBar.serializer|(), R|<local>/encoded|)
when () {
!==(R|<local>/decoded|, Q|FooBar|) -> {
^box String(Incorrect object instance)
}
}
^box String(OK)
}
@@ -0,0 +1,15 @@
// WITH_STDLIB
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
object FooBar
fun box(): String {
val encoded = Json.encodeToString(FooBar.serializer(), FooBar)
if (encoded != "{}") return encoded
val decoded = Json.decodeFromString(FooBar.serializer(), encoded)
if (decoded !== FooBar) return "Incorrect object instance"
return "OK"
}