[K/JS] Change js-call inlining strategy and + ability to referencethis safely from the js call ^Fixed KT-54134
This commit is contained in:
+17
-50
@@ -7,75 +7,42 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.backend.common.compilationException
|
import org.jetbrains.kotlin.backend.common.compilationException
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext
|
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.expressions.IrCall
|
||||||
import org.jetbrains.kotlin.ir.util.file
|
|
||||||
import org.jetbrains.kotlin.js.backend.ast.*
|
import org.jetbrains.kotlin.js.backend.ast.*
|
||||||
|
|
||||||
private typealias Replacement = Pair<JsExpression, IrValueParameter>
|
|
||||||
|
|
||||||
class FunctionWithJsFuncAnnotationInliner(private val jsFuncCall: IrCall, private val context: JsGenerationContext) {
|
class FunctionWithJsFuncAnnotationInliner(private val jsFuncCall: IrCall, private val context: JsGenerationContext) {
|
||||||
private val function = getJsFunctionImplementation()
|
private val function = getJsFunctionImplementation()
|
||||||
private val replacements = collectReplacementsForCall()
|
private val replacements = collectReplacementsForCall()
|
||||||
|
|
||||||
fun generateResultStatement(): List<JsStatement> {
|
fun generateResultStatement(): List<JsStatement> =
|
||||||
val irFunction = jsFuncCall.symbol.owner
|
function.body.statements.also {
|
||||||
val newContext = context.newFile(irFunction.file, irFunction, context.localNames)
|
JsNameRemappingTransformer(replacements).apply { acceptList(it) }
|
||||||
return function.body.statements
|
}
|
||||||
.run {
|
|
||||||
SimpleJsCodeInliner(replacements, newContext)
|
|
||||||
.apply { acceptList(this@run) }
|
|
||||||
.withTemporaryVariablesForExpressions(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getJsFunctionImplementation(): JsFunction =
|
private fun getJsFunctionImplementation(): JsFunction =
|
||||||
context.staticContext.backendContext.getJsCodeForFunction(jsFuncCall.symbol)?.deepCopy()
|
context.staticContext.backendContext.getJsCodeForFunction(jsFuncCall.symbol)?.deepCopy()
|
||||||
?: compilationException("JS function not found", jsFuncCall)
|
?: compilationException("JS function not found", jsFuncCall)
|
||||||
|
|
||||||
private fun collectReplacementsForCall(): Map<JsName, Replacement> {
|
private fun collectReplacementsForCall(): Map<JsName, JsExpression> {
|
||||||
val translatedArguments = Array(jsFuncCall.valueArgumentsCount) {
|
val translatedArguments = List(jsFuncCall.valueArgumentsCount) {
|
||||||
jsFuncCall.getValueArgument(it)!!
|
jsFuncCall.getValueArgument(it)!!
|
||||||
.accept(IrElementToJsExpressionTransformer(), context) to jsFuncCall.symbol.owner.valueParameters[it]
|
.accept(IrElementToJsExpressionTransformer(), context)
|
||||||
}
|
}
|
||||||
return function.parameters
|
return function.parameters
|
||||||
.mapIndexed { i, param -> param.name to translatedArguments[i] }
|
.map { it.name }
|
||||||
|
.zip(translatedArguments)
|
||||||
.toMap()
|
.toMap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SimpleJsCodeInliner(private val replacements: Map<JsName, Replacement>, val context: JsGenerationContext) :
|
private class JsNameRemappingTransformer(private val replacements: Map<JsName, JsExpression>) : JsVisitorWithContextImpl() {
|
||||||
RecursiveJsVisitor()
|
private val JsName.replacement: JsExpression? get() = replacements[this]
|
||||||
{
|
|
||||||
private val temporaryNamesForExpressions = mutableMapOf<JsName, Replacement>()
|
|
||||||
|
|
||||||
fun withTemporaryVariablesForExpressions(statements: List<JsStatement>): List<JsStatement> {
|
override fun visit(nameRef: JsNameRef, ctx: JsContext<JsNode>): Boolean {
|
||||||
if (temporaryNamesForExpressions.isEmpty()) {
|
super.visit(nameRef, ctx)
|
||||||
return statements
|
if (nameRef.qualifier != null) return true
|
||||||
}
|
val replacement = nameRef.name?.replacement ?: return true
|
||||||
|
ctx.replaceMe(replacement)
|
||||||
val variableDeclarations = temporaryNamesForExpressions.map {
|
return false
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-1
@@ -7,7 +7,9 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.backend.common.compilationException
|
import org.jetbrains.kotlin.backend.common.compilationException
|
||||||
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.emptyScope
|
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.ir.expressions.IrCall
|
||||||
import org.jetbrains.kotlin.js.backend.ast.*
|
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), "")
|
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<JsStatement> {
|
private fun getJsStatements(): List<JsStatement> {
|
||||||
@@ -78,6 +90,7 @@ class JsCallTransformer(private val jsOrJsFuncCall: IrCall, private val context:
|
|||||||
context.checkIfHasAssociatedJsCode(jsOrJsFuncCall.symbol) ->
|
context.checkIfHasAssociatedJsCode(jsOrJsFuncCall.symbol) ->
|
||||||
FunctionWithJsFuncAnnotationInliner(jsOrJsFuncCall, context).generateResultStatement()
|
FunctionWithJsFuncAnnotationInliner(jsOrJsFuncCall, context).generateResultStatement()
|
||||||
|
|
||||||
|
|
||||||
else -> compilationException("`js` function call or function with @JsFunc annotation expected", jsOrJsFuncCall)
|
else -> compilationException("`js` function call or function with @JsFunc annotation expected", jsOrJsFuncCall)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-14
@@ -64,27 +64,25 @@ fun box() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EXPECTATIONS
|
// EXPECTATIONS
|
||||||
// test.kt:41 box:
|
// a.kt:11 box:
|
||||||
// test.kt:42 box: a!="hello":kotlin.String
|
|
||||||
// a.kt:11 box: a!="hello":kotlin.String, b!="world":kotlin.String
|
|
||||||
// a.kt:6 exclamate: s="hello":kotlin.String
|
// 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
|
// a.kt:6 exclamate: s="world":kotlin.String
|
||||||
// test.kt:43 box: a!="hello":kotlin.String, b!="world":kotlin.String
|
// test.kt:43 box:
|
||||||
// test.kt:45 box: a!="hello":kotlin.String, b!="world":kotlin.String, jesse="Jesse":kotlin.String
|
// test.kt:45 box: jesse="Jesse":kotlin.String
|
||||||
// a.kt:6 exclamate: s="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
|
// 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
|
// 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
|
// 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
|
// 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: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:
|
// 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: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
|
||||||
@@ -814,6 +814,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
|||||||
runTest("js/js.translator/testData/box/coroutines/dynamicSuspendReturnWithOperator.kt");
|
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
|
@Test
|
||||||
@TestMetadata("kt54382.kt")
|
@TestMetadata("kt54382.kt")
|
||||||
public void testKt54382() throws Exception {
|
public void testKt54382() throws Exception {
|
||||||
|
|||||||
+6
@@ -878,6 +878,12 @@ public class FirJsBoxTestGenerated extends AbstractFirJsBoxTest {
|
|||||||
runTest("js/js.translator/testData/box/coroutines/dynamicSuspendReturnWithOperator.kt");
|
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
|
@Test
|
||||||
@TestMetadata("kt54382.kt")
|
@TestMetadata("kt54382.kt")
|
||||||
public void testKt54382() throws Exception {
|
public void testKt54382() throws Exception {
|
||||||
|
|||||||
+6
@@ -878,6 +878,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
|
|||||||
runTest("js/js.translator/testData/box/coroutines/dynamicSuspendReturnWithOperator.kt");
|
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
|
@Test
|
||||||
@TestMetadata("kt54382.kt")
|
@TestMetadata("kt54382.kt")
|
||||||
public void testKt54382() throws Exception {
|
public void testKt54382() throws Exception {
|
||||||
|
|||||||
@@ -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 <T> 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<Unit> {
|
||||||
|
override val context = EmptyCoroutineContext
|
||||||
|
override fun resumeWith(result: Result<Unit>) {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
var result = ""
|
||||||
|
|
||||||
|
builder {
|
||||||
|
result += foo().test
|
||||||
|
result += bar().test
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user