From 8654dbdc6e820130fe94026cc7744cce572d8666 Mon Sep 17 00:00:00 2001 From: Roman Artemev Date: Thu, 27 Feb 2020 19:58:47 +0300 Subject: [PATCH] [JS IR] Refactor property references - implement separate lowering - fix issues with type checks - clean code up --- .../ir/backend/js/JsIrBackendContext.kt | 6 +- .../kotlin/ir/backend/js/JsLoweringPhases.kt | 7 + .../lower/InteropCallableReferenceLowering.kt | 520 ------------------ .../js/lower/PropertyReferenceLowering.kt | 258 +++++++++ .../transformers/irToJs/JsClassGenerator.kt | 4 +- 5 files changed, 270 insertions(+), 525 deletions(-) create mode 100644 compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PropertyReferenceLowering.kt diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt index da46bfa8074..b2bc01dab7b 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt @@ -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() - 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().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> { val primitiveIrSymbols = irBuiltIns.primitiveIrTypes.map { it.classifierOrFail as IrClassSymbol } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt index 96943188c2c..6906e3836cc 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt @@ -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( enumUsageLoweringPhase, enumEntryRemovalLoweringPhase, suspendFunctionsLoweringPhase, + propertyReferenceLoweringPhase, interopCallableReferenceLoweringPhase, returnableBlockLoweringPhase, forLoopsLoweringPhase, diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/InteropCallableReferenceLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/InteropCallableReferenceLowering.kt index 8b39b10fd67..22b65058346 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/InteropCallableReferenceLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/InteropCallableReferenceLowering.kt @@ -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() 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 { - // [ 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, 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 { - // if ($cache$ == null) { // very likely property reference is going to be cached - // val x = fun Foo_bar_KreferenceClosure_get(r: Foo): PType { - // return r.() - // } - // x.get = x - // x.callableName = "bar" - // if (mutable) { - // x.set = fun Foo_bar_KreferenceClosure_set(r: Foo, v: PType>) { - // r.(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(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 { - // 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(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, IrValueSymbol> - ): List { - - val (bodyStatements, varSymbol) = builder() - val statements = mutableListOf() - val returnValue: IrExpression - val returnStatements: List - if (factoryFunction.valueParameters.isEmpty()) { - // compose cache for 'direct' closure - // if ($cache$ === null) { - // $cache$ = - // } - // - 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().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 { - val result = mutableListOf() - - /* - * 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() - - 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 { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PropertyReferenceLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PropertyReferenceLowering.kt new file mode 100644 index 00000000000..06828aa637e --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PropertyReferenceLowering.kt @@ -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() + + 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(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): IrExpression { + val getter = reference.getter?.owner ?: error("Getter expected") + return buildAccessorLambda(factory, getter, reference, boundValueParameters) + } + + private fun buildSetterLambda(factory: IrSimpleFunction, reference: IrPropertyReference, boundValueParameters: List): 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): 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().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) + } + } + } +} \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt index 00a551317ba..93a1cc5d932 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt @@ -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 }