Add serializer(vararg KSerializer<*>) override from SerializerFactory when necessary

SerializerFactory is an implementation detail for Kotlin/JS and Native:
it should be added as a supertype to a companion object of certain serializable classes
and `serializer(vararg KSerializer<*>)` function from it should be implemented.
Existing implementation added the supertype, but did not add proper override to FirClass
of a companion, which led to various warnings and errors like 'Abstract function 'serializer' is not implemented in non-abstract companion object'.

Also implemented the addition of SerializerFactory supertype to user-defined companions within @Serializable and @MetaSerializable when necessary.

Also set up proper box tests for FIR+Kotlin/JS combination.

#KT-58501 Fixed
#KT-59768 Fixed
This commit is contained in:
Leonid Startsev
2023-06-28 18:18:40 +02:00
committed by Space Team
parent 351e9c7592
commit f3833fdcf8
11 changed files with 276 additions and 39 deletions
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.fir.analysis.checkers.getContainingDeclarationSymbol
import org.jetbrains.kotlin.fir.containingClassForStaticMemberAttr
import org.jetbrains.kotlin.fir.copy
import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.fir.declarations.builder.buildSimpleFunctionCopy
import org.jetbrains.kotlin.fir.declarations.origin
import org.jetbrains.kotlin.fir.declarations.utils.isCompanion
@@ -28,17 +29,12 @@ import org.jetbrains.kotlin.fir.scopes.impl.toConeType
import org.jetbrains.kotlin.fir.scopes.scopeForSupertype
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.ConeTypeProjection
import org.jetbrains.kotlin.fir.types.classId
import org.jetbrains.kotlin.fir.types.constructClassLikeType
import org.jetbrains.kotlin.fir.types.constructType
import org.jetbrains.kotlin.fir.types.*
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.platform.isJs
import org.jetbrains.kotlin.platform.isWasm
import org.jetbrains.kotlin.platform.konan.isNative
import org.jetbrains.kotlin.utils.addToStdlib.runIf
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.SERIALIZER_FACTORY_INTERFACE_NAME
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationPackages
@@ -151,7 +147,7 @@ class SerializationFirResolveExtension(session: FirSession) : FirDeclarationGene
useSiteSuperType.scopeForSupertype(session, scopeSession, owner.fir, memberRequiredPhase = null)
}
val targets = scopes.flatMap { extractor(it) }
return targets.singleOrNull() ?: error("Multiple overrides found for ${callableId.callableName}")
return targets.singleOrNull() ?: error("Zero or multiple overrides found for ${callableId.callableName} in $owner")
}
@OptIn(SymbolInternals::class)
@@ -165,8 +161,13 @@ class SerializationFirResolveExtension(session: FirSession) : FirDeclarationGene
if (with(session) { owner.isSerializableObject }) owner else null
}
serializableClass ?: return emptyList()
return listOf(generateSerializerGetterInCompanion(owner, serializableClass, callableId))
if (serializableClass == null) return emptyList()
val serializableGetterInCompanion = generateSerializerGetterInCompanion(owner, serializableClass, callableId)
val serializableGetterFromFactory = runIf (with(session) { serializableClass.companionNeedsSerializerFactory }) {
val original = getFromSupertype(callableId, owner) { it.getFunctions(callableId.callableName) }.fir
generateSerializerFactoryVararg(owner, callableId, original)
}
return listOfNotNull(serializableGetterInCompanion, serializableGetterFromFactory)
}
if (!owner.isSerializer) return emptyList()
if (callableId.callableName !in setOf(
@@ -188,6 +189,18 @@ class SerializationFirResolveExtension(session: FirSession) : FirDeclarationGene
return listOf(copy.symbol)
}
private fun generateSerializerFactoryVararg(
owner: FirClassSymbol<*>,
callableId: CallableId,
original: FirSimpleFunction
): FirNamedFunctionSymbol =
createMemberFunction(owner, SerializationPluginKey, callableId.callableName, original.returnTypeRef.coneType) {
val vpo = original.valueParameters.single()
valueParameter(vpo.name, vpo.returnTypeRef.coneType, vpo.isCrossinline, vpo.isNoinline, vpo.isVararg, vpo.defaultValue != null)
}.apply {
excludeFromJsExport()
}.symbol
private fun generateSerializerGetterInCompanion(
owner: FirClassSymbol<*>,
serializableClassSymbol: FirClassSymbol<*>,
@@ -309,16 +322,4 @@ class SerializationFirResolveExtension(session: FirSession) : FirDeclarationGene
private val FirClassSymbol<*>.isExternalSerializer: Boolean
get() = session.predicateBasedProvider.matches(FirSerializationPredicates.serializerFor, this)
context(FirSession)
private val FirClassSymbol<*>.companionNeedsSerializerFactory: Boolean
get() {
if (!(moduleData.platform.isNative() || moduleData.platform.isJs() || moduleData.platform.isWasm())) return false
if (isSerializableObject) return true
if (isSerializableEnum) return true
if (isAbstractOrSealedSerializableClass) return true
if (isSealedSerializableInterface) return true
if (typeParameterSymbols.isEmpty()) return false
return true
}
}
@@ -5,8 +5,13 @@
package org.jetbrains.kotlinx.serialization.compiler.fir
import org.jetbrains.kotlin.descriptors.isObject
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingDeclarationSymbol
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirClassLikeDeclaration
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.utils.isCompanion
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirGetClassCall
import org.jetbrains.kotlin.fir.expressions.FirPropertyAccessExpression
@@ -14,22 +19,42 @@ import org.jetbrains.kotlin.fir.extensions.FirDeclarationPredicateRegistrar
import org.jetbrains.kotlin.fir.extensions.FirSupertypeGenerationExtension
import org.jetbrains.kotlin.fir.extensions.buildUserTypeFromQualifierParts
import org.jetbrains.kotlin.fir.extensions.predicateBasedProvider
import org.jetbrains.kotlin.fir.moduleData
import org.jetbrains.kotlin.fir.references.impl.FirSimpleNamedReference
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.types.*
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.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.platform.isJs
import org.jetbrains.kotlin.platform.isWasm
import org.jetbrains.kotlin.platform.konan.isNative
import org.jetbrains.kotlinx.serialization.compiler.fir.FirSerializationPredicates.annotatedWithSerializableOrMeta
import org.jetbrains.kotlinx.serialization.compiler.fir.FirSerializationPredicates.serializerFor
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationPackages
class SerializationFirSupertypesExtension(session: FirSession) : FirSupertypeGenerationExtension(session) {
private val isJvmOrMetadata = !session.moduleData.platform.run { isNative() || isJs() || isWasm() }
override fun needTransformSupertypes(declaration: FirClassLikeDeclaration): Boolean =
session.predicateBasedProvider.matches(serializerFor, declaration)
session.predicateBasedProvider.matches(serializerFor, declaration) || isSerializableObjectAndNeedsFactory(declaration) || isCompanionAndNeedsFactory(declaration)
private fun isSerializableObjectAndNeedsFactory(declaration: FirClassLikeDeclaration): Boolean = with(session) {
if (isJvmOrMetadata) return false
return declaration is FirClass && declaration.classKind.isObject
&& session.predicateBasedProvider.matches(annotatedWithSerializableOrMeta, declaration)
}
private fun isCompanionAndNeedsFactory(declaration: FirClassLikeDeclaration): Boolean = with(session) {
if (isJvmOrMetadata) return false
if (declaration !is FirRegularClass) return false
if (!declaration.isCompanion) return false
val parentSymbol = declaration.symbol.getContainingDeclarationSymbol(session) as FirClassSymbol<*>
return session.predicateBasedProvider.matches(annotatedWithSerializableOrMeta, parentSymbol)
&& parentSymbol.companionNeedsSerializerFactory
}
override fun FirDeclarationPredicateRegistrar.registerPredicates() {
register(serializerFor)
@@ -38,22 +63,33 @@ class SerializationFirSupertypesExtension(session: FirSession) : FirSupertypeGen
context(TypeResolveServiceContainer)
override fun computeAdditionalSupertypes(
classLikeDeclaration: FirClassLikeDeclaration,
resolvedSupertypes: List<FirResolvedTypeRef>
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()
return if (session.predicateBasedProvider.matches(serializerFor, classLikeDeclaration)) {
val getClassArgument = classLikeDeclaration.getSerializerFor(session) ?: return emptyList()
val serializerConeType = resolveConeTypeFromArgument(getClassArgument)
return when {
session.predicateBasedProvider.matches(serializerFor, classLikeDeclaration) -> {
if (resolvedSupertypes.any { it.type.classId == kSerializerClassId || it.type.classId == generatedSerializerClassId }) return emptyList()
val getClassArgument = classLikeDeclaration.getSerializerFor(session) ?: return emptyList()
val serializerConeType = resolveConeTypeFromArgument(getClassArgument)
listOf(
buildResolvedTypeRef {
type = kSerializerClassId.constructClassLikeType(arrayOf(serializerConeType), isNullable = false)
}
)
} else emptyList()
listOf(
buildResolvedTypeRef {
type = kSerializerClassId.constructClassLikeType(arrayOf(serializerConeType), isNullable = false)
}
)
}
isSerializableObjectAndNeedsFactory(classLikeDeclaration) || isCompanionAndNeedsFactory(classLikeDeclaration) -> {
val serializerFactoryClassId = ClassId(
SerializationPackages.internalPackageFqName,
SerialEntityNames.SERIALIZER_FACTORY_INTERFACE_NAME
)
if (resolvedSupertypes.any { it.type.classId == serializerFactoryClassId }) return emptyList()
listOf(serializerFactoryClassId.constructClassLikeType(emptyArray(), false).toFirResolvedTypeRef())
}
else -> emptyList()
}
}
// Function helps to resolve class call from annotation argument to `ConeKotlinType`
@@ -29,6 +29,8 @@ import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.platform.isJs
import org.jetbrains.kotlin.platform.isWasm
import org.jetbrains.kotlin.platform.konan.isNative
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.kotlinx.serialization.compiler.fir.services.dependencySerializationInfoProvider
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
@@ -145,6 +147,18 @@ internal val FirClassSymbol<*>.shouldHaveGeneratedMethodsInCompanion: Boolean
|| (classKind == ClassKind.CLASS && hasSerializableOrMetaAnnotation)
|| isSealedSerializableInterface
context(FirSession)
internal val FirClassSymbol<*>.companionNeedsSerializerFactory: Boolean
get() {
if (!moduleData.platform.run { isNative() || isJs() || isWasm() }) return false
if (isSerializableObject) return true
if (isSerializableEnum) return true
if (isAbstractOrSealedSerializableClass) return true
if (isSealedSerializableInterface) return true
if (typeParameterSymbols.isEmpty()) return false
return true
}
context(FirSession)
internal val FirClassSymbol<*>.isInternalSerializable: Boolean
get() {