K2 Scripting: implement basic metadata serialization support

#KT-62305 fixed
NB: kotlin reflection do not see script class constructor after
this change, and it's ok, since the fact that the script is compiled
into a class is an implementation detail.
If needed, java reflection could be used to access the constructor.
This commit is contained in:
Ilya Chernikov
2023-10-04 13:50:38 +02:00
committed by Space Team
parent 122f16fc18
commit 31f9e9e7a8
17 changed files with 251 additions and 24 deletions
@@ -37,6 +37,7 @@ import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.scopes.*
import org.jetbrains.kotlin.fir.scopes.impl.FirScriptDeclarationsScope
import org.jetbrains.kotlin.fir.scopes.impl.nestedClassifierScope
import org.jetbrains.kotlin.fir.serialization.constant.hasConstantValue
import org.jetbrains.kotlin.fir.serialization.constant.toConstantValue
@@ -293,6 +294,88 @@ class FirElementSerializer private constructor(
return builder
}
fun scriptProto(script: FirScript): ProtoBuf.Class.Builder = whileAnalysing(session, script) {
val builder = ProtoBuf.Class.newBuilder()
val flags = Flags.getClassFlags(
extension.hasAdditionalAnnotations(script),
ProtoEnumFlags.visibility(Visibilities.Public),
ProtoEnumFlags.modality(Modality.FINAL),
ProtoEnumFlags.classKind(ClassKind.CLASS, false),
/* inner = */ false,
/* isData = */ false,
/* isExternal = */ false,
/* isExpect = */ false,
/* isValue = */ false,
/* isFun = */ false,
/* hasEnumEntries = */ false,
)
if (flags != builder.flags) {
builder.flags = flags
}
val name = script.name.let {
if (it.isSpecial) {
NameUtils.getScriptNameForFile(it.asStringStripSpecialMarkers().removePrefix("script-"))
} else it
}
val classId = ClassId(script.symbol.fqName.parentOrNull() ?: FqName.ROOT, name)
builder.fqName = getClassifierId(classId)
val memberScope = FirScriptDeclarationsScope(session, script)
val callableMembers = buildList {
memberScope.processAllCallables { add(it.fir) }
}
for (declaration in callableMembers) {
when (declaration) {
is FirProperty -> propertyProto(declaration)?.let { builder.addProperty(it) }
is FirSimpleFunction -> functionProto(declaration)?.let { builder.addFunction(it) }
else -> {}
}
}
memberScope.getClassifierNames().forEach { classifierName ->
memberScope.processClassifiersByName(classifierName) { nestedClassifier ->
when (nestedClassifier) {
is FirRegularClassSymbol -> {
builder.addNestedClassName(getSimpleNameIndex(nestedClassifier.name))
}
else -> {}
}
}
}
for (contextReceiver in script.contextReceivers) {
val typeRef = contextReceiver.typeRef
if (useTypeTable()) {
builder.addContextReceiverTypeId(typeId(typeRef))
} else {
builder.addContextReceiverType(typeProto(contextReceiver.typeRef))
}
}
if (versionRequirementTable == null) error("Version requirements must be serialized for scripts: ${script.render()}")
builder.addAllVersionRequirement(versionRequirementTable.serializeVersionRequirements(script))
extension.serializeScript(script, builder, versionRequirementTable, this)
if (metDefinitelyNotNullType) {
builder.addVersionRequirement(
writeLanguageVersionRequirement(LanguageFeature.DefinitelyNonNullableTypes, versionRequirementTable)
)
}
typeTable.serialize()?.let { builder.typeTable = it }
versionRequirementTable.serialize()?.let { builder.versionRequirementTable = it }
return builder
}
/*
* Order of nested classifiers:
* - declared classifiers in declaration order
@@ -1237,6 +1320,25 @@ class FirElementSerializer private constructor(
return serializer
}
@JvmStatic
fun createForScript(
session: FirSession,
scopeSession: ScopeSession,
script: FirScript,
extension: FirSerializerExtension,
typeApproximator: AbstractTypeApproximator,
languageVersionSettings: LanguageVersionSettings,
produceHeaderKlib: Boolean = false,
): FirElementSerializer =
FirElementSerializer(
session, scopeSession, script,
Interner(), extension, MutableTypeTable(), MutableVersionRequirementTable(),
serializeTypeTableToFunction = false,
typeApproximator,
languageVersionSettings,
produceHeaderKlib,
)
private fun writeLanguageVersionRequirement(
languageFeature: LanguageFeature,
versionRequirementTable: MutableVersionRequirementTable
@@ -54,6 +54,14 @@ abstract class FirSerializerExtension {
) {
}
open fun serializeScript(
script: FirScript,
proto: ProtoBuf.Class.Builder,
versionRequirementTable: MutableVersionRequirementTable,
childSerializer: FirElementSerializer
) {
}
open fun serializeConstructor(
constructor: FirConstructor,
proto: ProtoBuf.Constructor.Builder,
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.serialization.constant.toConstantValue
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.ProtoBuf.Class.Builder
import org.jetbrains.kotlin.metadata.deserialization.Flags
import org.jetbrains.kotlin.metadata.serialization.MutableVersionRequirementTable
import org.jetbrains.kotlin.name.FqName
@@ -37,6 +38,13 @@ abstract class FirSerializerExtensionBase(
klass.serializeAnnotations(proto, protocol.classAnnotation)
}
override fun serializeScript(
script: FirScript,
proto: Builder,
versionRequirementTable: MutableVersionRequirementTable,
childSerializer: FirElementSerializer,
) {}
override fun serializeConstructor(
constructor: FirConstructor,
proto: ProtoBuf.Constructor.Builder,
@@ -113,6 +113,19 @@ class FirJvmSerializerExtension(
}
}
override fun serializeScript(
script: FirScript,
proto: ProtoBuf.Class.Builder,
versionRequirementTable: MutableVersionRequirementTable,
childSerializer: FirElementSerializer
) {
assert((metadata as FirMetadataSource.Script).fir == script)
if (moduleName != JvmProtoBufUtil.DEFAULT_MODULE_NAME) {
proto.setExtension(JvmProtoBuf.classModuleName, stringTable.getStringIndex(moduleName))
}
writeLocalProperties(proto, JvmProtoBuf.classLocalVariable)
}
// Interfaces which have @JvmDefault members somewhere in the hierarchy need the compiler 1.2.40+
// so that the generated bridges in subclasses would call the super members correctly
private fun writeVersionRequirementForJvmDefaultIfNeeded(
@@ -135,6 +135,7 @@ class FirMetadataSerializer(
}
serializer!!.functionProto(withTypeParameters)?.build()
}
is FirMetadataSource.Script -> serializer!!.scriptProto(metadata.fir).build()
else -> null
} ?: return null
return message to serializer!!.stringTable as JvmStringTable
@@ -205,6 +206,13 @@ internal fun makeElementSerializer(
approximator,
languageVersionSettings,
)
is FirMetadataSource.Script -> FirElementSerializer.createForScript(
session, scopeSession,
metadata.fir,
serializerExtension,
approximator,
languageVersionSettings
)
else -> null
}
@@ -103,6 +103,7 @@ class Fir2IrAnnotationsFromPluginRegistrar(private val components: Fir2IrCompone
?.topmostParentClassId
?.toSymbol(session)
?.fir
is FirScript -> this
else -> error("Unsupported declaration type: $this")
} ?: this
}
@@ -93,7 +93,7 @@ private class ScriptsToClassesLowering(val context: JvmBackendContext, val inner
origin = IrDeclarationOrigin.SCRIPT_CLASS
name = irScript.name.let {
if (it.isSpecial) {
NameUtils.getScriptNameForFile(irScript.name.asStringStripSpecialMarkers().removePrefix("script-"))
NameUtils.getScriptNameForFile(it.asStringStripSpecialMarkers().removePrefix("script-"))
} else it
}
kind = ClassKind.CLASS
@@ -1,4 +1,3 @@
// IGNORE_BACKEND_K2: JVM_IR
// WITH_REFLECT
import kotlin.reflect.KProperty