[JS IR BE] Refactored js("...") function
- support object expression - do not wrap in function in statement-level position - support implicit return - code clean up
This commit is contained in:
+21
-157
@@ -113,7 +113,7 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
|
|||||||
val fromPrimary = context.currentFunction is IrConstructor
|
val fromPrimary = context.currentFunction is IrConstructor
|
||||||
val thisRef =
|
val thisRef =
|
||||||
if (fromPrimary) JsThisRef() else context.getNameForValueDeclaration(context.currentFunction!!.valueParameters.last()).makeRef()
|
if (fromPrimary) JsThisRef() else context.getNameForValueDeclaration(context.currentFunction!!.valueParameters.last()).makeRef()
|
||||||
val arguments = translateCallArguments(expression, context)
|
val arguments = translateCallArguments(expression, context, this)
|
||||||
|
|
||||||
val constructor = expression.symbol.owner
|
val constructor = expression.symbol.owner
|
||||||
if (constructor.parentAsClass.isInline) {
|
if (constructor.parentAsClass.isInline) {
|
||||||
@@ -128,13 +128,7 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
|
|||||||
|
|
||||||
override fun visitConstructorCall(expression: IrConstructorCall, context: JsGenerationContext): JsExpression {
|
override fun visitConstructorCall(expression: IrConstructorCall, context: JsGenerationContext): JsExpression {
|
||||||
val function = expression.symbol.owner
|
val function = expression.symbol.owner
|
||||||
val symbol = expression.symbol
|
val arguments = translateCallArguments(expression, context, this)
|
||||||
|
|
||||||
context.staticContext.intrinsics[symbol]?.let {
|
|
||||||
return it(expression, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
val arguments = translateCallArguments(expression, context)
|
|
||||||
val klass = function.parentAsClass
|
val klass = function.parentAsClass
|
||||||
return if (klass.isInline) {
|
return if (klass.isInline) {
|
||||||
assert(function.isPrimary) {
|
assert(function.isPrimary) {
|
||||||
@@ -155,153 +149,32 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visitCall(expression: IrCall, context: JsGenerationContext): JsExpression {
|
override fun visitCall(expression: IrCall, context: JsGenerationContext): JsExpression {
|
||||||
val function = expression.symbol.owner.realOverrideTarget
|
if (context.checkIfJsCode(expression.symbol)) {
|
||||||
val symbol = function.symbol
|
val statements = translateJsCodeIntoStatementList(expression.getValueArgument(0) ?: error("JsCode is expected"))
|
||||||
|
if (statements.isEmpty()) return JsPrefixOperation(JsUnaryOperator.VOID, JsIntLiteral(3)) // TODO: report warning or even error
|
||||||
|
|
||||||
context.staticContext.intrinsics[symbol]?.let {
|
val lastStatement = statements.last()
|
||||||
return it(expression, context)
|
if (statements.size == 1) {
|
||||||
}
|
if (lastStatement is JsExpressionStatement) return lastStatement.expression
|
||||||
|
|
||||||
val jsDispatchReceiver = expression.dispatchReceiver?.accept(this, context)
|
|
||||||
val jsExtensionReceiver = expression.extensionReceiver?.accept(this, context)
|
|
||||||
val arguments = translateCallArguments(expression, context)
|
|
||||||
|
|
||||||
// Transform external property accessor call
|
|
||||||
// @JsName-annotated external property accessors are translated as function calls
|
|
||||||
if (function is IrSimpleFunction && function.getJsName() == null) {
|
|
||||||
val property = function.correspondingPropertySymbol?.owner
|
|
||||||
if (property != null && property.isEffectivelyExternal()) {
|
|
||||||
val nameRef = JsNameRef(context.getNameForProperty(property), jsDispatchReceiver)
|
|
||||||
return when (function) {
|
|
||||||
property.getter -> nameRef
|
|
||||||
property.setter -> jsAssignment(nameRef, arguments.single())
|
|
||||||
else -> error("Function must be an accessor of corresponding property")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNativeInvoke(expression)) {
|
|
||||||
return JsInvocation(jsDispatchReceiver!!, arguments)
|
|
||||||
}
|
|
||||||
|
|
||||||
expression.superQualifierSymbol?.let { superQualifier ->
|
|
||||||
require(function is IrSimpleFunction)
|
|
||||||
|
|
||||||
val (target, klass) = if (superQualifier.owner.isInterface) {
|
|
||||||
val impl = function.resolveFakeOverride()!!
|
|
||||||
Pair(impl, impl.parentAsClass)
|
|
||||||
} else {
|
|
||||||
Pair(function, superQualifier.owner)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val qualifierName = context.getNameForClass(klass).makeRef()
|
val newStatements = statements.toMutableList()
|
||||||
val targetName = context.getNameForMemberFunction(target)
|
|
||||||
val qPrototype = JsNameRef(targetName, prototypeOf(qualifierName))
|
|
||||||
val callRef = JsNameRef(Namer.CALL_FUNCTION, qPrototype)
|
|
||||||
return JsInvocation(callRef, jsDispatchReceiver?.let { receiver -> listOf(receiver) + arguments } ?: arguments)
|
|
||||||
}
|
|
||||||
|
|
||||||
val varargParameterIndex = function.valueParameters.indexOfFirst { it.varargElementType != null }
|
when (lastStatement) {
|
||||||
val isExternalVararg = function.isEffectivelyExternal() && varargParameterIndex != -1
|
is JsReturn -> {
|
||||||
|
|
||||||
|
|
||||||
if (function is IrConstructor) {
|
|
||||||
// Inline class primary constructor takes a single value of to
|
|
||||||
// initialize underlying property.
|
|
||||||
// TODO: Support initialization block
|
|
||||||
val klass = function.parentAsClass
|
|
||||||
return if (klass.isInline) {
|
|
||||||
assert(function.isPrimary) {
|
|
||||||
"Inline class secondary constructors must be lowered into static methods"
|
|
||||||
}
|
}
|
||||||
// Argument value constructs unboxed inline class instance
|
is JsExpressionStatement -> {
|
||||||
arguments.single()
|
newStatements[statements.lastIndex] = JsReturn(lastStatement.expression)
|
||||||
} else {
|
|
||||||
val ref = when {
|
|
||||||
klass.isEffectivelyExternal() ->
|
|
||||||
context.getRefForExternalClass(klass)
|
|
||||||
|
|
||||||
else ->
|
|
||||||
context.getNameForClass(klass).makeRef()
|
|
||||||
}
|
}
|
||||||
JsNew(ref, arguments)
|
// TODO: report warning or even error
|
||||||
|
else -> newStatements += JsReturn(JsPrefixOperation(JsUnaryOperator.VOID, JsIntLiteral(3)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val syntheticFunction = JsFunction(emptyScope, JsBlock(newStatements), "")
|
||||||
|
return JsInvocation(syntheticFunction)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
return translateCall(expression, context, this)
|
||||||
require(function is IrSimpleFunction)
|
|
||||||
|
|
||||||
val symbolName = when (jsDispatchReceiver) {
|
|
||||||
null -> context.getNameForStaticFunction(function)
|
|
||||||
else -> context.getNameForMemberFunction(function)
|
|
||||||
}
|
|
||||||
|
|
||||||
val ref = when (jsDispatchReceiver) {
|
|
||||||
null -> JsNameRef(symbolName)
|
|
||||||
else -> JsNameRef(symbolName, jsDispatchReceiver)
|
|
||||||
}
|
|
||||||
|
|
||||||
return if (isExternalVararg) {
|
|
||||||
|
|
||||||
// External vararg arguments should be represented in JS as multiple "plain" arguments (opposed to arrays in Kotlin)
|
|
||||||
// We are using `Function.prototype.apply` function to pass all arguments as a single array.
|
|
||||||
// For this purpose are concatenating non-vararg arguments with vararg.
|
|
||||||
// TODO: Don't use `Function.prototype.apply` when number of arguments is known at compile time (e.g. there are no spread operators)
|
|
||||||
val arrayConcat = JsNameRef("concat", JsArrayLiteral())
|
|
||||||
val arraySliceCall = JsNameRef("call", JsNameRef("slice", JsArrayLiteral()))
|
|
||||||
|
|
||||||
val argumentsAsSingleArray = JsInvocation(
|
|
||||||
arrayConcat,
|
|
||||||
listOfNotNull(jsExtensionReceiver) + arguments.mapIndexed { index, argument ->
|
|
||||||
when (index) {
|
|
||||||
|
|
||||||
// Call `Array.prototype.slice` on vararg arguments in order to convert array-like objects into proper arrays
|
|
||||||
// TODO: Optimize for proper arrays
|
|
||||||
varargParameterIndex -> JsInvocation(arraySliceCall, argument)
|
|
||||||
|
|
||||||
// TODO: Don't wrap non-array-like arguments with array literal
|
|
||||||
// TODO: Wrap adjacent non-vararg arguments in a single array literal
|
|
||||||
else -> JsArrayLiteral(listOf(argument))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (jsDispatchReceiver != null) {
|
|
||||||
// TODO: Do not create IIFE when receiver expression is simple or has no side effects
|
|
||||||
// TODO: Do not create IIFE at all? (Currently there is no reliable way to create temporary variable in current scope)
|
|
||||||
val receiverName = JsName("\$externalVarargReceiverTmp")
|
|
||||||
val receiverRef = receiverName.makeRef()
|
|
||||||
JsInvocation(
|
|
||||||
// Create scope for temporary variable holding dispatch receiver
|
|
||||||
// It is used both during method reference and passing `this` value to `apply` function.
|
|
||||||
JsNameRef(
|
|
||||||
"call",
|
|
||||||
JsFunction(
|
|
||||||
emptyScope,
|
|
||||||
JsBlock(
|
|
||||||
JsVars(JsVars.JsVar(receiverName, jsDispatchReceiver)),
|
|
||||||
JsReturn(
|
|
||||||
JsInvocation(
|
|
||||||
JsNameRef("apply", JsNameRef(symbolName, receiverRef)),
|
|
||||||
listOf(
|
|
||||||
receiverRef,
|
|
||||||
argumentsAsSingleArray
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
"VarargIIFE"
|
|
||||||
)),
|
|
||||||
JsThisRef()
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
JsInvocation(
|
|
||||||
JsNameRef("apply", JsNameRef(symbolName)),
|
|
||||||
listOf(JsNullLiteral(), argumentsAsSingleArray)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
JsInvocation(ref, listOfNotNull(jsExtensionReceiver) + arguments)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitWhen(expression: IrWhen, context: JsGenerationContext): JsExpression {
|
override fun visitWhen(expression: IrWhen, context: JsGenerationContext): JsExpression {
|
||||||
@@ -320,7 +193,7 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
|
|||||||
override fun visitTypeOperator(expression: IrTypeOperatorCall, data: JsGenerationContext): JsExpression {
|
override fun visitTypeOperator(expression: IrTypeOperatorCall, data: JsGenerationContext): JsExpression {
|
||||||
return when (expression.operator) {
|
return when (expression.operator) {
|
||||||
IrTypeOperator.IMPLICIT_CAST -> expression.argument.accept(this, data)
|
IrTypeOperator.IMPLICIT_CAST -> expression.argument.accept(this, data)
|
||||||
else -> throw IllegalStateException("All type operator calls except IMPLICIT_CAST should be lowered at this point")
|
else -> error("All type operator calls except IMPLICIT_CAST should be lowered at this point")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,13 +269,4 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
|
|||||||
expression.left.accept(this, data),
|
expression.left.accept(this, data),
|
||||||
expression.right.accept(this, data)
|
expression.right.accept(this, data)
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun isNativeInvoke(call: IrCall): Boolean {
|
|
||||||
val simpleFunction = call.symbol.owner as? IrSimpleFunction ?: return false
|
|
||||||
val receiverType = simpleFunction.dispatchReceiverParameter?.type ?: return false
|
|
||||||
|
|
||||||
if (simpleFunction.isSuspend) return false
|
|
||||||
|
|
||||||
return simpleFunction.name == OperatorNameConventions.INVOKE && receiverType.isFunctionTypeOrSubtype()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+13
@@ -71,6 +71,19 @@ class IrElementToJsStatementTransformer : BaseIrElementToJsNodeTransformer<JsSta
|
|||||||
return expression.accept(IrElementToJsExpressionTransformer(), context).makeStmt()
|
return expression.accept(IrElementToJsExpressionTransformer(), context).makeStmt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun visitCall(expression: IrCall, data: JsGenerationContext): JsStatement {
|
||||||
|
if (data.checkIfJsCode(expression.symbol)) {
|
||||||
|
val statements = translateJsCodeIntoStatementList(expression.getValueArgument(0) ?: error("JsCode is expected"))
|
||||||
|
return when (statements.size) {
|
||||||
|
0 -> JsEmpty
|
||||||
|
1 -> statements.single()
|
||||||
|
// TODO: use transparent block (e.g. JsCompositeBlock)
|
||||||
|
else -> JsBlock(statements)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return translateCall(expression, data, IrElementToJsExpressionTransformer()).makeStmt()
|
||||||
|
}
|
||||||
|
|
||||||
override fun visitInstanceInitializerCall(expression: IrInstanceInitializerCall, context: JsGenerationContext): JsStatement {
|
override fun visitInstanceInitializerCall(expression: IrInstanceInitializerCall, context: JsGenerationContext): JsStatement {
|
||||||
|
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
|
|||||||
+17
-20
@@ -8,13 +8,14 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
|||||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext
|
import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
|
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
|
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||||
import org.jetbrains.kotlin.ir.declarations.*
|
import org.jetbrains.kotlin.ir.declarations.IrConstructor
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
|
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrFunctionReference
|
import org.jetbrains.kotlin.ir.expressions.IrFunctionReference
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
||||||
import org.jetbrains.kotlin.ir.types.classifierOrFail
|
import org.jetbrains.kotlin.ir.types.classifierOrFail
|
||||||
import org.jetbrains.kotlin.ir.util.getInlineClassBackingField
|
import org.jetbrains.kotlin.ir.util.getInlineClassBackingField
|
||||||
@@ -22,7 +23,7 @@ import org.jetbrains.kotlin.ir.util.getInlinedClass
|
|||||||
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
|
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
|
||||||
import org.jetbrains.kotlin.js.backend.ast.*
|
import org.jetbrains.kotlin.js.backend.ast.*
|
||||||
|
|
||||||
typealias IrCallTransformer = (IrFunctionAccessExpression, context: JsGenerationContext) -> JsExpression
|
typealias IrCallTransformer = (IrCall, context: JsGenerationContext) -> JsExpression
|
||||||
|
|
||||||
class JsIntrinsicTransformers(backendContext: JsIrBackendContext) {
|
class JsIntrinsicTransformers(backendContext: JsIrBackendContext) {
|
||||||
private val transformers: Map<IrSymbol, IrCallTransformer>
|
private val transformers: Map<IrSymbol, IrCallTransformer>
|
||||||
@@ -100,15 +101,7 @@ class JsIntrinsicTransformers(backendContext: JsIrBackendContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addIfNotNull(intrinsics.jsCode) { call, context ->
|
addIfNotNull(intrinsics.jsCode) { _, _ -> error("Should not be called") }
|
||||||
val jsCode = translateJsCode(call as IrCall)
|
|
||||||
|
|
||||||
when (jsCode) {
|
|
||||||
is JsExpression -> jsCode
|
|
||||||
// TODO don't generate function for this case
|
|
||||||
else -> JsInvocation(JsFunction(emptyScope, jsCode as? JsBlock ?: JsBlock(jsCode as JsStatement), ""))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
add(intrinsics.jsGetContinuation) { _, context: JsGenerationContext ->
|
add(intrinsics.jsGetContinuation) { _, context: JsGenerationContext ->
|
||||||
context.continuation
|
context.continuation
|
||||||
@@ -181,7 +174,7 @@ class JsIntrinsicTransformers(backendContext: JsIrBackendContext) {
|
|||||||
add(intrinsics.jsBind) { call, context: JsGenerationContext ->
|
add(intrinsics.jsBind) { call, context: JsGenerationContext ->
|
||||||
val receiver = call.getValueArgument(0)!!
|
val receiver = call.getValueArgument(0)!!
|
||||||
val reference = call.getValueArgument(1) as IrFunctionReference
|
val reference = call.getValueArgument(1) as IrFunctionReference
|
||||||
val superClass = (call as IrCall).superQualifierSymbol!!
|
val superClass = call.superQualifierSymbol!!
|
||||||
|
|
||||||
val jsReceiver = receiver.accept(IrElementToJsExpressionTransformer(), context)
|
val jsReceiver = receiver.accept(IrElementToJsExpressionTransformer(), context)
|
||||||
val functionName = context.getNameForMemberFunction(reference.symbol.owner as IrSimpleFunction)
|
val functionName = context.getNameForMemberFunction(reference.symbol.owner as IrSimpleFunction)
|
||||||
@@ -201,11 +194,15 @@ class JsIntrinsicTransformers(backendContext: JsIrBackendContext) {
|
|||||||
operator fun get(symbol: IrSymbol): IrCallTransformer? = transformers[symbol]
|
operator fun get(symbol: IrSymbol): IrCallTransformer? = transformers[symbol]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun translateCallArguments(expression: IrCall, context: JsGenerationContext): List<JsExpression> {
|
||||||
|
return translateCallArguments(expression, context, IrElementToJsExpressionTransformer())
|
||||||
|
}
|
||||||
|
|
||||||
private fun MutableMap<IrSymbol, IrCallTransformer>.add(functionSymbol: IrSymbol, t: IrCallTransformer) {
|
private fun MutableMap<IrSymbol, IrCallTransformer>.add(functionSymbol: IrSymbol, t: IrCallTransformer) {
|
||||||
put(functionSymbol, t)
|
put(functionSymbol, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MutableMap<IrSymbol, IrCallTransformer>.add(function: IrFunction, t: IrCallTransformer) {
|
private fun MutableMap<IrSymbol, IrCallTransformer>.add(function: IrSimpleFunction, t: IrCallTransformer) {
|
||||||
put(function.symbol, t)
|
put(function.symbol, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,20 +211,20 @@ private fun MutableMap<IrSymbol, IrCallTransformer>.addIfNotNull(symbol: IrSymbo
|
|||||||
put(symbol, t)
|
put(symbol, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MutableMap<IrSymbol, IrCallTransformer>.binOp(function: IrFunctionSymbol, op: JsBinaryOperator) {
|
private fun MutableMap<IrSymbol, IrCallTransformer>.binOp(function: IrSimpleFunctionSymbol, op: JsBinaryOperator) {
|
||||||
withTranslatedArgs(function) { JsBinaryOperation(op, it[0], it[1]) }
|
withTranslatedArgs(function) { JsBinaryOperation(op, it[0], it[1]) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MutableMap<IrSymbol, IrCallTransformer>.prefixOp(function: IrFunctionSymbol, op: JsUnaryOperator) {
|
private fun MutableMap<IrSymbol, IrCallTransformer>.prefixOp(function: IrSimpleFunctionSymbol, op: JsUnaryOperator) {
|
||||||
withTranslatedArgs(function) { JsPrefixOperation(op, it[0]) }
|
withTranslatedArgs(function) { JsPrefixOperation(op, it[0]) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MutableMap<IrSymbol, IrCallTransformer>.postfixOp(function: IrFunctionSymbol, op: JsUnaryOperator) {
|
private fun MutableMap<IrSymbol, IrCallTransformer>.postfixOp(function: IrSimpleFunctionSymbol, op: JsUnaryOperator) {
|
||||||
withTranslatedArgs(function) { JsPostfixOperation(op, it[0]) }
|
withTranslatedArgs(function) { JsPostfixOperation(op, it[0]) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun MutableMap<IrSymbol, IrCallTransformer>.withTranslatedArgs(
|
private inline fun MutableMap<IrSymbol, IrCallTransformer>.withTranslatedArgs(
|
||||||
function: IrFunctionSymbol,
|
function: IrSimpleFunctionSymbol,
|
||||||
crossinline t: (List<JsExpression>) -> JsExpression
|
crossinline t: (List<JsExpression>) -> JsExpression
|
||||||
) {
|
) {
|
||||||
put(function) { call, context -> t(translateCallArguments(call, context)) }
|
put(function) { call, context -> t(translateCallArguments(call, context)) }
|
||||||
|
|||||||
+144
-11
@@ -7,16 +7,13 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.backend.common.descriptors.isSuspend
|
import org.jetbrains.kotlin.backend.common.descriptors.isSuspend
|
||||||
import org.jetbrains.kotlin.backend.common.ir.isElseBranch
|
import org.jetbrains.kotlin.backend.common.ir.isElseBranch
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext
|
import org.jetbrains.kotlin.ir.backend.js.utils.*
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
|
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
|
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
|
import org.jetbrains.kotlin.ir.util.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrWhen
|
|
||||||
import org.jetbrains.kotlin.ir.util.isExternalOrInheritedFromExternal
|
|
||||||
import org.jetbrains.kotlin.js.backend.ast.*
|
import org.jetbrains.kotlin.js.backend.ast.*
|
||||||
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||||
|
|
||||||
fun jsVar(name: JsName, initializer: IrExpression?, context: JsGenerationContext): JsVars {
|
fun jsVar(name: JsName, initializer: IrExpression?, context: JsGenerationContext): JsVars {
|
||||||
val jsInitializer = initializer?.accept(IrElementToJsExpressionTransformer(), context)
|
val jsInitializer = initializer?.accept(IrElementToJsExpressionTransformer(), context)
|
||||||
@@ -64,8 +61,145 @@ fun translateFunction(declaration: IrFunction, name: JsName?, context: JsGenerat
|
|||||||
return function
|
return function
|
||||||
}
|
}
|
||||||
|
|
||||||
fun translateCallArguments(expression: IrMemberAccessExpression, context: JsGenerationContext): List<JsExpression> {
|
private fun isNativeInvoke(call: IrCall): Boolean {
|
||||||
val transformer = IrElementToJsExpressionTransformer()
|
val simpleFunction = call.symbol.owner as? IrSimpleFunction ?: return false
|
||||||
|
val receiverType = simpleFunction.dispatchReceiverParameter?.type ?: return false
|
||||||
|
|
||||||
|
if (simpleFunction.isSuspend) return false
|
||||||
|
|
||||||
|
return simpleFunction.name == OperatorNameConventions.INVOKE && receiverType.isFunctionTypeOrSubtype()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun translateCall(
|
||||||
|
expression: IrCall,
|
||||||
|
context: JsGenerationContext,
|
||||||
|
transformer: IrElementToJsExpressionTransformer
|
||||||
|
): JsExpression {
|
||||||
|
val function = expression.symbol.owner.realOverrideTarget
|
||||||
|
require(function is IrSimpleFunction) { "Only IrSimpleFunction could be called via IrCall" } // TODO: fix it in IrCall
|
||||||
|
|
||||||
|
val symbol = function.symbol
|
||||||
|
|
||||||
|
context.staticContext.intrinsics[symbol]?.let {
|
||||||
|
return it(expression, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
val jsDispatchReceiver = expression.dispatchReceiver?.accept(transformer, context)
|
||||||
|
val jsExtensionReceiver = expression.extensionReceiver?.accept(transformer, context)
|
||||||
|
val arguments = translateCallArguments(expression, context, transformer)
|
||||||
|
|
||||||
|
// Transform external property accessor call
|
||||||
|
// @JsName-annotated external property accessors are translated as function calls
|
||||||
|
if (function.getJsName() == null) {
|
||||||
|
val property = function.correspondingPropertySymbol?.owner
|
||||||
|
if (property != null && property.isEffectivelyExternal()) {
|
||||||
|
val nameRef = JsNameRef(context.getNameForProperty(property), jsDispatchReceiver)
|
||||||
|
return when (function) {
|
||||||
|
property.getter -> nameRef
|
||||||
|
property.setter -> jsAssignment(nameRef, arguments.single())
|
||||||
|
else -> error("Function must be an accessor of corresponding property")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNativeInvoke(expression)) {
|
||||||
|
return JsInvocation(jsDispatchReceiver!!, arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
expression.superQualifierSymbol?.let { superQualifier ->
|
||||||
|
val (target, klass) = if (superQualifier.owner.isInterface) {
|
||||||
|
val impl = function.resolveFakeOverride()!!
|
||||||
|
Pair(impl, impl.parentAsClass)
|
||||||
|
} else {
|
||||||
|
Pair(function, superQualifier.owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
val qualifierName = context.getNameForClass(klass).makeRef()
|
||||||
|
val targetName = context.getNameForMemberFunction(target)
|
||||||
|
val qPrototype = JsNameRef(targetName, prototypeOf(qualifierName))
|
||||||
|
val callRef = JsNameRef(Namer.CALL_FUNCTION, qPrototype)
|
||||||
|
return JsInvocation(callRef, jsDispatchReceiver?.let { receiver -> listOf(receiver) + arguments } ?: arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
val varargParameterIndex = function.valueParameters.indexOfFirst { it.varargElementType != null }
|
||||||
|
val isExternalVararg = function.isEffectivelyExternal() && varargParameterIndex != -1
|
||||||
|
|
||||||
|
val symbolName = when (jsDispatchReceiver) {
|
||||||
|
null -> context.getNameForStaticFunction(function)
|
||||||
|
else -> context.getNameForMemberFunction(function)
|
||||||
|
}
|
||||||
|
|
||||||
|
val ref = when (jsDispatchReceiver) {
|
||||||
|
null -> JsNameRef(symbolName)
|
||||||
|
else -> JsNameRef(symbolName, jsDispatchReceiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (isExternalVararg) {
|
||||||
|
|
||||||
|
// External vararg arguments should be represented in JS as multiple "plain" arguments (opposed to arrays in Kotlin)
|
||||||
|
// We are using `Function.prototype.apply` function to pass all arguments as a single array.
|
||||||
|
// For this purpose are concatenating non-vararg arguments with vararg.
|
||||||
|
// TODO: Don't use `Function.prototype.apply` when number of arguments is known at compile time (e.g. there are no spread operators)
|
||||||
|
val arrayConcat = JsNameRef("concat", JsArrayLiteral())
|
||||||
|
val arraySliceCall = JsNameRef("call", JsNameRef("slice", JsArrayLiteral()))
|
||||||
|
|
||||||
|
val argumentsAsSingleArray = JsInvocation(
|
||||||
|
arrayConcat,
|
||||||
|
listOfNotNull(jsExtensionReceiver) + arguments.mapIndexed { index, argument ->
|
||||||
|
when (index) {
|
||||||
|
|
||||||
|
// Call `Array.prototype.slice` on vararg arguments in order to convert array-like objects into proper arrays
|
||||||
|
// TODO: Optimize for proper arrays
|
||||||
|
varargParameterIndex -> JsInvocation(arraySliceCall, argument)
|
||||||
|
|
||||||
|
// TODO: Don't wrap non-array-like arguments with array literal
|
||||||
|
// TODO: Wrap adjacent non-vararg arguments in a single array literal
|
||||||
|
else -> JsArrayLiteral(listOf(argument))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (jsDispatchReceiver != null) {
|
||||||
|
// TODO: Do not create IIFE when receiver expression is simple or has no side effects
|
||||||
|
// TODO: Do not create IIFE at all? (Currently there is no reliable way to create temporary variable in current scope)
|
||||||
|
val receiverName = JsName("\$externalVarargReceiverTmp")
|
||||||
|
val receiverRef = receiverName.makeRef()
|
||||||
|
JsInvocation(
|
||||||
|
// Create scope for temporary variable holding dispatch receiver
|
||||||
|
// It is used both during method reference and passing `this` value to `apply` function.
|
||||||
|
JsNameRef(
|
||||||
|
"call",
|
||||||
|
JsFunction(
|
||||||
|
emptyScope,
|
||||||
|
JsBlock(
|
||||||
|
JsVars(JsVars.JsVar(receiverName, jsDispatchReceiver)),
|
||||||
|
JsReturn(
|
||||||
|
JsInvocation(
|
||||||
|
JsNameRef("apply", JsNameRef(symbolName, receiverRef)),
|
||||||
|
listOf(
|
||||||
|
receiverRef,
|
||||||
|
argumentsAsSingleArray
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"VarargIIFE"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
JsThisRef()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
JsInvocation(
|
||||||
|
JsNameRef("apply", JsNameRef(symbolName)),
|
||||||
|
listOf(JsNullLiteral(), argumentsAsSingleArray)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
JsInvocation(ref, listOfNotNull(jsExtensionReceiver) + arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun translateCallArguments(expression: IrMemberAccessExpression, context: JsGenerationContext, transformer: IrElementToJsExpressionTransformer): List<JsExpression> {
|
||||||
val size = expression.valueArgumentsCount
|
val size = expression.valueArgumentsCount
|
||||||
|
|
||||||
val arguments = (0 until size).mapTo(ArrayList(size)) { index ->
|
val arguments = (0 until size).mapTo(ArrayList(size)) { index ->
|
||||||
@@ -90,7 +224,6 @@ fun defineProperty(receiver: JsExpression, name: String, value: () -> JsExpressi
|
|||||||
return JsInvocation(objectDefineProperty, receiver, JsStringLiteral(name), value())
|
return JsInvocation(objectDefineProperty, receiver, JsStringLiteral(name), value())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun defineProperty(receiver: JsExpression, name: String, getter: JsExpression?, setter: JsExpression? = null) =
|
fun defineProperty(receiver: JsExpression, name: String, getter: JsExpression?, setter: JsExpression? = null) =
|
||||||
defineProperty(receiver, name) {
|
defineProperty(receiver, name) {
|
||||||
val literal = JsObjectLiteral(true)
|
val literal = JsObjectLiteral(true)
|
||||||
|
|||||||
+14
-18
@@ -6,8 +6,8 @@
|
|||||||
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
||||||
|
|
||||||
import com.google.gwt.dev.js.ThrowExceptionOnErrorReporter
|
import com.google.gwt.dev.js.ThrowExceptionOnErrorReporter
|
||||||
|
import com.google.gwt.dev.js.rhino.CodePosition
|
||||||
import org.jetbrains.kotlin.ir.IrElement
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrConst
|
import org.jetbrains.kotlin.ir.expressions.IrConst
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrStringConcatenation
|
import org.jetbrains.kotlin.ir.expressions.IrStringConcatenation
|
||||||
@@ -15,17 +15,21 @@ import org.jetbrains.kotlin.ir.util.render
|
|||||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||||
import org.jetbrains.kotlin.js.backend.ast.*
|
import org.jetbrains.kotlin.js.backend.ast.JsFunctionScope
|
||||||
import org.jetbrains.kotlin.js.parser.parse
|
import org.jetbrains.kotlin.js.backend.ast.JsProgram
|
||||||
|
import org.jetbrains.kotlin.js.backend.ast.JsRootScope
|
||||||
|
import org.jetbrains.kotlin.js.backend.ast.JsStatement
|
||||||
|
import org.jetbrains.kotlin.js.parser.parseExpressionOrStatement
|
||||||
|
|
||||||
|
fun translateJsCodeIntoStatementList(code: IrExpression): List<JsStatement> {
|
||||||
fun translateJsCode(call: IrCall): JsNode {
|
// TODO: check non simple compile time constants (expressions)
|
||||||
//TODO check non simple compile time constants (expressions)
|
// TODO: support proper symbol linkage and label clash resolution
|
||||||
|
|
||||||
fun foldString(expression: IrExpression): String {
|
fun foldString(expression: IrExpression): String {
|
||||||
val builder = StringBuilder()
|
val builder = StringBuilder()
|
||||||
expression.acceptVoid(object : IrElementVisitorVoid {
|
expression.acceptVoid(object : IrElementVisitorVoid {
|
||||||
override fun visitElement(element: IrElement) = error("Parameter of js function must be compile time String constant, not ${element.render()}")
|
override fun visitElement(element: IrElement) =
|
||||||
|
error("Parameter of js function must be compile time String constant, not ${element.render()}")
|
||||||
|
|
||||||
override fun <T> visitConst(expression: IrConst<T>) {
|
override fun <T> visitConst(expression: IrConst<T>) {
|
||||||
builder.append(expression.kind.valueOf(expression))
|
builder.append(expression.kind.valueOf(expression))
|
||||||
@@ -37,15 +41,7 @@ fun translateJsCode(call: IrCall): JsNode {
|
|||||||
return builder.toString()
|
return builder.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
val code = call.getValueArgument(0)!!
|
return parseJsCode(foldString(code)) ?: emptyList()
|
||||||
val statements = parseJsCode(foldString(code)).orEmpty()
|
|
||||||
val size = statements.size
|
|
||||||
|
|
||||||
return when (size) {
|
|
||||||
0 -> JsEmpty
|
|
||||||
1 -> statements[0].let { (it as? JsExpressionStatement)?.expression ?: it }
|
|
||||||
else -> JsBlock(statements)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseJsCode(jsCode: String): List<JsStatement>? {
|
private fun parseJsCode(jsCode: String): List<JsStatement>? {
|
||||||
@@ -56,7 +52,7 @@ private fun parseJsCode(jsCode: String): List<JsStatement>? {
|
|||||||
val temporaryRootScope = JsRootScope(JsProgram())
|
val temporaryRootScope = JsRootScope(JsProgram())
|
||||||
val currentScope = JsFunctionScope(temporaryRootScope, "js")
|
val currentScope = JsFunctionScope(temporaryRootScope, "js")
|
||||||
|
|
||||||
// TODO write debug info, see how it's done in CallExpressionTranslator.parseJsCode
|
// TODO: write debug info, see how it's done in CallExpressionTranslator.parseJsCode
|
||||||
|
|
||||||
return parse(jsCode, ThrowExceptionOnErrorReporter, currentScope, "<js-code>")
|
return parseExpressionOrStatement(jsCode, ThrowExceptionOnErrorReporter, currentScope, CodePosition(0, 0), "<js-code>")
|
||||||
}
|
}
|
||||||
+7
-1
@@ -7,7 +7,11 @@ package org.jetbrains.kotlin.ir.backend.js.utils
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||||
import org.jetbrains.kotlin.js.backend.ast.*
|
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
||||||
|
import org.jetbrains.kotlin.js.backend.ast.JsName
|
||||||
|
import org.jetbrains.kotlin.js.backend.ast.JsNameRef
|
||||||
|
import org.jetbrains.kotlin.js.backend.ast.JsScope
|
||||||
|
import org.jetbrains.kotlin.js.backend.ast.JsThisRef
|
||||||
|
|
||||||
val emptyScope: JsScope
|
val emptyScope: JsScope
|
||||||
get() = object : JsScope("nil") {
|
get() = object : JsScope("nil") {
|
||||||
@@ -42,4 +46,6 @@ class JsGenerationContext(
|
|||||||
val overriddenSymbols = (currentFunction as? IrSimpleFunction)?.overriddenSymbols ?: return false
|
val overriddenSymbols = (currentFunction as? IrSimpleFunction)?.overriddenSymbols ?: return false
|
||||||
return staticContext.doResumeFunctionSymbol in overriddenSymbols
|
return staticContext.doResumeFunctionSymbol in overriddenSymbols
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun checkIfJsCode(symbol: IrFunctionSymbol): Boolean = symbol == staticContext.backendContext.intrinsics.jsCode
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JS_IR
|
|
||||||
// EXPECTED_REACHABLE_NODES: 1280
|
// EXPECTED_REACHABLE_NODES: 1280
|
||||||
fun box(): String {
|
fun box(): String {
|
||||||
return js("""
|
return js("""
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// IGNORE_BACKEND: JS_IR
|
||||||
// EXPECTED_REACHABLE_NODES: 1229
|
// EXPECTED_REACHABLE_NODES: 1229
|
||||||
package foo
|
package foo
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JS_IR
|
|
||||||
// EXPECTED_REACHABLE_NODES: 1282
|
// EXPECTED_REACHABLE_NODES: 1282
|
||||||
import kotlin.js.*
|
import kotlin.js.*
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user