From 2aae735a8eeb24d1d669def1aad2c15e5a7d29f2 Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Mon, 13 Apr 2020 18:23:03 +0300 Subject: [PATCH] Introduce FIR serializer (isn't in use yet, KT-38156) --- build.gradle.kts | 1 + .../fir/fir-serialization/build.gradle.kts | 18 + .../serialization/FirAnnotationSerializer.kt | 167 ++++ .../serialization/FirContractSerializer.kt | 216 +++++ .../FirElementAwareStringTable.kt | 26 + .../fir/serialization/FirElementSerializer.kt | 863 ++++++++++++++++++ .../serialization/FirSerializerExtension.kt | 97 ++ compiler/fir/fir2ir/build.gradle.kts | 1 + .../kotlin/fir/resolve/calls/Arguments.kt | 2 +- prepare/idea-plugin/build.gradle.kts | 1 + settings.gradle | 1 + 11 files changed, 1392 insertions(+), 1 deletion(-) create mode 100644 compiler/fir/fir-serialization/build.gradle.kts create mode 100644 compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirAnnotationSerializer.kt create mode 100644 compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirContractSerializer.kt create mode 100644 compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirElementAwareStringTable.kt create mode 100644 compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirElementSerializer.kt create mode 100644 compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirSerializerExtension.kt diff --git a/build.gradle.kts b/build.gradle.kts index 98ad80707d5..3f39b6852b4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -263,6 +263,7 @@ extra["compilerModules"] = arrayOf( ":core:type-system", ":compiler:fir:cones", ":compiler:fir:resolve", + ":compiler:fir:fir-serialization", ":compiler:fir:tree", ":compiler:fir:raw-fir:fir-common", ":compiler:fir:raw-fir:psi2fir", diff --git a/compiler/fir/fir-serialization/build.gradle.kts b/compiler/fir/fir-serialization/build.gradle.kts new file mode 100644 index 00000000000..1c49e07f037 --- /dev/null +++ b/compiler/fir/fir-serialization/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + kotlin("jvm") + id("jps-compatible") +} + +dependencies { + compile(project(":core:descriptors")) + compile(project(":compiler:fir:cones")) + compile(project(":compiler:fir:tree")) + compile(project(":compiler:fir:resolve")) + + compileOnly(intellijCoreDep()) { includeJars("intellij-core", rootProject = rootProject) } +} + +sourceSets { + "main" { projectDefault() } + "test" { none() } +} diff --git a/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirAnnotationSerializer.kt b/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirAnnotationSerializer.kt new file mode 100644 index 00000000000..9059c9c7c6b --- /dev/null +++ b/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirAnnotationSerializer.kt @@ -0,0 +1,167 @@ +/* + * Copyright 2010-2020 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.kotlin.fir.serialization + +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.annotations.AnnotationArgumentVisitor +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall +import org.jetbrains.kotlin.fir.expressions.FirConstExpression +import org.jetbrains.kotlin.fir.expressions.FirNamedArgumentExpression +import org.jetbrains.kotlin.fir.resolve.toSymbol +import org.jetbrains.kotlin.fir.types.ConeClassLikeType +import org.jetbrains.kotlin.fir.types.coneTypeSafe +import org.jetbrains.kotlin.metadata.ProtoBuf +import org.jetbrains.kotlin.metadata.deserialization.Flags +import org.jetbrains.kotlin.resolve.constants.* + +class FirAnnotationSerializer(private val session: FirSession, private val stringTable: FirElementAwareStringTable) { + fun serializeAnnotation(annotation: FirAnnotationCall): ProtoBuf.Annotation = ProtoBuf.Annotation.newBuilder().apply { + val annotationSymbol = annotation.typeRef.coneTypeSafe()?.lookupTag?.toSymbol(session) + val annotationClass = annotationSymbol?.fir ?: error("Annotation type is not a class: ${annotationSymbol?.fir}") + + id = stringTable.getFqNameIndex(annotationClass) + + for (argumentExpression in annotation.argumentList.arguments) { + if (argumentExpression !is FirNamedArgumentExpression) continue + val argument = ProtoBuf.Annotation.Argument.newBuilder() + argument.nameId = stringTable.getStringIndex(argumentExpression.name.asString()) + val constant = argumentExpression.expression as? FirConstExpression<*> ?: continue + argument.setValue(valueProto(constant.value as? ConstantValue<*> ?: continue)) + addArgument(argument) + } + }.build() + + fun valueProto(constant: ConstantValue<*>): ProtoBuf.Annotation.Argument.Value.Builder = + ProtoBuf.Annotation.Argument.Value.newBuilder().apply { + constant.accept(object : AnnotationArgumentVisitor { + override fun visitAnnotationValue(value: AnnotationValue, data: Unit) { + type = ProtoBuf.Annotation.Argument.Value.Type.ANNOTATION + // TODO: annotation = serializeAnnotation(value.value) + } + + override fun visitArrayValue(value: ArrayValue, data: Unit) { + type = ProtoBuf.Annotation.Argument.Value.Type.ARRAY + for (element in value.value) { + addArrayElement(valueProto(element).build()) + } + } + + override fun visitBooleanValue(value: BooleanValue, data: Unit) { + type = ProtoBuf.Annotation.Argument.Value.Type.BOOLEAN + intValue = if (value.value) 1 else 0 + } + + override fun visitByteValue(value: ByteValue, data: Unit) { + type = ProtoBuf.Annotation.Argument.Value.Type.BYTE + intValue = value.value.toLong() + } + + override fun visitCharValue(value: CharValue, data: Unit) { + type = ProtoBuf.Annotation.Argument.Value.Type.CHAR + intValue = value.value.toLong() + } + + override fun visitDoubleValue(value: DoubleValue, data: Unit) { + type = ProtoBuf.Annotation.Argument.Value.Type.DOUBLE + doubleValue = value.value + } + + override fun visitEnumValue(value: EnumValue, data: Unit) { + type = ProtoBuf.Annotation.Argument.Value.Type.ENUM + classId = stringTable.getQualifiedClassNameIndex(value.enumClassId) + enumValueId = stringTable.getStringIndex(value.enumEntryName.asString()) + } + + override fun visitErrorValue(value: ErrorValue, data: Unit) { + throw UnsupportedOperationException("Error value: $value") + } + + override fun visitFloatValue(value: FloatValue, data: Unit) { + type = ProtoBuf.Annotation.Argument.Value.Type.FLOAT + floatValue = value.value + } + + override fun visitIntValue(value: IntValue, data: Unit) { + type = ProtoBuf.Annotation.Argument.Value.Type.INT + intValue = value.value.toLong() + } + + override fun visitKClassValue(value: KClassValue, data: Unit) { + type = ProtoBuf.Annotation.Argument.Value.Type.CLASS + + when (val classValue = value.value) { + is KClassValue.Value.NormalClass -> { + classId = stringTable.getQualifiedClassNameIndex(classValue.classId) + + if (classValue.arrayDimensions > 0) { + arrayDimensionCount = classValue.arrayDimensions + } + } + is KClassValue.Value.LocalClass -> { + var arrayDimensions = 0 + var type = classValue.type + while (KotlinBuiltIns.isArray(type)) { + arrayDimensions++ + type = type.arguments.single().type + } + + //val descriptor = type.constructor.declarationDescriptor as? ClassDescriptor + // ?: error("Type parameters are not allowed in class literal annotation arguments: $classValue") + // TODO: classId = stringTable.getFqNameIndex(descriptor) + + if (arrayDimensions > 0) { + arrayDimensionCount = arrayDimensions + } + } + } + } + + override fun visitLongValue(value: LongValue, data: Unit) { + type = ProtoBuf.Annotation.Argument.Value.Type.LONG + intValue = value.value + } + + override fun visitNullValue(value: NullValue, data: Unit) { + throw UnsupportedOperationException("Null should not appear in annotation arguments") + } + + override fun visitShortValue(value: ShortValue, data: Unit) { + type = ProtoBuf.Annotation.Argument.Value.Type.SHORT + intValue = value.value.toLong() + } + + override fun visitStringValue(value: StringValue, data: Unit) { + type = ProtoBuf.Annotation.Argument.Value.Type.STRING + stringValue = stringTable.getStringIndex(value.value) + } + + override fun visitUByteValue(value: UByteValue, data: Unit?) { + type = ProtoBuf.Annotation.Argument.Value.Type.BYTE + intValue = value.value.toLong() + flags = Flags.IS_UNSIGNED.toFlags(true) + } + + override fun visitUShortValue(value: UShortValue, data: Unit?) { + type = ProtoBuf.Annotation.Argument.Value.Type.SHORT + intValue = value.value.toLong() + flags = Flags.IS_UNSIGNED.toFlags(true) + } + + override fun visitUIntValue(value: UIntValue, data: Unit?) { + type = ProtoBuf.Annotation.Argument.Value.Type.INT + intValue = value.value.toLong() + flags = Flags.IS_UNSIGNED.toFlags(true) + } + + override fun visitULongValue(value: ULongValue, data: Unit?) { + type = ProtoBuf.Annotation.Argument.Value.Type.LONG + intValue = value.value + flags = Flags.IS_UNSIGNED.toFlags(true) + } + }, Unit) + } +} \ No newline at end of file diff --git a/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirContractSerializer.kt b/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirContractSerializer.kt new file mode 100644 index 00000000000..288b25ead1c --- /dev/null +++ b/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirContractSerializer.kt @@ -0,0 +1,216 @@ +/* + * Copyright 2010-2020 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.kotlin.fir.serialization + +import org.jetbrains.kotlin.contracts.description.* +import org.jetbrains.kotlin.contracts.description.expressions.* +import org.jetbrains.kotlin.fir.contracts.FirContractDescription +import org.jetbrains.kotlin.fir.contracts.description.* +import org.jetbrains.kotlin.fir.contracts.effects +import org.jetbrains.kotlin.fir.declarations.FirFunction +import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction +import org.jetbrains.kotlin.fir.expressions.LogicOperationKind +import org.jetbrains.kotlin.metadata.ProtoBuf +import org.jetbrains.kotlin.metadata.deserialization.Flags + +class FirContractSerializer { + fun serializeContractOfFunctionIfAny( + function: FirFunction<*>, + proto: ProtoBuf.Function.Builder, + parentSerializer: FirElementSerializer + ) { + val contractDescription = (function as? FirSimpleFunction)?.contractDescription + if (contractDescription == null || contractDescription.effects.isNullOrEmpty()) { + return + } + val worker = ContractSerializerWorker(parentSerializer) + proto.setContract(worker.contractProto(contractDescription)) + } + + private class ContractSerializerWorker(private val parentSerializer: FirElementSerializer) { + fun contractProto(contractDescription: FirContractDescription): ProtoBuf.Contract.Builder { + return ProtoBuf.Contract.newBuilder().apply { + contractDescription.effects?.forEach { addEffect(effectProto(it, contractDescription)) } + } + } + + private fun effectProto( + effectDeclaration: ConeEffectDeclaration, contractDescription: FirContractDescription + ): ProtoBuf.Effect.Builder { + return ProtoBuf.Effect.newBuilder().apply { + fillEffectProto(this, effectDeclaration, contractDescription) + } + } + + private fun fillEffectProto( + builder: ProtoBuf.Effect.Builder, + effectDeclaration: ConeEffectDeclaration, + contractDescription: FirContractDescription + ) { + when (effectDeclaration) { + is ConeConditionalEffectDeclaration -> { + builder.setConclusionOfConditionalEffect(contractExpressionProto(effectDeclaration.condition, contractDescription)) + fillEffectProto(builder, effectDeclaration.effect, contractDescription) + } + + is ConeReturnsEffectDeclaration -> { + when (effectDeclaration.value) { + ConstantReference.NOT_NULL -> + builder.effectType = ProtoBuf.Effect.EffectType.RETURNS_NOT_NULL + ConstantReference.WILDCARD -> + builder.effectType = ProtoBuf.Effect.EffectType.RETURNS_CONSTANT + else -> { + builder.effectType = ProtoBuf.Effect.EffectType.RETURNS_CONSTANT + builder.addEffectConstructorArgument(contractExpressionProto(effectDeclaration.value, contractDescription)) + } + } + } + + is ConeCallsEffectDeclaration -> { + builder.effectType = ProtoBuf.Effect.EffectType.CALLS + builder.addEffectConstructorArgument( + contractExpressionProto(effectDeclaration.valueParameterReference, contractDescription) + ) + val invocationKindProtobufEnum = invocationKindProtobufEnum(effectDeclaration.kind) + if (invocationKindProtobufEnum != null) { + builder.kind = invocationKindProtobufEnum + } + } + + else -> { + throw IllegalStateException("Unsupported effect type: ${effectDeclaration::class.simpleName}") + } + } + } + + private fun contractExpressionProto( + contractDescriptionElement: ConeContractDescriptionElement, + contractDescription: FirContractDescription + ): ProtoBuf.Expression.Builder { + return contractDescriptionElement.accept(object : ConeContractDescriptionVisitor() { + override fun visitLogicalBinaryOperationContractExpression( + binaryLogicExpression: ConeBinaryLogicExpression, + data: Unit + ): ProtoBuf.Expression.Builder { + return if (binaryLogicExpression.kind == LogicOperationKind.AND) { + visitLogicalAnd(binaryLogicExpression, data) + } else { + visitLogicalOr(binaryLogicExpression, data) + } + } + + private fun visitLogicalOr(logicalOr: ConeBinaryLogicExpression, data: Unit): ProtoBuf.Expression.Builder { + val leftBuilder = logicalOr.left.accept(this, data) + + return if (leftBuilder.andArgumentCount != 0) { + // can't flatten and re-use left builder + ProtoBuf.Expression.newBuilder().apply { + addOrArgument(leftBuilder) + addOrArgument(contractExpressionProto(logicalOr.right, contractDescription)) + } + } else { + // we can save some space by re-using left builder instead of nesting new one + leftBuilder.apply { addOrArgument(contractExpressionProto(logicalOr.right, contractDescription)) } + } + } + + private fun visitLogicalAnd(logicalAnd: ConeBinaryLogicExpression, data: Unit): ProtoBuf.Expression.Builder { + val leftBuilder = logicalAnd.left.accept(this, data) + + return if (leftBuilder.orArgumentCount != 0) { + // leftBuilder is already a sequence of Or-operators, so we can't re-use it + ProtoBuf.Expression.newBuilder().apply { + addAndArgument(leftBuilder) + addAndArgument(contractExpressionProto(logicalAnd.right, contractDescription)) + } + } else { + // we can save some space by re-using left builder instead of nesting new one + leftBuilder.apply { addAndArgument(contractExpressionProto(logicalAnd.right, contractDescription)) } + } + } + + override fun visitLogicalNot(logicalNot: ConeLogicalNot, data: Unit): ProtoBuf.Expression.Builder = + logicalNot.arg.accept(this, data).apply { + writeFlags(Flags.IS_NEGATED.invert(flags)) + } + + override fun visitIsInstancePredicate( + isInstancePredicate: ConeIsInstancePredicate, data: Unit + ): ProtoBuf.Expression.Builder { + // write variable + val builder = visitValueParameterReference(isInstancePredicate.arg, data) + + // write rhs type + builder.isInstanceTypeId = parentSerializer.typeId(isInstancePredicate.type) + + // set flags + builder.writeFlags(Flags.getContractExpressionFlags(isInstancePredicate.isNegated, false)) + + return builder + } + + override fun visitIsNullPredicate(isNullPredicate: ConeIsNullPredicate, data: Unit): ProtoBuf.Expression.Builder { + // get builder with variable embedded into it + val builder = visitValueParameterReference(isNullPredicate.arg, data) + + // set flags + builder.writeFlags(Flags.getContractExpressionFlags(isNullPredicate.isNegated, true)) + + return builder + } + + override fun visitConstantDescriptor(constantReference: ConeConstantReference, data: Unit): ProtoBuf.Expression.Builder { + val builder = ProtoBuf.Expression.newBuilder() + + // write constant value + val constantValueProtobufEnum = constantValueProtobufEnum(constantReference) + if (constantValueProtobufEnum != null) { + builder.constantValue = constantValueProtobufEnum + } + + return builder + } + + override fun visitValueParameterReference( + valueParameterReference: ConeValueParameterReference, data: Unit + ): ProtoBuf.Expression.Builder { + val builder = ProtoBuf.Expression.newBuilder() + + val indexOfParameter = valueParameterReference.parameterIndex + builder.valueParameterReference = indexOfParameter + + return builder + } + }, Unit) + } + + private fun ProtoBuf.Expression.Builder.writeFlags(newFlagsValue: Int) { + if (flags != newFlagsValue) { + flags = newFlagsValue + } + } + + private fun invocationKindProtobufEnum(kind: InvocationKind): ProtoBuf.Effect.InvocationKind? = when (kind) { + InvocationKind.AT_MOST_ONCE -> ProtoBuf.Effect.InvocationKind.AT_MOST_ONCE + InvocationKind.EXACTLY_ONCE -> ProtoBuf.Effect.InvocationKind.EXACTLY_ONCE + InvocationKind.AT_LEAST_ONCE -> ProtoBuf.Effect.InvocationKind.AT_LEAST_ONCE + InvocationKind.UNKNOWN -> null + } + + private fun constantValueProtobufEnum(constantReference: ConeConstantReference): ProtoBuf.Expression.ConstantValue? = + when (constantReference) { + ConeBooleanConstantReference.TRUE -> ProtoBuf.Expression.ConstantValue.TRUE + ConeBooleanConstantReference.FALSE -> ProtoBuf.Expression.ConstantValue.FALSE + ConeConstantReference.NULL -> ProtoBuf.Expression.ConstantValue.NULL + ConeConstantReference.NOT_NULL -> throw IllegalStateException( + "Internal error during serialization of function contract: NOT_NULL constant isn't denotable in protobuf format. " + + "Its serialization should be handled at higher level" + ) + ConeConstantReference.WILDCARD -> null + else -> throw IllegalArgumentException("Unknown constant: $constantReference") + } + } +} \ No newline at end of file diff --git a/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirElementAwareStringTable.kt b/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirElementAwareStringTable.kt new file mode 100644 index 00000000000..381c1291da4 --- /dev/null +++ b/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirElementAwareStringTable.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2010-2020 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.kotlin.fir.serialization + +import org.jetbrains.kotlin.fir.declarations.FirClassLikeDeclaration +import org.jetbrains.kotlin.fir.render +import org.jetbrains.kotlin.metadata.serialization.StringTable +import org.jetbrains.kotlin.name.ClassId + +interface FirElementAwareStringTable : StringTable { + fun getQualifiedClassNameIndex(classId: ClassId): Int = + getQualifiedClassNameIndex(classId.asString(), classId.isLocal) + + fun getFqNameIndex(classLikeDeclaration: FirClassLikeDeclaration<*>): Int { + val classId = classLikeDeclaration.symbol.classId.takeIf { !it.isLocal } + ?: getLocalClassIdReplacement(classLikeDeclaration) + ?: throw IllegalStateException("Cannot get FQ name of local class: ${classLikeDeclaration.render()}") + + return getQualifiedClassNameIndex(classId) + } + + fun getLocalClassIdReplacement(classLikeDeclaration: FirClassLikeDeclaration<*>): ClassId? = null +} diff --git a/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirElementSerializer.kt b/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirElementSerializer.kt new file mode 100644 index 00000000000..28dbac1a4ed --- /dev/null +++ b/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirElementSerializer.kt @@ -0,0 +1,863 @@ +/* + * Copyright 2010-2020 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.kotlin.fir.serialization + +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.fir.FirAnnotationContainer +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor +import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall +import org.jetbrains.kotlin.fir.expressions.FirArgumentList +import org.jetbrains.kotlin.fir.expressions.FirConstExpression +import org.jetbrains.kotlin.fir.expressions.FirNamedArgumentExpression +import org.jetbrains.kotlin.fir.render +import org.jetbrains.kotlin.fir.resolve.calls.varargElementType +import org.jetbrains.kotlin.fir.resolve.firSymbolProvider +import org.jetbrains.kotlin.fir.resolve.fullyExpandedType +import org.jetbrains.kotlin.fir.resolve.inference.isSuspendFunctionType +import org.jetbrains.kotlin.fir.resolve.toSymbol +import org.jetbrains.kotlin.fir.symbols.StandardClassIds +import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.fir.types.impl.FirImplicitNullableAnyTypeRef +import org.jetbrains.kotlin.metadata.ProtoBuf +import org.jetbrains.kotlin.metadata.deserialization.Flags +import org.jetbrains.kotlin.metadata.deserialization.VersionRequirement +import org.jetbrains.kotlin.metadata.deserialization.isKotlin1Dot4OrLater +import org.jetbrains.kotlin.metadata.serialization.Interner +import org.jetbrains.kotlin.metadata.serialization.MutableTypeTable +import org.jetbrains.kotlin.metadata.serialization.MutableVersionRequirementTable +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.RequireKotlinConstants +import org.jetbrains.kotlin.resolve.constants.ConstantValue +import org.jetbrains.kotlin.resolve.constants.EnumValue +import org.jetbrains.kotlin.resolve.constants.IntValue +import org.jetbrains.kotlin.resolve.constants.StringValue +import org.jetbrains.kotlin.serialization.deserialization.ProtoEnumFlags +import org.jetbrains.kotlin.types.Variance + +class FirElementSerializer private constructor( + private val session: FirSession, + private val containingDeclaration: FirDeclaration?, + private val typeParameters: Interner, + private val extension: FirSerializerExtension, + val typeTable: MutableTypeTable, + private val versionRequirementTable: MutableVersionRequirementTable?, + private val serializeTypeTableToFunction: Boolean, +) { + private val contractSerializer = FirContractSerializer() + + fun classProto(klass: FirClass<*>): ProtoBuf.Class.Builder { + val builder = ProtoBuf.Class.newBuilder() + + val regularClass = klass as? FirRegularClass + val modality = regularClass?.modality ?: Modality.FINAL + val flags = Flags.getClassFlags( + klass.annotations.isNotEmpty(), + ProtoEnumFlags.visibility(regularClass?.let { normalizeVisibility(it) } ?: Visibilities.LOCAL), + ProtoEnumFlags.modality(modality), + ProtoEnumFlags.classKind(klass.classKind, regularClass?.isCompanion == true), + regularClass?.isInner == true, + regularClass?.isData == true, + regularClass?.isExternal == true, + regularClass?.isExpect == true, + regularClass?.isInline == true, + false // TODO: klass.isFun not supported yet + ) + if (flags != builder.flags) { + builder.flags = flags + } + + builder.fqName = getClassifierId(klass) + + for (typeParameter in klass.typeParameters) { + if (typeParameter !is FirTypeParameter) continue + builder.addTypeParameter(typeParameterProto(typeParameter)) + } + + val classId = klass.symbol.classId + if (classId != StandardClassIds.Any && classId != StandardClassIds.Nothing) { + // Special classes (Any, Nothing) have no supertypes + for (superTypeRef in klass.superTypeRefs) { + if (useTypeTable()) { + builder.addSupertypeId(typeId(superTypeRef)) + } else { + builder.addSupertype(typeProto(superTypeRef)) + } + } + } + + if (regularClass != null && regularClass.classKind != ClassKind.ENUM_ENTRY) { + for (constructor in regularClass.declarations.filterIsInstance()) { + builder.addConstructor(constructorProto(constructor)) + } + } + + // TODO: member sorting, see KT-20980 + val callableMembers = + extension.customClassMembersProducer?.getCallableMembers(klass) + ?: klass.declarations.filterIsInstance>() + + for (declaration in callableMembers) { + when (declaration) { + is FirProperty -> propertyProto(declaration)?.let { builder.addProperty(it) } + is FirSimpleFunction -> functionProto(declaration)?.let { builder.addFunction(it) } + is FirEnumEntry -> enumEntryProto(declaration).let { builder.addEnumEntry(it) } + } + } + + val nestedClassifiers = klass.declarations.filterIsInstance>() + for (nestedClassifier in nestedClassifiers) { + if (nestedClassifier is FirTypeAlias) { + typeAliasProto(nestedClassifier)?.let { builder.addTypeAlias(it) } + } else if (nestedClassifier is FirRegularClass) { + if (!extension.shouldSerializeNestedClass(nestedClassifier)) { + continue + } + + builder.addNestedClassName(getSimpleNameIndex(nestedClassifier.name)) + } + } + + if (klass is FirSealedClass) { + for (inheritorId in klass.inheritors) { + builder.addSealedSubclassFqName(stringTable.getQualifiedClassNameIndex(inheritorId)) + } + } + + val companionObject = regularClass?.companionObject + if (companionObject != null) { + builder.companionObjectName = getSimpleNameIndex(companionObject.name) + } + + val typeTableProto = typeTable.serialize() + if (typeTableProto != null) { + builder.typeTable = typeTableProto + } + + if (versionRequirementTable == null) error("Version requirements must be serialized for classes: ${klass.render()}") + + builder.addAllVersionRequirement(versionRequirementTable.serializeVersionRequirements(klass)) + + extension.serializeClass(klass, builder, versionRequirementTable, this) + + writeVersionRequirementForInlineClasses(klass, builder, versionRequirementTable) + + val versionRequirementTableProto = versionRequirementTable.serialize() + if (versionRequirementTableProto != null) { + builder.versionRequirementTable = versionRequirementTableProto + } + return builder + } + + fun propertyProto(property: FirProperty): ProtoBuf.Property.Builder? { + if (!extension.shouldSerializeProperty(property)) return null + + val builder = ProtoBuf.Property.newBuilder() + + val local = createChildSerializer(property) + + var hasGetter = false + var hasSetter = false + + //val compileTimeConstant = property.compileTimeInitializer + val hasConstant = false // TODO: compileTimeConstant != null && compileTimeConstant !is NullValue + + val hasAnnotations = property.annotations.isNotEmpty() + // TODO: hasAnnotations(descriptor) || hasAnnotations(descriptor.backingField) || hasAnnotations(descriptor.delegateField) + + val modality = property.modality!! + val defaultAccessorFlags = Flags.getAccessorFlags( + hasAnnotations, + ProtoEnumFlags.visibility(normalizeVisibility(property)), + ProtoEnumFlags.modality(modality), + false, false, false + ) + + val getter = property.getter + if (getter != null) { + hasGetter = true + val accessorFlags = getAccessorFlags(getter, property) + if (accessorFlags != defaultAccessorFlags) { + builder.getterFlags = accessorFlags + } + } + + val setter = property.setter + if (setter != null) { + hasSetter = true + val accessorFlags = getAccessorFlags(setter, property) + if (accessorFlags != defaultAccessorFlags) { + builder.setterFlags = accessorFlags + } + + if (setter !is FirDefaultPropertyAccessor || + setter.annotations.isNotEmpty() || + setter.visibility != property.visibility + ) { + val setterLocal = local.createChildSerializer(setter) + for (valueParameterDescriptor in setter.valueParameters) { + builder.setSetterValueParameter(setterLocal.valueParameterProto(valueParameterDescriptor)) + } + } + } + + val flags = Flags.getPropertyFlags( + hasAnnotations, + ProtoEnumFlags.visibility(normalizeVisibility(property)), + ProtoEnumFlags.modality(modality), + ProtoEnumFlags.memberKind(CallableMemberDescriptor.Kind.DECLARATION), + property.isVar, hasGetter, hasSetter, hasConstant, property.isConst, property.isLateInit, + property.isExternal, property.delegateFieldSymbol != null, property.isExpect + ) + if (flags != builder.flags) { + builder.flags = flags + } + + builder.name = getSimpleNameIndex(property.name) + + if (useTypeTable()) { + builder.returnTypeId = local.typeId(property.returnTypeRef) + } else { + builder.setReturnType(local.typeProto(property.returnTypeRef)) + } + + for (typeParameter in property.typeParameters) { + builder.addTypeParameter(local.typeParameterProto(typeParameter)) + } + + val receiverTypeRef = property.receiverTypeRef + if (receiverTypeRef != null) { + if (useTypeTable()) { + builder.receiverTypeId = local.typeId(receiverTypeRef) + } else { + builder.setReceiverType(local.typeProto(receiverTypeRef)) + } + } + + versionRequirementTable?.run { + builder.addAllVersionRequirement(serializeVersionRequirements(property)) + + if (property.isSuspendOrHasSuspendTypesInSignature()) { + builder.addVersionRequirement(writeVersionRequirementDependingOnCoroutinesVersion()) + } + + if (property.hasInlineClassTypesInSignature()) { + builder.addVersionRequirement(writeVersionRequirement(LanguageFeature.InlineClasses)) + } + } + + extension.serializeProperty(property, builder, versionRequirementTable, local) + + return builder + } + + fun functionProto(function: FirFunction<*>): ProtoBuf.Function.Builder? { + if (!extension.shouldSerializeFunction(function)) return null + + val builder = ProtoBuf.Function.newBuilder() + val simpleFunction = function as? FirSimpleFunction + + val local = createChildSerializer(function) + + val flags = Flags.getFunctionFlags( + function.annotations.isNotEmpty(), + ProtoEnumFlags.visibility(simpleFunction?.let { normalizeVisibility(it) } ?: Visibilities.LOCAL), + ProtoEnumFlags.modality(simpleFunction?.modality ?: Modality.FINAL), + ProtoEnumFlags.memberKind(CallableMemberDescriptor.Kind.DECLARATION), + simpleFunction?.isOperator == true, + simpleFunction?.isInfix == true, + simpleFunction?.isInline == true, + simpleFunction?.isTailRec == true, + simpleFunction?.isExternal == true, + simpleFunction?.isSuspend == true, + simpleFunction?.isExpect == true + ) + if (flags != builder.flags) { + builder.flags = flags + } + + val name = when (function) { + is FirSimpleFunction -> { + function.name + } + is FirAnonymousFunction -> { + if (function.isLambda) Name.special("") else Name.special("") + } + else -> throw AssertionError("Unsupported function: ${function.render()}") + } + builder.name = getSimpleNameIndex(name) + + if (useTypeTable()) { + builder.returnTypeId = local.typeId(function.returnTypeRef) + } else { + builder.setReturnType(local.typeProto(function.returnTypeRef)) + } + + for (typeParameter in function.typeParameters) { + if (typeParameter !is FirTypeParameter) continue + builder.addTypeParameter(local.typeParameterProto(typeParameter)) + } + + val receiverTypeRef = function.receiverTypeRef + if (receiverTypeRef != null) { + if (useTypeTable()) { + builder.receiverTypeId = local.typeId(receiverTypeRef) + } else { + builder.setReceiverType(local.typeProto(receiverTypeRef)) + } + } + + for (valueParameter in function.valueParameters) { + builder.addValueParameter(local.valueParameterProto(valueParameter)) + } + + if (serializeTypeTableToFunction) { + val typeTableProto = typeTable.serialize() + if (typeTableProto != null) { + builder.typeTable = typeTableProto + } + } + + versionRequirementTable?.run { + builder.addAllVersionRequirement(serializeVersionRequirements(function)) + + if (function.isSuspendOrHasSuspendTypesInSignature()) { + builder.addVersionRequirement(writeVersionRequirementDependingOnCoroutinesVersion()) + } + + if (function.hasInlineClassTypesInSignature()) { + builder.addVersionRequirement(writeVersionRequirement(LanguageFeature.InlineClasses)) + } + } + + contractSerializer.serializeContractOfFunctionIfAny(function, builder, this) + + extension.serializeFunction(function, builder, versionRequirementTable, local) + + return builder + } + + private fun typeAliasProto(typeAlias: FirTypeAlias): ProtoBuf.TypeAlias.Builder? { + if (!extension.shouldSerializeTypeAlias(typeAlias)) return null + + val builder = ProtoBuf.TypeAlias.newBuilder() + val local = createChildSerializer(typeAlias) + + val flags = Flags.getTypeAliasFlags( + typeAlias.annotations.isNotEmpty(), + ProtoEnumFlags.visibility(normalizeVisibility(typeAlias)) + ) + if (flags != builder.flags) { + builder.flags = flags + } + + builder.name = getSimpleNameIndex(typeAlias.name) + + for (typeParameter in typeAlias.typeParameters) { + builder.addTypeParameter(local.typeParameterProto(typeParameter)) + } + + val underlyingType = typeAlias.expandedConeType!! + if (useTypeTable()) { + builder.underlyingTypeId = local.typeId(underlyingType) + } else { + builder.setUnderlyingType(local.typeProto(underlyingType)) + } + + val expandedType = underlyingType.fullyExpandedType(session) + if (useTypeTable()) { + builder.expandedTypeId = local.typeId(expandedType) + } else { + builder.setExpandedType(local.typeProto(expandedType)) + } + + versionRequirementTable?.run { + builder.addAllVersionRequirement(serializeVersionRequirements(typeAlias)) + } + + for (annotation in typeAlias.annotations) { + builder.addAnnotation(extension.annotationSerializer.serializeAnnotation(annotation)) + } + + extension.serializeTypeAlias(typeAlias, builder) + + return builder + } + + private fun enumEntryProto(enumEntry: FirEnumEntry): ProtoBuf.EnumEntry.Builder { + val builder = ProtoBuf.EnumEntry.newBuilder() + builder.name = getSimpleNameIndex(enumEntry.name) + extension.serializeEnumEntry(enumEntry, builder) + return builder + } + + private fun constructorProto(constructor: FirConstructor): ProtoBuf.Constructor.Builder { + val builder = ProtoBuf.Constructor.newBuilder() + + val local = createChildSerializer(constructor) + + val flags = Flags.getConstructorFlags( + constructor.annotations.isNotEmpty(), + ProtoEnumFlags.visibility(normalizeVisibility(constructor)), + !constructor.isPrimary + ) + if (flags != builder.flags) { + builder.flags = flags + } + + for (valueParameter in constructor.valueParameters) { + builder.addValueParameter(local.valueParameterProto(valueParameter)) + } + + versionRequirementTable?.run { + builder.addAllVersionRequirement(serializeVersionRequirements(constructor)) + + if (constructor.isSuspendOrHasSuspendTypesInSignature()) { + builder.addVersionRequirement(writeVersionRequirementDependingOnCoroutinesVersion()) + } + + if (constructor.hasInlineClassTypesInSignature()) { + builder.addVersionRequirement(writeVersionRequirement(LanguageFeature.InlineClasses)) + } + } + + extension.serializeConstructor(constructor, builder, local) + + return builder + } + + private fun valueParameterProto(parameter: FirValueParameter): ProtoBuf.ValueParameter.Builder { + val builder = ProtoBuf.ValueParameter.newBuilder() + + val declaresDefaultValue = parameter.defaultValue != null // TODO: || parameter.isActualParameterWithAnyExpectedDefault + + val flags = Flags.getValueParameterFlags( + parameter.annotations.isNotEmpty(), declaresDefaultValue, + parameter.isCrossinline, parameter.isNoinline + ) + if (flags != builder.flags) { + builder.flags = flags + } + + builder.name = getSimpleNameIndex(parameter.name) + + if (useTypeTable()) { + builder.typeId = typeId(parameter.returnTypeRef) + } else { + builder.setType(typeProto(parameter.returnTypeRef)) + } + + if (parameter.isVararg) { + val varargElementType = parameter.returnTypeRef.coneTypeSafe()?.varargElementType(session) + if (varargElementType != null) { + if (useTypeTable()) { + builder.varargElementTypeId = typeId(varargElementType) + } else { + builder.setVarargElementType(typeProto(varargElementType)) + } + } + } + + extension.serializeValueParameter(parameter, builder) + + return builder + } + + private fun typeParameterProto(typeParameter: FirTypeParameter): ProtoBuf.TypeParameter.Builder { + val builder = ProtoBuf.TypeParameter.newBuilder() + + builder.id = getTypeParameterId(typeParameter) + + builder.name = getSimpleNameIndex(typeParameter.name) + + if (typeParameter.isReified != builder.reified) { + builder.reified = typeParameter.isReified + } + + val variance = variance(typeParameter.variance) + if (variance != builder.variance) { + builder.variance = variance + } + extension.serializeTypeParameter(typeParameter, builder) + + val upperBounds = typeParameter.bounds + if (upperBounds.size == 1 && upperBounds.single() is FirImplicitNullableAnyTypeRef) return builder + + for (upperBound in upperBounds) { + if (useTypeTable()) { + builder.addUpperBoundId(typeId(upperBound)) + } else { + builder.addUpperBound(typeProto(upperBound)) + } + } + + return builder + } + + fun typeId(typeRef: FirTypeRef): Int { + if (typeRef !is FirResolvedTypeRef) { + return -1 // TODO: serializeErrorType? + } + return typeId(typeRef.type) + } + + fun typeId(type: ConeKotlinType): Int = typeTable[typeProto(type)] + + internal fun typeProto(typeRef: FirTypeRef): ProtoBuf.Type.Builder { + return typeProto((typeRef as FirResolvedTypeRef).type) + } + + internal fun typeProto(type: ConeKotlinType): ProtoBuf.Type.Builder { + val builder = ProtoBuf.Type.newBuilder() + + if (type is ConeKotlinErrorType) { + extension.serializeErrorType(type, builder) + return builder + } + + if (type is ConeFlexibleType) { + val lowerBound = typeProto(type.lowerBound) + val upperBound = typeProto(type.upperBound) + extension.serializeFlexibleType(type, lowerBound, upperBound) + if (useTypeTable()) { + lowerBound.flexibleUpperBoundId = typeTable[upperBound] + } else { + lowerBound.setFlexibleUpperBound(upperBound) + } + return lowerBound + } + + + if (type is ConeClassLikeType && type.isSuspendFunctionType(session)) { + // TODO: suspend function type +// val functionType = typeProto(transformSuspendFunctionToRuntimeFunctionType(type, extension.releaseCoroutines())) +// functionType.flags = Flags.getTypeFlags(true) +// return functionType + } + + when (type) { + is ConeClassLikeType -> { + fillFromPossiblyInnerType(builder, type) + } + is ConeTypeParameterType -> { + // TODO: type parameter containing declaration? + if (false /*descriptor.containingDeclaration === containingDeclaration*/) { + //builder.typeParameterName = getSimpleNameIndex(descriptor.name) + } else { + builder.typeParameter = getTypeParameterId(type.lookupTag.typeParameterSymbol.fir) + } + } + } + + if (type.isMarkedNullable != builder.nullable) { + builder.nullable = type.isMarkedNullable + } + + // TODO: abbreviated type +// val abbreviation = type.getAbbreviatedType()?.abbreviation +// if (abbreviation != null) { +// if (useTypeTable()) { +// builder.abbreviatedTypeId = typeId(abbreviation) +// } else { +// builder.setAbbreviatedType(typeProto(abbreviation)) +// } +// } + + extension.serializeType(type, builder) + + return builder + } + + private fun fillFromPossiblyInnerType(builder: ProtoBuf.Type.Builder, type: ConeClassLikeType) { + val classifierSymbol = type.lookupTag.toSymbol(session)!! + val classifier = classifierSymbol.fir + val classifierId = getClassifierId(classifier) + builder.className = classifierId + + for (projection in type.typeArguments) { + builder.addArgument(typeArgument(projection)) + } + + // TODO: outer type +// if (type.outerType != null) { +// val outerBuilder = ProtoBuf.Type.newBuilder() +// fillFromPossiblyInnerType(outerBuilder, type.outerType!!) +// if (useTypeTable()) { +// builder.outerTypeId = typeTable[outerBuilder] +// } else { +// builder.setOuterType(outerBuilder) +// } +// } + } + + private fun typeArgument(typeProjection: ConeTypeProjection): ProtoBuf.Type.Argument.Builder { + val builder = ProtoBuf.Type.Argument.newBuilder() + + if (typeProjection is ConeStarProjection) { + builder.projection = ProtoBuf.Type.Argument.Projection.STAR + } else if (typeProjection is ConeKotlinTypeProjection) { + val projection = projection(typeProjection.kind) + + if (projection != builder.projection) { + builder.projection = projection + } + + if (useTypeTable()) { + builder.typeId = typeId(typeProjection.type) + } else { + builder.setType(typeProto(typeProjection.type)) + } + } + + return builder + } + + private fun getAccessorFlags(accessor: FirPropertyAccessor, property: FirProperty): Int { + val isDefault = accessor is FirDefaultPropertyAccessor && + accessor.annotations.isEmpty() && accessor.visibility == property.visibility + return Flags.getAccessorFlags( + accessor.annotations.isNotEmpty(), + ProtoEnumFlags.visibility(normalizeVisibility(accessor)), + ProtoEnumFlags.modality(accessor.modality!!), + !isDefault, + accessor.status.isExternal, + accessor.status.isInline + ) + } + + private fun createChildSerializer(declaration: FirDeclaration): FirElementSerializer = + FirElementSerializer( + session, declaration, Interner(typeParameters), extension, + typeTable, versionRequirementTable, serializeTypeTableToFunction = false + ) + + private val stringTable: FirElementAwareStringTable + get() = extension.stringTable + + private fun useTypeTable(): Boolean = extension.shouldUseTypeTable() + + private fun FirDeclaration.hasInlineClassTypesInSignature(): Boolean { + // TODO + return false + } + + private fun FirCallableDeclaration<*>.isSuspendOrHasSuspendTypesInSignature(): Boolean { + // TODO + return false + } + + private fun writeVersionRequirementForInlineClasses( + klass: FirClass<*>, + builder: ProtoBuf.Class.Builder, + versionRequirementTable: MutableVersionRequirementTable + ) { + if (klass !is FirRegularClass || !klass.isInline && !klass.hasInlineClassTypesInSignature()) return + + builder.addVersionRequirement( + writeLanguageVersionRequirement(LanguageFeature.InlineClasses, versionRequirementTable) + ) + } + + private fun MutableVersionRequirementTable.serializeVersionRequirements(container: FirAnnotationContainer): List = + serializeVersionRequirements(container.annotations) + + private fun MutableVersionRequirementTable.serializeVersionRequirements(annotations: List): List = + annotations + .filter { + val annotationConeType = it.annotationTypeRef.coneTypeSafe() + annotationConeType?.lookupTag?.classId?.asSingleFqName() == RequireKotlinConstants.FQ_NAME + } + .mapNotNull(::serializeVersionRequirementFromRequireKotlin) + .map(::get) + + private fun MutableVersionRequirementTable.writeVersionRequirement(languageFeature: LanguageFeature): Int { + return writeLanguageVersionRequirement(languageFeature, this) + } + + private fun MutableVersionRequirementTable.writeVersionRequirementDependingOnCoroutinesVersion(): Int = + writeVersionRequirement(LanguageFeature.ReleaseCoroutines) + + private operator fun FirArgumentList.get(name: Name): ConstantValue<*>? { + // TODO: constant evaluation + val expression = arguments.filterIsInstance().find { + name == name + }?.expression + if (expression !is FirConstExpression<*>) { + return null + } + return expression.value as? ConstantValue<*> + } + + private fun serializeVersionRequirementFromRequireKotlin(annotation: FirAnnotationCall): ProtoBuf.VersionRequirement.Builder? { + val args = annotation.argumentList + + val versionString = (args[RequireKotlinConstants.VERSION] as? StringValue)?.value ?: return null + val matchResult = RequireKotlinConstants.VERSION_REGEX.matchEntire(versionString) ?: return null + + val major = matchResult.groupValues.getOrNull(1)?.toIntOrNull() ?: return null + val minor = matchResult.groupValues.getOrNull(2)?.toIntOrNull() ?: 0 + val patch = matchResult.groupValues.getOrNull(4)?.toIntOrNull() ?: 0 + + val proto = ProtoBuf.VersionRequirement.newBuilder() + VersionRequirement.Version(major, minor, patch).encode( + writeVersion = { proto.version = it }, + writeVersionFull = { proto.versionFull = it } + ) + + val message = (args[RequireKotlinConstants.MESSAGE] as? StringValue)?.value + if (message != null) { + proto.message = stringTable.getStringIndex(message) + } + + when ((args[RequireKotlinConstants.LEVEL] as? EnumValue)?.enumEntryName?.asString()) { + DeprecationLevel.ERROR.name -> { + // ERROR is the default level + } + DeprecationLevel.WARNING.name -> proto.level = ProtoBuf.VersionRequirement.Level.WARNING + DeprecationLevel.HIDDEN.name -> proto.level = ProtoBuf.VersionRequirement.Level.HIDDEN + } + + when ((args[RequireKotlinConstants.VERSION_KIND] as? EnumValue)?.enumEntryName?.asString()) { + ProtoBuf.VersionRequirement.VersionKind.LANGUAGE_VERSION.name -> { + // LANGUAGE_VERSION is the default kind + } + ProtoBuf.VersionRequirement.VersionKind.COMPILER_VERSION.name -> + proto.versionKind = ProtoBuf.VersionRequirement.VersionKind.COMPILER_VERSION + ProtoBuf.VersionRequirement.VersionKind.API_VERSION.name -> + proto.versionKind = ProtoBuf.VersionRequirement.VersionKind.API_VERSION + } + + val errorCode = (args[RequireKotlinConstants.ERROR_CODE] as? IntValue)?.value + if (errorCode != null && errorCode != -1) { + proto.errorCode = errorCode + } + + return proto + } + + + private fun normalizeVisibility(declaration: FirMemberDeclaration): Visibility { + // It can be necessary for Java classes serialization having package-private visibility + return if (extension.shouldUseNormalizedVisibility()) declaration.visibility.normalize() + else declaration.visibility + } + + private fun normalizeVisibility(declaration: FirPropertyAccessor): Visibility { + // It can be necessary for Java classes serialization having package-private visibility + return if (extension.shouldUseNormalizedVisibility()) declaration.visibility.normalize() + else declaration.visibility + } + + private fun getClassifierId(declaration: FirClassLikeDeclaration<*>): Int = + stringTable.getFqNameIndex(declaration) + + private fun getSimpleNameIndex(name: Name): Int = + stringTable.getStringIndex(name.asString()) + + private fun getTypeParameterId(typeParameter: FirTypeParameter): Int = + typeParameters.intern(typeParameter) + + companion object { + @JvmStatic + fun createTopLevel(session: FirSession, extension: FirSerializerExtension): FirElementSerializer = + FirElementSerializer( + session, null, + Interner(), extension, MutableTypeTable(), MutableVersionRequirementTable(), + serializeTypeTableToFunction = false + ) + + @JvmStatic + fun createForLambda(session: FirSession, extension: FirSerializerExtension): FirElementSerializer = + FirElementSerializer( + session, null, + Interner(), extension, MutableTypeTable(), + versionRequirementTable = null, serializeTypeTableToFunction = true + ) + + @JvmStatic + fun create( + klass: FirClass<*>, + extension: FirSerializerExtension, + parentSerializer: FirElementSerializer? + ): FirElementSerializer { + val parentClassId = klass.symbol.classId.outerClassId + val parent = if (parentClassId != null && !parentClassId.isLocal) { + val parentClass = klass.session.firSymbolProvider.getClassLikeSymbolByFqName(parentClassId)!!.fir as FirRegularClass + parentSerializer ?: create(parentClass, extension, null) + } else { + createTopLevel(klass.session, extension) + } + + // Calculate type parameter ids for the outer class beforehand, as it would've had happened if we were always + // serializing outer classes before nested classes. + // Otherwise our interner can get wrong ids because we may serialize classes in any order. + val serializer = FirElementSerializer( + klass.session, + klass, + Interner(parent.typeParameters), + extension, + MutableTypeTable(), + if (parentClassId != null && !isKotlin1Dot4OrLater(extension.metadataVersion)) { + parent.versionRequirementTable + } else { + MutableVersionRequirementTable() + }, + serializeTypeTableToFunction = false + ) + for (typeParameter in klass.typeParameters) { + if (typeParameter !is FirTypeParameter) continue + serializer.typeParameters.intern(typeParameter) + } + return serializer + } + + fun writeLanguageVersionRequirement( + languageFeature: LanguageFeature, + versionRequirementTable: MutableVersionRequirementTable + ): Int { + val languageVersion = languageFeature.sinceVersion!! + return writeVersionRequirement( + languageVersion.major, languageVersion.minor, 0, + ProtoBuf.VersionRequirement.VersionKind.LANGUAGE_VERSION, + versionRequirementTable + ) + } + + fun writeVersionRequirement( + major: Int, + minor: Int, + patch: Int, + versionKind: ProtoBuf.VersionRequirement.VersionKind, + versionRequirementTable: MutableVersionRequirementTable + ): Int { + val requirement = ProtoBuf.VersionRequirement.newBuilder().apply { + VersionRequirement.Version(major, minor, patch).encode( + writeVersion = { version = it }, + writeVersionFull = { versionFull = it } + ) + if (versionKind != defaultInstanceForType.versionKind) { + this.versionKind = versionKind + } + } + return versionRequirementTable[requirement] + } + + private fun variance(variance: Variance): ProtoBuf.TypeParameter.Variance = when (variance) { + Variance.INVARIANT -> ProtoBuf.TypeParameter.Variance.INV + Variance.IN_VARIANCE -> ProtoBuf.TypeParameter.Variance.IN + Variance.OUT_VARIANCE -> ProtoBuf.TypeParameter.Variance.OUT + } + + private fun projection(projectionKind: ProjectionKind): ProtoBuf.Type.Argument.Projection = when (projectionKind) { + ProjectionKind.INVARIANT -> ProtoBuf.Type.Argument.Projection.INV + ProjectionKind.IN -> ProtoBuf.Type.Argument.Projection.IN + ProjectionKind.OUT -> ProtoBuf.Type.Argument.Projection.OUT + ProjectionKind.STAR -> throw AssertionError("Should not be here") + } + } +} \ No newline at end of file diff --git a/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirSerializerExtension.kt b/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirSerializerExtension.kt new file mode 100644 index 00000000000..90b88bacb90 --- /dev/null +++ b/compiler/fir/fir-serialization/src/org/jetbrains/kotlin/fir/serialization/FirSerializerExtension.kt @@ -0,0 +1,97 @@ +/* + * Copyright 2010-2020 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.kotlin.fir.serialization + +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.types.ConeFlexibleType +import org.jetbrains.kotlin.fir.types.ConeKotlinErrorType +import org.jetbrains.kotlin.fir.types.ConeKotlinType +import org.jetbrains.kotlin.metadata.ProtoBuf +import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion +import org.jetbrains.kotlin.metadata.serialization.MutableVersionRequirementTable +import org.jetbrains.kotlin.name.FqName + +abstract class FirSerializerExtension { + abstract val session: FirSession + + abstract val stringTable: FirElementAwareStringTable + + abstract val metadataVersion: BinaryVersion + + val annotationSerializer by lazy { FirAnnotationSerializer(session, stringTable) } + + open fun shouldSerializeNestedClass(nestedClass: FirRegularClass): Boolean = true + open fun shouldSerializeTypeAlias(typeAlias: FirTypeAlias): Boolean = true + open fun shouldUseTypeTable(): Boolean = false + open fun shouldUseNormalizedVisibility(): Boolean = false + open fun shouldSerializeFunction(function: FirFunction<*>): Boolean = false + open fun shouldSerializeProperty(property: FirProperty): Boolean = false + + open fun serializePackage(packageFqName: FqName, proto: ProtoBuf.Package.Builder) { + } + + open fun serializeClass( + klass: FirClass<*>, + proto: ProtoBuf.Class.Builder, + versionRequirementTable: MutableVersionRequirementTable, + childSerializer: FirElementSerializer + ) { + } + + open fun serializeConstructor( + constructor: FirConstructor, + proto: ProtoBuf.Constructor.Builder, + childSerializer: FirElementSerializer + ) { + } + + open fun serializeFunction( + function: FirFunction<*>, + proto: ProtoBuf.Function.Builder, + versionRequirementTable: MutableVersionRequirementTable?, + childSerializer: FirElementSerializer + ) { + } + + open fun serializeProperty( + property: FirProperty, + proto: ProtoBuf.Property.Builder, + versionRequirementTable: MutableVersionRequirementTable?, + childSerializer: FirElementSerializer + ) { + } + + open fun serializeEnumEntry(enumEntry: FirEnumEntry, proto: ProtoBuf.EnumEntry.Builder) { + } + + open fun serializeValueParameter(parameter: FirValueParameter, proto: ProtoBuf.ValueParameter.Builder) { + } + + open fun serializeFlexibleType(type: ConeFlexibleType, lowerProto: ProtoBuf.Type.Builder, upperProto: ProtoBuf.Type.Builder) { + } + + open fun serializeType(type: ConeKotlinType, proto: ProtoBuf.Type.Builder) { + } + + open fun serializeTypeParameter(typeParameter: FirTypeParameter, proto: ProtoBuf.TypeParameter.Builder) { + } + + open fun serializeTypeAlias(typeAlias: FirTypeAlias, proto: ProtoBuf.TypeAlias.Builder) { + } + + open fun serializeErrorType(type: ConeKotlinErrorType, builder: ProtoBuf.Type.Builder) { + throw IllegalStateException("Cannot serialize error type: $type") + } + + open val customClassMembersProducer: ClassMembersProducer? + get() = null + + interface ClassMembersProducer { + fun getCallableMembers(klass: FirClass<*>): Collection> + } + +} \ No newline at end of file diff --git a/compiler/fir/fir2ir/build.gradle.kts b/compiler/fir/fir2ir/build.gradle.kts index 4605fa6403c..aaaaa86c4fc 100644 --- a/compiler/fir/fir2ir/build.gradle.kts +++ b/compiler/fir/fir2ir/build.gradle.kts @@ -7,6 +7,7 @@ dependencies { compileOnly(project(":core:descriptors")) compileOnly(project(":compiler:fir:cones")) compileOnly(project(":compiler:fir:resolve")) + compileOnly(project(":compiler:fir:fir-serialization")) compileOnly(project(":compiler:fir:tree")) compileOnly(project(":compiler:ir.tree")) compileOnly(project(":compiler:ir.psi2ir")) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/Arguments.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/Arguments.kt index cf59e1fd3f1..cb0513f11da 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/Arguments.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/Arguments.kt @@ -341,7 +341,7 @@ internal fun FirExpression.getExpectedType( } -private fun ConeKotlinType.varargElementType(session: FirSession): ConeKotlinType { +fun ConeKotlinType.varargElementType(session: FirSession): ConeKotlinType { return this.arrayElementType(session) ?: error("Failed to extract! ${this.render()}!") } diff --git a/prepare/idea-plugin/build.gradle.kts b/prepare/idea-plugin/build.gradle.kts index 68970b60edc..0b7e2df0b1c 100644 --- a/prepare/idea-plugin/build.gradle.kts +++ b/prepare/idea-plugin/build.gradle.kts @@ -43,6 +43,7 @@ val projectsToShadow by extra(listOf( ":compiler:fir:cones", ":compiler:fir:checkers", ":compiler:fir:resolve", + ":compiler:fir:fir-serialization", ":compiler:fir:tree", ":compiler:fir:java", ":compiler:fir:jvm", diff --git a/settings.gradle b/settings.gradle index b517b2110fa..9e774fa603a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -89,6 +89,7 @@ include ":kotlin-build-common", ":compiler:fir:fir2ir", ":compiler:fir:fir2ir:jvm-backend", ":compiler:fir:resolve", + ":compiler:fir:fir-serialization", ":compiler:fir:java", ":compiler:fir:modularized-tests", ":compiler:fir:dump",