[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 thisRef =
|
||||
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
|
||||
if (constructor.parentAsClass.isInline) {
|
||||
@@ -128,13 +128,7 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
|
||||
|
||||
override fun visitConstructorCall(expression: IrConstructorCall, context: JsGenerationContext): JsExpression {
|
||||
val function = expression.symbol.owner
|
||||
val symbol = expression.symbol
|
||||
|
||||
context.staticContext.intrinsics[symbol]?.let {
|
||||
return it(expression, context)
|
||||
}
|
||||
|
||||
val arguments = translateCallArguments(expression, context)
|
||||
val arguments = translateCallArguments(expression, context, this)
|
||||
val klass = function.parentAsClass
|
||||
return if (klass.isInline) {
|
||||
assert(function.isPrimary) {
|
||||
@@ -155,153 +149,32 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall, context: JsGenerationContext): JsExpression {
|
||||
val function = expression.symbol.owner.realOverrideTarget
|
||||
val symbol = function.symbol
|
||||
if (context.checkIfJsCode(expression.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 {
|
||||
return it(expression, context)
|
||||
}
|
||||
|
||||
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 lastStatement = statements.last()
|
||||
if (statements.size == 1) {
|
||||
if (lastStatement is JsExpressionStatement) return lastStatement.expression
|
||||
}
|
||||
|
||||
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 newStatements = statements.toMutableList()
|
||||
|
||||
val varargParameterIndex = function.valueParameters.indexOfFirst { it.varargElementType != null }
|
||||
val isExternalVararg = function.isEffectivelyExternal() && varargParameterIndex != -1
|
||||
|
||||
|
||||
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"
|
||||
when (lastStatement) {
|
||||
is JsReturn -> {
|
||||
}
|
||||
// Argument value constructs unboxed inline class instance
|
||||
arguments.single()
|
||||
} else {
|
||||
val ref = when {
|
||||
klass.isEffectivelyExternal() ->
|
||||
context.getRefForExternalClass(klass)
|
||||
|
||||
else ->
|
||||
context.getNameForClass(klass).makeRef()
|
||||
is JsExpressionStatement -> {
|
||||
newStatements[statements.lastIndex] = JsReturn(lastStatement.expression)
|
||||
}
|
||||
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)
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
return translateCall(expression, context, this)
|
||||
}
|
||||
|
||||
override fun visitWhen(expression: IrWhen, context: JsGenerationContext): JsExpression {
|
||||
@@ -320,7 +193,7 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
|
||||
override fun visitTypeOperator(expression: IrTypeOperatorCall, data: JsGenerationContext): JsExpression {
|
||||
return when (expression.operator) {
|
||||
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.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()
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
// 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.utils.JsGenerationContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
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.IrFunctionAccessExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrFunctionReference
|
||||
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.types.classifierOrFail
|
||||
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.js.backend.ast.*
|
||||
|
||||
typealias IrCallTransformer = (IrFunctionAccessExpression, context: JsGenerationContext) -> JsExpression
|
||||
typealias IrCallTransformer = (IrCall, context: JsGenerationContext) -> JsExpression
|
||||
|
||||
class JsIntrinsicTransformers(backendContext: JsIrBackendContext) {
|
||||
private val transformers: Map<IrSymbol, IrCallTransformer>
|
||||
@@ -100,15 +101,7 @@ class JsIntrinsicTransformers(backendContext: JsIrBackendContext) {
|
||||
}
|
||||
}
|
||||
|
||||
addIfNotNull(intrinsics.jsCode) { call, context ->
|
||||
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), ""))
|
||||
}
|
||||
}
|
||||
addIfNotNull(intrinsics.jsCode) { _, _ -> error("Should not be called") }
|
||||
|
||||
add(intrinsics.jsGetContinuation) { _, context: JsGenerationContext ->
|
||||
context.continuation
|
||||
@@ -181,7 +174,7 @@ class JsIntrinsicTransformers(backendContext: JsIrBackendContext) {
|
||||
add(intrinsics.jsBind) { call, context: JsGenerationContext ->
|
||||
val receiver = call.getValueArgument(0)!!
|
||||
val reference = call.getValueArgument(1) as IrFunctionReference
|
||||
val superClass = (call as IrCall).superQualifierSymbol!!
|
||||
val superClass = call.superQualifierSymbol!!
|
||||
|
||||
val jsReceiver = receiver.accept(IrElementToJsExpressionTransformer(), context)
|
||||
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]
|
||||
}
|
||||
|
||||
private fun translateCallArguments(expression: IrCall, context: JsGenerationContext): List<JsExpression> {
|
||||
return translateCallArguments(expression, context, IrElementToJsExpressionTransformer())
|
||||
}
|
||||
|
||||
private fun MutableMap<IrSymbol, IrCallTransformer>.add(functionSymbol: IrSymbol, t: IrCallTransformer) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -214,20 +211,20 @@ private fun MutableMap<IrSymbol, IrCallTransformer>.addIfNotNull(symbol: IrSymbo
|
||||
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]) }
|
||||
}
|
||||
|
||||
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]) }
|
||||
}
|
||||
|
||||
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]) }
|
||||
}
|
||||
|
||||
private inline fun MutableMap<IrSymbol, IrCallTransformer>.withTranslatedArgs(
|
||||
function: IrFunctionSymbol,
|
||||
function: IrSimpleFunctionSymbol,
|
||||
crossinline t: (List<JsExpression>) -> JsExpression
|
||||
) {
|
||||
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.ir.isElseBranch
|
||||
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.emptyScope
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.*
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrWhen
|
||||
import org.jetbrains.kotlin.ir.util.isExternalOrInheritedFromExternal
|
||||
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.js.backend.ast.*
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
|
||||
fun jsVar(name: JsName, initializer: IrExpression?, context: JsGenerationContext): JsVars {
|
||||
val jsInitializer = initializer?.accept(IrElementToJsExpressionTransformer(), context)
|
||||
@@ -64,8 +61,145 @@ fun translateFunction(declaration: IrFunction, name: JsName?, context: JsGenerat
|
||||
return function
|
||||
}
|
||||
|
||||
fun translateCallArguments(expression: IrMemberAccessExpression, context: JsGenerationContext): List<JsExpression> {
|
||||
val transformer = IrElementToJsExpressionTransformer()
|
||||
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()
|
||||
}
|
||||
|
||||
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 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())
|
||||
}
|
||||
|
||||
|
||||
fun defineProperty(receiver: JsExpression, name: String, getter: JsExpression?, setter: JsExpression? = null) =
|
||||
defineProperty(receiver, name) {
|
||||
val literal = JsObjectLiteral(true)
|
||||
|
||||
+14
-18
@@ -6,8 +6,8 @@
|
||||
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
||||
|
||||
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.expressions.IrCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrConst
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
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.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||
import org.jetbrains.kotlin.js.backend.ast.*
|
||||
import org.jetbrains.kotlin.js.parser.parse
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsFunctionScope
|
||||
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 translateJsCode(call: IrCall): JsNode {
|
||||
//TODO check non simple compile time constants (expressions)
|
||||
fun translateJsCodeIntoStatementList(code: IrExpression): List<JsStatement> {
|
||||
// TODO: check non simple compile time constants (expressions)
|
||||
// TODO: support proper symbol linkage and label clash resolution
|
||||
|
||||
fun foldString(expression: IrExpression): String {
|
||||
val builder = StringBuilder()
|
||||
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>) {
|
||||
builder.append(expression.kind.valueOf(expression))
|
||||
@@ -37,15 +41,7 @@ fun translateJsCode(call: IrCall): JsNode {
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
val code = call.getValueArgument(0)!!
|
||||
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)
|
||||
}
|
||||
return parseJsCode(foldString(code)) ?: emptyList()
|
||||
}
|
||||
|
||||
private fun parseJsCode(jsCode: String): List<JsStatement>? {
|
||||
@@ -56,7 +52,7 @@ private fun parseJsCode(jsCode: String): List<JsStatement>? {
|
||||
val temporaryRootScope = JsRootScope(JsProgram())
|
||||
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.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
|
||||
get() = object : JsScope("nil") {
|
||||
@@ -42,4 +46,6 @@ class JsGenerationContext(
|
||||
val overriddenSymbols = (currentFunction as? IrSimpleFunction)?.overriddenSymbols ?: return false
|
||||
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
|
||||
fun box(): String {
|
||||
return js("""
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
// EXPECTED_REACHABLE_NODES: 1229
|
||||
package foo
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
// EXPECTED_REACHABLE_NODES: 1282
|
||||
import kotlin.js.*
|
||||
|
||||
|
||||
Reference in New Issue
Block a user