Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.jsAstUtils.kt Maven / Gradle / Ivy
/*
* 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.transformers.irToJs
import org.jetbrains.kotlin.backend.common.ir.isElseBranch
import org.jetbrains.kotlin.backend.common.ir.isSuspend
import org.jetbrains.kotlin.ir.backend.js.lower.InteropCallableReferenceLowering
import 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.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
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)
return JsVars(JsVars.JsVar(name, jsInitializer))
}
fun IrWhen.toJsNode(
tr: BaseIrElementToJsNodeTransformer,
data: JsGenerationContext,
node: (JsExpression, T, T?) -> T,
implicitElse: T? = null
): T? =
branches.foldRight(implicitElse) { br, n ->
val body = br.result.accept(tr, data)
if (isElseBranch(br)) body
else {
val condition = br.condition.accept(IrElementToJsExpressionTransformer(), data)
node(condition, body, n)
}
}
fun jsAssignment(left: JsExpression, right: JsExpression) = JsBinaryOperation(JsBinaryOperator.ASG, left, right)
fun prototypeOf(classNameRef: JsExpression) = JsNameRef(Namer.PROTOTYPE_NAME, classNameRef)
fun translateFunction(declaration: IrFunction, name: JsName?, context: JsGenerationContext): JsFunction {
val functionContext = context.newDeclaration(declaration)
val functionParams = declaration.valueParameters.map { functionContext.getNameForValueDeclaration(it) }
val body = declaration.body?.accept(IrElementToJsStatementTransformer(), functionContext) as? JsBlock ?: JsBlock()
val function = JsFunction(emptyScope, body, "member function ${name ?: "annon"}")
function.name = name
fun JsFunction.addParameter(parameter: JsName) {
parameters.add(JsParameter(parameter))
}
declaration.extensionReceiverParameter?.let { function.addParameter(functionContext.getNameForValueDeclaration(it)) }
functionParams.forEach { function.addParameter(it) }
if (declaration.isSuspend) {
function.addParameter(JsName(Namer.CONTINUATION)) // TODO: Use namer?
}
return function
}
private fun isFunctionTypeInvoke(receiver: JsExpression?, call: IrCall): Boolean {
if (receiver == null || receiver is JsThisRef) return false
val simpleFunction = call.symbol.owner as? IrSimpleFunction ?: return false
val receiverType = simpleFunction.dispatchReceiverParameter?.type ?: return false
if (call.origin === InteropCallableReferenceLowering.Companion.EXPLICIT_INVOKE) return false
return simpleFunction.name == OperatorNameConventions.INVOKE && receiverType.isFunctionTypeOrSubtype()
}
fun translateCall(
expression: IrCall,
context: JsGenerationContext,
transformer: IrElementToJsExpressionTransformer
): JsExpression {
val function = expression.symbol.owner.realOverrideTarget
context.staticContext.intrinsics[function.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 (isFunctionTypeInvoke(jsDispatchReceiver, expression) || expression.symbol.owner.isJsNativeInvoke()) {
return JsInvocation(jsDispatchReceiver ?: jsExtensionReceiver!!, 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.varargParameterIndex()
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) {
// TODO: Don't use `Function.prototype.apply` when number of arguments is known at compile time (e.g. there are no spread operators)
val argumentsAsSingleArray = argumentsWithVarargAsSingleArray(
jsExtensionReceiver,
arguments,
varargParameterIndex
)
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 argumentsWithVarargAsSingleArray(
additionalReceiver: JsExpression?,
arguments: List,
varargParameterIndex: Int,
): JsExpression {
// 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.
var arraysForConcat: MutableList = mutableListOf().apply {
additionalReceiver?.let { add(it) }
}
val concatElements: MutableList = mutableListOf()
arguments
.forEachIndexed { index, argument ->
when (index) {
// Call `Array.prototype.slice` on vararg arguments in order to convert array-like objects into proper arrays
varargParameterIndex -> {
concatElements.add(JsArrayLiteral(arraysForConcat))
arraysForConcat = mutableListOf()
val varargArgument = if (argument is JsArrayLiteral) {
argument
} else {
val arraySliceCall = JsNameRef("call", JsNameRef("slice", JsArrayLiteral()))
JsInvocation(arraySliceCall, argument)
}
concatElements.add(varargArgument)
}
else -> {
arraysForConcat.add(argument)
}
}
}
if (arraysForConcat.isNotEmpty() || concatElements.isEmpty()) {
concatElements.add(JsArrayLiteral(arraysForConcat))
}
return concatElements.singleOrNull()
?: JsInvocation(
JsNameRef("concat", concatElements.first()),
concatElements.drop(1)
)
}
fun IrFunction.varargParameterIndex() = valueParameters.indexOfFirst { it.varargElementType != null }
fun translateCallArguments(
expression: IrMemberAccessExpression,
context: JsGenerationContext,
transformer: IrElementToJsExpressionTransformer,
): List {
val size = expression.valueArgumentsCount
val varargParameterIndex = expression.symbol.owner.realOverrideTarget.varargParameterIndex()
val validWithNullArgs = expression.validWithNullArgs()
val arguments = (0 until size)
.mapTo(ArrayList(size)) { index ->
val argument = expression.getValueArgument(index)
argument?.accept(transformer, context)
}
.onEach { result ->
if (result == null) {
assert(validWithNullArgs)
}
}
.mapIndexed { index, result ->
val isEmptyExternalVararg = validWithNullArgs &&
varargParameterIndex == index &&
result is JsArrayLiteral &&
result.expressions.isEmpty()
if (isEmptyExternalVararg) {
null
} else result
}
.dropLastWhile { it == null }
.map { it ?: JsPrefixOperation(JsUnaryOperator.VOID, JsIntLiteral(1)) }
return if (expression.symbol.isSuspend) {
arguments + context.continuation
} else arguments
}
private fun IrMemberAccessExpression<*>.validWithNullArgs() =
this is IrFunctionAccessExpression && symbol.owner.isExternalOrInheritedFromExternal()
fun JsStatement.asBlock() = this as? JsBlock ?: JsBlock(this)
fun defineProperty(receiver: JsExpression, name: String, value: () -> JsExpression): JsInvocation {
val objectDefineProperty = JsNameRef("defineProperty", Namer.JS_OBJECT)
return JsInvocation(objectDefineProperty, receiver, JsStringLiteral(name), value())
}
fun defineProperty(receiver: JsExpression, name: String, getter: JsExpression?, setter: JsExpression? = null) =
defineProperty(receiver, name) {
JsObjectLiteral(true).apply {
propertyInitializers += JsPropertyInitializer(JsStringLiteral("configurable"), JsBooleanLiteral(true))
if (getter != null)
propertyInitializers += JsPropertyInitializer(JsStringLiteral("get"), getter)
if (setter != null)
propertyInitializers += JsPropertyInitializer(JsStringLiteral("set"), setter)
}
}
// Partially copied from org.jetbrains.kotlin.js.translate.utils.JsAstUtils
object JsAstUtils {
private fun deBlockIfPossible(statement: JsStatement): JsStatement {
return if (statement is JsBlock && statement.statements.size == 1) {
statement.statements[0]
} else {
statement
}
}
fun newJsIf(
ifExpression: JsExpression,
thenStatement: JsStatement,
elseStatement: JsStatement? = null
): JsIf {
return JsIf(ifExpression, deBlockIfPossible(thenStatement), elseStatement?.let { deBlockIfPossible(it) })
}
fun and(op1: JsExpression, op2: JsExpression): JsBinaryOperation {
return JsBinaryOperation(JsBinaryOperator.AND, op1, op2)
}
fun or(op1: JsExpression, op2: JsExpression): JsBinaryOperation {
return JsBinaryOperation(JsBinaryOperator.OR, op1, op2)
}
fun equality(arg1: JsExpression, arg2: JsExpression): JsBinaryOperation {
return JsBinaryOperation(JsBinaryOperator.REF_EQ, arg1, arg2)
}
fun inequality(arg1: JsExpression, arg2: JsExpression): JsBinaryOperation {
return JsBinaryOperation(JsBinaryOperator.REF_NEQ, arg1, arg2)
}
fun lessThanEq(arg1: JsExpression, arg2: JsExpression): JsBinaryOperation {
return JsBinaryOperation(JsBinaryOperator.LTE, arg1, arg2)
}
fun lessThan(arg1: JsExpression, arg2: JsExpression): JsBinaryOperation {
return JsBinaryOperation(JsBinaryOperator.LT, arg1, arg2)
}
fun greaterThan(arg1: JsExpression, arg2: JsExpression): JsBinaryOperation {
return JsBinaryOperation(JsBinaryOperator.GT, arg1, arg2)
}
fun greaterThanEq(arg1: JsExpression, arg2: JsExpression): JsBinaryOperation {
return JsBinaryOperation(JsBinaryOperator.GTE, arg1, arg2)
}
fun assignment(left: JsExpression, right: JsExpression): JsBinaryOperation {
return JsBinaryOperation(JsBinaryOperator.ASG, left, right)
}
fun assignmentToThisField(fieldName: String, right: JsExpression): JsStatement {
return assignment(JsNameRef(fieldName, JsThisRef()), right).source(right.source).makeStmt()
}
fun decomposeAssignment(expr: JsExpression): Pair? {
if (expr !is JsBinaryOperation) return null
return if (expr.operator != JsBinaryOperator.ASG) null else Pair(expr.arg1, expr.arg2)
}
fun sum(left: JsExpression, right: JsExpression): JsBinaryOperation {
return JsBinaryOperation(JsBinaryOperator.ADD, left, right)
}
fun addAssign(left: JsExpression, right: JsExpression): JsBinaryOperation {
return JsBinaryOperation(JsBinaryOperator.ASG_ADD, left, right)
}
fun subtract(left: JsExpression, right: JsExpression): JsBinaryOperation {
return JsBinaryOperation(JsBinaryOperator.SUB, left, right)
}
fun mul(left: JsExpression, right: JsExpression): JsBinaryOperation {
return JsBinaryOperation(JsBinaryOperator.MUL, left, right)
}
fun div(left: JsExpression, right: JsExpression): JsBinaryOperation {
return JsBinaryOperation(JsBinaryOperator.DIV, left, right)
}
fun mod(left: JsExpression, right: JsExpression): JsBinaryOperation {
return JsBinaryOperation(JsBinaryOperator.MOD, left, right)
}
fun not(expression: JsExpression): JsPrefixOperation {
return JsPrefixOperation(JsUnaryOperator.NOT, expression)
}
fun typeOfIs(expression: JsExpression, string: JsStringLiteral): JsBinaryOperation {
return equality(JsPrefixOperation(JsUnaryOperator.TYPEOF, expression), string)
}
fun newVar(name: JsName, expr: JsExpression?): JsVars {
return JsVars(JsVars.JsVar(name, expr))
}
}