Implement support for synthetic script params in IR:

implicit receivers and provided properties
also support script imports
This commit is contained in:
Ilya Chernikov
2020-10-15 22:58:16 +02:00
parent 7572b50385
commit de340e9bc8
6 changed files with 184 additions and 110 deletions
@@ -389,7 +389,7 @@ val jvmPhases = NamedCompilerPhase(
lower = validateIrBeforeLowering then
processOptionalAnnotationsPhase then
expectDeclarationsRemovingPhase then
scriptToClassPhase then
scriptsToClassesPhase then
fileClassPhase then
performByIrFile(lower = jvmFilePhases) then
generateMultifileFacadesPhase then
@@ -5,11 +5,10 @@
package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.ir.copyTo
import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.phaser.makeIrModulePhase
import org.jetbrains.kotlin.backend.common.phaser.makeCustomPhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
@@ -36,23 +35,40 @@ import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
internal val scriptToClassPhase = makeIrModulePhase(
::ScriptToClassLowering,
name = "ScriptToClass",
description = "Put script declarations into a class",
stickyPostconditions = setOf(::checkAllFileLevelDeclarationsAreClasses)
internal val scriptsToClassesPhase = makeCustomPhase<JvmBackendContext, IrModuleFragment>(
name = "ScriptsToClasses",
description = "Put script declarations into classes",
op = { context, input ->
ScriptsToClassesLowering(context).lower(input)
}
)
private class ScriptToClassLowering(val context: JvmBackendContext) : FileLoweringPass {
override fun lower(irFile: IrFile) {
irFile.declarations.replaceAll { declaration ->
if (declaration is IrScript) makeScriptClass(irFile, declaration)
else declaration
private class ScriptsToClassesLowering(val context: JvmBackendContext) {
fun lower(module: IrModuleFragment) {
val scriptsToClasses = mutableMapOf<IrScript, IrClass>()
for (irFile in module.files) {
val iterator = irFile.declarations.listIterator()
while (iterator.hasNext()) {
val declaration = iterator.next()
if (declaration is IrScript) {
val scriptClass = prepareScriptClass(irFile, declaration)
scriptsToClasses[declaration] = scriptClass
iterator.set(scriptClass)
}
}
}
val symbolRemapper = ScriptsToClassesSymbolRemapper(scriptsToClasses)
for ((irScript, irScriptClass) in scriptsToClasses) {
finalizeScriptClass(irScriptClass, irScript, symbolRemapper)
}
}
private fun makeScriptClass(irFile: IrFile, irScript: IrScript): IrClass {
private fun prepareScriptClass(irFile: IrFile, irScript: IrScript): IrClass {
val fileEntry = irFile.fileEntry
return context.irFactory.buildClass {
startOffset = 0
@@ -65,83 +81,93 @@ private class ScriptToClassLowering(val context: JvmBackendContext) : FileLoweri
}.also { irScriptClass ->
irScriptClass.superTypes += context.irBuiltIns.anyType
irScriptClass.parent = irFile
irScriptClass.createImplicitParameterDeclarationWithWrappedDescriptor()
val symbolRemapper = ScriptToClassSymbolRemapper(irScript.symbol, irScriptClass.symbol)
val typeRemapper = ScriptTypeRemapper(symbolRemapper)
val scriptTransformer = ScriptToClassTransformer(irScript, irScriptClass, symbolRemapper, typeRemapper)
irScriptClass.thisReceiver = irScript.thisReceiver.run {
transform(scriptTransformer, null)
}
irScriptClass.addConstructor {
isPrimary = true
}.also { irConstructor ->
irScript.explicitCallParameters.forEach { scriptCallParameter ->
val callParameter = irConstructor.addValueParameter {
updateFrom(scriptCallParameter)
name = scriptCallParameter.name
}
}
}
private fun finalizeScriptClass(irScriptClass: IrClass, irScript: IrScript, symbolRemapper: ScriptsToClassesSymbolRemapper) {
val typeRemapper = ScriptTypeRemapper(symbolRemapper)
val scriptTransformer = ScriptToClassTransformer(irScript, irScriptClass, symbolRemapper, typeRemapper)
irScriptClass.thisReceiver = irScript.thisReceiver.run {
transform(scriptTransformer, null)
}
irScriptClass.addConstructor {
isPrimary = true
}.also { irConstructor ->
fun addConstructorParameter(valueParameter: IrValueParameter, createCorrespondingProperty: Boolean) {
valueParameter.type = typeRemapper.remapType(valueParameter.type)
if (valueParameter.varargElementType != null) {
valueParameter.varargElementType = typeRemapper.remapType(valueParameter.varargElementType!!)
}
irConstructor.valueParameters = irConstructor.valueParameters + valueParameter
if (createCorrespondingProperty) {
irScriptClass.addSimplePropertyFrom(
callParameter,
valueParameter,
IrExpressionBodyImpl(
IrGetValueImpl(
callParameter.startOffset, callParameter.endOffset,
callParameter.type,
callParameter.symbol,
valueParameter.startOffset, valueParameter.endOffset,
valueParameter.type,
valueParameter.symbol,
IrStatementOrigin.INITIALIZE_PROPERTY_FROM_PARAMETER
)
)
)
}
irConstructor.body = context.createIrBuilder(irConstructor.symbol).irBlockBody {
+irDelegatingConstructorCall(context.irBuiltIns.anyClass.owner.constructors.single())
+IrInstanceInitializerCallImpl(
irScript.startOffset, irScript.endOffset,
irScriptClass.symbol,
context.irBuiltIns.unitType
)
}
}
var hasMain = false
irScript.statements.forEach { scriptStatement ->
when (scriptStatement) {
is IrVariable -> irScriptClass.addSimplePropertyFrom(scriptStatement)
is IrDeclaration -> {
val copy = scriptStatement.transform(scriptTransformer, null) as IrDeclaration
irScriptClass.declarations.add(copy)
// temporary way to avoid name clashes
// TODO: remove as soon as main generation become an explicit configuration option
if (copy is IrSimpleFunction && copy.name.asString() == "main") {
hasMain = true
}
irScript.explicitCallParameters.forEach { addConstructorParameter(it, true) }
irScript.implicitReceiversParameters.forEach { addConstructorParameter(it, false) }
irScript.providedProperties.forEach { addConstructorParameter(it.first, false) }
irConstructor.body = context.createIrBuilder(irConstructor.symbol).irBlockBody {
+irDelegatingConstructorCall(context.irBuiltIns.anyClass.owner.constructors.single())
+IrInstanceInitializerCallImpl(
irScript.startOffset, irScript.endOffset,
irScriptClass.symbol,
context.irBuiltIns.unitType
)
}
}
var hasMain = false
irScript.statements.forEach { scriptStatement ->
when (scriptStatement) {
is IrVariable -> irScriptClass.addSimplePropertyFrom(scriptStatement)
is IrDeclaration -> {
val copy = scriptStatement.transform(scriptTransformer, null) as IrDeclaration
irScriptClass.declarations.add(copy)
// temporary way to avoid name clashes
// TODO: remove as soon as main generation become an explicit configuration option
if (copy is IrSimpleFunction && copy.name.asString() == "main") {
hasMain = true
}
else -> {
val transformedStatement = scriptStatement.transformStatement(scriptTransformer)
irScriptClass.addAnonymousInitializer().also { irInitializer ->
irInitializer.body =
context.createIrBuilder(irInitializer.symbol).irBlockBody {
if (transformedStatement is IrComposite) {
for (statement in transformedStatement.statements)
+statement
} else {
+transformedStatement
}
}
else -> {
val transformedStatement = scriptStatement.transformStatement(scriptTransformer)
irScriptClass.addAnonymousInitializer().also { irInitializer ->
irInitializer.body =
context.createIrBuilder(irInitializer.symbol).irBlockBody {
if (transformedStatement is IrComposite) {
for (statement in transformedStatement.statements)
+statement
} else {
+transformedStatement
}
}
}
}
}
}
if (!hasMain) {
irScriptClass.addScriptMainFun()
}
}
if (!hasMain) {
irScriptClass.addScriptMainFun()
}
irScriptClass.annotations += irFile.annotations
irScriptClass.metadata = irFile.metadata
irScriptClass.annotations += (irScriptClass.parent as IrFile).annotations
irScriptClass.metadata = (irScriptClass.parent as IrFile).metadata
irScript.resultProperty?.owner?.let { irResultProperty ->
context.state.scriptSpecific.resultFieldName = irResultProperty.name.identifier
context.state.scriptSpecific.resultTypeString = irResultProperty.backingField?.type?.render()
}
irScript.resultProperty?.owner?.let { irResultProperty ->
context.state.scriptSpecific.resultFieldName = irResultProperty.name.identifier
context.state.scriptSpecific.resultTypeString = irResultProperty.backingField?.type?.render()
}
}
@@ -261,8 +287,8 @@ private class ScriptToClassLowering(val context: JvmBackendContext) : FileLoweri
private class ScriptToClassTransformer(
val irScript: IrScript,
val irScriptClass: IrClass,
val symbolRemapper: SymbolRemapper = ScriptToClassSymbolRemapper(irScript.symbol, irScriptClass.symbol),
val typeRemapper: TypeRemapper = ScriptTypeRemapper(symbolRemapper)
val symbolRemapper: SymbolRemapper,
val typeRemapper: TypeRemapper
) : IrElementTransformerVoid() {
private fun IrType.remapType() = typeRemapper.remapType(this)
@@ -389,16 +415,14 @@ private class ScriptToClassTransformer(
}
}
private class ScriptToClassSymbolRemapper(
val irScriptSymbol: IrScriptSymbol,
val irScriptClassSymbol: IrClassSymbol
private class ScriptsToClassesSymbolRemapper(
val scriptsToClasses: Map<IrScript, IrClass>
) : SymbolRemapper.Empty() {
override fun getReferencedClassifier(symbol: IrClassifierSymbol): IrClassifierSymbol =
if (symbol != irScriptSymbol) symbol
else irScriptClassSymbol
(symbol.owner as? IrScript)?.let { scriptsToClasses[it] }?.symbol ?: symbol
}
class ScriptTypeRemapper(
private class ScriptTypeRemapper(
private val symbolRemapper: SymbolRemapper
) : TypeRemapper {
@@ -5,7 +5,9 @@
package org.jetbrains.kotlin.psi2ir.generators
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.ParameterDescriptor
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.ir.assertCast
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
@@ -15,6 +17,9 @@ import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.expressions.impl.IrCompositeImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrSetFieldImpl
import org.jetbrains.kotlin.ir.util.indexOrMinusOne
import org.jetbrains.kotlin.ir.util.isCrossinline
import org.jetbrains.kotlin.ir.util.isNoinline
import org.jetbrains.kotlin.ir.util.varargElementType
import org.jetbrains.kotlin.psi.KtDestructuringDeclaration
import org.jetbrains.kotlin.psi.KtScript
@@ -37,10 +42,17 @@ class ScriptGenerator(declarationGenerator: DeclarationGenerator) : DeclarationG
return context.symbolTable.declareScript(descriptor).buildWithScope { irScript ->
// A workaround for the JS/REPL backend:
// JS backend doesn't save previously executed snippets anywhere, they should be taken for now from the context.symbolTable.listExistedScripts()
// on the other hand imported scripts are also stored there so to avoid clashes the imported scripts should be filtered out
// NOTE: that JVM IR is not properly tested with REPL, so it might have the same problem
// TODO: design and implement other schema for handling previous snippets
val importedScripts = descriptor.implicitReceivers.filterIsInstanceTo(HashSet<ScriptDescriptor>())
// TODO: since script could reference instances of previous one their receivers have to be enlisted in its scope
// Remove this code once script is no longer represented by Class
existedScripts.forEach {
if (it.owner != irScript) {
if (it.owner != irScript && it.descriptor !in importedScripts) {
context.symbolTable.introduceValueParameter(it.owner.thisReceiver)
}
}
@@ -48,21 +60,60 @@ class ScriptGenerator(declarationGenerator: DeclarationGenerator) : DeclarationG
val startOffset = ktScript.pureStartOffset
val endOffset = ktScript.pureEndOffset
fun makeReceiver(descriptor: ClassDescriptor): IrValueParameter {
val receiverParameterDescriptor = descriptor.thisAsReceiverParameter
fun makeParameter(descriptor: ParameterDescriptor, origin: IrDeclarationOrigin, index: Int = -1): IrValueParameter {
val type = descriptor.type.toIrType()
val varargElementType = descriptor.varargElementType?.toIrType()
return context.symbolTable.declareValueParameter(
startOffset, endOffset,
IrDeclarationOrigin.INSTANCE_RECEIVER,
receiverParameterDescriptor,
receiverParameterDescriptor.type.toIrType()
).also { it.parent = irScript }
origin,
descriptor,
type
) { symbol ->
context.irFactory.createValueParameter(
startOffset, endOffset,
origin, symbol, context.symbolTable.nameProvider.nameForDeclaration(descriptor),
if (index != -1) index else descriptor.indexOrMinusOne,
type, varargElementType,
descriptor.isCrossinline, descriptor.isNoinline, false
)
} .also { it.parent = irScript }
}
irScript.thisReceiver = makeReceiver(descriptor)
irScript.thisReceiver = makeParameter(descriptor.thisAsReceiverParameter, IrDeclarationOrigin.INSTANCE_RECEIVER)
irScript.baseClass = descriptor.typeConstructor.supertypes.single().toIrType()
irScript.implicitReceivers = descriptor.implicitReceivers.map(::makeReceiver)
// This is part of a hack for implicit receivers that converted to value parameters below
// The proper schema would be to get properly indexed parameters from frontend (descriptor.implicitReceiversParameters),
// but it seems would require a proper remapping for the script body
// TODO: implement implicit receiver parameters handlin properly
var parametersIndex = 0
irScript.explicitCallParameters = descriptor.explicitConstructorParameters.map { valueParameterDescriptor ->
parametersIndex++
valueParameterDescriptor.toIrValueParameter(startOffset, endOffset, IrDeclarationOrigin.SCRIPT_CALL_PARAMETER).also { it.parent = irScript }
}
irScript.implicitReceiversParameters = descriptor.implicitReceivers.map {
makeParameter(it.thisAsReceiverParameter, IrDeclarationOrigin.SCRIPT_IMPLICIT_RECEIVER, parametersIndex++)
}
irScript.providedProperties = descriptor.scriptProvidedProperties.zip(descriptor.scriptProvidedPropertiesParameters)
.map { (providedProperty, parameter) ->
// TODO: initializer
// TODO: do not keep direct links
val type = providedProperty.type.toIrType()
val valueParameter = context.symbolTable.declareValueParameter(
startOffset, endOffset, IrDeclarationOrigin.SCRIPT_PROVIDED_PROPERTY, parameter, type
)
val irProperty =
PropertyGenerator(declarationGenerator).generateSyntheticProperty(ktScript, providedProperty, valueParameter)
irProperty.origin = IrDeclarationOrigin.SCRIPT_PROVIDED_PROPERTY
irScript.statements += irProperty
valueParameter to irProperty.symbol
}
irScript.earlierScripts = existedScripts
for (d in ktScript.declarations) {
when (d) {
@@ -146,19 +197,6 @@ class ScriptGenerator(declarationGenerator: DeclarationGenerator) : DeclarationG
else -> irScript.statements += declarationGenerator.generateMemberDeclaration(d)!!
}
}
irScript.explicitCallParameters = descriptor.unsubstitutedPrimaryConstructor.valueParameters.map { valueParameterDescriptor ->
valueParameterDescriptor.toIrValueParameter(startOffset, endOffset, IrDeclarationOrigin.SCRIPT_CALL_PARAMETER)
}
irScript.providedProperties = descriptor.scriptProvidedProperties.map { providedProperty ->
// TODO: initializer
// TODO: do not keep direct links
val irProperty = PropertyGenerator(declarationGenerator).generateSyntheticProperty(ktScript, providedProperty, null)
irProperty.origin = IrDeclarationOrigin.SCRIPT_PROVIDED_PROPERTY
irScript.statements += irProperty
irProperty.symbol
}
}
}
@@ -24,9 +24,11 @@ abstract class IrScript :
abstract var explicitCallParameters: List<IrValueParameter>
abstract var implicitReceivers: List<IrValueParameter>
abstract var implicitReceiversParameters: List<IrValueParameter>
abstract var providedProperties: List<IrPropertySymbol>
abstract var providedProperties: List<Pair<IrValueParameter, IrPropertySymbol>>
abstract var resultProperty: IrPropertySymbol?
abstract var earlierScripts: List<IrScriptSymbol>?
}
@@ -46,9 +46,10 @@ class IrScriptImpl(
override lateinit var baseClass: IrType
override lateinit var explicitCallParameters: List<IrValueParameter>
override lateinit var implicitReceivers: List<IrValueParameter>
override lateinit var providedProperties: List<IrPropertySymbol>
override lateinit var implicitReceiversParameters: List<IrValueParameter>
override lateinit var providedProperties: List<Pair<IrValueParameter, IrPropertySymbol>>
override var resultProperty: IrPropertySymbol? = null
override var earlierScripts: List<IrScriptSymbol>? = null
@ObsoleteDescriptorBasedAPI
override val descriptor: ScriptDescriptor
@@ -68,10 +69,16 @@ class IrScriptImpl(
override fun <D> acceptChildren(visitor: IrElementVisitor<Unit, D>, data: D) {
statements.forEach { it.accept(visitor, data) }
thisReceiver.accept(visitor, data)
explicitCallParameters.forEach { it.accept(visitor, data) }
implicitReceiversParameters.forEach { it.accept(visitor, data) }
providedProperties.forEach { it.first.accept(visitor, data) }
}
override fun <D> transformChildren(transformer: IrElementTransformer<D>, data: D) {
statements.transformInPlace(transformer, data)
thisReceiver = thisReceiver.transform(transformer, data)
explicitCallParameters = explicitCallParameters.map { it.transform(transformer, data) }
implicitReceiversParameters = implicitReceiversParameters.map { it.transform(transformer, data) }
providedProperties = providedProperties.map { it.first.transform(transformer, data) to it.second }
}
}
@@ -119,6 +119,9 @@ open class DeepCopyIrTreeWithSymbols(
).also { scriptCopy ->
scriptCopy.thisReceiver = declaration.thisReceiver.transform()
declaration.statements.mapTo(scriptCopy.statements) { it.transform() }
scriptCopy.explicitCallParameters = declaration.explicitCallParameters.map { it.transform() }
scriptCopy.implicitReceiversParameters = declaration.implicitReceiversParameters.map { it.transform() }
scriptCopy.providedProperties = declaration.providedProperties.map { it.first.transform() to it.second }
}
}