K2 Scripting: add support for imported scripts

This commit is contained in:
Ilya Chernikov
2023-06-19 10:13:03 +02:00
committed by Space Team
parent d24fc3b581
commit 96bde033e1
21 changed files with 306 additions and 38 deletions
@@ -45,6 +45,7 @@ import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmBackendErrors
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.kotlin.utils.topologicalSort
internal val scriptsToClassesPhase = makeCustomPhase<JvmBackendContext, IrModuleFragment>(
name = "ScriptsToClasses",
@@ -58,7 +59,8 @@ internal val scriptsToClassesPhase = makeCustomPhase<JvmBackendContext, IrModule
private class ScriptsToClassesLowering(val context: JvmBackendContext, val innerClassesSupport: JvmInnerClassesSupport) {
fun lower(module: IrModuleFragment) {
val scriptsToClasses = mutableMapOf<IrScript, IrClass>()
val scripts = mutableListOf<IrScript>()
val scriptDependencies = mutableMapOf<IrScript, List<IrScript>>()
for (irFile in module.files) {
val iterator = irFile.declarations.listIterator()
@@ -66,7 +68,10 @@ private class ScriptsToClassesLowering(val context: JvmBackendContext, val inner
val declaration = iterator.next()
if (declaration is IrScript) {
val scriptClass = prepareScriptClass(irFile, declaration)
scriptsToClasses[declaration] = scriptClass
scripts.add(declaration)
declaration.importedScripts.takeUnless { it.isNullOrEmpty() }?.let {
scriptDependencies[declaration] = it.map { it.owner }
}
iterator.set(scriptClass)
}
}
@@ -74,10 +79,11 @@ private class ScriptsToClassesLowering(val context: JvmBackendContext, val inner
val symbolRemapper = ScriptsToClassesSymbolRemapper()
for ((irScript, irScriptClass) in scriptsToClasses) {
finalizeScriptClass(irScriptClass, irScript, symbolRemapper)
val orderedScripts = topologicalSort(scripts) { scriptDependencies[this] ?: emptyList() }.reversed()
for (irScript in orderedScripts) {
finalizeScriptClass(irScript, symbolRemapper)
// TODO fix parents in script classes
irScriptClass.patchDeclarationParents(irScript.parent)
irScript.targetClass!!.owner.patchDeclarationParents(irScript.parent)
}
}
@@ -111,6 +117,9 @@ private class ScriptsToClassesLowering(val context: JvmBackendContext, val inner
it.addIfNotNull(irScript.thisReceiver?.type)
}
irScript.earlierScripts?.forEach { scriptsReceivers.addIfNotNull(it.owner.thisReceiver?.type) }
irScript.importedScripts?.forEach {
scriptsReceivers.add(it.owner.targetClass!!.owner.thisReceiver!!.type)
}
irScript.implicitReceiversParameters.forEach {
scriptsReceivers.add(it.type)
scriptsReceivers.add(typeRemapper.remapType(it.type))
@@ -165,7 +174,8 @@ private class ScriptsToClassesLowering(val context: JvmBackendContext, val inner
return capturingClasses
}
private fun finalizeScriptClass(irScriptClass: IrClass, irScript: IrScript, symbolRemapper: ScriptsToClassesSymbolRemapper) {
private fun finalizeScriptClass(irScript: IrScript, symbolRemapper: ScriptsToClassesSymbolRemapper) {
val irScriptClass = irScript.targetClass!!.owner
val typeRemapper = SimpleTypeRemapper(symbolRemapper)
val capturingClasses = collectCapturingClasses(irScript, typeRemapper)
@@ -188,22 +198,44 @@ private class ScriptsToClassesLowering(val context: JvmBackendContext, val inner
}
val implicitReceiversFieldsWithParameters = arrayListOf<Pair<IrField, IrValueParameter>>().apply {
irScript.implicitReceiversParameters.forEach { param ->
fun createField(name: Name, type: IrType): IrField {
val field = irScriptClass.factory.createField(
startOffset = UNDEFINED_OFFSET,
endOffset = UNDEFINED_OFFSET,
origin = IrDeclarationOrigin.SCRIPT_IMPLICIT_RECEIVER,
name = Name.identifier("\$\$implicitReceiver_${param.type.classFqName?.shortName()?.asString()!!}"),
name = name,
visibility = DescriptorVisibilities.PRIVATE,
symbol = IrFieldSymbolImpl(),
type = typeRemapper.remapType(param.type),
type = typeRemapper.remapType(type),
isFinal = true,
isStatic = false,
isExternal = false
)
field.parent = irScriptClass
irScriptClass.declarations.add(field)
add(field to param)
return field
}
irScript.importedScripts?.forEach {
val importedScriptClass = it.owner.targetClass!!.owner
val type = importedScriptClass.defaultType
val name = Name.identifier("\$\$importedScript_${type.classFqName?.shortName()?.asString()!!}")
val param = irScriptClass.factory.createValueParameter(
UNDEFINED_OFFSET, UNDEFINED_OFFSET, IrDeclarationOrigin.SCRIPT_IMPLICIT_RECEIVER, name, type, isAssignable = false,
IrValueParameterSymbolImpl(), UNDEFINED_PARAMETER_INDEX, varargElementType = null,
isCrossinline = false, isNoinline = false, isHidden = false,
)
param.parent = irScriptClass
add(createField(name, type) to param)
}
irScript.implicitReceiversParameters.forEach { param ->
add(
createField(
Name.identifier("\$\$implicitReceiver_${param.type.classFqName?.shortName()?.asString()!!}"),
param.type
) to param
)
}
}
@@ -221,19 +253,30 @@ private class ScriptsToClassesLowering(val context: JvmBackendContext, val inner
irScriptClass.thisReceiver = scriptTransformer.scriptClassReceiver
val defaultContext = ScriptToClassTransformerContext(
val defaultContextForDeclarations = ScriptToClassTransformerContext(
valueParameterForScriptThis = irScriptClass.thisReceiver?.symbol,
fieldForScriptThis = null,
valueParameterForFieldReceiver = null,
isInScriptConstructor = false
)
fun <E : IrElement> E.patchForClass(): IrElement =
val defaultContextForTopLevelStatements = ScriptToClassTransformerContext(
valueParameterForScriptThis = irScriptClass.thisReceiver?.symbol,
fieldForScriptThis = null,
valueParameterForFieldReceiver = null,
isInScriptConstructor = true
)
fun <E : IrElement> E.patchDeclarationForClass(): IrElement =
transform(
scriptTransformer,
(this as? IrDeclaration)?.let { defaultContext.copy( topLevelDeclaration = it) } ?: defaultContext
(this as? IrDeclaration)?.let { defaultContextForDeclarations.copy(topLevelDeclaration = it) } ?: defaultContextForDeclarations
).transform(lambdaPatcher, ScriptFixLambdasTransformerContext())
fun <E : IrElement> E.patchTopLevelStatementForClass(): IrElement =
transform(scriptTransformer, defaultContextForTopLevelStatements)
.transform(lambdaPatcher, ScriptFixLambdasTransformerContext())
val explicitParametersWithFields = irScript.explicitCallParameters.map { parameter ->
val field = irScriptClass.addField {
startOffset = parameter.startOffset
@@ -247,7 +290,7 @@ private class ScriptsToClassesLowering(val context: JvmBackendContext, val inner
parameter to field
}
(irScript.constructor?.patchForClass() as? IrConstructor
(irScript.constructor?.patchDeclarationForClass() as? IrConstructor
?: createConstructor(irScriptClass, irScript, implicitReceiversFieldsWithParameters)).also { constructor ->
val explicitParamsStartIndex = if (irScript.earlierScriptsParameter == null) 0 else 1
val explicitParameters = constructor.valueParameters.subList(
@@ -286,7 +329,7 @@ private class ScriptsToClassesLowering(val context: JvmBackendContext, val inner
+irSetField(
irGet(irScriptClass.thisReceiver!!),
field,
irGet(correspondingParameter.patchForClass() as IrValueParameter)
irGet(correspondingParameter.patchDeclarationForClass() as IrValueParameter)
)
}
+IrInstanceInitializerCallImpl(
@@ -303,11 +346,11 @@ private class ScriptsToClassesLowering(val context: JvmBackendContext, val inner
irScript.statements.forEach { scriptStatement ->
when (scriptStatement) {
is IrVariable -> {
val copy = scriptStatement.patchForClass() as IrVariable
val copy = scriptStatement.patchDeclarationForClass() as IrVariable
irScriptClass.addSimplePropertyFrom(copy)
}
is IrDeclaration -> {
val copy = scriptStatement.patchForClass() as IrDeclaration
val copy = scriptStatement.patchDeclarationForClass() as IrDeclaration
irScriptClass.declarations.add(copy)
// temporary way to avoid name clashes
// TODO: remove as soon as main generation become an explicit configuration option
@@ -316,7 +359,7 @@ private class ScriptsToClassesLowering(val context: JvmBackendContext, val inner
}
}
else -> {
val transformedStatement = scriptStatement.patchForClass() as IrStatement
val transformedStatement = scriptStatement.patchTopLevelStatementForClass() as IrStatement
irScriptClass.addAnonymousInitializer().also { irInitializer ->
irInitializer.body =
context.createIrBuilder(irInitializer.symbol).irBlockBody {
@@ -707,10 +750,20 @@ private class ScriptToClassTransformer(
expression.putTypeArgument(i, expression.getTypeArgument(i)?.remapType())
}
if (expression.dispatchReceiver == null && (expression.symbol.owner as? IrDeclaration)?.needsScriptReceiver() == true) {
val memberAccessTargetReceiverType = when (val callee = expression.symbol.owner) {
is IrFunction -> callee.dispatchReceiverParameter?.type
is IrProperty -> callee.getter?.dispatchReceiverParameter?.type
else -> null
}
expression.dispatchReceiver =
getAccessCallForScriptInstance(
data, expression.startOffset, expression.endOffset, expression.origin, originalReceiverParameter = null
)
if (memberAccessTargetReceiverType != null && memberAccessTargetReceiverType != scriptClassReceiver.type)
getAccessCallForImplicitReceiver(
data, expression, memberAccessTargetReceiverType, expression.origin, originalReceiverParameter = null
)
else
getAccessCallForScriptInstance(
data, expression.startOffset, expression.endOffset, expression.origin, originalReceiverParameter = null
)
}
return super.visitMemberAccess(expression, data) as IrExpression
}
@@ -909,7 +962,17 @@ private class ScriptToClassTransformer(
}
private fun IrDeclaration.needsScriptReceiver() =
(this as? IrFunction)?.dispatchReceiverParameter?.origin == IrDeclarationOrigin.SCRIPT_THIS_RECEIVER
when (this) {
is IrFunction -> this.dispatchReceiverParameter
is IrProperty -> {
this.getter?.takeIf {
// without this exception, the PropertyReferenceLowering generates clinit with attempt to use script as receiver
// TODO: find whether it is a valid exception and maybe how to make it more obvious
it.origin != IrDeclarationOrigin.DELEGATED_PROPERTY_ACCESSOR
}?.dispatchReceiverParameter
}
else -> null
}?.origin == IrDeclarationOrigin.SCRIPT_THIS_RECEIVER
}
private class ScriptFixLambdasTransformer(val irScriptClass: IrClass) : IrElementTransformer<ScriptFixLambdasTransformerContext> {
@@ -994,4 +1057,4 @@ private inline fun IrClass.addAnonymousInitializer(builder: IrFunctionBuilder.()
private val IrScript.needsReceiverProcessing: Boolean
// in K2 we need to add dispatch receiver to the top-level declarations, and in all cases receivers should be replaced
// for all kinds of implicit receivers
get() = origin == SCRIPT_K2_ORIGIN || earlierScripts?.isNotEmpty() == true || implicitReceiversParameters.isNotEmpty()
get() = origin == SCRIPT_K2_ORIGIN || importedScripts?.isNotEmpty() == true || earlierScripts?.isNotEmpty() == true || implicitReceiversParameters.isNotEmpty()
@@ -43,6 +43,8 @@ abstract class IrScript : IrDeclarationBase(), IrDeclarationWithName, IrDeclarat
abstract var earlierScriptsParameter: IrValueParameter?
abstract var importedScripts: List<IrScriptSymbol>?
abstract var earlierScripts: List<IrScriptSymbol>?
abstract var targetClass: IrClassSymbol?
@@ -53,6 +53,7 @@ class IrScriptImpl(
override lateinit var providedPropertiesParameters: List<IrValueParameter>
override var resultProperty: IrPropertySymbol? = null
override var earlierScriptsParameter: IrValueParameter? = null
override var importedScripts: List<IrScriptSymbol>? = null
override var earlierScripts: List<IrScriptSymbol>? = null
override var targetClass: IrClassSymbol? = null
override var constructor: IrConstructor? = null
@@ -131,6 +131,7 @@ open class DeepCopyIrTreeWithSymbols(
).also { scriptCopy ->
scriptCopy.thisReceiver = declaration.thisReceiver?.transform()
declaration.statements.mapTo(scriptCopy.statements) { it.transform() }
scriptCopy.importedScripts = declaration.importedScripts
scriptCopy.earlierScripts = declaration.earlierScripts
scriptCopy.earlierScriptsParameter = declaration.earlierScriptsParameter
scriptCopy.explicitCallParameters = declaration.explicitCallParameters.memoryOptimizedMap { it.transform() }
@@ -548,6 +548,7 @@ object IrTree : AbstractTreeBuilder() {
+listField("providedPropertiesParameters", valueParameter, mutability = Var, isChild = true)
+field("resultProperty", propertySymbolType, nullable = true)
+field("earlierScriptsParameter", valueParameter, nullable = true, isChild = true)
+listField("importedScripts", scriptSymbolType, mutability = Var, nullable = true)
+listField("earlierScripts", scriptSymbolType, mutability = Var, nullable = true)
+field("targetClass", classSymbolType, nullable = true)
+field("constructor", constructor, nullable = true) // K1