diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/FunctionWithJsFuncAnnotationInliner.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/FunctionWithJsFuncAnnotationInliner.kt index 57a3dedb053..657234ef426 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/FunctionWithJsFuncAnnotationInliner.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/FunctionWithJsFuncAnnotationInliner.kt @@ -7,75 +7,42 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs import org.jetbrains.kotlin.backend.common.compilationException import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext -import org.jetbrains.kotlin.ir.declarations.IrValueParameter import org.jetbrains.kotlin.ir.expressions.IrCall -import org.jetbrains.kotlin.ir.util.file import org.jetbrains.kotlin.js.backend.ast.* -private typealias Replacement = Pair - class FunctionWithJsFuncAnnotationInliner(private val jsFuncCall: IrCall, private val context: JsGenerationContext) { private val function = getJsFunctionImplementation() private val replacements = collectReplacementsForCall() - fun generateResultStatement(): List { - val irFunction = jsFuncCall.symbol.owner - val newContext = context.newFile(irFunction.file, irFunction, context.localNames) - return function.body.statements - .run { - SimpleJsCodeInliner(replacements, newContext) - .apply { acceptList(this@run) } - .withTemporaryVariablesForExpressions(this) - } - } + fun generateResultStatement(): List = + function.body.statements.also { + JsNameRemappingTransformer(replacements).apply { acceptList(it) } + } private fun getJsFunctionImplementation(): JsFunction = context.staticContext.backendContext.getJsCodeForFunction(jsFuncCall.symbol)?.deepCopy() ?: compilationException("JS function not found", jsFuncCall) - private fun collectReplacementsForCall(): Map { - val translatedArguments = Array(jsFuncCall.valueArgumentsCount) { + private fun collectReplacementsForCall(): Map { + val translatedArguments = List(jsFuncCall.valueArgumentsCount) { jsFuncCall.getValueArgument(it)!! - .accept(IrElementToJsExpressionTransformer(), context) to jsFuncCall.symbol.owner.valueParameters[it] + .accept(IrElementToJsExpressionTransformer(), context) } return function.parameters - .mapIndexed { i, param -> param.name to translatedArguments[i] } + .map { it.name } + .zip(translatedArguments) .toMap() } } -private class SimpleJsCodeInliner(private val replacements: Map, val context: JsGenerationContext) : - RecursiveJsVisitor() -{ - private val temporaryNamesForExpressions = mutableMapOf() +private class JsNameRemappingTransformer(private val replacements: Map) : JsVisitorWithContextImpl() { + private val JsName.replacement: JsExpression? get() = replacements[this] - fun withTemporaryVariablesForExpressions(statements: List): List { - if (temporaryNamesForExpressions.isEmpty()) { - return statements - } - - val variableDeclarations = temporaryNamesForExpressions.map { - JsVars(JsVars.JsVar(it.key, it.value.first).withSource(it.value.second, context, useNameOf = it.value.second)) - } - return variableDeclarations + statements - } - - override fun visitNameRef(nameRef: JsNameRef) { - super.visitNameRef(nameRef) - if (nameRef.qualifier != null) return - nameRef.name = nameRef.name?.getReplacement() ?: return - } - - private fun JsName.declareNewTemporaryFor(expression: JsExpression, irValueParameter: IrValueParameter): JsName { - return JsName(ident, true) - .also { temporaryNamesForExpressions[it] = expression to irValueParameter } - } - - private fun JsName.getReplacement(): JsName? { - val (expression, irValueParameter) = replacements[this] ?: return null - return when { - expression is JsNameRef && expression.qualifier == null -> expression.name!! - else -> declareNewTemporaryFor(expression, irValueParameter) - } + override fun visit(nameRef: JsNameRef, ctx: JsContext): Boolean { + super.visit(nameRef, ctx) + if (nameRef.qualifier != null) return true + val replacement = nameRef.name?.replacement ?: return true + ctx.replaceMe(replacement) + return false } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsCallTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsCallTransformer.kt index 4677033cfb7..0835fd97c2b 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsCallTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsCallTransformer.kt @@ -7,7 +7,9 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs import org.jetbrains.kotlin.backend.common.compilationException 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.IrConstructor import org.jetbrains.kotlin.ir.expressions.IrCall import org.jetbrains.kotlin.js.backend.ast.* @@ -61,7 +63,17 @@ class JsCallTransformer(private val jsOrJsFuncCall: IrCall, private val context: } val syntheticFunction = JsFunction(emptyScope, JsBlock(newStatements), "") - return JsInvocation(syntheticFunction).withSource(jsOrJsFuncCall, context) + val currentFunction = context.currentFunction + + return if ( + currentFunction != null && + !currentFunction.isInline && + (currentFunction.dispatchReceiverParameter != null || currentFunction is IrConstructor) + ) { + JsInvocation(JsNameRef(Namer.CALL_FUNCTION, syntheticFunction), JsThisRef()) + } else { + JsInvocation(syntheticFunction) + }.withSource(jsOrJsFuncCall, context) } private fun getJsStatements(): List { @@ -78,6 +90,7 @@ class JsCallTransformer(private val jsOrJsFuncCall: IrCall, private val context: context.checkIfHasAssociatedJsCode(jsOrJsFuncCall.symbol) -> FunctionWithJsFuncAnnotationInliner(jsOrJsFuncCall, context).generateResultStatement() + else -> compilationException("`js` function call or function with @JsFunc annotation expected", jsOrJsFuncCall) } } diff --git a/compiler/testData/debug/localVariables/jsCode.kt b/compiler/testData/debug/localVariables/jsCode.kt index 52f91ac7f4a..c054b18525e 100644 --- a/compiler/testData/debug/localVariables/jsCode.kt +++ b/compiler/testData/debug/localVariables/jsCode.kt @@ -64,27 +64,25 @@ fun box() { } // EXPECTATIONS -// test.kt:41 box: -// test.kt:42 box: a!="hello":kotlin.String -// a.kt:11 box: a!="hello":kotlin.String, b!="world":kotlin.String +// a.kt:11 box: // a.kt:6 exclamate: s="hello":kotlin.String -// a.kt:12 box: a!="hello":kotlin.String, b!="world":kotlin.String +// a.kt:12 box: // a.kt:6 exclamate: s="world":kotlin.String -// test.kt:43 box: a!="hello":kotlin.String, b!="world":kotlin.String -// test.kt:45 box: a!="hello":kotlin.String, b!="world":kotlin.String, jesse="Jesse":kotlin.String +// test.kt:43 box: +// test.kt:45 box: jesse="Jesse":kotlin.String // a.kt:6 exclamate: s="Jesse":kotlin.String -// test.kt:47 box: a!="hello":kotlin.String, b!="world":kotlin.String, jesse="Jesse":kotlin.String +// test.kt:47 box: jesse="Jesse":kotlin.String // a.kt:6 exclamate: s="Walter":kotlin.String -// test.kt:49 box: a!="hello":kotlin.String, b!="world":kotlin.String, jesse="Jesse":kotlin.String +// test.kt:49 box: jesse="Jesse":kotlin.String // a.kt:6 exclamate: s="Walter":kotlin.String -// test.kt:49 box: a!="hello":kotlin.String, b!="world":kotlin.String, jesse="Jesse":kotlin.String, walter1="Walter!":kotlin.String +// test.kt:49 box: jesse="Jesse":kotlin.String, walter1="Walter!":kotlin.String // a.kt:6 exclamate: s="Walter!":kotlin.String -// test.kt:52 box: a!="hello":kotlin.String, b!="world":kotlin.String, jesse="Jesse":kotlin.String, walter1="Walter!":kotlin.String +// test.kt:52 box: jesse="Jesse":kotlin.String, walter1="Walter!":kotlin.String // a.kt:6 exclamate: s="Jesse":kotlin.String -// test.kt:52 box: a!="hello":kotlin.String, b!="world":kotlin.String, jesse="Jesse":kotlin.String, walter1="Walter!":kotlin.String, jesse1="Jesse!":kotlin.String +// test.kt:52 box: jesse="Jesse":kotlin.String, walter1="Walter!":kotlin.String, jesse1="Jesse!":kotlin.String // a.kt:6 exclamate: s="Jesse!":kotlin.String -// a.kt:29 box: a!="hello":kotlin.String, b!="world":kotlin.String, jesse="Jesse":kotlin.String, walter1="Walter!":kotlin.String, jesse1="Jesse!":kotlin.String +// a.kt:29 box: jesse="Jesse":kotlin.String, walter1="Walter!":kotlin.String, jesse1="Jesse!":kotlin.String // a.kt:22 value: -// test.kt:63 box: a!="hello":kotlin.String, b!="world":kotlin.String, jesse="Jesse":kotlin.String, walter1="Walter!":kotlin.String, jesse1="Jesse!":kotlin.String +// test.kt:63 box: jesse="Jesse":kotlin.String, walter1="Walter!":kotlin.String, jesse1="Jesse!":kotlin.String // test.kt:59 localFun: hello="hello":kotlin.String, world="world":kotlin.String -// test.kt:64 box: a!="hello":kotlin.String, b!="world":kotlin.String, jesse="Jesse":kotlin.String, walter1="Walter!":kotlin.String, jesse1="Jesse!":kotlin.String +// test.kt:64 box: jesse="Jesse":kotlin.String, walter1="Walter!":kotlin.String, jesse1="Jesse!":kotlin.String \ No newline at end of file diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java index bc270e16190..34fe2a936e8 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java @@ -814,6 +814,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { runTest("js/js.translator/testData/box/coroutines/dynamicSuspendReturnWithOperator.kt"); } + @Test + @TestMetadata("jsCallInsideCoroutine.kt") + public void testJsCallInsideCoroutine() throws Exception { + runTest("js/js.translator/testData/box/coroutines/jsCallInsideCoroutine.kt"); + } + @Test @TestMetadata("kt54382.kt") public void testKt54382() throws Exception { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java index 22430babb5e..6db0f6ac32a 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java @@ -878,6 +878,12 @@ public class FirJsBoxTestGenerated extends AbstractFirJsBoxTest { runTest("js/js.translator/testData/box/coroutines/dynamicSuspendReturnWithOperator.kt"); } + @Test + @TestMetadata("jsCallInsideCoroutine.kt") + public void testJsCallInsideCoroutine() throws Exception { + runTest("js/js.translator/testData/box/coroutines/jsCallInsideCoroutine.kt"); + } + @Test @TestMetadata("kt54382.kt") public void testKt54382() throws Exception { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java index 90b48cfbfc6..1f50a3efea5 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java @@ -878,6 +878,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { runTest("js/js.translator/testData/box/coroutines/dynamicSuspendReturnWithOperator.kt"); } + @Test + @TestMetadata("jsCallInsideCoroutine.kt") + public void testJsCallInsideCoroutine() throws Exception { + runTest("js/js.translator/testData/box/coroutines/jsCallInsideCoroutine.kt"); + } + @Test @TestMetadata("kt54382.kt") public void testKt54382() throws Exception { diff --git a/js/js.translator/testData/box/coroutines/jsCallInsideCoroutine.kt b/js/js.translator/testData/box/coroutines/jsCallInsideCoroutine.kt new file mode 100644 index 00000000000..e3ce5c7eb04 --- /dev/null +++ b/js/js.translator/testData/box/coroutines/jsCallInsideCoroutine.kt @@ -0,0 +1,44 @@ +// KT-54134 +// WITH_STDLIB +// EXPECTED_REACHABLE_NODES: 1292 + +import kotlin.coroutines.* +import kotlin.coroutines.intrinsics.* + +external interface Test { + val test: String +} + +suspend fun smth(xx: T): T = xx + +suspend fun foo(): Test { + val x1 = smth(js("{}")) + val node = js("Object.assign({ test: 'O' }, x1)") + return smth(node) +} +suspend fun bar(): Test { + val x1 = smth(js("{}")) + val node = js(""" + x1.test = 'K' + Object.assign({}, x1) + """) + return smth(node) +} + +fun builder(c: suspend () -> Unit) { + c.startCoroutine(object : Continuation { + override val context = EmptyCoroutineContext + override fun resumeWith(result: Result) {} + }) +} + +fun box(): String { + var result = "" + + builder { + result += foo().test + result += bar().test + } + + return result +} \ No newline at end of file