[Serialization] Implement serialization checker for K2 version of plugin

^KT-53178 Fixed
This commit is contained in:
Dmitriy Novozhilov
2022-08-24 10:59:11 +03:00
committed by teamcity
parent e048ffcf6d
commit 671083c701
69 changed files with 1767 additions and 453 deletions
+1
View File
@@ -372,6 +372,7 @@ val projectsWithEnabledContextReceivers by extra {
":compiler:frontend.common",
":compiler:fir:fir2ir",
":kotlin-lombok-compiler-plugin.k1",
":kotlinx-serialization-compiler-plugin.k2",
)
}
@@ -114,7 +114,7 @@ fun FirTypeRef.toRegularClassSymbol(session: FirSession): FirRegularClassSymbol?
* or null if no proper declaration has been found.
*/
fun FirDeclaration.getContainingClassSymbol(session: FirSession): FirClassLikeSymbol<*>? =
this.safeAs<FirCallableDeclaration>()?.containingClass()?.toSymbol(session)
(this as? FirCallableDeclaration)?.containingClass()?.toSymbol(session)
@OptIn(SymbolInternals::class)
fun FirBasedSymbol<*>.getContainingClassSymbol(session: FirSession): FirClassLikeSymbol<*>? = fir.getContainingClassSymbol(session)
@@ -23,7 +23,6 @@ import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.isLocal
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirValueParameterSymbol
import org.jetbrains.kotlin.fir.types.*
@@ -223,14 +222,4 @@ object FirAnnotationClassDeclarationChecker : FirRegularClassChecker() {
return annotationHasCycle(referencedAnnotation)
}
}
private fun FirRegularClassSymbol.primaryConstructorSymbol(): FirConstructorSymbol? {
for (declarationSymbol in this.declarationSymbols) {
if (declarationSymbol is FirConstructorSymbol && declarationSymbol.isPrimary) {
return declarationSymbol
}
}
return null
}
}
@@ -101,3 +101,12 @@ internal val FirCallableDeclaration.isExtensionMember: Boolean
internal val FirCallableSymbol<*>.isExtensionMember: Boolean
get() = resolvedReceiverTypeRef != null && dispatchReceiverType != null
fun FirClassSymbol<*>.primaryConstructorSymbol(): FirConstructorSymbol? {
for (declarationSymbol in this.declarationSymbols) {
if (declarationSymbol is FirConstructorSymbol && declarationSymbol.isPrimary) {
return declarationSymbol
}
}
return null
}
@@ -101,6 +101,9 @@ object FirDiagnosticRenderers {
t.renderReadableWithFqNames()
}
// TODO: properly implement
val RENDER_TYPE_WITH_ANNOTATIONS = RENDER_TYPE
val FQ_NAMES_IN_TYPES = Renderer { symbol: FirBasedSymbol<*> ->
@OptIn(SymbolInternals::class)
FirRenderer(
@@ -8,18 +8,28 @@ package org.jetbrains.kotlin.fir.types
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.StandardClassIds
val ConeKotlinType.isByte: Boolean get() = isBuiltinType(StandardClassIds.Byte, false)
val ConeKotlinType.isShort: Boolean get() = isBuiltinType(StandardClassIds.Short, false)
val ConeKotlinType.isInt: Boolean get() = isBuiltinType(StandardClassIds.Int, false)
val ConeKotlinType.isLong: Boolean get() = isBuiltinType(StandardClassIds.Long, false)
val ConeKotlinType.isFloat: Boolean get() = isBuiltinType(StandardClassIds.Float, false)
val ConeKotlinType.isDouble: Boolean get() = isBuiltinType(StandardClassIds.Double, false)
val ConeKotlinType.isAny: Boolean get() = isBuiltinType(StandardClassIds.Any, false)
val ConeKotlinType.isNullableAny: Boolean get() = isBuiltinType(StandardClassIds.Any, true)
val ConeKotlinType.isNothing: Boolean get() = isBuiltinType(StandardClassIds.Nothing, false)
val ConeKotlinType.isNullableNothing: Boolean get() = isBuiltinType(StandardClassIds.Nothing, true)
val ConeKotlinType.isUnit: Boolean get() = isBuiltinType(StandardClassIds.Unit, false)
val ConeKotlinType.isBoolean: Boolean get() = isBuiltinType(StandardClassIds.Boolean, false)
val ConeKotlinType.isNullableBoolean: Boolean get() = isBuiltinType(StandardClassIds.Boolean, true)
val ConeKotlinType.isBooleanOrNullableBoolean: Boolean get() = isAnyOfBuiltinType(setOf(StandardClassIds.Boolean))
val ConeKotlinType.isEnum: Boolean get() = isBuiltinType(StandardClassIds.Enum, false)
val ConeKotlinType.isChar: Boolean get() = isBuiltinType(StandardClassIds.Char, false)
val ConeKotlinType.isString: Boolean get() = isBuiltinType(StandardClassIds.String, false)
val ConeKotlinType.isInt: Boolean get() = isBuiltinType(StandardClassIds.Int, false)
val ConeKotlinType.isLong: Boolean get() = isBuiltinType(StandardClassIds.Long, false)
val ConeKotlinType.isEnum: Boolean get() = isBuiltinType(StandardClassIds.Enum, false)
val ConeKotlinType.isUInt: Boolean get() = isBuiltinType(StandardClassIds.UInt, false)
val ConeKotlinType.isULong: Boolean get() = isBuiltinType(StandardClassIds.ULong, false)
val ConeKotlinType.isPrimitiveOrNullablePrimitive: Boolean get() = isAnyOfBuiltinType(StandardClassIds.primitiveTypes)
@@ -16,10 +16,7 @@ import org.jetbrains.kotlin.fir.resolvedSymbol
import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.types.coneTypeSafe
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
@@ -99,11 +96,15 @@ private val DEFAULT_USE_SITE_TARGETS: Set<AnnotationUseSiteTarget> =
USE_SITE_TARGET_NAME_MAP.values.fold(setOf<AnnotationUseSiteTarget>()) { a, b -> a + b } - setOf(AnnotationUseSiteTarget.FILE)
fun FirDeclaration.hasAnnotation(classId: ClassId): Boolean {
return annotations.any { it.toAnnotationClassId() == classId }
return annotations.hasAnnotation(classId)
}
fun FirBasedSymbol<*>.hasAnnotation(classId: ClassId): Boolean {
return resolvedAnnotationsWithClassIds.any { it.toAnnotationClassId() == classId }
return resolvedAnnotationsWithClassIds.hasAnnotation(classId)
}
fun List<FirAnnotation>.hasAnnotation(classId: ClassId): Boolean {
return this.any { it.toAnnotationClassId() == classId }
}
fun <D> FirBasedSymbol<out D>.getAnnotationByClassId(classId: ClassId): FirAnnotation? where D : FirAnnotationContainer, D : FirDeclaration {
@@ -111,7 +112,11 @@ fun <D> FirBasedSymbol<out D>.getAnnotationByClassId(classId: ClassId): FirAnnot
}
fun FirAnnotationContainer.getAnnotationByClassId(classId: ClassId): FirAnnotation? {
return annotations.find {
return annotations.getAnnotationByClassId(classId)
}
fun List<FirAnnotation>.getAnnotationByClassId(classId: ClassId): FirAnnotation? {
return find {
it.annotationTypeRef.coneTypeSafe<ConeClassLikeType>()?.lookupTag?.classId == classId
}
}
@@ -173,6 +178,15 @@ private inline fun <reified T> FirAnnotation.getPrimitiveArgumentValue(name: Nam
}
}
fun FirAnnotation.getKClassArgument(name: Name): ConeKotlinType? {
val argument = findArgumentByName(name) as? FirGetClassCall ?: return null
return argument.getTargetType()
}
fun FirGetClassCall.getTargetType(): ConeKotlinType? {
return typeRef.coneType.typeArguments.getOrNull(0)?.type
}
fun FirAnnotationContainer.getJvmNameFromAnnotation(target: AnnotationUseSiteTarget? = null): String? {
val annotationCalls = getAnnotationsByClassId(StandardClassIds.Annotations.JvmName)
return annotationCalls.firstNotNullOfOrNull { call ->
@@ -32,12 +32,13 @@ fun FirClass.primaryConstructorIfAny(session: FirSession): FirConstructorSymbol?
return constructors(session).find(FirConstructorSymbol::isPrimary)
}
fun FirRegularClass.collectEnumEntries(): Collection<FirEnumEntry> {
// TODO: dog shit, rewrite with scopes
fun FirClass.collectEnumEntries(): Collection<FirEnumEntry> {
assert(classKind == ClassKind.ENUM_CLASS)
return declarations.filterIsInstance<FirEnumEntry>()
}
fun FirRegularClassSymbol.collectEnumEntries(): Collection<FirEnumEntrySymbol> {
fun FirClassSymbol<*>.collectEnumEntries(): Collection<FirEnumEntrySymbol> {
return fir.collectEnumEntries().map { it.symbol }
}
@@ -118,6 +118,10 @@ fun FirSession.declaredMemberScope(klass: FirClassSymbol<*>): FirContainingNames
return declaredMemberScope(klass.fir)
}
fun FirClassSymbol<*>.declaredMemberScope(session: FirSession): FirContainingNamesAwareScope {
return session.declaredMemberScope(fir)
}
fun FirSession.declaredMemberScopeWithLazyNestedScope(
klass: FirClass,
existingNames: List<Name>,
@@ -5,6 +5,8 @@
package org.jetbrains.kotlin.fir.caches
import kotlin.reflect.KProperty
abstract class FirCache<in K : Any, out V, in CONTEXT> {
abstract fun getValue(key: K, context: CONTEXT): V
abstract fun getValueIfComputed(key: K): V?
@@ -24,6 +26,10 @@ class FirLazyValue<out V, in CONTEXT>(private val cache: FirCache<Unit, V, CONTE
}
}
operator fun <V> FirLazyValue<V, Nothing?>.getValue(thisRef: Any?, property: KProperty<*>): V {
return getValue(context = null)
}
@Suppress("NOTHING_TO_INLINE")
inline fun <V> FirLazyValue<V, Nothing?>.getValue(): V {
return getValue(null)
@@ -10,7 +10,12 @@ import org.jetbrains.kotlin.descriptors.SourceElement
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyBackingField
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyGetter
import org.jetbrains.kotlin.fir.expressions.FirPropertyAccessExpression
import org.jetbrains.kotlin.fir.references.impl.FirPropertyFromParameterResolvedNamedReference
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirValueParameterSymbol
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.name.Name
@@ -27,6 +32,12 @@ var FirProperty.fromPrimaryConstructor: Boolean? by FirDeclarationDataRegistry.d
var FirClassLikeDeclaration.sourceElement: SourceElement? by FirDeclarationDataRegistry.data(SourceElementKey)
var FirRegularClass.moduleName: String? by FirDeclarationDataRegistry.data(ModuleNameKey)
val FirClassLikeSymbol<*>.sourceElement: SourceElement?
get() = fir.sourceElement
val FirPropertySymbol.fromPrimaryConstructor: Boolean
get() = fir.fromPrimaryConstructor ?: false
/**
* Constraint without corresponding type argument
*/
@@ -93,6 +104,12 @@ val FirProperty.hasBackingField: Boolean
}
}
val FirPropertySymbol.hasBackingField: Boolean
get() {
lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
return fir.hasBackingField
}
fun FirDeclaration.getDanglingTypeConstraintsOrEmpty(): List<DanglingTypeConstraint> {
return when (this) {
is FirRegularClass -> danglingTypeConstraints
@@ -101,3 +118,11 @@ fun FirDeclaration.getDanglingTypeConstraintsOrEmpty(): List<DanglingTypeConstra
else -> null
} ?: emptyList()
}
val FirPropertySymbol.correspondingValueParameterFromPrimaryConstructor: FirValueParameterSymbol?
get() {
if (!fromPrimaryConstructor) return null
val initializer = resolvedInitializer as? FirPropertyAccessExpression ?: return null
val reference = initializer.calleeReference as? FirPropertyFromParameterResolvedNamedReference ?: return null
return reference.resolvedSymbol as? FirValueParameterSymbol
}
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.symbols.impl
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.references.FirControlFlowGraphReference
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
import org.jetbrains.kotlin.name.CallableId
@@ -39,6 +40,12 @@ open class FirPropertySymbol(
val hasInitializer: Boolean
get() = fir.initializer != null
val resolvedInitializer: FirExpression?
get() {
lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
return fir.initializer
}
val hasDelegate: Boolean
get() = fir.delegate != null
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.descriptors.deserialization.PLATFORM_DEPENDENT_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.load.java.JvmAbi.JVM_FIELD_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor
@@ -38,6 +39,9 @@ val VOLATILE_ANNOTATION_FQ_NAME = FqName("kotlin.jvm.Volatile")
@JvmField
val TRANSIENT_ANNOTATION_FQ_NAME = FqName("kotlin.jvm.Transient")
@JvmField
val TRANSIENT_ANNOTATION_CLASS_ID = ClassId.topLevel(TRANSIENT_ANNOTATION_FQ_NAME)
@JvmField
val JVM_SERIALIZABLE_LAMBDA_ANNOTATION_FQ_NAME = FqName("kotlin.jvm.JvmSerializableLambda")
@@ -27,11 +27,17 @@ enum class ClassKind(val codeRepresentation: String?) {
get() = this == OBJECT || this == ENUM_ENTRY
}
inline val ClassKind.isClass: Boolean
get() = this == ClassKind.CLASS
inline val ClassKind.isInterface: Boolean
get() = this == ClassKind.INTERFACE
inline val ClassKind.isEnumClass: Boolean
get() = this == ClassKind.ENUM_CLASS
inline val ClassKind.isClass: Boolean
get() = this == ClassKind.CLASS
inline val ClassKind.isAnnotationClass: Boolean
get() = this == ClassKind.ANNOTATION_CLASS
inline val ClassKind.isObject: Boolean
get() = this == ClassKind.OBJECT
@@ -86,6 +86,7 @@
<arg>-Xjvm-default=enable</arg>
<arg>-opt-in=org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI</arg>
<arg>-opt-in=org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi</arg>
<arg>-Xcontext-receivers</arg>
</args>
</configuration>
</plugin>
@@ -34,9 +34,14 @@ import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.*
import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
import org.jetbrains.kotlinx.serialization.compiler.resolve.*
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.contextSerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.enumSerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.objectSerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.polymorphicSerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.referenceArraySerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.sealedSerializerId
abstract class BaseIrGenerator(private val currentClass: IrClass, final override val compilerContext: SerializationPluginContext) :
IrBuilderWithPluginContext {
@@ -581,4 +586,4 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override
)
}
}
}
@@ -16,13 +16,13 @@ import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlinx.serialization.compiler.backend.common.findStandardKotlinTypeSerializer
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.*
import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationPackages
import org.jetbrains.kotlinx.serialization.compiler.resolve.SpecialBuiltins
import org.jetbrains.kotlinx.serialization.compiler.resolve.*
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.contextSerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.enumSerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.objectSerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.polymorphicSerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.referenceArraySerializerId
class IrSerialTypeInfo(
val property: IrSerializableProperty,
@@ -182,7 +182,7 @@ fun findStandardKotlinTypeSerializer(context: SerializationPluginContext, type:
"D" -> if (type.isDouble()) "DoubleSerializer" else null
"C" -> if (type.isChar()) "CharSerializer" else null
null -> null
else -> findStandardKotlinTypeSerializer(typeName)
else -> findStandardKotlinTypeSerializerName(typeName)
} ?: return null
return context.getClassFromRuntimeOrNull(name, SerializationPackages.internalPackageFqName, SerializationPackages.packageFqName)
}
@@ -24,8 +24,13 @@ import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
import org.jetbrains.kotlin.types.typeUtil.representativeUpperBound
import org.jetbrains.kotlinx.serialization.compiler.backend.common.*
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.*
import org.jetbrains.kotlinx.serialization.compiler.resolve.*
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.contextSerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.enumSerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.objectSerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.polymorphicSerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.referenceArraySerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.sealedSerializerId
internal class JsBlockBuilder {
val block: JsBlock = JsBlock()
@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.kotlin.TypeMappingMode
import org.jetbrains.kotlin.load.kotlin.internalName
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.ValueArgument
import org.jetbrains.kotlin.resolve.DescriptorFactory
@@ -57,8 +56,12 @@ import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.ST
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.STRUCTURE_ENCODER_CLASS
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.UNKNOWN_FIELD_EXC
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.typeArgPrefix
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationPackages.internalPackageFqName
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationPackages.packageFqName
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.contextSerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.enumSerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.objectSerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.polymorphicSerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.referenceArraySerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.sealedSerializerId
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
@@ -8,6 +8,7 @@ plugins {
dependencies {
compileOnly(project(":compiler:util"))
compileOnly(project(":core:compiler.common"))
compileOnly(project(":core:deserialization.common.jvm"))
compileOnly(intellijCore())
}
@@ -0,0 +1,56 @@
/*
* 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.diagnostic
import com.intellij.openapi.util.io.JarUtil
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.KotlinCompilerVersion
import org.jetbrains.kotlin.descriptors.SourceElement
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinarySourceElement
import java.io.File
import java.util.jar.Attributes
data class RuntimeVersions(val implementationVersion: ApiVersion?, val requireKotlinVersion: ApiVersion?) {
companion object {
val MINIMAL_SUPPORTED_VERSION = ApiVersion.parse("1.0-M1-SNAPSHOT")!!
val MINIMAL_VERSION_FOR_INLINE_CLASSES = ApiVersion.parse("1.1-M1-SNAPSHOT")!!
}
fun currentCompilerMatchRequired(): Boolean {
val current = requireNotNull(KotlinCompilerVersion.getVersion()?.let(ApiVersion.Companion::parse))
return requireKotlinVersion == null || requireKotlinVersion <= current
}
fun implementationVersionMatchSupported(): Boolean {
return implementationVersion != null && implementationVersion >= MINIMAL_SUPPORTED_VERSION
}
}
object CommonVersionReader {
private val REQUIRE_KOTLIN_VERSION = Attributes.Name("Require-Kotlin-Version")
private const val CLASS_SUFFIX = "!/kotlinx/serialization/KSerializer.class"
fun computeRuntimeVersions(sourceElement: SourceElement?): RuntimeVersions? {
val location = (sourceElement as? KotlinJvmBinarySourceElement)?.binaryClass?.location ?: return null
val jarFile = location.removeSuffix(CLASS_SUFFIX)
if (!jarFile.endsWith(".jar")) return null
val file = File(jarFile)
if (!file.exists()) return null
return getVersionsFromManifest(file)
}
fun getVersionsFromManifest(runtimeLibraryPath: File): RuntimeVersions {
val version = JarUtil.getJarAttribute(runtimeLibraryPath, Attributes.Name.IMPLEMENTATION_VERSION)?.let(ApiVersion.Companion::parse)
val kotlinVersion = JarUtil.getJarAttribute(runtimeLibraryPath, REQUIRE_KOTLIN_VERSION)?.let(ApiVersion.Companion::parse)
return RuntimeVersions(version, kotlinVersion)
}
fun canSupportInlineClasses(currentVersion: RuntimeVersions?): Boolean {
if (currentVersion == null) return true
val implVersion = currentVersion.implementationVersion ?: return false
return implVersion >= RuntimeVersions.MINIMAL_VERSION_FOR_INLINE_CLASSES
}
}
@@ -0,0 +1,44 @@
/*
* 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.resolve
interface ISerializableProperties<S : ISerializableProperty> {
val serializableProperties: List<S>
val isExternallySerializable: Boolean
val serializableConstructorProperties: List<S>
val serializableStandaloneProperties: List<S>
}
val ISerializableProperties<*>.goldenMask: Int
get() {
var goldenMask = 0
var requiredBit = 1
for (property in serializableProperties) {
if (!property.optional) {
goldenMask = goldenMask or requiredBit
}
requiredBit = requiredBit shl 1
}
return goldenMask
}
val ISerializableProperties<*>.goldenMaskList: List<Int>
get() {
val maskSlotCount = serializableProperties.bitMaskSlotCount()
val goldenMaskList = MutableList(maskSlotCount) { 0 }
for (i in serializableProperties.indices) {
if (!serializableProperties[i].optional) {
val slotNumber = i / 32
val bitInSlot = i % 32
goldenMaskList[slotNumber] = goldenMaskList[slotNumber] or (1 shl bitInSlot)
}
}
return goldenMaskList
}
fun List<ISerializableProperty>.bitMaskSlotCount(): Int = size / 32 + 1
fun bitMaskSlotAt(propertyIndex: Int): Int = propertyIndex / 32
@@ -0,0 +1,16 @@
/*
* 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.resolve
import org.jetbrains.kotlin.name.Name
interface ISerializableProperty {
val isConstructorParameterWithDefault: Boolean
val name: String
val originalDescriptorName: Name
val optional: Boolean
val transient: Boolean
}
@@ -41,6 +41,22 @@ object SerializationAnnotations {
val contextualOnPropertyFqName = FqName("kotlinx.serialization.Contextual")
val polymorphicFqName = FqName("kotlinx.serialization.Polymorphic")
val additionalSerializersFqName = FqName("kotlinx.serialization.UseSerializers")
val serializableAnnotationClassId = ClassId.topLevel(serializableAnnotationFqName)
val serializerAnnotationClassId = ClassId.topLevel(serializerAnnotationFqName)
val serialNameAnnotationClassId = ClassId.topLevel(serialNameAnnotationFqName)
val requiredAnnotationClassId = ClassId.topLevel(requiredAnnotationFqName)
val serialTransientClassId = ClassId.topLevel(serialTransientFqName)
val serialInfoClassId = ClassId.topLevel(serialInfoFqName)
val inheritableSerialInfoClassId = ClassId.topLevel(inheritableSerialInfoFqName)
val metaSerializableAnnotationClassId = ClassId.topLevel(metaSerializableAnnotationFqName)
val encodeDefaultClassId = ClassId.topLevel(encodeDefaultFqName)
val contextualClassId = ClassId.topLevel(contextualFqName)
val contextualOnFileClassId = ClassId.topLevel(contextualOnFileFqName)
val contextualOnPropertyClassId = ClassId.topLevel(contextualOnPropertyFqName)
val polymorphicClassId = ClassId.topLevel(polymorphicFqName)
val additionalSerializersClassId = ClassId.topLevel(additionalSerializersFqName)
}
object SerialEntityNames {
@@ -55,9 +71,12 @@ object SerialEntityNames {
// classes
val KCLASS_NAME_FQ = FqName("kotlin.reflect.KClass")
val KCLASS_NAME_CLASS_ID = ClassId.topLevel(KCLASS_NAME_FQ)
val KSERIALIZER_NAME = Name.identifier(KSERIALIZER_CLASS)
val SERIAL_CTOR_MARKER_NAME = Name.identifier("SerializationConstructorMarker")
val KSERIALIZER_NAME_FQ = SerializationPackages.packageFqName.child(KSERIALIZER_NAME)
val KSERIALIZER_CLASS_ID = ClassId.topLevel(KSERIALIZER_NAME_FQ)
val SERIALIZER_CLASS_NAME = Name.identifier(SERIALIZER_CLASS)
val IMPL_NAME = Name.identifier("Impl")
@@ -123,8 +142,30 @@ object SpecialBuiltins {
const val sealedSerializer = "SealedClassSerializer"
const val contextSerializer = "ContextualSerializer"
const val nullableSerializer = "NullableSerializer"
object Names {
val referenceArraySerializer = Name.identifier(SpecialBuiltins.referenceArraySerializer)
val objectSerializer = Name.identifier(SpecialBuiltins.objectSerializer)
val enumSerializer = Name.identifier(SpecialBuiltins.enumSerializer)
val polymorphicSerializer = Name.identifier(SpecialBuiltins.polymorphicSerializer)
val sealedSerializer = Name.identifier(SpecialBuiltins.sealedSerializer)
val contextSerializer = Name.identifier(SpecialBuiltins.contextSerializer)
val nullableSerializer = Name.identifier(SpecialBuiltins.nullableSerializer)
}
}
object PrimitiveBuiltins {
const val booleanSerializer = "BooleanSerializer"
const val byteSerializer = "ByteSerializer"
const val shortSerializer = "ShortSerializer"
const val intSerializer = "IntSerializer"
const val longSerializer = "LongSerializer"
const val floatSerializer = "FloatSerializer"
const val doubleSerializer = "DoubleSerializer"
const val charSerializer = "CharSerializer"
}
object CallingConventions {
const val begin = "beginStructure"
const val end = "endStructure"
@@ -153,3 +194,71 @@ object SerializationDependencies {
val FUNCTION0_FQ = FqName("kotlin.Function0")
val LAZY_PUBLICATION_MODE_NAME = Name.identifier("PUBLICATION")
}
object SerializersClassIds {
val enumSerializerId = ClassId(SerializationPackages.internalPackageFqName, Name.identifier(SpecialBuiltins.enumSerializer))
val polymorphicSerializerId = ClassId(SerializationPackages.packageFqName, Name.identifier(SpecialBuiltins.polymorphicSerializer))
val referenceArraySerializerId = ClassId(SerializationPackages.internalPackageFqName, Name.identifier(SpecialBuiltins.referenceArraySerializer))
val objectSerializerId = ClassId(SerializationPackages.internalPackageFqName, Name.identifier(SpecialBuiltins.objectSerializer))
val sealedSerializerId = ClassId(SerializationPackages.packageFqName, Name.identifier(SpecialBuiltins.sealedSerializer))
val contextSerializerId = ClassId(SerializationPackages.packageFqName, Name.identifier(SpecialBuiltins.contextSerializer))
}
fun findStandardKotlinTypeSerializerName(typeName: String?): String? {
return when (typeName) {
null -> null
"kotlin.Unit" -> "UnitSerializer"
"kotlin.Nothing" -> "NothingSerializer"
"kotlin.Boolean" -> "BooleanSerializer"
"kotlin.Byte" -> "ByteSerializer"
"kotlin.Short" -> "ShortSerializer"
"kotlin.Int" -> "IntSerializer"
"kotlin.Long" -> "LongSerializer"
"kotlin.Float" -> "FloatSerializer"
"kotlin.Double" -> "DoubleSerializer"
"kotlin.Char" -> "CharSerializer"
"kotlin.UInt" -> "UIntSerializer"
"kotlin.ULong" -> "ULongSerializer"
"kotlin.UByte" -> "UByteSerializer"
"kotlin.UShort" -> "UShortSerializer"
"kotlin.String" -> "StringSerializer"
"kotlin.Pair" -> "PairSerializer"
"kotlin.Triple" -> "TripleSerializer"
"kotlin.collections.Collection", "kotlin.collections.List",
"kotlin.collections.ArrayList", "kotlin.collections.MutableList" -> "ArrayListSerializer"
"kotlin.collections.Set", "kotlin.collections.LinkedHashSet", "kotlin.collections.MutableSet" -> "LinkedHashSetSerializer"
"kotlin.collections.HashSet" -> "HashSetSerializer"
"kotlin.collections.Map", "kotlin.collections.LinkedHashMap", "kotlin.collections.MutableMap" -> "LinkedHashMapSerializer"
"kotlin.collections.HashMap" -> "HashMapSerializer"
"kotlin.collections.Map.Entry" -> "MapEntrySerializer"
"kotlin.ByteArray" -> "ByteArraySerializer"
"kotlin.ShortArray" -> "ShortArraySerializer"
"kotlin.IntArray" -> "IntArraySerializer"
"kotlin.LongArray" -> "LongArraySerializer"
"kotlin.UByteArray" -> "UByteArraySerializer"
"kotlin.UShortArray" -> "UShortArraySerializer"
"kotlin.UIntArray" -> "UIntArraySerializer"
"kotlin.ULongArray" -> "ULongArraySerializer"
"kotlin.CharArray" -> "CharArraySerializer"
"kotlin.FloatArray" -> "FloatArraySerializer"
"kotlin.DoubleArray" -> "DoubleArraySerializer"
"kotlin.BooleanArray" -> "BooleanArraySerializer"
"kotlin.time.Duration" -> "DurationSerializer"
"java.lang.Boolean" -> "BooleanSerializer"
"java.lang.Byte" -> "ByteSerializer"
"java.lang.Short" -> "ShortSerializer"
"java.lang.Integer" -> "IntSerializer"
"java.lang.Long" -> "LongSerializer"
"java.lang.Float" -> "FloatSerializer"
"java.lang.Double" -> "DoubleSerializer"
"java.lang.Character" -> "CharSerializer"
"java.lang.String" -> "StringSerializer"
"java.util.Collection", "java.util.List", "java.util.ArrayList" -> "ArrayListSerializer"
"java.util.Set", "java.util.LinkedHashSet" -> "LinkedHashSetSerializer"
"java.util.HashSet" -> "HashSetSerializer"
"java.util.Map", "java.util.LinkedHashMap" -> "LinkedHashMapSerializer"
"java.util.HashMap" -> "HashMapSerializer"
"java.util.Map.Entry" -> "MapEntrySerializer"
else -> return null
}
}
@@ -22,9 +22,9 @@ import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.*
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.enumSerializerId
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.referenceArraySerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.*
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.enumSerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds.referenceArraySerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationPackages.internalPackageFqName
open class SerialTypeInfo(
@@ -164,79 +164,21 @@ fun findTypeSerializer(module: ModuleDescriptor, kType: KotlinType): ClassDescri
fun findStandardKotlinTypeSerializer(module: ModuleDescriptor, kType: KotlinType): ClassDescriptor? {
val typeName = kType.getJetTypeFqName(false)
val name = when (typeName) {
"Z" -> if (kType.isBoolean()) "BooleanSerializer" else null
"B" -> if (kType.isByte()) "ByteSerializer" else null
"S" -> if (kType.isShort()) "ShortSerializer" else null
"I" -> if (kType.isInt()) "IntSerializer" else null
"J" -> if (kType.isLong()) "LongSerializer" else null
"F" -> if (kType.isFloat()) "FloatSerializer" else null
"D" -> if (kType.isDouble()) "DoubleSerializer" else null
"C" -> if (kType.isChar()) "CharSerializer" else null
else -> findStandardKotlinTypeSerializer(typeName)
"Z" -> if (kType.isBoolean()) PrimitiveBuiltins.booleanSerializer else null
"B" -> if (kType.isByte()) PrimitiveBuiltins.byteSerializer else null
"S" -> if (kType.isShort()) PrimitiveBuiltins.shortSerializer else null
"I" -> if (kType.isInt()) PrimitiveBuiltins.intSerializer else null
"J" -> if (kType.isLong()) PrimitiveBuiltins.longSerializer else null
"F" -> if (kType.isFloat()) PrimitiveBuiltins.floatSerializer else null
"D" -> if (kType.isDouble()) PrimitiveBuiltins.doubleSerializer else null
"C" -> if (kType.isChar()) PrimitiveBuiltins.charSerializer else null
else -> findStandardKotlinTypeSerializerName(typeName)
} ?: return null
val identifier = Name.identifier(name)
return module.findClassAcrossModuleDependencies(ClassId(internalPackageFqName, identifier))
?: module.findClassAcrossModuleDependencies(ClassId(SerializationPackages.packageFqName, identifier))
}
fun findStandardKotlinTypeSerializer(typeName: String): String? {
return when (typeName) {
"kotlin.Unit" -> "UnitSerializer"
"kotlin.Nothing" -> "NothingSerializer"
"kotlin.Boolean" -> "BooleanSerializer"
"kotlin.Byte" -> "ByteSerializer"
"kotlin.Short" -> "ShortSerializer"
"kotlin.Int" -> "IntSerializer"
"kotlin.Long" -> "LongSerializer"
"kotlin.Float" -> "FloatSerializer"
"kotlin.Double" -> "DoubleSerializer"
"kotlin.Char" -> "CharSerializer"
"kotlin.UInt" -> "UIntSerializer"
"kotlin.ULong" -> "ULongSerializer"
"kotlin.UByte" -> "UByteSerializer"
"kotlin.UShort" -> "UShortSerializer"
"kotlin.String" -> "StringSerializer"
"kotlin.Pair" -> "PairSerializer"
"kotlin.Triple" -> "TripleSerializer"
"kotlin.collections.Collection", "kotlin.collections.List",
"kotlin.collections.ArrayList", "kotlin.collections.MutableList" -> "ArrayListSerializer"
"kotlin.collections.Set", "kotlin.collections.LinkedHashSet", "kotlin.collections.MutableSet" -> "LinkedHashSetSerializer"
"kotlin.collections.HashSet" -> "HashSetSerializer"
"kotlin.collections.Map", "kotlin.collections.LinkedHashMap", "kotlin.collections.MutableMap" -> "LinkedHashMapSerializer"
"kotlin.collections.HashMap" -> "HashMapSerializer"
"kotlin.collections.Map.Entry" -> "MapEntrySerializer"
"kotlin.ByteArray" -> "ByteArraySerializer"
"kotlin.ShortArray" -> "ShortArraySerializer"
"kotlin.IntArray" -> "IntArraySerializer"
"kotlin.LongArray" -> "LongArraySerializer"
"kotlin.UByteArray" -> "UByteArraySerializer"
"kotlin.UShortArray" -> "UShortArraySerializer"
"kotlin.UIntArray" -> "UIntArraySerializer"
"kotlin.ULongArray" -> "ULongArraySerializer"
"kotlin.CharArray" -> "CharArraySerializer"
"kotlin.FloatArray" -> "FloatArraySerializer"
"kotlin.DoubleArray" -> "DoubleArraySerializer"
"kotlin.BooleanArray" -> "BooleanArraySerializer"
"kotlin.time.Duration" -> "DurationSerializer"
"java.lang.Boolean" -> "BooleanSerializer"
"java.lang.Byte" -> "ByteSerializer"
"java.lang.Short" -> "ShortSerializer"
"java.lang.Integer" -> "IntSerializer"
"java.lang.Long" -> "LongSerializer"
"java.lang.Float" -> "FloatSerializer"
"java.lang.Double" -> "DoubleSerializer"
"java.lang.Character" -> "CharSerializer"
"java.lang.String" -> "StringSerializer"
"java.util.Collection", "java.util.List", "java.util.ArrayList" -> "ArrayListSerializer"
"java.util.Set", "java.util.LinkedHashSet" -> "LinkedHashSetSerializer"
"java.util.HashSet" -> "HashSetSerializer"
"java.util.Map", "java.util.LinkedHashMap" -> "LinkedHashMapSerializer"
"java.util.HashMap" -> "HashMapSerializer"
"java.util.Map.Entry" -> "MapEntrySerializer"
else -> return null
}
}
fun findEnumTypeSerializer(module: ModuleDescriptor, kType: KotlinType): ClassDescriptor? {
val classDescriptor = kType.toClassDescriptor ?: return null
return if (classDescriptor.kind == ClassKind.ENUM_CLASS && !classDescriptor.isEnumWithLegacyGeneratedSerializer())
@@ -1,18 +0,0 @@
/*
* 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.backend.jvm
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationPackages
import org.jetbrains.kotlinx.serialization.compiler.resolve.SpecialBuiltins
val enumSerializerId = ClassId(SerializationPackages.internalPackageFqName, Name.identifier(SpecialBuiltins.enumSerializer))
val polymorphicSerializerId = ClassId(SerializationPackages.packageFqName, Name.identifier(SpecialBuiltins.polymorphicSerializer))
val referenceArraySerializerId = ClassId(SerializationPackages.internalPackageFqName, Name.identifier(SpecialBuiltins.referenceArraySerializer))
val objectSerializerId = ClassId(SerializationPackages.internalPackageFqName, Name.identifier(SpecialBuiltins.objectSerializer))
val sealedSerializerId = ClassId(SerializationPackages.packageFqName, Name.identifier(SpecialBuiltins.sealedSerializer))
val contextSerializerId = ClassId(SerializationPackages.packageFqName, Name.identifier(SpecialBuiltins.contextSerializer))
@@ -107,7 +107,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
}
}
private fun checkMinRuntime(versions: VersionReader.RuntimeVersions, descriptor: ClassDescriptor, trace: BindingTrace) {
private fun checkMinRuntime(versions: RuntimeVersions, descriptor: ClassDescriptor, trace: BindingTrace) {
// if RuntimeVersions are present, but implementation version is not,
// it means that we are reading from jar which does not have this manifest parameter - a pre-1.0 serialization runtime.
// For non-JAR distributions (klib, js) this method is not invoked, since getVersionsForCurrentModule
@@ -119,14 +119,14 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
it,
versions.implementationVersion?.toString() ?: "too low",
KotlinCompilerVersion.getVersion() ?: "unknown",
VersionReader.MINIMAL_SUPPORTED_VERSION.toString(),
RuntimeVersions.MINIMAL_SUPPORTED_VERSION.toString(),
)
)
}
}
}
private fun checkMinKotlin(versions: VersionReader.RuntimeVersions, descriptor: ClassDescriptor, trace: BindingTrace) {
private fun checkMinKotlin(versions: RuntimeVersions, descriptor: ClassDescriptor, trace: BindingTrace) {
if (versions.currentCompilerMatchRequired()) return
descriptor.onSerializableOrMetaAnnotation {
trace.report(
@@ -192,7 +192,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
trace.report(
SerializationErrors.INLINE_CLASSES_NOT_SUPPORTED.on(
it,
VersionReader.minVersionForInlineClasses.toString(),
RuntimeVersions.MINIMAL_VERSION_FOR_INLINE_CLASSES.toString(),
VersionReader.getVersionsForCurrentModuleFromTrace(descriptor.module, trace)?.implementationVersion.toString()
)
)
@@ -386,7 +386,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
trace.report(
SerializationErrors.INLINE_CLASSES_NOT_SUPPORTED.on(
element ?: fallbackElement,
VersionReader.minVersionForInlineClasses.toString(),
RuntimeVersions.MINIMAL_VERSION_FOR_INLINE_CLASSES.toString(),
VersionReader.getVersionsForCurrentModuleFromTrace(module, trace)?.implementationVersion.toString()
)
)
@@ -5,11 +5,7 @@
package org.jetbrains.kotlinx.serialization.compiler.diagnostic
import com.intellij.openapi.util.io.JarUtil
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.KotlinCompilerVersion
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinarySourceElement
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.util.slicedMap.Slices
@@ -17,31 +13,8 @@ import org.jetbrains.kotlin.util.slicedMap.WritableSlice
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
import org.jetbrains.kotlinx.serialization.compiler.resolve.getClassFromSerializationPackage
import java.io.File
import java.util.jar.Attributes
object VersionReader {
data class RuntimeVersions(val implementationVersion: ApiVersion?, val requireKotlinVersion: ApiVersion?) {
fun currentCompilerMatchRequired(): Boolean {
val current = requireNotNull(KotlinCompilerVersion.getVersion()?.let(ApiVersion.Companion::parse))
return requireKotlinVersion == null || requireKotlinVersion <= current
}
fun implementationVersionMatchSupported(): Boolean {
return implementationVersion != null && implementationVersion >= MINIMAL_SUPPORTED_VERSION
}
}
fun getVersionsFromManifest(runtimeLibraryPath: File): RuntimeVersions {
val version = JarUtil.getJarAttribute(runtimeLibraryPath, Attributes.Name.IMPLEMENTATION_VERSION)?.let(ApiVersion.Companion::parse)
val kotlinVersion = JarUtil.getJarAttribute(runtimeLibraryPath, REQUIRE_KOTLIN_VERSION)?.let(ApiVersion.Companion::parse)
return RuntimeVersions(version, kotlinVersion)
}
val MINIMAL_SUPPORTED_VERSION = ApiVersion.parse("1.0-M1-SNAPSHOT")!!
private val REQUIRE_KOTLIN_VERSION = Attributes.Name("Require-Kotlin-Version")
private const val CLASS_SUFFIX = "!/kotlinx/serialization/KSerializer.class"
private val VERSIONS_SLICE: WritableSlice<ModuleDescriptor, RuntimeVersions> = Slices.createSimpleSlice()
fun getVersionsForCurrentModuleFromTrace(module: ModuleDescriptor, trace: BindingTrace): RuntimeVersions? {
@@ -58,20 +31,16 @@ object VersionReader {
fun getVersionsForCurrentModule(module: ModuleDescriptor): RuntimeVersions? {
val markerClass = module.getClassFromSerializationPackage(SerialEntityNames.KSERIALIZER_CLASS)
val location = (markerClass.source as? KotlinJvmBinarySourceElement)?.binaryClass?.location ?: return null
val jarFile = location.removeSuffix(CLASS_SUFFIX)
if (!jarFile.endsWith(".jar")) return null
val file = File(jarFile)
if (!file.exists()) return null
return getVersionsFromManifest(file)
return CommonVersionReader.computeRuntimeVersions(markerClass.source)
}
internal val minVersionForInlineClasses = ApiVersion.parse("1.1-M1-SNAPSHOT")!!
fun canSupportInlineClasses(module: ModuleDescriptor, trace: BindingTrace): Boolean {
// Klibs do not have manifest file, unfortunately, so we hope for the better
val currentVersion = getVersionsForCurrentModuleFromTrace(module, trace) ?: return true
val implVersion = currentVersion.implementationVersion ?: return false
return implVersion >= minVersionForInlineClasses
return CommonVersionReader.canSupportInlineClasses(getVersionsForCurrentModuleFromTrace(module, trace))
}
// This method is needed to keep compatibility with IDE plugin
fun getVersionsFromManifest(runtimeLibraryPath: File): RuntimeVersions {
return CommonVersionReader.getVersionsFromManifest(runtimeLibraryPath)
}
}
@@ -21,13 +21,6 @@ import org.jetbrains.kotlinx.serialization.compiler.diagnostic.SERIALIZABLE_PROP
import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationDescriptorSerializerPlugin
import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginMetadataExtensions
interface ISerializableProperties<S : ISerializableProperty> {
val serializableProperties: List<S>
val isExternallySerializable: Boolean
val serializableConstructorProperties: List<S>
val serializableStandaloneProperties: List<S>
}
class SerializableProperties(private val serializableClass: ClassDescriptor, val bindingContext: BindingContext) :
ISerializableProperties<SerializableProperty> {
private val primaryConstructorParameters: List<ValueParameterDescriptor> =
@@ -119,38 +112,6 @@ fun PropertyDescriptor.declaresDefaultValue(): Boolean {
return false
}
val ISerializableProperties<*>.goldenMask: Int
get() {
var goldenMask = 0
var requiredBit = 1
for (property in serializableProperties) {
if (!property.optional) {
goldenMask = goldenMask or requiredBit
}
requiredBit = requiredBit shl 1
}
return goldenMask
}
val ISerializableProperties<*>.goldenMaskList: List<Int>
get() {
val maskSlotCount = serializableProperties.bitMaskSlotCount()
val goldenMaskList = MutableList(maskSlotCount) { 0 }
for (i in serializableProperties.indices) {
if (!serializableProperties[i].optional) {
val slotNumber = i / 32
val bitInSlot = i % 32
goldenMaskList[slotNumber] = goldenMaskList[slotNumber] or (1 shl bitInSlot)
}
}
return goldenMaskList
}
fun List<ISerializableProperty>.bitMaskSlotCount() = size / 32 + 1
fun bitMaskSlotAt(propertyIndex: Int) = propertyIndex / 32
fun BindingContext.serializablePropertiesFor(
classDescriptor: ClassDescriptor,
serializationDescriptorSerializer: SerializationDescriptorSerializerPlugin? = null
@@ -160,7 +121,7 @@ fun BindingContext.serializablePropertiesFor(
return props
}
fun <P: ISerializableProperty> restoreCorrectOrderFromClassProtoExtension(descriptor: ClassDescriptor, props: List<P>): List<P> {
fun <P : ISerializableProperty> restoreCorrectOrderFromClassProtoExtension(descriptor: ClassDescriptor, props: List<P>): List<P> {
if (descriptor !is DeserializedClassDescriptor) return props
val correctOrder: List<Name> = descriptor.classProto.getExtension(SerializationPluginMetadataExtensions.propertiesNamesInProgramOrder)
.map { descriptor.c.nameResolver.getName(it) }
@@ -21,14 +21,6 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlinx.serialization.compiler.backend.common.analyzeSpecialSerializers
interface ISerializableProperty {
val isConstructorParameterWithDefault: Boolean
val name: String
val originalDescriptorName: Name
val optional: Boolean
val transient: Boolean
}
class SerializableProperty(
val descriptor: PropertyDescriptor,
override val isConstructorParameterWithDefault: Boolean,
@@ -0,0 +1,42 @@
/*
* 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.utils.hasBackingField
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlinx.serialization.compiler.fir.services.analyzeSpecialSerializers
import org.jetbrains.kotlinx.serialization.compiler.resolve.ISerializableProperties
import org.jetbrains.kotlinx.serialization.compiler.resolve.ISerializableProperty
class FirSerializableProperty(
session: FirSession,
val propertySymbol: FirPropertySymbol,
override val isConstructorParameterWithDefault: Boolean,
declaresDefaultValue: Boolean
) : ISerializableProperty {
override val name: String = propertySymbol.serialNameValue ?: propertySymbol.name.asString()
override val originalDescriptorName: Name
get() = propertySymbol.name
override val optional: Boolean = !propertySymbol.serialRequired && declaresDefaultValue
override val transient: Boolean = propertySymbol.hasSerialTransient || !propertySymbol.hasBackingField
val serializableWith: ConeKotlinType? = propertySymbol.serializableWith
?: analyzeSpecialSerializers(session, propertySymbol.resolvedAnnotationsWithArguments)?.defaultType()
}
class FirSerializableProperties(
override val serializableProperties: List<FirSerializableProperty>,
override val isExternallySerializable: Boolean,
override val serializableConstructorProperties: List<FirSerializableProperty>,
override val serializableStandaloneProperties: List<FirSerializableProperty>,
) : ISerializableProperties<FirSerializableProperty>
@@ -6,10 +6,22 @@
package org.jetbrains.kotlinx.serialization.compiler.fir
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar
import org.jetbrains.kotlinx.serialization.compiler.fir.checkers.FirSerializationCheckersComponent
import org.jetbrains.kotlinx.serialization.compiler.fir.services.ContextualSerializersProvider
import org.jetbrains.kotlinx.serialization.compiler.fir.services.DependencySerializationInfoProvider
import org.jetbrains.kotlinx.serialization.compiler.fir.services.FirSerializablePropertiesProvider
import org.jetbrains.kotlinx.serialization.compiler.fir.services.FirVersionReader
class FirSerializationExtensionRegistrar : FirExtensionRegistrar() {
override fun ExtensionRegistrarContext.configurePlugin() {
+::SerializationFirResolveExtension
+::SerializationFirSupertypesExtension
+::FirSerializationCheckersComponent
// services
+::DependencySerializationInfoProvider
+::FirSerializablePropertiesProvider
+::FirVersionReader
+::ContextualSerializersProvider
}
}
@@ -135,7 +135,7 @@ class SerializationFirResolveExtension(session: FirSession) : FirDeclarationGene
override fun generateFunctions(callableId: CallableId, context: MemberGenerationContext?): List<FirNamedFunctionSymbol> {
val owner = context?.owner ?: return emptyList()
if (callableId.callableName == SerialEntityNames.SERIALIZER_PROVIDER_NAME) {
val serializableClass = session.getSerializableClassDescriptorByCompanion(owner) ?: return emptyList()
val serializableClass = owner.getSerializableClassSymbolIfCompanion(session) ?: return emptyList()
return listOf(generateSerializerGetterInCompanion(owner, serializableClass, callableId))
}
if (owner.name != SerialEntityNames.SERIALIZER_CLASS_NAME) return emptyList()
@@ -5,50 +5,163 @@
package org.jetbrains.kotlinx.serialization.compiler.fir
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingDeclarationSymbol
import org.jetbrains.kotlin.fir.declarations.findArgumentByName
import org.jetbrains.kotlin.fir.declarations.utils.isCompanion
import org.jetbrains.kotlin.fir.expressions.classId
import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClassSymbol
import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.*
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
import org.jetbrains.kotlin.fir.expressions.arguments
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.scopes.getSingleClassifier
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlinx.serialization.compiler.fir.services.dependencySerializationInfoProvider
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations.inheritableSerialInfoClassId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations.metaSerializableAnnotationClassId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations.serialInfoClassId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations.serialNameAnnotationClassId
import org.jetbrains.kotlinx.serialization.compiler.resolve.SpecialBuiltins
internal val FirClassSymbol<*>.hasSerializableAnnotation
get() = this.annotations.any {
it.classId?.asSingleFqName()?.asString() == SerializationAnnotations.serializableAnnotationFqName.asString()
}
object AnnotationParameterNames {
val VALUE = Name.identifier("value")
val WITH = Name.identifier("with")
val FOR_CLASS = Name.identifier("forClass")
}
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
}
// ---------------------- annotations utils ----------------------
val FirBasedSymbol<*>.isSerialInfoAnnotation: Boolean
get() = hasAnnotation(serialInfoClassId)
|| hasAnnotation(inheritableSerialInfoClassId)
|| hasAnnotation(metaSerializableAnnotationClassId)
val FirBasedSymbol<*>.isInheritableSerialInfoAnnotation: Boolean
get() = hasAnnotation(inheritableSerialInfoClassId)
val FirBasedSymbol<*>.serialNameAnnotation: FirAnnotation?
get() = resolvedAnnotationsWithArguments.getAnnotationByClassId(serialNameAnnotationClassId)
val FirBasedSymbol<*>.serialNameValue: String?
get() = serialNameAnnotation?.getStringArgument(AnnotationParameterNames.VALUE)
val FirBasedSymbol<*>.serialRequired: Boolean
get() = hasAnnotation(SerializationAnnotations.requiredAnnotationClassId)
val FirBasedSymbol<*>.hasSerialTransient: Boolean
get() = serialTransientAnnotation != null
val FirBasedSymbol<*>.serialTransientAnnotation: FirAnnotation?
get() = getAnnotationByClassId(SerializationAnnotations.serialTransientClassId)
context(CheckerContext)
val FirBasedSymbol<*>.hasAnySerialAnnotation: Boolean
get() = serialNameValue != null || resolvedAnnotationsWithClassIds.any { it.annotationClassSymbol?.isSerialInfoAnnotation == true }
val FirClassSymbol<*>.hasSerializableAnnotation: Boolean
get() = serializableAnnotation(needArguments = false) != null
fun FirBasedSymbol<*>.serializableAnnotation(needArguments: Boolean): FirAnnotation? {
val annotations = if (needArguments) resolvedAnnotationsWithClassIds else resolvedAnnotationsWithArguments
return annotations.serializableAnnotation()
}
fun List<FirAnnotation>.serializableAnnotation(): FirAnnotation? {
return getAnnotationByClassId(SerializationAnnotations.serializableAnnotationClassId)
}
val FirClassSymbol<*>.hasSerializableAnnotationWithoutArgs: Boolean
get() = serializableAnnotation(needArguments = false)?.let {
if (it is FirAnnotationCall) {
it.arguments.isEmpty()
} else {
it.argumentMapping.mapping.isEmpty()
}
} ?: false
context(CheckerContext)
val FirClassSymbol<*>.hasSerializableOrMetaAnnotationWithoutArgs: Boolean
get() = hasSerializableAnnotationWithoutArgs || (!hasSerializableAnnotation && hasMetaSerializableAnnotation)
context(CheckerContext)
val FirClassSymbol<*>.hasSerializableOrMetaAnnotation
get() = hasSerializableAnnotation || hasMetaSerializableAnnotation
context(CheckerContext)
val FirClassSymbol<*>.hasMetaSerializableAnnotation: Boolean
get() = metaSerializableAnnotation(needArguments = false) != null
context(CheckerContext)
fun FirClassSymbol<*>.metaSerializableAnnotation(needArguments: Boolean): FirAnnotation? {
val annotations = if (needArguments) resolvedAnnotationsWithClassIds else resolvedAnnotationsWithArguments
return annotations.firstOrNull { it.isMetaSerializableAnnotation }
}
context(CheckerContext)
val FirAnnotation.isMetaSerializableAnnotation: Boolean
get() = annotationClassSymbol?.hasAnnotation(metaSerializableAnnotationClassId) ?: false
context(CheckerContext)
val ConeKotlinType.serializableWith: ConeKotlinType?
get() = customAnnotations.serializableWith ?: toRegularClassSymbol(session)?.serializableWith
internal val FirBasedSymbol<*>.serializableWith: ConeKotlinType?
get() = serializableAnnotation(needArguments = true)?.getKClassArgument(AnnotationParameterNames.WITH)
internal val List<FirAnnotation>.serializableWith: ConeKotlinType?
get() = serializableAnnotation()?.getKClassArgument(AnnotationParameterNames.WITH)
internal val FirClassSymbol<*>.serializerForClass: ConeKotlinType?
get() = resolvedAnnotationsWithArguments
.getAnnotationByClassId(SerializationAnnotations.serializerAnnotationClassId)
?.getKClassArgument(AnnotationParameterNames.FOR_CLASS)
internal val FirClassSymbol<*>.serializerAnnotation: FirAnnotation?
get() = getAnnotationByClassId(SerializationAnnotations.serializerAnnotationClassId)
context(CheckerContext)
private val FirAnnotation.annotationClassSymbol: FirRegularClassSymbol?
get() = annotationTypeRef.coneType
.fullyExpandedType(session)
.toRegularClassSymbol(session)
// ---------------------- class utils ----------------------
internal val FirClassSymbol<*>.shouldHaveGeneratedMethodsInCompanion: Boolean
get() = this.isSerializableObject || this.isSerializableEnum() || this.classKind == ClassKind.CLASS && hasSerializableAnnotation || this.isSealedSerializableInterface
get() = this.isSerializableObject || this.isSerializableEnum || this.classKind == ClassKind.CLASS && hasSerializableAnnotation || this.isSealedSerializableInterface
internal val FirClassSymbol<*>.isSerializableObject: Boolean
get() = classKind == ClassKind.OBJECT && hasSerializableAnnotation
get() = classKind.isObject && hasSerializableAnnotation
internal val FirClassSymbol<*>.isInternallySerializableObject: Boolean
get() = classKind == ClassKind.OBJECT && hasSerializableAnnotationWithoutArgs
get() = classKind.isObject && hasSerializableAnnotationWithoutArgs
internal val FirClassSymbol<*>.isSealedSerializableInterface: Boolean
get() = classKind == ClassKind.INTERFACE && rawStatus.modality == Modality.SEALED && hasSerializableAnnotation
get() = classKind.isInterface && rawStatus.modality == Modality.SEALED && hasSerializableAnnotation
internal val FirClassSymbol<*>.isInternalSerializable: Boolean
get() {
if (classKind != ClassKind.CLASS) return false
if (!classKind.isClass) return false
return hasSerializableAnnotationWithoutArgs
}
internal fun FirClassSymbol<*>.isSerializableEnum(): Boolean = classKind == ClassKind.ENUM_CLASS && hasSerializableAnnotation
internal val FirClassSymbol<*>.isInternallySerializableEnum: Boolean
get() = classKind.isEnumClass && hasSerializableAnnotationWithoutArgs
internal val FirClassSymbol<*>.isSerializableEnum: Boolean
get() = classKind.isEnumClass && hasSerializableAnnotation
internal fun FirClassSymbol<*>.isFinalOrOpen(): Boolean {
val modality = rawStatus.modality
@@ -56,10 +169,102 @@ internal fun FirClassSymbol<*>.isFinalOrOpen(): Boolean {
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
internal fun FirClassSymbol<*>.getSerializableClassSymbolIfCompanion(session: FirSession): FirClassSymbol<*>? {
if (isSerializableObject) return this
if (!isCompanion) return null
val classDescriptor = (getContainingDeclarationSymbol(session) as? FirClassSymbol<*>) ?: return null
if (!classDescriptor.shouldHaveGeneratedMethodsInCompanion) return null
return classDescriptor
}
}
val FirClassSymbol<*>.hasCompanionObjectAsSerializer: Boolean
get() = isInternallySerializableObject ||
(this as? FirRegularClassSymbol)?.companionObjectSymbol?.serializerForClass == this.defaultType()
fun FirClassSymbol<*>.getSuperClassNotAny(session: FirSession): FirRegularClassSymbol? {
return getSuperClassOrAny(session).takeUnless { it.classId == StandardClassIds.Any }
}
fun FirClassSymbol<*>.getSuperClassOrAny(session: FirSession): FirRegularClassSymbol {
return resolvedSuperTypes.firstNotNullOfOrNull { superType ->
superType.fullyExpandedType(session)
.toRegularClassSymbol(session)
?.takeIf { it.classKind == ClassKind.CLASS }
} ?: session.builtinTypes.anyType.toRegularClassSymbol(session) ?: error("Symbol for kotlin/Any not found")
}
context(CheckerContext)
val FirClassSymbol<*>?.classSerializer: FirClassSymbol<*>?
get() {
if (this == null) return null
// serializer annotation on class?
serializableWith?.let { return it.toRegularClassSymbol(session) }
// companion object serializer?
if (this is FirRegularClassSymbol && hasCompanionObjectAsSerializer) return companionObjectSymbol
// can infer @Poly?
polymorphicSerializerIfApplicableAutomatically?.let { return it }
// default serializable?
if (shouldHaveGeneratedSerializer) {
// $serializer nested class
return unsubstitutedScope(this@CheckerContext)
.getSingleClassifier(SerialEntityNames.SERIALIZER_CLASS_NAME) as? FirClassSymbol<*>
}
return null
}
context(CheckerContext)
val FirClassSymbol<*>.polymorphicSerializerIfApplicableAutomatically: FirClassSymbol<*>?
get() {
val serializerName = when {
isInterface -> when (modality) {
Modality.SEALED -> SpecialBuiltins.sealedSerializer
else -> SpecialBuiltins.polymorphicSerializer
}
isInternalSerializable -> when (modality) {
Modality.SEALED -> SpecialBuiltins.sealedSerializer
Modality.ABSTRACT -> SpecialBuiltins.polymorphicSerializer
else -> null
}
else -> null
}
return serializerName?.let { session.dependencySerializationInfoProvider.getClassFromSerializationPackage(Name.identifier(it)) }
}
context(CheckerContext)
val FirClassSymbol<*>.isEnumWithLegacyGeneratedSerializer: Boolean
get() = isInternallySerializableEnum && session.dependencySerializationInfoProvider.useGeneratedEnumSerializer
context(CheckerContext)
val FirClassSymbol<*>.shouldHaveGeneratedSerializer: Boolean
get() = (isInternalSerializable && (modality == Modality.FINAL || modality == Modality.OPEN)) || isEnumWithLegacyGeneratedSerializer
// ---------------------- type utils ----------------------
val ConeKotlinType.isKSerializer: Boolean
get() = classId == SerialEntityNames.KSERIALIZER_CLASS_ID
fun ConeKotlinType.serializerForType(session: FirSession): ConeKotlinType? {
return this.fullyExpandedType(session)
.toRegularClassSymbol(session)
?.resolvedSuperTypes
?.find { it.isKSerializer }
?.typeArguments
?.firstOrNull()
?.type
}
val ConeKotlinType.isTypeParameter: Boolean
get() = this is ConeTypeParameterType
context(CheckerContext)
val ConeKotlinType.overriddenSerializer: ConeKotlinType?
get() = toRegularClassSymbol(session)?.serializableWith
context(CheckerContext)
val ConeKotlinType.isGeneratedSerializableObject: Boolean
get() = toRegularClassSymbol(session)?.let { it.classKind.isObject && it.hasSerializableOrMetaAnnotationWithoutArgs } ?: false
// ---------------------- other ----------------------
val CheckerContext.currentFile: FirFile
get() = containingDeclarations.first() as FirFile
@@ -0,0 +1,17 @@
/*
* 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.checkers
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker
import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension
class FirSerializationCheckersComponent(session: FirSession) : FirAdditionalCheckersExtension(session) {
override val declarationCheckers: DeclarationCheckers = object : DeclarationCheckers() {
override val classCheckers: Set<FirClassChecker> = setOf(FirSerializationPluginClassChecker)
}
}
@@ -0,0 +1,45 @@
/*
* 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.checkers
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.diagnostics.*
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.psi.KtAnnotationEntry
object FirSerializationErrors {
val INLINE_CLASSES_NOT_SUPPORTED by error2<PsiElement, String, String>()
val PLUGIN_IS_NOT_ENABLED by warning0<PsiElement>()
val ANONYMOUS_OBJECTS_NOT_SUPPORTED by error0<PsiElement>()
val INNER_CLASSES_NOT_SUPPORTED by error0<PsiElement>()
val EXPLICIT_SERIALIZABLE_IS_REQUIRED by warning0<PsiElement>()
val SERIALIZABLE_ANNOTATION_IGNORED by error0<KtAnnotationEntry>()
val NON_SERIALIZABLE_PARENT_MUST_HAVE_NOARG_CTOR by error0<KtAnnotationEntry>()
val PRIMARY_CONSTRUCTOR_PARAMETER_IS_NOT_A_PROPERTY by error0<KtAnnotationEntry>()
val DUPLICATE_SERIAL_NAME by error1<KtAnnotationEntry, String>()
val DUPLICATE_SERIAL_NAME_ENUM by error3<PsiElement, FirClassSymbol<*>, String, String>()
val SERIALIZER_NOT_FOUND by error1<PsiElement, ConeKotlinType>()
val SERIALIZER_NULLABILITY_INCOMPATIBLE by error2<PsiElement, ConeKotlinType, ConeKotlinType>()
val SERIALIZER_TYPE_INCOMPATIBLE by warning3<PsiElement, ConeKotlinType, ConeKotlinType, ConeKotlinType>()
val LOCAL_SERIALIZER_USAGE by error1<PsiElement, ConeKotlinType>()
val TRANSIENT_MISSING_INITIALIZER by error0<PsiElement>()
val TRANSIENT_IS_REDUNDANT by warning0<PsiElement>()
val INCORRECT_TRANSIENT by warning0<PsiElement>()
val REQUIRED_KOTLIN_TOO_HIGH by error3<KtAnnotationEntry, String, String, String>()
val PROVIDED_RUNTIME_TOO_LOW by error3<KtAnnotationEntry, String, String, String>()
val INCONSISTENT_INHERITABLE_SERIALINFO by error2<PsiElement, ConeKotlinType, ConeKotlinType>()
val EXTERNAL_CLASS_NOT_SERIALIZABLE by error2<PsiElement, FirClassSymbol<*>, ConeKotlinType>()
val EXTERNAL_CLASS_IN_ANOTHER_MODULE by error2<PsiElement, FirClassSymbol<*>, ConeKotlinType>()
}
@@ -0,0 +1,509 @@
/*
* 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.checkers
import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.config.KotlinCompilerVersion
import org.jetbrains.kotlin.descriptors.isEnumClass
import org.jetbrains.kotlin.diagnostics.*
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker
import org.jetbrains.kotlin.fir.analysis.checkers.isInlineClass
import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClassSymbol
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.*
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.moduleData
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.*
import org.jetbrains.kotlin.resolve.jvm.annotations.TRANSIENT_ANNOTATION_CLASS_ID
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlinx.serialization.compiler.diagnostic.RuntimeVersions
import org.jetbrains.kotlinx.serialization.compiler.fir.*
import org.jetbrains.kotlinx.serialization.compiler.fir.serializerForClass
import org.jetbrains.kotlinx.serialization.compiler.fir.services.dependencySerializationInfoProvider
import org.jetbrains.kotlinx.serialization.compiler.fir.services.findTypeSerializerOrContextUnchecked
import org.jetbrains.kotlinx.serialization.compiler.fir.services.serializablePropertiesProvider
import org.jetbrains.kotlinx.serialization.compiler.fir.services.versionReader
object FirSerializationPluginClassChecker : FirClassChecker() {
private val JAVA_SERIALIZABLE_ID = ClassId.topLevel(FqName("java.io.Serializable"))
private const val TOO_LOW = "too low"
private const val UNKNOWN = "unknown"
private const val NA = "N/A"
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
with(context) {
val classSymbol = declaration.symbol
checkEnum(classSymbol, reporter)
checkExternalSerializer(classSymbol, reporter)
if (!canBeSerializedInternally(classSymbol, reporter)) return
if (classSymbol !is FirRegularClassSymbol) return
val properties = buildSerializableProperties(classSymbol, reporter) ?: return
checkCorrectTransientAnnotationIsUsed(classSymbol, properties.serializableProperties, reporter)
checkTransients(classSymbol, reporter)
analyzePropertiesSerializers(classSymbol, properties.serializableProperties, reporter)
checkInheritedAnnotations(classSymbol, reporter)
checkVersions(classSymbol, reporter)
}
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private fun checkExternalSerializer(classSymbol: FirClassSymbol<*>, reporter: DiagnosticReporter) {
val serializableKType = classSymbol.serializerForClass ?: return
val serializableClassSymbol = serializableKType.toRegularClassSymbol(session) ?: return
val properties = session.serializablePropertiesProvider.getSerializablePropertiesForClass(classSymbol)
if (!properties.isExternallySerializable) {
val source = classSymbol.serializerAnnotation?.source ?: classSymbol.source
val error = if (serializableClassSymbol.moduleData == session.moduleData) {
FirSerializationErrors.EXTERNAL_CLASS_NOT_SERIALIZABLE
} else {
FirSerializationErrors.EXTERNAL_CLASS_IN_ANOTHER_MODULE
}
reporter.reportOn(source, error, serializableClassSymbol, serializableKType)
}
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private fun checkInheritedAnnotations(classSymbol: FirClassSymbol<*>, reporter: DiagnosticReporter) {
fun annotationsFilter(annotations: List<FirAnnotation>): List<Pair<ClassId, FirAnnotation>> {
return annotations
.filter { it.annotationTypeRef.toRegularClassSymbol(session)?.isInheritableSerialInfoAnnotation == true }
.mapNotNull { annotation -> annotation.classId?.let { it to annotation } }
}
val annotationByClassId = buildMap {
putAll(annotationsFilter(classSymbol.resolvedAnnotationsWithClassIds))
}
for (superType in classSymbol.resolvedSuperTypes) {
val superSymbol = superType.toRegularClassSymbol(session) ?: continue
val superAnnotations = annotationsFilter(superSymbol.resolvedAnnotationsWithClassIds)
for ((classId, superAnnotation) in superAnnotations) {
val existingAnnotation = annotationByClassId[classId] ?: continue
if (!existingAnnotation.hasSameArguments(superAnnotation)) {
reporter.reportOn(
existingAnnotation.source,
FirSerializationErrors.INCONSISTENT_INHERITABLE_SERIALINFO,
existingAnnotation.annotationTypeRef.coneType,
classSymbol.defaultType()
)
}
}
}
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private fun FirAnnotation.hasSameArguments(other: FirAnnotation): Boolean {
val m1 = argumentMapping.mapping
val m2 = other.argumentMapping.mapping
if (m1.keys != m2.keys) return false
for ((key, v1) in m1) {
val v2 = m2.getValue(key)
if (!v1.isEqualTo(v2)) return false
}
return true
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private fun FirExpression.isEqualTo(other: FirExpression): Boolean {
return when {
this is FirConstExpression<*> && other is FirConstExpression<*> -> kind == other.kind && value == other.value
this is FirGetClassCall && other is FirGetClassCall -> AbstractTypeChecker.equalTypes(
session.typeContext,
typeRef.coneType,
other.typeRef.coneType
)
this is FirPropertyAccessExpression && other is FirPropertyAccessExpression ->
this.toResolvedCallableReference()?.resolvedSymbol == other.toResolvedCallableReference()?.resolvedSymbol
else -> {
val argumentsIfArray1 = when (this) {
is FirVarargArgumentsExpression -> arguments
is FirArrayOfCall -> arguments
else -> return false
}
val argumentsIfArray2 = when (other) {
is FirVarargArgumentsExpression -> other.arguments
is FirArrayOfCall -> other.arguments
else -> return false
}
argumentsIfArray1.size == argumentsIfArray2.size && argumentsIfArray1.zip(argumentsIfArray2)
.all { (a, b) -> a.isEqualTo(b) }
}
}
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private fun checkVersions(classSymbol: FirClassSymbol<*>, reporter: DiagnosticReporter) {
val currentVersions = session.versionReader.runtimeVersions ?: return
if (!currentVersions.implementationVersionMatchSupported()) {
reporter.reportOn(
classSymbol.serializableOrMetaAnnotationSource,
FirSerializationErrors.PROVIDED_RUNTIME_TOO_LOW,
KotlinCompilerVersion.getVersion() ?: TOO_LOW,
currentVersions.implementationVersion?.toString() ?: UNKNOWN,
RuntimeVersions.MINIMAL_SUPPORTED_VERSION.toString()
)
}
if (!currentVersions.currentCompilerMatchRequired()) {
reporter.reportOn(
classSymbol.serializableOrMetaAnnotationSource,
FirSerializationErrors.REQUIRED_KOTLIN_TOO_HIGH,
KotlinCompilerVersion.getVersion() ?: TOO_LOW,
currentVersions.implementationVersion?.toString() ?: UNKNOWN,
currentVersions.requireKotlinVersion?.toString() ?: NA
)
}
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private fun checkCorrectTransientAnnotationIsUsed(
classSymbol: FirClassSymbol<*>,
properties: List<FirSerializableProperty>,
reporter: DiagnosticReporter
) {
if (classSymbol.resolvedSuperTypes.any { it.classId == JAVA_SERIALIZABLE_ID }) return // do not check
for (property in properties) {
if (property.transient) continue
val incorrectTransient = property.propertySymbol.annotations.getAnnotationByClassId(TRANSIENT_ANNOTATION_CLASS_ID)
if (incorrectTransient != null) {
reporter.reportOn(
source = incorrectTransient.source ?: property.propertySymbol.source,
FirSerializationErrors.INCORRECT_TRANSIENT
)
}
}
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private fun canBeSerializedInternally(classSymbol: FirClassSymbol<*>, reporter: DiagnosticReporter): Boolean {
// if enum has meta or SerialInfo annotation on a class or entries and used plugin-generated serializer
if (session.dependencySerializationInfoProvider.useGeneratedEnumSerializer && classSymbol.isSerializableEnumWithMissingSerializer) {
reporter.reportOn(
classSymbol.source,
FirSerializationErrors.EXPLICIT_SERIALIZABLE_IS_REQUIRED,
positioningStrategy = SourceElementPositioningStrategies.ENUM_MODIFIER
)
return false
}
if (!classSymbol.hasSerializableOrMetaAnnotation) return false
if (classSymbol.isAnonymousObjectOrInsideIt) {
reporter.reportOn(classSymbol.serializableOrMetaAnnotationSource, FirSerializationErrors.ANONYMOUS_OBJECTS_NOT_SUPPORTED)
return false
}
if (classSymbol.isInner) {
reporter.reportOn(classSymbol.serializableOrMetaAnnotationSource, FirSerializationErrors.INNER_CLASSES_NOT_SUPPORTED)
return false
}
if (classSymbol.isInline && !session.versionReader.canSupportInlineClasses) {
reporter.reportOn(
classSymbol.serializableOrMetaAnnotationSource,
FirSerializationErrors.INLINE_CLASSES_NOT_SUPPORTED,
RuntimeVersions.MINIMAL_VERSION_FOR_INLINE_CLASSES.toString(),
session.versionReader.runtimeVersions?.implementationVersion.toString()
)
return false
}
if (!classSymbol.hasSerializableOrMetaAnnotationWithoutArgs) {
// defined custom serializer
checkClassWithCustomSerializer(classSymbol, reporter)
return false
}
if (classSymbol.serializableAnnotationIsUseless) {
reporter.reportOn(classSymbol.serializableOrMetaAnnotationSource, FirSerializationErrors.SERIALIZABLE_ANNOTATION_IGNORED)
return false
}
// check that we can instantiate supertype
if (!classSymbol.isEnumClass) { // enums are inherited from java.lang.Enum and can't be inherited from other classes
val superClassSymbol = classSymbol.getSuperClassOrAny(session)
if (!superClassSymbol.isInternalSerializable) {
val noArgConstructorSymbol = superClassSymbol.declarationSymbols.firstOrNull { it is FirConstructorSymbol && it.valueParameterSymbols.isEmpty() }
if (noArgConstructorSymbol == null) {
reporter.reportOn(classSymbol.serializableOrMetaAnnotationSource, FirSerializationErrors.NON_SERIALIZABLE_PARENT_MUST_HAVE_NOARG_CTOR)
return false
}
}
}
return true
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private fun checkClassWithCustomSerializer(classSymbol: FirClassSymbol<*>, reporter: DiagnosticReporter) {
val serializerType = classSymbol.serializableWith?.fullyExpandedType(session) ?: return
checkCustomSerializerMatch(classSymbol, source = null, classSymbol.defaultType(), serializerType, reporter)
checkCustomSerializerIsNotLocal(source = null, classSymbol, serializerType, reporter)
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private val FirClassSymbol<*>.isAnonymousObjectOrInsideIt: Boolean
get() {
if (this is FirAnonymousObjectSymbol) return true
return containingDeclarations.any { it is FirAnonymousObject }
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private fun checkEnum(classSymbol: FirClassSymbol<*>, reporter: DiagnosticReporter) {
if (!classSymbol.isEnumClass) return
val entryBySerialName = mutableMapOf<String, FirEnumEntrySymbol>()
for (enumEntrySymbol in classSymbol.collectEnumEntries()) {
val serialNameAnnotation = enumEntrySymbol.serialNameAnnotation
val serialName = serialNameAnnotation?.getStringArgument(AnnotationParameterNames.VALUE) ?: enumEntrySymbol.name.asString()
val firstEntry = entryBySerialName[serialName]
if (firstEntry != null) {
reporter.reportOn(
source = serialNameAnnotation?.source ?: firstEntry.serialNameAnnotation?.source ?: enumEntrySymbol.source,
FirSerializationErrors.DUPLICATE_SERIAL_NAME_ENUM,
classSymbol,
serialName,
enumEntrySymbol.name.asString()
)
} else {
entryBySerialName[serialName] = enumEntrySymbol
}
}
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private fun buildSerializableProperties(classSymbol: FirClassSymbol<*>, reporter: DiagnosticReporter): FirSerializableProperties? {
if (!classSymbol.hasSerializableOrMetaAnnotation) return null
if (!classSymbol.isInternalSerializable) return null
if (classSymbol.hasCompanionObjectAsSerializer) return null
val properties = session.serializablePropertiesProvider.getSerializablePropertiesForClass(classSymbol)
if (!properties.isExternallySerializable) {
reporter.reportOn(classSymbol.serializableOrMetaAnnotationSource, FirSerializationErrors.PRIMARY_CONSTRUCTOR_PARAMETER_IS_NOT_A_PROPERTY)
}
// check that all names are unique
val namesSet = mutableSetOf<String>()
for (property in properties.serializableProperties) {
val name = property.name
if (!namesSet.add(name)) {
reporter.reportOn(classSymbol.serializableOrMetaAnnotationSource, FirSerializationErrors.DUPLICATE_SERIAL_NAME, name)
}
}
return properties
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private fun checkTransients(classSymbol: FirClassSymbol<*>, reporter: DiagnosticReporter) {
for (propertySymbol in classSymbol.declarationSymbols.filterIsInstance<FirPropertySymbol>()) {
val isInitialized = propertySymbol.isLateInit || declarationHasInitializer(propertySymbol)
val transientAnnotation = propertySymbol.serialTransientAnnotation ?: continue
val hasBackingField = propertySymbol.hasBackingField
if (!hasBackingField) {
reporter.reportOn(transientAnnotation.source ?: propertySymbol.source, FirSerializationErrors.TRANSIENT_IS_REDUNDANT)
} else if (!isInitialized) {
reporter.reportOn(propertySymbol.source, FirSerializationErrors.TRANSIENT_MISSING_INITIALIZER)
}
}
}
private fun declarationHasInitializer(propertySymbol: FirPropertySymbol): Boolean {
return when {
propertySymbol.fromPrimaryConstructor -> propertySymbol.correspondingValueParameterFromPrimaryConstructor?.hasDefaultValue ?: false
else -> propertySymbol.hasInitializer || propertySymbol.hasDelegate
}
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private fun analyzePropertiesSerializers(
classSymbol: FirClassSymbol<*>,
properties: List<FirSerializableProperty>,
reporter: DiagnosticReporter
) {
for (property in properties) {
// TODO: reporting diagnostics on properties from superclasses looks a bad idea
val serializerType = property.serializableWith
val serializerSymbol = serializerType?.toRegularClassSymbol(session)
val propertySymbol = property.propertySymbol
val typeRef = propertySymbol.resolvedReturnTypeRef
val propertyType = typeRef.coneType
val source = typeRef.source ?: propertySymbol.source
if (serializerType != null && serializerSymbol != null) {
checkCustomSerializerMatch(
classSymbol,
source = typeRef.source ?: propertySymbol.source,
propertyType,
serializerType,
reporter
)
checkCustomSerializerIsNotLocal(
source = propertySymbol.serializableAnnotation(needArguments = false)?.source,
classSymbol,
serializerType,
reporter
)
checkSerializerNullability(propertyType, serializerType, source, reporter)
} else {
checkType(propertyType, source, reporter)
}
}
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private fun checkTypeArguments(type: ConeKotlinType, source: KtSourceElement?, reporter: DiagnosticReporter) {
for (typeArgument in type.typeArguments) {
checkType(
typeArgument.type ?: continue,
source,
reporter
)
}
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private fun canSupportInlineClasses(): Boolean {
return session.versionReader.canSupportInlineClasses
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private val ConeKotlinType.isUnsupportedInlineType: Boolean
get() = isInlineClass(session) && !isPrimitiveOrNullablePrimitive
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private fun checkType(type: ConeKotlinType, source: KtSourceElement?, reporter: DiagnosticReporter) {
if (type.lowerBoundIfFlexible().isTypeParameter) return // type parameters always have serializer stored in class' field
if (type.isUnsupportedInlineType && !canSupportInlineClasses()) {
reporter.reportOn(
source,
FirSerializationErrors.INLINE_CLASSES_NOT_SUPPORTED,
RuntimeVersions.MINIMAL_VERSION_FOR_INLINE_CLASSES.toString(),
session.versionReader.runtimeVersions?.implementationVersion.toString()
)
}
val serializer = findTypeSerializerOrContextUnchecked(type)
if (serializer != null) {
val classSymbol = type.toRegularClassSymbol(session) ?: return
type.serializableWith?.fullyExpandedType(session)?.let { serializerType ->
checkCustomSerializerMatch(classSymbol, source, type, serializerType, reporter)
checkCustomSerializerIsNotLocal(source, classSymbol, serializerType, reporter)
checkSerializerNullability(type, serializerType, source, reporter)
}
checkTypeArguments(type, source, reporter)
} else {
reporter.reportOn(source, FirSerializationErrors.SERIALIZER_NOT_FOUND, type)
}
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private fun checkCustomSerializerMatch(
containingClassSymbol: FirClassSymbol<*>,
source: KtSourceElement?,
declarationType: ConeKotlinType,
serializerType: ConeKotlinType,
reporter: DiagnosticReporter
) {
val serializerForType = serializerType.serializerForType(session) ?: return
val declarationTypeClassId = declarationType.classId
if (declarationTypeClassId == null || declarationTypeClassId != serializerForType.classId) {
// TODO: report on specific type argument (KT-53861)
reporter.reportOn(
source ?: containingClassSymbol.serializableOrMetaAnnotationSource,
FirSerializationErrors.SERIALIZER_TYPE_INCOMPATIBLE,
containingClassSymbol.defaultType(),
serializerType,
serializerForType
)
}
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private fun checkCustomSerializerIsNotLocal(
source: KtSourceElement?,
classSymbol: FirClassSymbol<*>,
serializerType: ConeKotlinType,
reporter: DiagnosticReporter
) {
val serializerClassId = serializerType.classId ?: return
if (serializerClassId.isLocal) {
reporter.reportOn(source ?: classSymbol.serializableOrMetaAnnotationSource, FirSerializationErrors.LOCAL_SERIALIZER_USAGE, serializerType)
}
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private fun checkSerializerNullability(
classType: ConeKotlinType,
serializerType: ConeKotlinType,
source: KtSourceElement?,
reporter: DiagnosticReporter
) {
// @Serializable annotation has proper signature so this error would be caught in type checker
val serializerForType = serializerType.serializerForType(session) ?: return
if (!classType.isMarkedNullable && serializerForType.isMarkedNullable) {
reporter.reportOn(source, FirSerializationErrors.SERIALIZER_NULLABILITY_INCOMPATIBLE, serializerType, classType)
}
}
// --------------------------------------------------------------------------------------
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private val FirClassSymbol<*>.isSerializableEnumWithMissingSerializer: Boolean
get() {
if (!isEnumClass) return false
if (hasSerializableOrMetaAnnotation) return false
if (hasAnySerialAnnotation) return true
return collectEnumEntries().any { it.hasAnySerialAnnotation }
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private val FirClassSymbol<*>.serializableOrMetaAnnotationSource: KtSourceElement?
get() {
serializableAnnotation(needArguments = false)?.source?.let { return it }
metaSerializableAnnotation(needArguments = false)?.source?.let { return it }
return null
}
context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227
private val FirClassSymbol<*>.serializableAnnotationIsUseless: Boolean
get() = !classKind.isEnumClass &&
hasSerializableOrMetaAnnotationWithoutArgs &&
!isInternalSerializable &&
!hasCompanionObjectAsSerializer &&
!isSealedSerializableInterface
}
@@ -0,0 +1,144 @@
/*
* 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.checkers
import org.jetbrains.kotlin.diagnostics.KtDiagnostic
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.diagnostics.KtDiagnosticRenderer
import org.jetbrains.kotlin.diagnostics.rendering.CommonRenderers
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers
object KtDefaultErrorMessagesSerialization {
fun getRendererForDiagnostic(diagnostic: KtDiagnostic): KtDiagnosticRenderer {
val factory = diagnostic.factory
return MAP[factory] ?: factory.ktRenderer
}
val MAP = KtDiagnosticFactoryToRendererMap("Serialization").apply {
put(
FirSerializationErrors.INLINE_CLASSES_NOT_SUPPORTED,
"Inline classes require runtime serialization library version at least {0}, while your classpath has {1}.",
CommonRenderers.STRING,
CommonRenderers.STRING,
)
put(
FirSerializationErrors.PLUGIN_IS_NOT_ENABLED,
"kotlinx.serialization compiler plugin is not applied to the module, so this annotation would not be processed. " +
"Make sure that you've setup your buildscript correctly and re-import project."
)
put(
FirSerializationErrors.ANONYMOUS_OBJECTS_NOT_SUPPORTED,
"Anonymous objects or contained in it classes can not be serializable."
)
put(
FirSerializationErrors.INNER_CLASSES_NOT_SUPPORTED,
"Inner (with reference to outer this) serializable classes are not supported. Remove @Serializable annotation or 'inner' keyword."
)
put(
FirSerializationErrors.EXPLICIT_SERIALIZABLE_IS_REQUIRED,
"Explicit @Serializable annotation on enum class is required when @SerialName or @SerialInfo annotations are used on its members."
)
put(
FirSerializationErrors.SERIALIZABLE_ANNOTATION_IGNORED,
"@Serializable annotation without arguments can be used only on sealed interfaces." +
"Non-sealed interfaces are polymorphically serializable by default."
)
put(
FirSerializationErrors.NON_SERIALIZABLE_PARENT_MUST_HAVE_NOARG_CTOR,
"Impossible to make this class serializable because its parent is not serializable and does not have exactly one constructor without parameters"
)
put(
FirSerializationErrors.PRIMARY_CONSTRUCTOR_PARAMETER_IS_NOT_A_PROPERTY,
"This class is not serializable automatically because it has primary constructor parameters that are not properties"
)
put(
FirSerializationErrors.DUPLICATE_SERIAL_NAME,
"Serializable class has duplicate serial name of property ''{0}'', either in the class itself or its supertypes",
CommonRenderers.STRING
)
put(
FirSerializationErrors.DUPLICATE_SERIAL_NAME_ENUM,
"Enum class ''{0}'' has duplicate serial name ''{1}'' in entry ''{2}''",
FirDiagnosticRenderers.SYMBOL,
CommonRenderers.STRING,
CommonRenderers.STRING
)
put(
FirSerializationErrors.SERIALIZER_NOT_FOUND,
"Serializer has not been found for type ''{0}''. " +
"To use context serializer as fallback, explicitly annotate type or property with @Contextual",
FirDiagnosticRenderers.RENDER_TYPE_WITH_ANNOTATIONS
)
put(
FirSerializationErrors.SERIALIZER_NULLABILITY_INCOMPATIBLE,
"Type ''{1}'' is non-nullable and therefore can not be serialized with serializer for nullable type ''{0}''",
FirDiagnosticRenderers.RENDER_TYPE,
FirDiagnosticRenderers.RENDER_TYPE
)
put(
FirSerializationErrors.SERIALIZER_TYPE_INCOMPATIBLE,
"Class ''{1}'', which is serializer for type ''{2}'', is applied here to type ''{0}''. This may lead to errors or incorrect behavior.",
FirDiagnosticRenderers.RENDER_TYPE,
FirDiagnosticRenderers.RENDER_TYPE,
FirDiagnosticRenderers.RENDER_TYPE
)
put(
FirSerializationErrors.LOCAL_SERIALIZER_USAGE,
"Class ''{0}'' can't be used as a serializer since it is local",
FirDiagnosticRenderers.RENDER_TYPE
)
put(
FirSerializationErrors.TRANSIENT_MISSING_INITIALIZER,
"This property is marked as @Transient and therefore must have an initializing expression"
)
put(
FirSerializationErrors.TRANSIENT_IS_REDUNDANT,
"Property does not have backing field which makes it non-serializable and therefore @Transient is redundant"
)
put(
FirSerializationErrors.INCORRECT_TRANSIENT,
"@kotlin.jvm.Transient does not affect @Serializable classes. Please use @kotlinx.serialization.Transient instead."
)
put(
FirSerializationErrors.REQUIRED_KOTLIN_TOO_HIGH,
"Your current Kotlin version is {0}, while kotlinx.serialization core runtime {1} requires at least Kotlin {2}. " +
"Please update your Kotlin compiler and IDE plugin.",
CommonRenderers.STRING,
CommonRenderers.STRING,
CommonRenderers.STRING
)
put(
FirSerializationErrors.PROVIDED_RUNTIME_TOO_LOW,
"Your current kotlinx.serialization core version is {0}, while current Kotlin compiler plugin {1} requires at least {2}. " +
"Please update your kotlinx.serialization runtime dependency.",
CommonRenderers.STRING,
CommonRenderers.STRING,
CommonRenderers.STRING
)
put(
FirSerializationErrors.INCONSISTENT_INHERITABLE_SERIALINFO,
"Argument values for inheritable serial info annotation ''{0}'' must be the same as the values in parent type ''{1}''",
FirDiagnosticRenderers.RENDER_TYPE,
FirDiagnosticRenderers.RENDER_TYPE
)
put(
FirSerializationErrors.EXTERNAL_CLASS_NOT_SERIALIZABLE,
"Cannot generate external serializer ''{0}'': class ''{1}'' have constructor parameters which are not properties and therefore it is not serializable automatically",
FirDiagnosticRenderers.SYMBOL,
FirDiagnosticRenderers.RENDER_TYPE
)
put(
FirSerializationErrors.EXTERNAL_CLASS_IN_ANOTHER_MODULE,
"Cannot generate external serializer ''{0}'': class ''{1}'' is defined in another module",
FirDiagnosticRenderers.SYMBOL,
FirDiagnosticRenderers.RENDER_TYPE
)
}
}
@@ -0,0 +1,156 @@
/*
* 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.services
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.caches.FirCache
import org.jetbrains.kotlin.fir.caches.createCache
import org.jetbrains.kotlin.fir.caches.firCachesFactory
import org.jetbrains.kotlin.fir.caches.getValue
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.getAnnotationByClassId
import org.jetbrains.kotlin.fir.declarations.getTargetType
import org.jetbrains.kotlin.fir.declarations.hasAnnotation
import org.jetbrains.kotlin.fir.declarations.utils.isEnumClass
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.extensions.FirExtensionSessionComponent
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addToStdlib.runIf
import org.jetbrains.kotlinx.serialization.compiler.fir.*
import org.jetbrains.kotlinx.serialization.compiler.fir.serializableWith
import org.jetbrains.kotlinx.serialization.compiler.resolve.*
class ContextualSerializersProvider(session: FirSession) : FirExtensionSessionComponent(session) {
private val contextualKClassListCache: FirCache<FirFile, Set<ConeKotlinType>, Nothing?> =
session.firCachesFactory.createCache { file ->
buildSet {
addAll(getKClassListFromFileAnnotation(file, SerializationAnnotations.contextualClassId))
addAll(getKClassListFromFileAnnotation(file, SerializationAnnotations.contextualOnFileClassId))
}
}
fun getContextualKClassListForFile(file: FirFile): Set<ConeKotlinType> {
return contextualKClassListCache.getValue(file)
}
private val additionalSerializersInScopeCache: FirCache<FirFile, Map<Pair<FirClassSymbol<*>, Boolean>, FirClassSymbol<*>>, Nothing?> =
session.firCachesFactory.createCache { file ->
getKClassListFromFileAnnotation(file, SerializationAnnotations.additionalSerializersClassId).associateBy(
keySelector = {
val serializerType = it.serializerForType(session)
val symbol = serializerType?.toRegularClassSymbol(session)
?: throw AssertionError("Argument for ${SerializationAnnotations.additionalSerializersFqName} does not implement KSerializer or does not provide serializer for concrete type")
symbol to serializerType.isMarkedNullable
},
valueTransform = { it.toRegularClassSymbol(session)!! }
)
}
fun getAdditionalSerializersInScopeForFile(file: FirFile): Map<Pair<FirClassSymbol<*>, Boolean>, FirClassSymbol<*>> {
return additionalSerializersInScopeCache.getValue(file)
}
private fun getKClassListFromFileAnnotation(file: FirFile, annotationClassId: ClassId): List<ConeKotlinType> {
val annotation = file.symbol.resolvedAnnotationsWithArguments.getAnnotationByClassId(annotationClassId) ?: return emptyList()
val arguments = when (val argument = annotation.argumentMapping.mapping.values.firstOrNull()) {
is FirArrayOfCall -> argument.arguments
is FirVarargArgumentsExpression -> argument.arguments
else -> return emptyList()
}
return arguments.mapNotNull { (it as? FirGetClassCall)?.getTargetType() }
}
}
val FirSession.contextualSerializersProvider: ContextualSerializersProvider by FirSession.sessionComponentAccessor()
context(CheckerContext)
fun findTypeSerializerOrContextUnchecked(type: ConeKotlinType): FirClassSymbol<*>? {
if (type.isTypeParameter) return null
val annotations = type.customAnnotations
annotations.serializableWith?.let { return it.toRegularClassSymbol(session) }
val classSymbol = type.toRegularClassSymbol(session) ?: return null
val currentFile = currentFile
val provider = session.contextualSerializersProvider
provider.getAdditionalSerializersInScopeForFile(currentFile)[classSymbol to type.isMarkedNullable]?.let { return it }
if (type.isMarkedNullable) {
return findTypeSerializerOrContextUnchecked(type.withNullability(ConeNullability.NOT_NULL, session.typeContext))
}
if (type in provider.getContextualKClassListForFile(currentFile)) {
return session.dependencySerializationInfoProvider.getClassFromSerializationPackage(SpecialBuiltins.Names.contextSerializer)
}
return analyzeSpecialSerializers(session, annotations) ?: findTypeSerializer(type)
}
/**
* Returns class descriptor for ContextSerializer or PolymorphicSerializer
* if [annotations] contains @Contextual or @Polymorphic annotation
*/
fun analyzeSpecialSerializers(session: FirSession, annotations: List<FirAnnotation>): FirClassSymbol<*>? = when {
annotations.hasAnnotation(SerializationAnnotations.contextualClassId) ||
annotations.hasAnnotation(SerializationAnnotations.contextualOnPropertyClassId) -> {
session.dependencySerializationInfoProvider.getClassFromSerializationPackage(SpecialBuiltins.Names.contextSerializer)
}
// can be annotation on type usage, e.g. List<@Polymorphic Any>
annotations.hasAnnotation(SerializationAnnotations.polymorphicClassId) -> {
session.dependencySerializationInfoProvider.getClassFromSerializationPackage(SpecialBuiltins.Names.polymorphicSerializer)
}
else -> null
}
context(CheckerContext)
fun findTypeSerializer(type: ConeKotlinType): FirClassSymbol<*>? {
val userOverride = type.overriddenSerializer
if (userOverride != null) return userOverride.toRegularClassSymbol(session)
if (type.isTypeParameter) return null
val serializationProvider = session.dependencySerializationInfoProvider
if (type.isArrayType) {
return serializationProvider.getClassFromInternalSerializationPackage(SpecialBuiltins.Names.referenceArraySerializer)
}
if (type.isGeneratedSerializableObject) {
return serializationProvider.getClassFromInternalSerializationPackage(SpecialBuiltins.Names.objectSerializer)
}
// see if there is a standard serializer
val standardSerializer = findStandardKotlinTypeSerializer(type) ?: findEnumTypeSerializer(type)
if (standardSerializer != null) return standardSerializer
val symbol = type.toRegularClassSymbol(session) ?: return null
if (symbol.isSealedSerializableInterface) {
return serializationProvider.getClassFromSerializationPackage(SpecialBuiltins.Names.polymorphicSerializer)
}
return symbol.classSerializer // check for serializer defined on the type
}
context(CheckerContext)
fun findStandardKotlinTypeSerializer(type: ConeKotlinType): FirClassSymbol<*>? {
val name = when {
type.isBoolean -> PrimitiveBuiltins.booleanSerializer
type.isByte -> PrimitiveBuiltins.byteSerializer
type.isShort -> PrimitiveBuiltins.shortSerializer
type.isInt -> PrimitiveBuiltins.intSerializer
type.isLong -> PrimitiveBuiltins.longSerializer
type.isFloat -> PrimitiveBuiltins.floatSerializer
type.isDouble -> PrimitiveBuiltins.doubleSerializer
type.isChar -> PrimitiveBuiltins.charSerializer
else -> findStandardKotlinTypeSerializerName(type.classId?.asFqNameString())
}?.let(Name::identifier) ?: return null
val symbolProvider = session.symbolProvider
return symbolProvider.getClassLikeSymbolByClassId(ClassId(SerializationPackages.internalPackageFqName, name)) as? FirClassSymbol<*>
?: symbolProvider.getClassLikeSymbolByClassId(ClassId(SerializationPackages.packageFqName, name)) as? FirClassSymbol<*>
}
context(CheckerContext)
fun findEnumTypeSerializer(type: ConeKotlinType): FirClassSymbol<*>? {
val symbol = type.toRegularClassSymbol(session) ?: return null
return runIf(symbol.isEnumClass && !symbol.isEnumWithLegacyGeneratedSerializer) {
session.symbolProvider.getClassLikeSymbolByClassId(SerializersClassIds.enumSerializerId) as? FirClassSymbol<*>
}
}
@@ -0,0 +1,55 @@
/*
* 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.services
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.caches.FirCache
import org.jetbrains.kotlin.fir.caches.firCachesFactory
import org.jetbrains.kotlin.fir.caches.getValue
import org.jetbrains.kotlin.fir.caches.createCache
import org.jetbrains.kotlin.fir.extensions.FirExtensionSessionComponent
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.ENUM_SERIALIZER_FACTORY_FUNC_NAME
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.MARKED_ENUM_SERIALIZER_FACTORY_FUNC_NAME
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationPackages
class DependencySerializationInfoProvider(session: FirSession) : FirExtensionSessionComponent(session) {
val useGeneratedEnumSerializer by session.firCachesFactory.createLazyValue {
val enumSerializerFactory = session.symbolProvider
.getTopLevelFunctionSymbols(SerializationPackages.internalPackageFqName, ENUM_SERIALIZER_FACTORY_FUNC_NAME)
val markedEnumSerializerFactory = session.symbolProvider
.getTopLevelFunctionSymbols(SerializationPackages.internalPackageFqName, MARKED_ENUM_SERIALIZER_FACTORY_FUNC_NAME)
enumSerializerFactory.isEmpty() || markedEnumSerializerFactory.isEmpty()
}
private val classesFromSerializationPackageCache: FirCache<Name, FirClassSymbol<*>, Nothing?> = session.firCachesFactory.createCache { name ->
SerializationPackages.allPublicPackages.firstNotNullOfOrNull { packageName ->
session.symbolProvider.getClassLikeSymbolByClassId(ClassId(packageName, name)) as? FirClassSymbol<*>
} ?: throw IllegalArgumentException("Can't locate cass ${name.identifier}")
}
fun getClassFromSerializationPackage(name: Name): FirClassSymbol<*> {
return classesFromSerializationPackageCache.getValue(name)
}
private val classesFromInternalSerializationPackageCache: FirCache<Name, FirClassSymbol<*>, Nothing?> = session.firCachesFactory.createCache { name ->
session.symbolProvider
.getClassLikeSymbolByClassId(ClassId(SerializationPackages.internalPackageFqName, name)) as? FirClassSymbol<*>
?: throw IllegalArgumentException("Can't locate cass ${name.identifier}")
}
fun getClassFromInternalSerializationPackage(name: Name): FirClassSymbol<*> {
return classesFromInternalSerializationPackageCache.getValue(name)
}
}
val FirSession.dependencySerializationInfoProvider: DependencySerializationInfoProvider by FirSession.sessionComponentAccessor()
@@ -0,0 +1,109 @@
/*
* 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.services
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.primaryConstructorSymbol
import org.jetbrains.kotlin.fir.caches.FirCache
import org.jetbrains.kotlin.fir.caches.createCache
import org.jetbrains.kotlin.fir.caches.firCachesFactory
import org.jetbrains.kotlin.fir.caches.getValue
import org.jetbrains.kotlin.fir.declarations.utils.correspondingValueParameterFromPrimaryConstructor
import org.jetbrains.kotlin.fir.declarations.utils.visibility
import org.jetbrains.kotlin.fir.expressions.toResolvedCallableReference
import org.jetbrains.kotlin.fir.extensions.FirExtensionSessionComponent
import org.jetbrains.kotlin.fir.references.impl.FirPropertyFromParameterResolvedNamedReference
import org.jetbrains.kotlin.fir.scopes.impl.declaredMemberScope
import org.jetbrains.kotlin.fir.scopes.processAllProperties
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirValueParameterSymbol
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlinx.serialization.compiler.fir.*
import org.jetbrains.kotlinx.serialization.compiler.fir.isInternalSerializable
import org.jetbrains.kotlinx.serialization.compiler.resolve.ISerializableProperty
class FirSerializablePropertiesProvider(session: FirSession) : FirExtensionSessionComponent(session) {
private val cache: FirCache<FirClassSymbol<*>, FirSerializableProperties, Nothing?> =
session.firCachesFactory.createCache(this::createSerializableProperties)
fun getSerializablePropertiesForClass(classSymbol: FirClassSymbol<*>): FirSerializableProperties {
return cache.getValue(classSymbol)
}
private fun createSerializableProperties(classSymbol: FirClassSymbol<*>): FirSerializableProperties {
val allPropertySymbols = buildList {
classSymbol
.declaredMemberScope(session)
.processAllProperties {
addIfNotNull(it as? FirPropertySymbol)
}
}
val primaryConstructorProperties = allPropertySymbols.mapNotNull {
val parameterSymbol = it.correspondingValueParameterFromPrimaryConstructor ?: return@mapNotNull null
it to parameterSymbol.hasDefaultValue
}.toMap().withDefault { false }
val isInternalSerializable = classSymbol.isInternalSerializable
fun isPropertySerializable(propertySymbol: FirPropertySymbol): Boolean {
return when {
isInternalSerializable -> !propertySymbol.hasSerialTransient
propertySymbol.visibility == Visibilities.Private -> false
else -> (propertySymbol.isVar && propertySymbol.hasSerialTransient) || propertySymbol in primaryConstructorProperties
}
}
val serializableProperties: List<FirSerializableProperty> = allPropertySymbols.asSequence()
.filter { isPropertySerializable(it) }
.map {
val declaresDefaultValue = it.declaresDefaultValue()
FirSerializableProperty(
session,
it,
primaryConstructorProperties.getValue(it),
declaresDefaultValue
)
}
.filterNot { it.transient }
.partition { it.propertySymbol in primaryConstructorProperties }
.let { (fromConstructor, standalone) ->
val superClassSymbol = classSymbol.getSuperClassNotAny(session)
buildList {
if (superClassSymbol != null && superClassSymbol.isInternalSerializable) {
addAll(getSerializablePropertiesForClass(superClassSymbol).serializableProperties)
}
addAll(fromConstructor)
addAll(standalone)
}
}
.let { restoreCorrectOrderFromClassProtoExtension(classSymbol, it) }
val isExternallySerializable = classSymbol.isInternallySerializableEnum ||
primaryConstructorProperties.size == classSymbol.primaryConstructorSymbol()?.valueParameterSymbols?.size
val (serializableConstructorProperties, serializableStandaloneProperties) = serializableProperties.partition { it.propertySymbol in primaryConstructorProperties }
return FirSerializableProperties(
serializableProperties, isExternallySerializable, serializableConstructorProperties, serializableStandaloneProperties
)
}
}
val FirSession.serializablePropertiesProvider: FirSerializablePropertiesProvider by FirSession.sessionComponentAccessor()
fun FirPropertySymbol.declaresDefaultValue(): Boolean {
if (hasInitializer) return true
// TODO: handle deserialized properties
return false
}
@Suppress("UNUSED_PARAMETER")
fun <P : ISerializableProperty> restoreCorrectOrderFromClassProtoExtension(classSymbol: FirClassSymbol<*>, props: List<P>): List<P> {
// TODO: handle deserialized properties
return props
}
@@ -0,0 +1,30 @@
/*
* 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.services
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.caches.firCachesFactory
import org.jetbrains.kotlin.fir.caches.getValue
import org.jetbrains.kotlin.fir.declarations.utils.sourceElement
import org.jetbrains.kotlin.fir.extensions.FirExtensionSessionComponent
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlinx.serialization.compiler.diagnostic.CommonVersionReader
import org.jetbrains.kotlinx.serialization.compiler.diagnostic.RuntimeVersions
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
class FirVersionReader(session: FirSession) : FirExtensionSessionComponent(session) {
val runtimeVersions: RuntimeVersions? by session.firCachesFactory.createLazyValue lazy@{
val markerClass = session.symbolProvider.getClassLikeSymbolByClassId(SerialEntityNames.KSERIALIZER_CLASS_ID) ?: return@lazy null
CommonVersionReader.computeRuntimeVersions(markerClass.sourceElement)
}
val canSupportInlineClasses by session.firCachesFactory.createLazyValue lazy@{
CommonVersionReader.canSupportInlineClasses(runtimeVersions)
}
}
val FirSession.versionReader: FirVersionReader by FirSession.sessionComponentAccessor()
@@ -1,9 +0,0 @@
// WITH_STDLIB
// FILE: test.kt
import kotlinx.serialization.*
@Serializable
open class Parent(open val arg: Int)
@Serializable
class Derived(override val arg: Int): Parent(arg)
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// WITH_STDLIB
// FILE: test.kt
import kotlinx.serialization.*
@@ -1,28 +0,0 @@
// WITH_STDLIB
// SKIP_TXT
import kotlinx.serialization.*
@Serializable
enum class ImplicitlyDuplicated {
@SerialName("foo")
FIRST,
@SerialName("foo")
SECOND
}
@Serializable
enum class ExplicitlyDuplicated {
FIRST,
SECOND,
@SerialName("FIRST")
THIRD
}
@Serializable
enum class ReversedExplicitlyDuplicated {
@SerialName("THIRD")
FIRST,
SECOND,
THIRD
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// WITH_STDLIB
// SKIP_TXT
import kotlinx.serialization.*
@@ -1,11 +0,0 @@
// WITH_STDLIB
// SKIP_TXT
import kotlinx.serialization.Serializable
import java.io.Serializable as JavaSerializable
@Serializable
class Data(val x: Int, @Transient val y: String)
@Serializable
class Data2(val x: Int, @Transient val y: String) : JavaSerializable
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// WITH_STDLIB
// SKIP_TXT
@@ -1,19 +0,0 @@
// WITH_STDLIB
// SKIP_TXT
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import java.io.Serializable as JavaSerializable
import kotlin.jvm.Transient as JavaTransient
@Serializable
class Data(val x: Int, @Transient val y: String = "a")
@Serializable
class Data2(val x: Int, @Transient val y: String = "a") : JavaSerializable
@Serializable
class Data3(val x: Int, @Transient @JavaTransient val y: String = "a") : JavaSerializable
@Serializable
class Data4(val x: Int, @JavaTransient val y: String)
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// WITH_STDLIB
// SKIP_TXT
@@ -1,25 +1,24 @@
// FIR_IDENTICAL
// WITH_STDLIB
// SKIP_TXT
// !USE_EXPERIMENTAL: kotlinx.serialization.ExperimentalSerializationApi
// USE_EXPERIMENTAL: kotlinx.serialization.ExperimentalSerializationApi
// FILE: test.kt
import kotlinx.serialization.*
import kotlin.reflect.KClass
// TODO: for this test to work, runtime dependency should be updated to (yet unreleased) serialization 1.3.0
//@InheritableSerialInfo
@InheritableSerialInfo
annotation class I(val value: String)
enum class E { A, B }
//@InheritableSerialInfo
@InheritableSerialInfo
annotation class I2(val e: E, val k: KClass<*>)
@Serializable
@I("a")
sealed class Result {
// @I("b")
<!INCONSISTENT_INHERITABLE_SERIALINFO!>@I("b")<!>
@Serializable class OK(val s: String): Result()
}
@@ -35,15 +34,15 @@ open class Correct: A()
@Serializable
@I("a")
//@I2(E.B, E::class)
<!INCONSISTENT_INHERITABLE_SERIALINFO!>@I2(E.B, E::class)<!>
open class B: A()
@Serializable
@I("a")
//@I2(E.A, I::class)
<!INCONSISTENT_INHERITABLE_SERIALINFO!>@I2(E.A, I::class)<!>
open class B2: A()
@Serializable
//@I("b")
//@I2(E.A, E::class)
<!INCONSISTENT_INHERITABLE_SERIALINFO!>@I("b")<!>
<!INCONSISTENT_INHERITABLE_SERIALINFO!>@I2(E.A, E::class)<!>
open class C: B()
@@ -1,46 +0,0 @@
// WITH_STDLIB
// SKIP_TXT
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
fun container() {
@Serializable
class X // local classes are allowed
val y = @Serializable object {
fun inObjectFun() {
@Serializable
class X // local classes in anonymous object functions are not allowed
}
}
class LocalSerializer : KSerializer<Any?> {
override val descriptor: SerialDescriptor = buildSerialDescriptor("tmp", PrimitiveKind.INT)
override fun serialize(encoder: Encoder, value: Any?) {
encoder.encodeNull()
}
override fun deserialize(decoder: Decoder): Any? {
return decoder.decodeNull()
}
}
@Serializable
class WithLocalSerializerInProperty(@Serializable(with = LocalSerializer::class) val x: Any?)
@Serializable(with = LocalSerializer::class)
data class WithLocalSerializer(val i: Int)
}
val topLevelAnon = @Serializable object {}
@Serializable class A {
@Serializable class B // nested classes are allowed
@Serializable inner class C // inner classes are not
@Serializable object F {} // regular named object, OK
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// WITH_STDLIB
// SKIP_TXT
@@ -1,8 +0,0 @@
// WITH_STDLIB
// FILE: test.kt
import kotlinx.serialization.*
open class NonSerializableParent(val arg: Int)
@Serializable
class Derived(val someData: String): NonSerializableParent(42)
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// WITH_STDLIB
// FILE: test.kt
import kotlinx.serialization.*
@@ -1,16 +1,17 @@
// WITH_STDLIB
// FIR_DIFFERENCE: KT-53861
// FILE: test.kt
import kotlinx.serialization.*
class NonSerializable
@Serializable
class Basic(val foo: NonSerializable)
class Basic(val foo: <!SERIALIZER_NOT_FOUND("NonSerializable")!>NonSerializable<!>)
@Serializable
class Inside(val foo: List<NonSerializable>)
class Inside(val foo: <!SERIALIZER_NOT_FOUND("NonSerializable")!>List<NonSerializable><!>)
@Serializable
class WithImplicitType {
val foo = NonSerializable()
<!SERIALIZER_NOT_FOUND("NonSerializable")!>val foo = NonSerializable()<!>
}
@@ -1,4 +1,5 @@
// WITH_STDLIB
// FIR_DIFFERENCE: KT-53861
// FILE: test.kt
import kotlinx.serialization.*
@@ -1,17 +0,0 @@
// WITH_STDLIB
// FILE: test.kt
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
@Serializable(NopeNullableSerializer::class)
class Nope {}
class NopeNullableSerializer: KSerializer<Nope?> {
override val descriptor: SerialDescriptor get() = TODO()
override fun deserialize(decoder: Decoder): Nope? = TODO()
override fun serialize(encoder: Encoder, value: Nope?) = TODO()
}
@Serializable
class Foo(val foo: Nope)
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// WITH_STDLIB
// FILE: test.kt
import kotlinx.serialization.*
@@ -1,6 +0,0 @@
// WITH_STDLIB
// FILE: test.kt
import kotlinx.serialization.*
@Serializable
class Test(val someData: String, cantBeDeserialized: Int)
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// WITH_STDLIB
// FILE: test.kt
import kotlinx.serialization.*
@@ -1,23 +0,0 @@
// WITH_STDLIB
// FILE: test.kt
import kotlinx.serialization.*
import kotlinx.serialization.encoding.*
enum class SimpleEnum { A, B }
enum class MarkedNameEnum { @SerialName("a") A, B}
@Serializable
enum class ExplicitlyMarkedEnum { @SerialName("a") A, B}
@Serializable(EnumSerializer::class)
enum class ExplicitlyMarkedEnumCustom { @SerialName("a") A, B}
object EnumSerializer: KSerializer<ExplicitlyMarkedEnumCustom> {
override val descriptor = TODO()
override fun serialize(encoder: Encoder, value: ExplicitlyMarkedEnumCustom) = TODO()
override fun deserialize(decoder: Decoder): ExplicitlyMarkedEnumCustom = TODO()
}
@Serializable
data class EnumUsage(val s: SimpleEnum, val m: MarkedNameEnum, val e: ExplicitlyMarkedEnum)
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// WITH_STDLIB
// FILE: test.kt
import kotlinx.serialization.*
@@ -1,10 +0,0 @@
// SKIP_TXT
// FILE: test.kt
import kotlinx.serialization.*
@Serializable
interface INonSerializable
@Serializable
sealed interface SealedSerializable
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// SKIP_TXT
// FILE: test.kt
@@ -13,14 +13,14 @@ class Baz
@Serializer(forClass = Baz::class)
<!ABSTRACT_MEMBER_NOT_IMPLEMENTED!>object NullableBazSerializer<!>: KSerializer<Baz?>
@Serializable(with = BazSerializer::class)
<!SERIALIZER_TYPE_INCOMPATIBLE!>@Serializable(with = BazSerializer::class)<!>
class Biz(val i: Int)
@Serializable
class Foo(@Serializable(with = BazSerializer::class) val i: Bar)
class Foo(@Serializable(with = BazSerializer::class) val i: <!SERIALIZER_TYPE_INCOMPATIBLE!>Bar<!>)
@Serializable
class Foo2(val li: List<@Serializable(with = BazSerializer::class) Bar>)
class Foo2(val li: <!SERIALIZER_TYPE_INCOMPATIBLE!>List<@Serializable(with = BazSerializer::class) Bar><!>)
@Serializable
class Foo3(@Serializable(with = BazSerializer::class) val i: Baz)
@@ -29,10 +29,10 @@ class Foo3(@Serializable(with = BazSerializer::class) val i: Baz)
class Foo4(val li: List<@Serializable(with = BazSerializer::class) Baz>)
@Serializable
class Foo5(@Serializable(with = BazSerializer::class) val i: Bar?)
class Foo5(@Serializable(with = BazSerializer::class) val i: <!SERIALIZER_TYPE_INCOMPATIBLE!>Bar?<!>)
@Serializable
class Foo6(@Serializable(with = NullableBazSerializer::class) val i: Bar)
class Foo6(@Serializable(with = NullableBazSerializer::class) val i: <!SERIALIZER_NULLABILITY_INCOMPATIBLE, SERIALIZER_TYPE_INCOMPATIBLE!>Bar<!>)
@Serializable
class Foo7(@Serializable(with = NullableBazSerializer::class) val i: Bar?)
class Foo7(@Serializable(with = NullableBazSerializer::class) val i: <!SERIALIZER_TYPE_INCOMPATIBLE!>Bar?<!>)
@@ -1,11 +0,0 @@
// WITH_STDLIB
// FILE: test.kt
import kotlinx.serialization.*
@Serializable
data class WithTransients(@Transient val missing: Int) {
@Transient val redundant: Int get() = 42
@Transient
lateinit var allowTransientLateinitWithoutInitializer: String
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// WITH_STDLIB
// FILE: test.kt
import kotlinx.serialization.*
@@ -6,6 +6,7 @@
package org.jetbrains.kotlinx.serialization
import org.jetbrains.kotlin.utils.PathUtil
import org.jetbrains.kotlinx.serialization.compiler.diagnostic.CommonVersionReader
import org.jetbrains.kotlinx.serialization.compiler.diagnostic.VersionReader
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertTrue
@@ -34,7 +35,7 @@ class RuntimeLibraryInClasspathTest {
@Test
fun testRuntimeHasSufficientVersion() {
val version = VersionReader.getVersionsFromManifest(coreLibraryPath!!)
val version = CommonVersionReader.getVersionsFromManifest(coreLibraryPath!!)
assertTrue(version.currentCompilerMatchRequired(), "Runtime version too high")
assertTrue(version.implementationVersionMatchSupported(), "Runtime version too low")
}