[JS IR] Refactor property references

- implement separate lowering
 - fix issues with type checks
 - clean code up
This commit is contained in:
Roman Artemev
2020-02-27 19:58:47 +03:00
committed by romanart
parent 5dcac16cf7
commit 8654dbdc6e
5 changed files with 270 additions and 525 deletions
@@ -19,7 +19,6 @@ import org.jetbrains.kotlin.ir.SourceManager
import org.jetbrains.kotlin.ir.SourceRangeInfo
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.lower.CallableReferenceKey
import org.jetbrains.kotlin.ir.backend.js.utils.OperatorNames
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
@@ -163,8 +162,6 @@ class JsIrBackendContext(
private val coroutinePackage = module.getPackage(COROUTINE_PACKAGE_FQNAME)
private val coroutineIntrinsicsPackage = module.getPackage(COROUTINE_INTRINSICS_PACKAGE_FQNAME)
val callableReferencesCache = mutableMapOf<CallableReferenceKey, IrSimpleFunction>()
val intrinsics = JsIntrinsics(irBuiltIns, this)
override val internalPackageFqn = JS_PACKAGE_FQNAME
@@ -308,6 +305,9 @@ class JsIrBackendContext(
val throwableConstructors by lazy2 { throwableClass.owner.declarations.filterIsInstance<IrConstructor>().map { it.symbol } }
val defaultThrowableCtor by lazy2 { throwableConstructors.single { !it.owner.isPrimary && it.owner.valueParameters.size == 0 } }
val kpropertyBuilder = getFunctions(FqName("kotlin.js.getPropertyCallableRef")).single().let { symbolTable.referenceSimpleFunction(it) }
val klocalDelegateBuilder = getFunctions(FqName("kotlin.js.getLocalDelegateReference")).single().let { symbolTable.referenceSimpleFunction(it) }
private fun referenceOperators(): Map<Name, MutableMap<IrClassifierSymbol, IrSimpleFunctionSymbol>> {
val primitiveIrSymbols = irBuiltIns.primitiveIrTypes.map { it.classifierOrFail as IrClassSymbol }
@@ -358,6 +358,12 @@ private val privateMemberUsagesLoweringPhase = makeBodyLoweringPhase(
description = "Rewrite the private member usages"
)
private val propertyReferenceLoweringPhase = makeBodyLoweringPhase(
::PropertyReferenceLowering,
name = "PropertyReferenceLowering",
description = "Transform property references"
)
private val interopCallableReferenceLoweringPhase = makeBodyLoweringPhase(
::InteropCallableReferenceLowering,
name = "InteropCallableReferenceLowering",
@@ -612,6 +618,7 @@ val loweringList = listOf<Lowering>(
enumUsageLoweringPhase,
enumEntryRemovalLoweringPhase,
suspendFunctionsLoweringPhase,
propertyReferenceLoweringPhase,
interopCallableReferenceLoweringPhase,
returnableBlockLoweringPhase,
forLoopsLoweringPhase,
@@ -8,48 +8,23 @@ package org.jetbrains.kotlin.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
import org.jetbrains.kotlin.backend.common.ir.copyTo
import org.jetbrains.kotlin.backend.common.ir.copyToWithoutSuperTypes
import org.jetbrains.kotlin.backend.common.lower.BOUND_VALUE_PARAMETER
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
import org.jetbrains.kotlin.ir.backend.js.utils.asString
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrTypeParameterImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl
import org.jetbrains.kotlin.ir.descriptors.WrappedTypeParameterDescriptor
import org.jetbrains.kotlin.ir.descriptors.WrappedValueParameterDescriptor
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrTypeParameterSymbolImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.IrTypeProjection
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
import org.jetbrains.kotlin.ir.types.makeNullable
import org.jetbrains.kotlin.ir.util.file
import org.jetbrains.kotlin.ir.util.isInlined
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.name.Name
data class CallableReferenceKey(
val declaration: IrFunction,
val hasDispatchReceiver: Boolean,
val hasExtensionReceiver: Boolean,
val signature: String
)
// TODO: generate $metadata$ property and fill it with corresponding KFunction/KProperty interface
class InteropCallableReferenceLowering(val context: JsIrBackendContext) : BodyLoweringPass {
private val callableToFactoryFunction = context.callableReferencesCache
private val newDeclarations = mutableListOf<IrDeclaration>()
private lateinit var implicitDeclarationFile: IrFile //= context.implicitDeclarationFile
@@ -60,39 +35,7 @@ class InteropCallableReferenceLowering(val context: JsIrBackendContext) : BodyLo
implicitDeclarationFile.declarations += newDeclarations
}
private fun makeCallableKey(declaration: IrFunction, reference: IrCallableReference) =
CallableReferenceKey(
declaration,
reference.dispatchReceiver != null,
reference.extensionReceiver != null,
reference.type.asString()
)
inner class CallableReferenceLowerTransformer : IrElementTransformerVoid() {
// override fun visitFunctionReference(expression: IrFunctionReference): IrExpression {
// expression.transformChildrenVoid(this)
// val declaration = expression.symbol.owner
// if (declaration.origin == JsIrBackendContext.callableClosureOrigin) return expression
// val key = makeCallableKey(declaration, expression)
// val factory = callableToFactoryFunction.getOrPut(key) { lowerKFunctionReference(declaration, expression) }
// return redirectToFunction(expression, factory)
// }
override fun visitPropertyReference(expression: IrPropertyReference): IrExpression {
expression.transformChildrenVoid(this)
val declaration = expression.getter!!.owner
val key = makeCallableKey(declaration, expression)
val factory = callableToFactoryFunction.getOrPut(key) { lowerKPropertyReference(declaration, expression) }
return redirectToFunction(expression, factory)
}
override fun visitLocalDelegatedPropertyReference(expression: IrLocalDelegatedPropertyReference): IrExpression {
expression.transformChildrenVoid(this)
val key = makeCallableKey(expression.getter.owner, expression)
val factory = callableToFactoryFunction.getOrPut(key) { lowerLocalKPropertyReference(expression) }
return redirectToFunction(expression, factory)
}
override fun visitConstructorCall(expression: IrConstructorCall): IrExpression {
expression.transformChildrenVoid(this)
if (expression.origin === CallableReferenceLowering.Companion.CALLABLE_REFERNCE_CREATE) {
@@ -269,469 +212,6 @@ class InteropCallableReferenceLowering(val context: JsIrBackendContext) : BodyLo
return factoryDeclaration
}
private fun redirectToFunction(callable: IrCallableReference, newTarget: IrFunction) =
IrCallImpl(
callable.startOffset, callable.endOffset,
newTarget.symbol.owner.returnType,
newTarget.symbol,
callable.origin
).apply {
copyTypeArgumentsFrom(callable)
var index = 0
callable.dispatchReceiver?.let { putValueArgument(index++, it) }
callable.extensionReceiver?.let { putValueArgument(index++, it) }
for (i in 0 until callable.valueArgumentsCount) {
val arg = callable.getValueArgument(i)
if (arg != null) {
putValueArgument(index++, arg)
}
}
}
private fun createFunctionFactoryName(declaration: IrDeclaration) = createHelperFunctionName(declaration, "KFunctionFactory")
private fun createPropertyFactoryName(declaration: IrDeclaration) = createHelperFunctionName(declaration, "KPropertyFactory")
private fun createClosureInstanceName(declaration: IrDeclaration) = createHelperFunctionName(declaration, "KReferenceClosure")
private fun createHelperFunctionName(declaration: IrDeclaration, suffix: String): String {
val nameBuilder = StringBuilder()
if (declaration is IrConstructor) {
val klass = declaration.parent as IrClass
nameBuilder.append(klass.name.asString())
nameBuilder.append('_')
}
when (declaration) {
is IrFunction -> nameBuilder.append(declaration.name)
is IrProperty -> nameBuilder.append(declaration.name)
is IrVariable -> nameBuilder.append(declaration.name)
else -> TODO("Unexpected declaration type")
}
nameBuilder.append('_')
nameBuilder.append(suffix)
return nameBuilder.toString()
}
private fun getReferenceName(declaration: IrDeclaration) = when (declaration) {
is IrConstructor -> (declaration.parent as IrClass).name.identifier
is IrProperty -> declaration.name.identifier
is IrSimpleFunction -> declaration.name.asString()
is IrVariable -> declaration.name.asString().replace("\$delegate", "")
else -> TODO("Unexpected declaration type")
}
private fun lowerKFunctionReference(declaration: IrFunction, functionReference: IrFunctionReference): IrSimpleFunction {
// transform
// x = Foo::bar ->
// x = Foo_bar_KreferenceGet(c1: closure$C1, c2: closure$C2) : KFunctionN<Foo, T2, ..., TN, TReturn> {
// [ if ($cache$ == null) { ] // in case reference has no closure param cache it
// val x = fun Foo_bar_KreferenceClosure(p0: Foo, p1: T2, p2: T3): TReturn {
// return p0.bar(c1, c2, p1, p2)
// }
// x.callableName = "bar"
// [ $cache$ = x } ]
// return {$cache$|x}
// }
// KFunctionN<Foo, T2, ..., TN, TReturn>, arguments.size = N + 1
val factoryFunction = buildFactoryFunction(declaration, functionReference, createFunctionFactoryName(declaration))
val closureFunction = buildClosureFunction(declaration, factoryFunction, functionReference, functionReference.type.arity)
val additionalDeclarations = generateFactoryBodyWithGuard(factoryFunction) {
val irClosureReference = JsIrBuilder.buildFunctionReference(functionReference.type, closureFunction.symbol)
val irVar = JsIrBuilder.buildVar(irClosureReference.type, factoryFunction, initializer = irClosureReference)
// TODO: fill other fields of callable reference (returnType, parameters, isFinal, etc.)
val irSetName = setDynamicProperty(irVar.symbol, Namer.KCALLABLE_NAME) {
JsIrBuilder.buildString(context.irBuiltIns.stringType, getReferenceName(declaration))
}
Pair(listOf(closureFunction, irVar, irSetName), irVar.symbol)
}
newDeclarations += additionalDeclarations + factoryFunction
return factoryFunction
}
private fun lowerKPropertyReference(getterDeclaration: IrSimpleFunction, propertyReference: IrPropertyReference): IrSimpleFunction {
// transform
// x = Foo::bar ->
// x = Foo_bar_KreferenceGet() : KPropertyN<Foo, PType> {
// if ($cache$ == null) { // very likely property reference is going to be cached
// val x = fun Foo_bar_KreferenceClosure_get(r: Foo): PType {
// return r.<get>()
// }
// x.get = x
// x.callableName = "bar"
// if (mutable) {
// x.set = fun Foo_bar_KreferenceClosure_set(r: Foo, v: PType>) {
// r.<set>(v)
// }
// }
// $cache$ = x
// }
// return $cache$
// }
val arity = propertyReference.type.arity
val factoryName = createPropertyFactoryName(getterDeclaration.correspondingPropertySymbol!!.owner)
val factoryFunction = buildFactoryFunction(propertyReference.getter!!.owner, propertyReference, factoryName)
val getterFunction = propertyReference.getter?.let { buildClosureFunction(it.owner, factoryFunction, propertyReference, arity) }!!
val setterFunction = propertyReference.setter?.let { buildClosureFunction(it.owner, factoryFunction, propertyReference, arity + 1) }
val additionalDeclarations = generateFactoryBodyWithGuard(factoryFunction) {
val statements = mutableListOf<IrStatement>(getterFunction)
val getterFunctionTypeSymbol = context.ir.symbols.functionN(getterFunction.valueParameters.size + 1)
val getterFunctionIrType = IrSimpleTypeImpl(getterFunctionTypeSymbol, false, emptyList(), emptyList())
val irGetReference = JsIrBuilder.buildFunctionReference(getterFunctionIrType, getterFunction.symbol)
val irVar = JsIrBuilder.buildVar(getterFunctionIrType, factoryFunction, initializer = irGetReference)
statements += irVar
statements += setDynamicProperty(irVar.symbol, Namer.KPROPERTY_GET) {
JsIrBuilder.buildGetValue(irVar.symbol)
}
if (setterFunction != null) {
statements += setterFunction
val setterFunctionTypeSymbol = context.ir.symbols.functionN(setterFunction.valueParameters.size + 1)
val setterFunctionIrType = IrSimpleTypeImpl(setterFunctionTypeSymbol, false, emptyList(), emptyList())
val irSetReference = JsIrBuilder.buildFunctionReference(setterFunctionIrType, setterFunction.symbol)
statements += setDynamicProperty(irVar.symbol, Namer.KPROPERTY_SET) { irSetReference }
}
// TODO: fill other fields of callable reference (returnType, parameters, isFinal, etc.)
statements += setDynamicProperty(irVar.symbol, Namer.KCALLABLE_NAME) {
JsIrBuilder.buildString(
context.irBuiltIns.stringType,
getReferenceName(getterDeclaration.correspondingPropertySymbol!!.owner)
)
}
Pair(statements, irVar.symbol)
}
newDeclarations += additionalDeclarations + factoryFunction
return factoryFunction
}
private fun lowerLocalKPropertyReference(propertyReference: IrLocalDelegatedPropertyReference): IrSimpleFunction {
// transform
// ::bar ->
// Foo_bar_KreferenceGet() : KPropertyN<Foo, PType> {
// if ($cache$ == null) {
// val x = fun Foo_bar_KreferenceClosure_get(): PType {
// throw IllegalStateException()
// }
// x.get = x
// x.callableName = "bar"
// $cache$ = x
// }
// return $cache$
// }
val arity = propertyReference.type.arity
val declaration = propertyReference.delegate.owner
val factoryName = createPropertyFactoryName(declaration)
val factoryFunction = buildFactoryFunction(propertyReference.getter.owner, propertyReference, factoryName)
val closureFunction = buildClosureFunction(context.irBuiltIns.throwIseSymbol.owner, factoryFunction, propertyReference, arity)
val additionalDeclarations = generateFactoryBodyWithGuard(factoryFunction) {
val statements = mutableListOf<IrStatement>(closureFunction)
val getterFunctionTypeSymbol = context.ir.symbols.functionN(closureFunction.valueParameters.size + 1)
val getterFunctionIrType = IrSimpleTypeImpl(getterFunctionTypeSymbol, false, emptyList(), emptyList())
val irGetReference = JsIrBuilder.buildFunctionReference(getterFunctionIrType, closureFunction.symbol)
val irVar = JsIrBuilder.buildVar(getterFunctionIrType, factoryFunction, initializer = irGetReference)
val irVarSymbol = irVar.symbol
statements += irVar
statements += setDynamicProperty(irVarSymbol, Namer.KPROPERTY_GET) {
JsIrBuilder.buildGetValue(irVarSymbol)
}
statements += setDynamicProperty(irVarSymbol, Namer.KCALLABLE_NAME) {
JsIrBuilder.buildString(context.irBuiltIns.stringType, getReferenceName(declaration))
}
Pair(statements, irVarSymbol)
}
newDeclarations += additionalDeclarations + factoryFunction
return factoryFunction
}
private fun generateFactoryBodyWithGuard(
factoryFunction: IrSimpleFunction,
builder: () -> Pair<List<IrStatement>, IrValueSymbol>
): List<IrDeclaration> {
val (bodyStatements, varSymbol) = builder()
val statements = mutableListOf<IrStatement>()
val returnValue: IrExpression
val returnStatements: List<IrDeclaration>
if (factoryFunction.valueParameters.isEmpty()) {
// compose cache for 'direct' closure
// if ($cache$ === null) {
// $cache$ = <body>
// }
//
val cacheName = "${factoryFunction.name}_${Namer.KCALLABLE_CACHE_SUFFIX}"
val type = factoryFunction.returnType.makeNullable()
val irNull = { JsIrBuilder.buildNull(context.irBuiltIns.nothingNType) }
val cacheVar = JsIrBuilder.buildVar(type, factoryFunction.parent, cacheName, true, initializer = irNull())
val irCacheValue = { JsIrBuilder.buildGetValue(cacheVar.symbol) }
val irIfCondition = JsIrBuilder.buildCall(context.irBuiltIns.eqeqSymbol).apply {
putValueArgument(0, irCacheValue())
putValueArgument(1, irNull())
}
val irSetCache =
JsIrBuilder.buildSetVariable(cacheVar.symbol, JsIrBuilder.buildGetValue(varSymbol), context.irBuiltIns.unitType)
val thenStatements = mutableListOf<IrStatement>().apply {
addAll(bodyStatements)
add(irSetCache)
}
val irThenBranch = JsIrBuilder.buildBlock(context.irBuiltIns.unitType, thenStatements)
val irIfNode = JsIrBuilder.buildIfElse(context.irBuiltIns.unitType, irIfCondition, irThenBranch)
statements += irIfNode
returnValue = irCacheValue()
returnStatements = listOf(cacheVar)
} else {
statements += bodyStatements
returnValue = JsIrBuilder.buildGetValue(varSymbol)
returnStatements = emptyList()
}
statements += JsIrBuilder.buildReturn(factoryFunction.symbol, returnValue, context.irBuiltIns.nothingType)
factoryFunction.body = JsIrBuilder.buildBlockBody(statements)
return returnStatements
}
private fun IrType.boxIfInlined() = if (isInlined()) {
context.irBuiltIns.anyNType
} else {
this
}
private fun generateSignatureForClosure(
callable: IrFunction,
factory: IrFunction,
closure: IrSimpleFunction,
reference: IrCallableReference,
arity: Int
): List<IrValueParameter> {
val result = mutableListOf<IrValueParameter>()
/*
* class D {
* fun foo(a, b, c) {} <= callable signature
* }
*
* val d = D()
* val reference = d::foo <= binds dispatch receiver
*
* is translated into
*
* function foo_get(d) { <= factory signature(d)
* return function (a, b, c) { <= result signature (a, b, c)
* return d.foo(a, b, c)
* }
* }
*/
var capturedParams = factory.valueParameters.size
val functionSignature = reference.type.arguments.dropLast(1).map { (it as IrTypeProjection).type }.toList()
callable.dispatchReceiverParameter?.let { dispatch ->
if (reference.dispatchReceiver == null) {
result.add(JsIrBuilder.buildValueParameter(dispatch.name, result.size, dispatch.type.boxIfInlined()).also {
it.parent = closure
})
} else {
// do not add dispatch receiver in result signature if it is bound
capturedParams--
}
}
callable.extensionReceiverParameter?.let { ext ->
if (reference.extensionReceiver == null) {
result.add(JsIrBuilder.buildValueParameter(ext.name, result.size, ext.type.boxIfInlined()).also { it.parent = closure })
} else {
// the same as for dispatch
capturedParams--
}
}
for ((index, param) in (result.size until arity).zip(callable.valueParameters.drop(capturedParams))) {
val type = if (index < functionSignature.size) functionSignature[index] else param.type
val paramName = param.name.run { if (!isSpecial) identifier else "p$index" }
result += JsIrBuilder.buildValueParameter(paramName, result.size, type.boxIfInlined()).also { it.parent = closure }
}
if (result.size < arity) {
// That means there are still implicit vararg arguments
val lastParam = result.last()
for (index in result.size until arity) {
val paramName = "${lastParam.name}_$index"
result += JsIrBuilder.buildValueParameter(paramName, result.size, lastParam.type).also { it.parent = closure }
}
}
return result
}
private fun buildFactoryFunction(declaration: IrFunction, reference: IrCallableReference, getterName: String): IrSimpleFunction {
// The `getter` function takes only closure parameters
val receivers = mutableListOf<IrValueParameter>()
reference.dispatchReceiver?.let {
// in case outer `this` for inner constructors
receivers += declaration.dispatchReceiverParameter ?: declaration.valueParameters[0]
}
reference.extensionReceiver?.let {
receivers += declaration.extensionReceiverParameter!!
}
val boundValueParameters = receivers + declaration.valueParameters.filter { it.origin == BOUND_VALUE_PARAMETER }
val factoryDeclaration = JsIrBuilder.buildFunction(getterName, reference.type, implicitDeclarationFile, Visibilities.INTERNAL)
for ((i, p) in boundValueParameters.withIndex()) {
val descriptor = WrappedValueParameterDescriptor()
factoryDeclaration.valueParameters += IrValueParameterImpl(
p.startOffset,
p.endOffset,
p.origin,
IrValueParameterSymbolImpl(descriptor),
p.name,
i,
p.type,
p.varargElementType,
p.isCrossinline,
p.isNoinline
).also {
descriptor.bind(it)
it.parent = factoryDeclaration
}
}
val typeParameters =
if (declaration is IrConstructor) (declaration.parent as IrClass).typeParameters else declaration.typeParameters
for (t in typeParameters) {
val descriptor = WrappedTypeParameterDescriptor()
factoryDeclaration.typeParameters += IrTypeParameterImpl(
t.startOffset,
t.endOffset,
t.origin,
IrTypeParameterSymbolImpl(descriptor),
t.name,
t.index,
t.isReified,
t.variance
).also {
descriptor.bind(it)
it.parent = factoryDeclaration
}
}
return factoryDeclaration
}
private val IrType.arguments get() = (this as? IrSimpleType)?.arguments ?: emptyList()
private val IrType.arity get() = arguments.size - 1
private fun buildClosureFunction(
declaration: IrFunction,
factoryFunction: IrSimpleFunction,
reference: IrCallableReference,
arity: Int
): IrFunction {
val closureName = createClosureInstanceName(declaration)
val returnType = declaration.returnType.boxIfInlined()
val closureFunction =
JsIrBuilder.buildFunction(
closureName,
returnType,
factoryFunction,
Visibilities.LOCAL,
origin = JsIrBackendContext.callableClosureOrigin
)
// the params which are passed to closure
val boundParamSymbols = factoryFunction.valueParameters.map { it.symbol }
val unboundParamDeclarations = generateSignatureForClosure(declaration, factoryFunction, closureFunction, reference, arity)
val unboundParamSymbols = unboundParamDeclarations.map { it.symbol }
closureFunction.valueParameters += unboundParamDeclarations
val callTarget = declaration
val target = callTarget.symbol
val irCall =
if (target is IrConstructorSymbol) IrConstructorCallImpl.fromSymbolOwner(returnType, target) else JsIrBuilder.buildCall(
callTarget.symbol,
type = returnType
)
var cp = 0
var gp = 0
if (callTarget.dispatchReceiverParameter != null) {
val dispatchReceiverDeclaration =
if (reference.dispatchReceiver != null) boundParamSymbols[gp++] else unboundParamSymbols[cp++]
irCall.dispatchReceiver = JsIrBuilder.buildGetValue(dispatchReceiverDeclaration)
}
if (callTarget.extensionReceiverParameter != null) {
val extensionReceiverDeclaration =
if (reference.extensionReceiver != null) boundParamSymbols[gp++] else unboundParamSymbols[cp++]
irCall.extensionReceiver = JsIrBuilder.buildGetValue(extensionReceiverDeclaration)
}
var j = 0
for (i in gp until boundParamSymbols.size) {
irCall.putValueArgument(j++, JsIrBuilder.buildGetValue(boundParamSymbols[i]))
}
for (i in cp until unboundParamSymbols.size) {
val closureParam = unboundParamSymbols[i].owner
val value = JsIrBuilder.buildGetValue(unboundParamSymbols[i])
val parameter = callTarget.valueParameters[j]
val argument =
if (parameter.varargElementType == closureParam.type) {
// fun foo(x: X, vararg y: Y): Z
// val r: (X, Y) -> Z = ::foo
val tailValues = unboundParamSymbols.drop(i)
IrVarargImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, parameter.type, parameter.varargElementType!!, tailValues.map {
JsIrBuilder.buildGetValue(it)
})
} else value
irCall.putValueArgument(j++, argument)
if (j == callTarget.valueParameters.size) break
}
val irClosureReturn = JsIrBuilder.buildReturn(closureFunction.symbol, irCall, context.irBuiltIns.nothingType)
closureFunction.body = JsIrBuilder.buildBlockBody(listOf(irClosureReturn))
return closureFunction
}
private inline fun setDynamicProperty(r: IrValueSymbol, property: String, value: () -> IrExpression): IrStatement {
return IrDynamicOperatorExpressionImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.unitType, IrDynamicOperator.EQ).apply {
@@ -0,0 +1,258 @@
/*
* 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.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
import org.jetbrains.kotlin.backend.common.ir.copyTo
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.builders.declarations.buildValueParameter
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionExpressionImpl
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.name.Name
class PropertyReferenceLowering(private val context: JsIrBackendContext) : BodyLoweringPass {
private val implicitDeclarationFile = context.implicitDeclarationFile
private val referenceBuilderSymbol = context.kpropertyBuilder
private val localDelegateBuilderSymbol = context.klocalDelegateBuilder
private val jsClassSymbol = context.intrinsics.jsClass
private val throwISE = context.throwISEsymbol
private val newDeclarations = mutableListOf<IrDeclaration>()
override fun lower(irBody: IrBody, container: IrDeclaration) {
newDeclarations.clear()
irBody.transformChildrenVoid(PropertyReferenceTransformer())
implicitDeclarationFile.declarations.addAll(newDeclarations)
}
private inner class PropertyReferenceTransformer : IrElementTransformerVoid() {
private fun buildFactoryFunction(reference: IrPropertyReference): IrSimpleFunction {
val property = reference.symbol.owner
val factoryDeclaration = buildFun {
startOffset = reference.startOffset
endOffset = reference.endOffset
returnType = reference.type
name = Name.identifier("${property.name.asString()}\$factory")
}
factoryDeclaration.parent = implicitDeclarationFile
val boundArguments = listOfNotNull(reference.dispatchReceiver, reference.extensionReceiver)
val valueParameters = ArrayList<IrValueParameter>(boundArguments.size)
factoryDeclaration.valueParameters = valueParameters
for ((i, arg) in boundArguments.withIndex()) {
val vp = buildValueParameter {
type = arg.type
index = i
name = Name.identifier("\$b$i")
}
vp.parent = factoryDeclaration
valueParameters.add(vp)
}
// TODO: type parameters
// 0 - name
// 1 - paramCount
// 2 - type
// 3 - getter
// 4 - setter
val arity = (reference.type as IrSimpleType).arguments.size - 1
val irBuilder = context.createIrBuilder(factoryDeclaration.symbol)
factoryDeclaration.body = irBuilder.irBlockBody {
+irReturn(irCall(referenceBuilderSymbol).apply {
putValueArgument(0, reference.nameExpression())
putValueArgument(1, irInt(arity))
putValueArgument(2, reference.getJsTypeConstructor())
putValueArgument(3, buildGetterLambda(factoryDeclaration, reference, valueParameters))
putValueArgument(4, buildSetterLambda(factoryDeclaration, reference, valueParameters))
})
}
newDeclarations.add(factoryDeclaration)
return factoryDeclaration
}
private fun buildGetterLambda(factory: IrSimpleFunction, reference: IrPropertyReference, boundValueParameters: List<IrValueParameter>): IrExpression {
val getter = reference.getter?.owner ?: error("Getter expected")
return buildAccessorLambda(factory, getter, reference, boundValueParameters)
}
private fun buildSetterLambda(factory: IrSimpleFunction, reference: IrPropertyReference, boundValueParameters: List<IrValueParameter>): IrExpression {
val setter = reference.run {
setter?.owner ?: return IrConstImpl.constNull(startOffset, endOffset, context.irBuiltIns.nothingNType)
}
return buildAccessorLambda(factory, setter, reference, boundValueParameters)
}
private fun buildAccessorLambda(factory: IrSimpleFunction, accessor: IrSimpleFunction, reference: IrPropertyReference, boundValueParameters: List<IrValueParameter>): IrExpression {
val superName = when (accessor.symbol) {
reference.getter -> "get"
reference.setter -> "set"
else -> error("Unexpected accessor ${accessor.render()}")
}
val classifier = (reference.type as IrSimpleType).classOrNull ?: error("Simple type expected")
val supperAccessor =
classifier.owner.declarations.filterIsInstance<IrSimpleFunction>().single { it.name.asString() == superName }
val function = buildFun {
startOffset = reference.startOffset
endOffset = reference.endOffset
returnType = supperAccessor.returnType
name = supperAccessor.name
}
function.parent = factory
val unboundValueParameters = supperAccessor.valueParameters.map { it.copyTo(function) }
function.valueParameters = unboundValueParameters
val arity = unboundValueParameters.size
val total = arity + boundValueParameters.size
var b = 0
var u = 0
val irBuilder = context.createIrBuilder(function.symbol)
function.body = irBuilder.irBlockBody {
val irAccessorCall = irCall(accessor.symbol)
if (accessor.dispatchReceiverParameter != null) {
irAccessorCall.dispatchReceiver =
if (reference.dispatchReceiver != null) irGet(boundValueParameters[b++]) else irGet(unboundValueParameters[u++])
}
if (accessor.extensionReceiverParameter != null) {
irAccessorCall.extensionReceiver =
if (reference.extensionReceiver != null) irGet(boundValueParameters[b++]) else irGet(unboundValueParameters[u++])
}
if (u < unboundValueParameters.size) {
irAccessorCall.putValueArgument(0, irGet(unboundValueParameters[u++]))
}
assert(u == arity)
assert((u + b) == total)
+irReturn(irAccessorCall)
}
return IrFunctionExpressionImpl(
reference.startOffset,
reference.endOffset,
context.irBuiltIns.anyType,
function,
IrStatementOrigin.LAMBDA
)
}
private fun IrPropertyReference.nameExpression(): IrExpression {
val propertyName = symbol.owner.name.asString()
return IrConstImpl.string(startOffset, endOffset, context.irBuiltIns.stringType, propertyName)
}
private fun IrExpression.getJsTypeConstructor(): IrExpression {
val irCall = IrCallImpl(startOffset, endOffset, jsClassSymbol.owner.returnType, jsClassSymbol, 1, 0)
irCall.putTypeArgument(0, type)
return irCall
}
override fun visitPropertyReference(expression: IrPropertyReference): IrExpression {
expression.transformChildrenVoid(this)
val factoryFunction = buildFactoryFunction(expression)
assert(expression.valueArgumentsCount == 0)
return IrCallImpl(
expression.startOffset,
expression.endOffset,
expression.type,
factoryFunction.symbol,
expression.typeArgumentsCount,
factoryFunction.valueParameters.size
).apply {
for (ti in 0 until typeArgumentsCount) {
putTypeArgument(ti, expression.getTypeArgument(ti))
}
var vi = 0
expression.dispatchReceiver?.let { putValueArgument(vi++, it) }
expression.extensionReceiver?.let { putValueArgument(vi++, it) }
}
}
override fun visitLocalDelegatedPropertyReference(expression: IrLocalDelegatedPropertyReference): IrExpression {
expression.transformChildrenVoid(this)
val builderCall = expression.run {
IrCallImpl(startOffset, endOffset, type, localDelegateBuilderSymbol, typeArgumentsCount, 4)
}
val localName = expression.symbol.owner.name.asString()
val isMutable = expression.setter != null
// 0 - name
// 1 - type
// 2 - isMutable
// 3 - lambda
expression.run {
builderCall.putValueArgument(0, IrConstImpl.string(startOffset, endOffset, context.irBuiltIns.stringType, localName))
builderCall.putValueArgument(1, expression.getJsTypeConstructor())
builderCall.putValueArgument(2, IrConstImpl.boolean(startOffset, endOffset, context.irBuiltIns.booleanType, isMutable))
builderCall.putValueArgument(3, buildLocalDelegateLambda(expression))
}
return builderCall
}
private fun buildLocalDelegateLambda(expression: IrLocalDelegatedPropertyReference): IrExpression {
val delegatedVar = expression.delegate.owner
val function = buildFun {
startOffset = expression.startOffset
endOffset = expression.endOffset
returnType = context.irBuiltIns.nothingType
name = Name.identifier("${delegatedVar.name}\$stub")
}
function.parent = delegatedVar.parent
function.body = with(context.createIrBuilder(function.symbol)) {
irBlockBody {
+irReturn(irCall(throwISE))
}
}
return expression.run {
IrFunctionExpressionImpl(startOffset, endOffset, context.irBuiltIns.anyType, function, IrStatementOrigin.LAMBDA)
}
}
}
}
@@ -204,14 +204,14 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
}
private fun generateSuperClasses(): JsPropertyInitializer {
val functionTypeOrSubtype = irClass.defaultType.isFunctionTypeOrSubtype()
return JsPropertyInitializer(
JsNameRef(Namer.METADATA_INTERFACES),
JsArrayLiteral(
irClass.superTypes.mapNotNull {
val symbol = it.classifierOrFail as IrClassSymbol
val isFunctionType = it.run { isFunctionOrKFunction() || isSuspendFunctionOrKFunction() }
// TODO: make sure that there is a test which breaks when isExternal is used here instead of isEffectivelyExternal
if (symbol.isInterface && !functionTypeOrSubtype && !symbol.isEffectivelyExternal) {
if (symbol.isInterface && !isFunctionType && !symbol.isEffectivelyExternal) {
JsNameRef(context.getNameForClass(symbol.owner))
} else null
}