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