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 lower = validateIrBeforeLowering then
processOptionalAnnotationsPhase then processOptionalAnnotationsPhase then
expectDeclarationsRemovingPhase then expectDeclarationsRemovingPhase then
scriptToClassPhase then scriptsToClassesPhase then
fileClassPhase then fileClassPhase then
performByIrFile(lower = jvmFilePhases) then performByIrFile(lower = jvmFilePhases) then
generateMultifileFacadesPhase then generateMultifileFacadesPhase then
@@ -5,11 +5,10 @@
package org.jetbrains.kotlin.backend.jvm.lower 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.copyTo
import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder 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.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities 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.FqName
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
internal val scriptToClassPhase = makeIrModulePhase( internal val scriptsToClassesPhase = makeCustomPhase<JvmBackendContext, IrModuleFragment>(
::ScriptToClassLowering, name = "ScriptsToClasses",
name = "ScriptToClass", description = "Put script declarations into classes",
description = "Put script declarations into a class", op = { context, input ->
stickyPostconditions = setOf(::checkAllFileLevelDeclarationsAreClasses) ScriptsToClassesLowering(context).lower(input)
}
) )
private class ScriptToClassLowering(val context: JvmBackendContext) : FileLoweringPass {
override fun lower(irFile: IrFile) { private class ScriptsToClassesLowering(val context: JvmBackendContext) {
irFile.declarations.replaceAll { declaration ->
if (declaration is IrScript) makeScriptClass(irFile, declaration) fun lower(module: IrModuleFragment) {
else declaration 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 val fileEntry = irFile.fileEntry
return context.irFactory.buildClass { return context.irFactory.buildClass {
startOffset = 0 startOffset = 0
@@ -65,83 +81,93 @@ private class ScriptToClassLowering(val context: JvmBackendContext) : FileLoweri
}.also { irScriptClass -> }.also { irScriptClass ->
irScriptClass.superTypes += context.irBuiltIns.anyType irScriptClass.superTypes += context.irBuiltIns.anyType
irScriptClass.parent = irFile irScriptClass.parent = irFile
irScriptClass.createImplicitParameterDeclarationWithWrappedDescriptor() }
val symbolRemapper = ScriptToClassSymbolRemapper(irScript.symbol, irScriptClass.symbol) }
val typeRemapper = ScriptTypeRemapper(symbolRemapper)
val scriptTransformer = ScriptToClassTransformer(irScript, irScriptClass, symbolRemapper, typeRemapper) private fun finalizeScriptClass(irScriptClass: IrClass, irScript: IrScript, symbolRemapper: ScriptsToClassesSymbolRemapper) {
irScriptClass.thisReceiver = irScript.thisReceiver.run { val typeRemapper = ScriptTypeRemapper(symbolRemapper)
transform(scriptTransformer, null) val scriptTransformer = ScriptToClassTransformer(irScript, irScriptClass, symbolRemapper, typeRemapper)
} irScriptClass.thisReceiver = irScript.thisReceiver.run {
irScriptClass.addConstructor { transform(scriptTransformer, null)
isPrimary = true }
}.also { irConstructor ->
irScript.explicitCallParameters.forEach { scriptCallParameter -> irScriptClass.addConstructor {
val callParameter = irConstructor.addValueParameter { isPrimary = true
updateFrom(scriptCallParameter) }.also { irConstructor ->
name = scriptCallParameter.name
} 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( irScriptClass.addSimplePropertyFrom(
callParameter, valueParameter,
IrExpressionBodyImpl( IrExpressionBodyImpl(
IrGetValueImpl( IrGetValueImpl(
callParameter.startOffset, callParameter.endOffset, valueParameter.startOffset, valueParameter.endOffset,
callParameter.type, valueParameter.type,
callParameter.symbol, valueParameter.symbol,
IrStatementOrigin.INITIALIZE_PROPERTY_FROM_PARAMETER 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 -> irScript.explicitCallParameters.forEach { addConstructorParameter(it, true) }
when (scriptStatement) { irScript.implicitReceiversParameters.forEach { addConstructorParameter(it, false) }
is IrVariable -> irScriptClass.addSimplePropertyFrom(scriptStatement) irScript.providedProperties.forEach { addConstructorParameter(it.first, false) }
is IrDeclaration -> {
val copy = scriptStatement.transform(scriptTransformer, null) as IrDeclaration irConstructor.body = context.createIrBuilder(irConstructor.symbol).irBlockBody {
irScriptClass.declarations.add(copy) +irDelegatingConstructorCall(context.irBuiltIns.anyClass.owner.constructors.single())
// temporary way to avoid name clashes +IrInstanceInitializerCallImpl(
// TODO: remove as soon as main generation become an explicit configuration option irScript.startOffset, irScript.endOffset,
if (copy is IrSimpleFunction && copy.name.asString() == "main") { irScriptClass.symbol,
hasMain = true 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) else -> {
irScriptClass.addAnonymousInitializer().also { irInitializer -> val transformedStatement = scriptStatement.transformStatement(scriptTransformer)
irInitializer.body = irScriptClass.addAnonymousInitializer().also { irInitializer ->
context.createIrBuilder(irInitializer.symbol).irBlockBody { irInitializer.body =
if (transformedStatement is IrComposite) { context.createIrBuilder(irInitializer.symbol).irBlockBody {
for (statement in transformedStatement.statements) if (transformedStatement is IrComposite) {
+statement for (statement in transformedStatement.statements)
} else { +statement
+transformedStatement } else {
} +transformedStatement
} }
} }
} }
} }
} }
if (!hasMain) { }
irScriptClass.addScriptMainFun() if (!hasMain) {
} irScriptClass.addScriptMainFun()
}
irScriptClass.annotations += irFile.annotations irScriptClass.annotations += (irScriptClass.parent as IrFile).annotations
irScriptClass.metadata = irFile.metadata irScriptClass.metadata = (irScriptClass.parent as IrFile).metadata
irScript.resultProperty?.owner?.let { irResultProperty -> irScript.resultProperty?.owner?.let { irResultProperty ->
context.state.scriptSpecific.resultFieldName = irResultProperty.name.identifier context.state.scriptSpecific.resultFieldName = irResultProperty.name.identifier
context.state.scriptSpecific.resultTypeString = irResultProperty.backingField?.type?.render() context.state.scriptSpecific.resultTypeString = irResultProperty.backingField?.type?.render()
}
} }
} }
@@ -261,8 +287,8 @@ private class ScriptToClassLowering(val context: JvmBackendContext) : FileLoweri
private class ScriptToClassTransformer( private class ScriptToClassTransformer(
val irScript: IrScript, val irScript: IrScript,
val irScriptClass: IrClass, val irScriptClass: IrClass,
val symbolRemapper: SymbolRemapper = ScriptToClassSymbolRemapper(irScript.symbol, irScriptClass.symbol), val symbolRemapper: SymbolRemapper,
val typeRemapper: TypeRemapper = ScriptTypeRemapper(symbolRemapper) val typeRemapper: TypeRemapper
) : IrElementTransformerVoid() { ) : IrElementTransformerVoid() {
private fun IrType.remapType() = typeRemapper.remapType(this) private fun IrType.remapType() = typeRemapper.remapType(this)
@@ -389,16 +415,14 @@ private class ScriptToClassTransformer(
} }
} }
private class ScriptToClassSymbolRemapper( private class ScriptsToClassesSymbolRemapper(
val irScriptSymbol: IrScriptSymbol, val scriptsToClasses: Map<IrScript, IrClass>
val irScriptClassSymbol: IrClassSymbol
) : SymbolRemapper.Empty() { ) : SymbolRemapper.Empty() {
override fun getReferencedClassifier(symbol: IrClassifierSymbol): IrClassifierSymbol = override fun getReferencedClassifier(symbol: IrClassifierSymbol): IrClassifierSymbol =
if (symbol != irScriptSymbol) symbol (symbol.owner as? IrScript)?.let { scriptsToClasses[it] }?.symbol ?: symbol
else irScriptClassSymbol
} }
class ScriptTypeRemapper( private class ScriptTypeRemapper(
private val symbolRemapper: SymbolRemapper private val symbolRemapper: SymbolRemapper
) : TypeRemapper { ) : TypeRemapper {
@@ -5,7 +5,9 @@
package org.jetbrains.kotlin.psi2ir.generators 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.assertCast
import org.jetbrains.kotlin.ir.declarations.IrDeclaration import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin 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.IrCompositeImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrSetFieldImpl 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.ir.util.varargElementType
import org.jetbrains.kotlin.psi.KtDestructuringDeclaration import org.jetbrains.kotlin.psi.KtDestructuringDeclaration
import org.jetbrains.kotlin.psi.KtScript import org.jetbrains.kotlin.psi.KtScript
@@ -37,10 +42,17 @@ class ScriptGenerator(declarationGenerator: DeclarationGenerator) : DeclarationG
return context.symbolTable.declareScript(descriptor).buildWithScope { irScript -> 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 // 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 // Remove this code once script is no longer represented by Class
existedScripts.forEach { existedScripts.forEach {
if (it.owner != irScript) { if (it.owner != irScript && it.descriptor !in importedScripts) {
context.symbolTable.introduceValueParameter(it.owner.thisReceiver) context.symbolTable.introduceValueParameter(it.owner.thisReceiver)
} }
} }
@@ -48,21 +60,60 @@ class ScriptGenerator(declarationGenerator: DeclarationGenerator) : DeclarationG
val startOffset = ktScript.pureStartOffset val startOffset = ktScript.pureStartOffset
val endOffset = ktScript.pureEndOffset val endOffset = ktScript.pureEndOffset
fun makeReceiver(descriptor: ClassDescriptor): IrValueParameter { fun makeParameter(descriptor: ParameterDescriptor, origin: IrDeclarationOrigin, index: Int = -1): IrValueParameter {
val receiverParameterDescriptor = descriptor.thisAsReceiverParameter val type = descriptor.type.toIrType()
val varargElementType = descriptor.varargElementType?.toIrType()
return context.symbolTable.declareValueParameter( return context.symbolTable.declareValueParameter(
startOffset, endOffset, startOffset, endOffset,
IrDeclarationOrigin.INSTANCE_RECEIVER, origin,
receiverParameterDescriptor, descriptor,
receiverParameterDescriptor.type.toIrType() type
).also { it.parent = irScript } ) { 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.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) { for (d in ktScript.declarations) {
when (d) { when (d) {
@@ -146,19 +197,6 @@ class ScriptGenerator(declarationGenerator: DeclarationGenerator) : DeclarationG
else -> irScript.statements += declarationGenerator.generateMemberDeclaration(d)!! 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 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 resultProperty: IrPropertySymbol?
abstract var earlierScripts: List<IrScriptSymbol>?
} }
@@ -46,9 +46,10 @@ class IrScriptImpl(
override lateinit var baseClass: IrType override lateinit var baseClass: IrType
override lateinit var explicitCallParameters: List<IrValueParameter> override lateinit var explicitCallParameters: List<IrValueParameter>
override lateinit var implicitReceivers: List<IrValueParameter> override lateinit var implicitReceiversParameters: List<IrValueParameter>
override lateinit var providedProperties: List<IrPropertySymbol> override lateinit var providedProperties: List<Pair<IrValueParameter, IrPropertySymbol>>
override var resultProperty: IrPropertySymbol? = null override var resultProperty: IrPropertySymbol? = null
override var earlierScripts: List<IrScriptSymbol>? = null
@ObsoleteDescriptorBasedAPI @ObsoleteDescriptorBasedAPI
override val descriptor: ScriptDescriptor override val descriptor: ScriptDescriptor
@@ -68,10 +69,16 @@ class IrScriptImpl(
override fun <D> acceptChildren(visitor: IrElementVisitor<Unit, D>, data: D) { override fun <D> acceptChildren(visitor: IrElementVisitor<Unit, D>, data: D) {
statements.forEach { it.accept(visitor, data) } statements.forEach { it.accept(visitor, data) }
thisReceiver.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) { override fun <D> transformChildren(transformer: IrElementTransformer<D>, data: D) {
statements.transformInPlace(transformer, data) statements.transformInPlace(transformer, data)
thisReceiver = thisReceiver.transform(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 -> ).also { scriptCopy ->
scriptCopy.thisReceiver = declaration.thisReceiver.transform() scriptCopy.thisReceiver = declaration.thisReceiver.transform()
declaration.statements.mapTo(scriptCopy.statements) { it.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 }
} }
} }