[JS IR] Fix referencing Kotin variables in inline JS code
Fixed by outlining JS code that uses Kotlin variables making usages of these locals explicit and preventing bugs due to one-sided variable renaming. This prevents using Kotlin variables as lvalue in JS code.
This commit is contained in:
@@ -17,9 +17,12 @@ import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
|||||||
import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
|
import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
|
||||||
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
|
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||||
import org.jetbrains.kotlin.ir.types.*
|
import org.jetbrains.kotlin.ir.types.IrType
|
||||||
|
import org.jetbrains.kotlin.ir.types.defaultType
|
||||||
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeBuilder
|
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeBuilder
|
||||||
import org.jetbrains.kotlin.ir.types.impl.buildSimpleType
|
import org.jetbrains.kotlin.ir.types.impl.buildSimpleType
|
||||||
|
import org.jetbrains.kotlin.ir.types.isLong
|
||||||
|
import org.jetbrains.kotlin.ir.types.typeWithParameters
|
||||||
import org.jetbrains.kotlin.ir.util.constructors
|
import org.jetbrains.kotlin.ir.util.constructors
|
||||||
import org.jetbrains.kotlin.ir.util.findDeclaration
|
import org.jetbrains.kotlin.ir.util.findDeclaration
|
||||||
import org.jetbrains.kotlin.ir.util.kotlinPackageFqn
|
import org.jetbrains.kotlin.ir.util.kotlinPackageFqn
|
||||||
@@ -278,6 +281,7 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
|
|||||||
|
|
||||||
// TODO move to IntrinsifyCallsLowering
|
// TODO move to IntrinsifyCallsLowering
|
||||||
val doNotIntrinsifyAnnotationSymbol = context.symbolTable.referenceClass(context.getJsInternalClass("DoNotIntrinsify"))
|
val doNotIntrinsifyAnnotationSymbol = context.symbolTable.referenceClass(context.getJsInternalClass("DoNotIntrinsify"))
|
||||||
|
val jsFunAnnotationSymbol = context.symbolTable.referenceClass(context.getJsInternalClass("JsFun"))
|
||||||
|
|
||||||
// TODO move CharSequence-related stiff to IntrinsifyCallsLowering
|
// TODO move CharSequence-related stiff to IntrinsifyCallsLowering
|
||||||
val charSequenceClassSymbol = context.symbolTable.referenceClass(context.getClass(FqName("kotlin.CharSequence")))
|
val charSequenceClassSymbol = context.symbolTable.referenceClass(context.getClass(FqName("kotlin.CharSequence")))
|
||||||
|
|||||||
@@ -185,6 +185,12 @@ private val stripTypeAliasDeclarationsPhase = makeDeclarationTransformerPhase(
|
|||||||
description = "Strip typealias declarations"
|
description = "Strip typealias declarations"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val jsCodeOutliningPhase = makeBodyLoweringPhase(
|
||||||
|
::JsCodeOutliningLowering,
|
||||||
|
name = "JsCodeOutliningLowering",
|
||||||
|
description = "Outline js() calls where JS code references Kotlin locals"
|
||||||
|
)
|
||||||
|
|
||||||
private val arrayConstructorPhase = makeBodyLoweringPhase(
|
private val arrayConstructorPhase = makeBodyLoweringPhase(
|
||||||
::ArrayConstructorLowering,
|
::ArrayConstructorLowering,
|
||||||
name = "ArrayConstructor",
|
name = "ArrayConstructor",
|
||||||
@@ -713,6 +719,7 @@ private val loweringList = listOf<Lowering>(
|
|||||||
validateIrBeforeLowering,
|
validateIrBeforeLowering,
|
||||||
expectDeclarationsRemovingPhase,
|
expectDeclarationsRemovingPhase,
|
||||||
stripTypeAliasDeclarationsPhase,
|
stripTypeAliasDeclarationsPhase,
|
||||||
|
jsCodeOutliningPhase,
|
||||||
arrayConstructorPhase,
|
arrayConstructorPhase,
|
||||||
lateinitNullableFieldsPhase,
|
lateinitNullableFieldsPhase,
|
||||||
lateinitDeclarationLoweringPhase,
|
lateinitDeclarationLoweringPhase,
|
||||||
|
|||||||
+187
@@ -0,0 +1,187 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 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.IrElementTransformerVoidWithContext
|
||||||
|
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||||
|
import org.jetbrains.kotlin.backend.common.pop
|
||||||
|
import org.jetbrains.kotlin.backend.common.push
|
||||||
|
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||||
|
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.transformers.irToJs.translateJsCodeIntoStatementList
|
||||||
|
import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
|
||||||
|
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
|
||||||
|
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
|
||||||
|
import org.jetbrains.kotlin.ir.builders.irCall
|
||||||
|
import org.jetbrains.kotlin.ir.builders.irComposite
|
||||||
|
import org.jetbrains.kotlin.ir.builders.irGet
|
||||||
|
import org.jetbrains.kotlin.ir.builders.irString
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.IrBody
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.IrContainerExpression
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||||
|
import org.jetbrains.kotlin.ir.util.constructors
|
||||||
|
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||||
|
import org.jetbrains.kotlin.js.backend.ast.*
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||||
|
|
||||||
|
// Outlines `kotlin.js.js(code: String)` calls where JS code references Kotlin locals.
|
||||||
|
// Makes locals usages explicit.
|
||||||
|
class JsCodeOutliningLowering(val backendContext: JsIrBackendContext) : BodyLoweringPass {
|
||||||
|
override fun lower(irBody: IrBody, container: IrDeclaration) {
|
||||||
|
val replacer = JsCodeOutlineTransformer(backendContext, container)
|
||||||
|
irBody.transformChildrenVoid(replacer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class JsCodeOutlineTransformer(
|
||||||
|
val backendContext: JsIrBackendContext,
|
||||||
|
val container: IrDeclaration,
|
||||||
|
) : IrElementTransformerVoidWithContext() {
|
||||||
|
val localScopes: MutableList<MutableMap<String, IrValueDeclaration>> =
|
||||||
|
mutableListOf(mutableMapOf())
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (container is IrFunction) {
|
||||||
|
container.valueParameters.forEach {
|
||||||
|
registerValueDeclaration(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T> withLocalScope(body: () -> T): T {
|
||||||
|
localScopes.push(mutableMapOf())
|
||||||
|
val res = body()
|
||||||
|
localScopes.pop()
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
fun registerValueDeclaration(irValueDeclaration: IrValueDeclaration) {
|
||||||
|
val name = irValueDeclaration.name
|
||||||
|
if (!name.isSpecial) {
|
||||||
|
val identifier = name.identifier
|
||||||
|
val currentScope = localScopes.lastOrNull() ?: error("Expecting a scope")
|
||||||
|
currentScope[identifier] = irValueDeclaration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findValueDeclarationWithName(name: String): IrValueDeclaration? {
|
||||||
|
for (i in (localScopes.size - 1) downTo 0) {
|
||||||
|
val scope = localScopes[i]
|
||||||
|
return scope[name] ?: continue
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitContainerExpression(expression: IrContainerExpression): IrExpression {
|
||||||
|
return withLocalScope { super.visitContainerExpression(expression) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitDeclaration(declaration: IrDeclarationBase): IrStatement {
|
||||||
|
return withLocalScope { super.visitDeclaration(declaration) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitValueParameterNew(declaration: IrValueParameter): IrStatement {
|
||||||
|
return super.visitValueParameterNew(declaration).also { registerValueDeclaration(declaration) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitVariable(declaration: IrVariable): IrStatement {
|
||||||
|
return super.visitVariable(declaration).also { registerValueDeclaration(declaration) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitCall(expression: IrCall): IrExpression {
|
||||||
|
return outlineJsCodeIfNeeded(expression) ?: super.visitCall(expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun outlineJsCodeIfNeeded(expression: IrCall): IrExpression? {
|
||||||
|
if (expression.symbol != backendContext.intrinsics.jsCode)
|
||||||
|
return null
|
||||||
|
|
||||||
|
val jsCodeArg = expression.getValueArgument(0) ?: error("Expected js code string")
|
||||||
|
val jsStatements = translateJsCodeIntoStatementList(jsCodeArg)
|
||||||
|
|
||||||
|
// Collect used Kotlin local variables and parameters.
|
||||||
|
val kotlinLocalsUsedInJs = mutableListOf<IrValueDeclaration>()
|
||||||
|
val processedNames = mutableSetOf<String>()
|
||||||
|
jsStatements.forEach { statement ->
|
||||||
|
object : RecursiveJsVisitor() {
|
||||||
|
override fun visitNameRef(nameRef: JsNameRef) {
|
||||||
|
super.visitNameRef(nameRef)
|
||||||
|
val name = nameRef.name
|
||||||
|
// With this approach we should be able to find all usages of Kotlin variables in JS code.
|
||||||
|
// We will also collect shadowed usages, but it is OK since the same shadowing will be present in generated JS code.
|
||||||
|
if (name != null && nameRef.qualifier == null) {
|
||||||
|
// Keeping track of processed names to avoid registering them multiple times
|
||||||
|
if (processedNames.add(name.ident)) {
|
||||||
|
kotlinLocalsUsedInJs.addIfNotNull(findValueDeclarationWithName(name.ident))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.accept(statement)
|
||||||
|
}
|
||||||
|
if (kotlinLocalsUsedInJs.isEmpty())
|
||||||
|
return null
|
||||||
|
|
||||||
|
// Building outlined IR function skeleton
|
||||||
|
val outlinedFunction = backendContext.irFactory.buildFun {
|
||||||
|
name = Name.identifier("outlinedJsCode$")
|
||||||
|
visibility = DescriptorVisibilities.LOCAL
|
||||||
|
returnType = backendContext.dynamicType
|
||||||
|
}
|
||||||
|
// We don't need this function's body. Using empty block body stub, because some code might expect all functions to have bodies.
|
||||||
|
outlinedFunction.body = backendContext.irFactory.createBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET)
|
||||||
|
outlinedFunction.parent = container as IrDeclarationParent
|
||||||
|
kotlinLocalsUsedInJs.forEach { local ->
|
||||||
|
outlinedFunction.addValueParameter {
|
||||||
|
name = local.name
|
||||||
|
type = local.type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Building JS Ast function
|
||||||
|
val lastStatement = jsStatements.last()
|
||||||
|
val newStatements = jsStatements.toMutableList()
|
||||||
|
when (lastStatement) {
|
||||||
|
is JsReturn -> {
|
||||||
|
}
|
||||||
|
is JsExpressionStatement -> {
|
||||||
|
newStatements[jsStatements.lastIndex] = JsReturn(lastStatement.expression)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
newStatements += JsReturn(JsPrefixOperation(JsUnaryOperator.VOID, JsIntLiteral(3)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val newFun = JsFunction(emptyScope, JsBlock(newStatements), "")
|
||||||
|
kotlinLocalsUsedInJs.forEach { irParameter ->
|
||||||
|
newFun.parameters.add(JsParameter(JsName(irParameter.name.identifier)))
|
||||||
|
}
|
||||||
|
|
||||||
|
with(backendContext.createIrBuilder(container.symbol)) {
|
||||||
|
// Add @JsFun("function (used_local1, used_local2, ..) { ... }") annotation to outlined function
|
||||||
|
val jsFunCtor = backendContext.intrinsics.jsFunAnnotationSymbol.constructors.single()
|
||||||
|
val jsFunCall =
|
||||||
|
irCall(jsFunCtor).apply {
|
||||||
|
putValueArgument(0, irString(newFun.toString()))
|
||||||
|
}
|
||||||
|
outlinedFunction.annotations = listOf(jsFunCall)
|
||||||
|
|
||||||
|
val outlinedFunctionCall = irCall(outlinedFunction).apply {
|
||||||
|
kotlinLocalsUsedInJs.forEachIndexed { index, local ->
|
||||||
|
putValueArgument(index, irGet(local))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return irComposite {
|
||||||
|
+outlinedFunction
|
||||||
|
+outlinedFunctionCall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
@@ -42,6 +42,14 @@ fun jsAssignment(left: JsExpression, right: JsExpression) = JsBinaryOperation(Js
|
|||||||
fun prototypeOf(classNameRef: JsExpression) = JsNameRef(Namer.PROTOTYPE_NAME, classNameRef)
|
fun prototypeOf(classNameRef: JsExpression) = JsNameRef(Namer.PROTOTYPE_NAME, classNameRef)
|
||||||
|
|
||||||
fun translateFunction(declaration: IrFunction, name: JsName?, context: JsGenerationContext): JsFunction {
|
fun translateFunction(declaration: IrFunction, name: JsName?, context: JsGenerationContext): JsFunction {
|
||||||
|
val jsFun = declaration.getJsFunAnnotation()
|
||||||
|
if (jsFun != null) {
|
||||||
|
// JsFun internal annotation must have string containing valid function expression
|
||||||
|
val function = (parseJsCode(jsFun)!!.single() as JsExpressionStatement).expression as JsFunction
|
||||||
|
function.name = name
|
||||||
|
return function
|
||||||
|
}
|
||||||
|
|
||||||
val functionContext = context.newDeclaration(declaration)
|
val functionContext = context.newDeclaration(declaration)
|
||||||
val functionParams = declaration.valueParameters.map { functionContext.getNameForValueDeclaration(it) }
|
val functionParams = declaration.valueParameters.map { functionContext.getNameForValueDeclaration(it) }
|
||||||
val body = declaration.body?.accept(IrElementToJsStatementTransformer(), functionContext) as? JsBlock ?: JsBlock()
|
val body = declaration.body?.accept(IrElementToJsStatementTransformer(), functionContext) as? JsBlock ?: JsBlock()
|
||||||
|
|||||||
+1
-1
@@ -44,7 +44,7 @@ fun translateJsCodeIntoStatementList(code: IrExpression): List<JsStatement> {
|
|||||||
return parseJsCode(foldString(code)) ?: emptyList()
|
return parseJsCode(foldString(code)) ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseJsCode(jsCode: String): List<JsStatement>? {
|
fun parseJsCode(jsCode: String): List<JsStatement>? {
|
||||||
// Parser can change local or global scope.
|
// Parser can change local or global scope.
|
||||||
// In case of js we want to keep new local names,
|
// In case of js we want to keep new local names,
|
||||||
// but no new global ones.
|
// but no new global ones.
|
||||||
|
|||||||
+7
-2
@@ -5,8 +5,9 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.ir.backend.js.utils
|
package org.jetbrains.kotlin.ir.backend.js.utils
|
||||||
|
|
||||||
import org.jetbrains.kotlin.ir.declarations.*
|
import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrClassReference
|
import org.jetbrains.kotlin.ir.expressions.IrClassReference
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrConst
|
import org.jetbrains.kotlin.ir.expressions.IrConst
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
|
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
|
||||||
@@ -24,6 +25,7 @@ object JsAnnotations {
|
|||||||
val jsNativeGetter = FqName("kotlin.js.nativeGetter")
|
val jsNativeGetter = FqName("kotlin.js.nativeGetter")
|
||||||
val jsNativeSetter = FqName("kotlin.js.nativeSetter")
|
val jsNativeSetter = FqName("kotlin.js.nativeSetter")
|
||||||
val jsNativeInvoke = FqName("kotlin.js.nativeInvoke")
|
val jsNativeInvoke = FqName("kotlin.js.nativeInvoke")
|
||||||
|
val jsFunFqn = FqName("kotlin.js.JsFun")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@@ -42,6 +44,9 @@ fun IrAnnotationContainer.getJsQualifier(): String? =
|
|||||||
fun IrAnnotationContainer.getJsName(): String? =
|
fun IrAnnotationContainer.getJsName(): String? =
|
||||||
getAnnotation(JsAnnotations.jsNameFqn)?.getSingleConstStringArgument()
|
getAnnotation(JsAnnotations.jsNameFqn)?.getSingleConstStringArgument()
|
||||||
|
|
||||||
|
fun IrAnnotationContainer.getJsFunAnnotation(): String? =
|
||||||
|
getAnnotation(JsAnnotations.jsFunFqn)?.getSingleConstStringArgument()
|
||||||
|
|
||||||
fun IrAnnotationContainer.isJsExport(): Boolean =
|
fun IrAnnotationContainer.isJsExport(): Boolean =
|
||||||
hasAnnotation(JsAnnotations.jsExportFqn)
|
hasAnnotation(JsAnnotations.jsExportFqn)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JS_IR
|
|
||||||
// IGNORE_BACKEND: JS_IR_ES6
|
|
||||||
// EXPECTED_REACHABLE_NODES: 1283
|
// EXPECTED_REACHABLE_NODES: 1283
|
||||||
package foo
|
package foo
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JS_IR
|
|
||||||
// IGNORE_BACKEND: JS_IR_ES6
|
|
||||||
// EXPECTED_REACHABLE_NODES: 1283
|
// EXPECTED_REACHABLE_NODES: 1283
|
||||||
package foo
|
package foo
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// IGNORE_BACKEND: JS_IR
|
||||||
// EXPECTED_REACHABLE_NODES: 1282
|
// EXPECTED_REACHABLE_NODES: 1282
|
||||||
package foo
|
package foo
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// EXPECTED_REACHABLE_NODES: 1282
|
// EXPECTED_REACHABLE_NODES: 1282
|
||||||
|
// IGNORE_BACKEND: JS_IR
|
||||||
package foo
|
package foo
|
||||||
|
|
||||||
fun box(): String {
|
fun box(): String {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// IGNORE_BACKEND: JS_IR
|
||||||
// EXPECTED_REACHABLE_NODES: 1285
|
// EXPECTED_REACHABLE_NODES: 1285
|
||||||
package foo
|
package foo
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// EXPECTED_REACHABLE_NODES: 1282
|
// EXPECTED_REACHABLE_NODES: 1282
|
||||||
|
// IGNORE_BACKEND: JS_IR
|
||||||
package foo
|
package foo
|
||||||
|
|
||||||
// CHECK_LABELS_COUNT: function=box name=block count=2
|
// CHECK_LABELS_COUNT: function=box name=block count=2
|
||||||
|
|||||||
+18
-14
@@ -21,13 +21,15 @@ fun box(): String {
|
|||||||
assertEquals(1, js("0 ^ 1"), "^")
|
assertEquals(1, js("0 ^ 1"), "^")
|
||||||
assertEquals(-2, js("~1"), "~")
|
assertEquals(-2, js("~1"), "~")
|
||||||
|
|
||||||
var i = 2
|
if (testUtils.isLegacyBackend()) {
|
||||||
assertEquals(1, js("--i"), "-- prefix")
|
var i = 2
|
||||||
assertEquals(1, js("i--"), "-- postfix (1)")
|
assertEquals(1, js("--i"), "-- prefix")
|
||||||
assertEquals(0, js("i"), "-- postfix (0)")
|
assertEquals(1, js("i--"), "-- postfix (1)")
|
||||||
assertEquals(1, js("++i"), "++ prefix")
|
assertEquals(0, js("i"), "-- postfix (0)")
|
||||||
assertEquals(1, js("i++"), "++ postfix (1)")
|
assertEquals(1, js("++i"), "++ prefix")
|
||||||
assertEquals(2, js("i"), "++ postfix (0)")
|
assertEquals(1, js("i++"), "++ postfix (1)")
|
||||||
|
assertEquals(2, js("i"), "++ postfix (0)")
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(true , js("true || false"), "||")
|
assertEquals(true , js("true || false"), "||")
|
||||||
assertEquals(false , js("true && false"), "&&")
|
assertEquals(false , js("true && false"), "&&")
|
||||||
@@ -47,13 +49,15 @@ fun box(): String {
|
|||||||
assertEquals("even", js("(4 % 2 === 0)?'even':'odd'"), "?:")
|
assertEquals("even", js("(4 % 2 === 0)?'even':'odd'"), "?:")
|
||||||
assertEquals(3, js("1,2,3"), ", (comma)")
|
assertEquals(3, js("1,2,3"), ", (comma)")
|
||||||
|
|
||||||
var j = 0
|
if (testUtils.isLegacyBackend()) {
|
||||||
assertEquals(1, js("j = 1"), "=")
|
var j = 0
|
||||||
assertEquals(3, js("j += 2"), "+=")
|
assertEquals(1, js("j = 1"), "=")
|
||||||
assertEquals(2, js("j -= 1"), "-=")
|
assertEquals(3, js("j += 2"), "+=")
|
||||||
assertEquals(14, js("j *= 7"), "*=")
|
assertEquals(2, js("j -= 1"), "-=")
|
||||||
assertEquals(7, js("j /= 2"), "/=")
|
assertEquals(14, js("j *= 7"), "*=")
|
||||||
assertEquals(1, js("j %= 2"), "%=")
|
assertEquals(7, js("j /= 2"), "/=")
|
||||||
|
assertEquals(1, js("j %= 2"), "%=")
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(undefined, js("(void 0)"), "void")
|
assertEquals(undefined, js("(void 0)"), "void")
|
||||||
assertEquals(true, js("'key' in {'key': 10}"), "in")
|
assertEquals(true, js("'key' in {'key': 10}"), "in")
|
||||||
|
|||||||
@@ -12,3 +12,19 @@ internal fun <T : Enum<T>> enumValuesIntrinsic(): Array<T> =
|
|||||||
@PublishedApi
|
@PublishedApi
|
||||||
internal fun <T : Enum<T>> enumValueOfIntrinsic(@Suppress("UNUSED_PARAMETER") name: String): T =
|
internal fun <T : Enum<T>> enumValueOfIntrinsic(@Suppress("UNUSED_PARAMETER") name: String): T =
|
||||||
throw IllegalStateException("Should be replaced by compiler")
|
throw IllegalStateException("Should be replaced by compiler")
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements annotated function in JavaScript.
|
||||||
|
* [code] string must contain JS expression that evaluates to JS function with signature that matches annotated kotlin function
|
||||||
|
*
|
||||||
|
* For example, a function that adds two Doubles:
|
||||||
|
*
|
||||||
|
* @JsFun("(x, y) => x + y")
|
||||||
|
* fun jsAdd(x: Double, y: Double): Double =
|
||||||
|
* error("...")
|
||||||
|
*
|
||||||
|
* Code gets inserted as is without syntax verification.
|
||||||
|
*/
|
||||||
|
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
|
||||||
|
internal annotation class JsFun(val code: String)
|
||||||
Reference in New Issue
Block a user