KT-11875 Moves code that uncovers the following JS function:
```
function foo (closureParam, closureParam2, ...) {
return function() {
// function body
}
}
```
from FunctionContext to FunctionInlineMutator. The old code causes bug when calling this function like this: `foo(this, ...)`, since first `closureParam` is replaced with `this`. Later, FunctionInlineMutator can't make difference between original `this` inside *function body* and former `closureParam` reference that became `this` after replacement. So, let FunctionInlineMutator replace `this` in *function body* and then substitute closure parameters.
KT-11875 Fixed
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
package org.jetbrains.kotlin.js.inline
|
package org.jetbrains.kotlin.js.inline
|
||||||
|
|
||||||
import com.google.dart.compiler.backend.js.ast.*
|
import com.google.dart.compiler.backend.js.ast.*
|
||||||
|
import com.google.dart.compiler.backend.js.ast.metadata.staticRef
|
||||||
import com.google.dart.compiler.backend.js.ast.metadata.synthetic
|
import com.google.dart.compiler.backend.js.ast.metadata.synthetic
|
||||||
import org.jetbrains.kotlin.js.inline.clean.removeDefaultInitializers
|
import org.jetbrains.kotlin.js.inline.clean.removeDefaultInitializers
|
||||||
import org.jetbrains.kotlin.js.inline.context.InliningContext
|
import org.jetbrains.kotlin.js.inline.context.InliningContext
|
||||||
@@ -37,18 +38,16 @@ private constructor(
|
|||||||
private val currentStatement = inliningContext.statementContext.currentNode
|
private val currentStatement = inliningContext.statementContext.currentNode
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
||||||
val functionContext = inliningContext.functionContext
|
|
||||||
invokedFunction = functionContext.getFunctionDefinition(call)
|
|
||||||
body = invokedFunction.body.deepCopy()
|
|
||||||
namingContext = inliningContext.newNamingContext()
|
namingContext = inliningContext.newNamingContext()
|
||||||
|
val functionContext = inliningContext.functionContext
|
||||||
|
invokedFunction = uncoverClosure(functionContext.getFunctionDefinition(call).deepCopy())
|
||||||
|
body = invokedFunction.body
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun process() {
|
private fun process() {
|
||||||
val arguments = getArguments()
|
val arguments = getArguments()
|
||||||
val parameters = getParameters()
|
val parameters = getParameters()
|
||||||
|
|
||||||
replaceThis()
|
|
||||||
removeDefaultInitializers(arguments, parameters, body)
|
removeDefaultInitializers(arguments, parameters, body)
|
||||||
aliasArgumentsIfNeeded(namingContext, arguments, parameters)
|
aliasArgumentsIfNeeded(namingContext, arguments, parameters)
|
||||||
renameLocalNames(namingContext, invokedFunction)
|
renameLocalNames(namingContext, invokedFunction)
|
||||||
@@ -60,8 +59,41 @@ private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun replaceThis() {
|
private fun uncoverClosure(invokedFunction: JsFunction): JsFunction {
|
||||||
if (!hasThisReference(body)) return
|
val innerFunction = invokedFunction.getInnerFunction()
|
||||||
|
val innerCall = getInnerCall(call.qualifier)
|
||||||
|
return if (innerCall != null && innerFunction != null) {
|
||||||
|
innerFunction.apply {
|
||||||
|
replaceThis(body)
|
||||||
|
applyCapturedArgs(innerCall, this, invokedFunction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
invokedFunction.apply { replaceThis(body) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInnerCall(qualifier: JsExpression): JsInvocation? {
|
||||||
|
return when (qualifier) {
|
||||||
|
is JsInvocation -> qualifier
|
||||||
|
is JsNameRef -> {
|
||||||
|
val callee = if (qualifier.ident == "call") qualifier.qualifier else (qualifier.name?.staticRef as? JsExpression)
|
||||||
|
callee?.let { getInnerCall(it) }
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyCapturedArgs(call: JsInvocation, inner: JsFunction, outer: JsFunction) {
|
||||||
|
val namingContext = inliningContext.newNamingContext()
|
||||||
|
val arguments = call.arguments
|
||||||
|
val parameters = outer.parameters
|
||||||
|
aliasArgumentsIfNeeded(namingContext, arguments, parameters)
|
||||||
|
namingContext.applyRenameTo(inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun replaceThis(block: JsBlock) {
|
||||||
|
if (!hasThisReference(block)) return
|
||||||
|
|
||||||
var thisReplacement = getThisReplacement(call)
|
var thisReplacement = getThisReplacement(call)
|
||||||
if (thisReplacement == null || thisReplacement is JsLiteral.JsThisRef) return
|
if (thisReplacement == null || thisReplacement is JsLiteral.JsThisRef) return
|
||||||
@@ -72,7 +104,7 @@ private constructor(
|
|||||||
thisReplacement = thisName.makeRef()
|
thisReplacement = thisName.makeRef()
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceThisReference(body, thisReplacement)
|
replaceThisReference(block, thisReplacement)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processReturns() {
|
private fun processReturns() {
|
||||||
|
|||||||
@@ -175,8 +175,6 @@ public class JsInliner extends JsVisitorWithContextImpl {
|
|||||||
|
|
||||||
private void inline(@NotNull JsInvocation call, @NotNull JsContext context) {
|
private void inline(@NotNull JsInvocation call, @NotNull JsContext context) {
|
||||||
JsInliningContext inliningContext = getInliningContext();
|
JsInliningContext inliningContext = getInliningContext();
|
||||||
FunctionContext functionContext = getFunctionContext();
|
|
||||||
functionContext.declareFunctionConstructorCalls(call.getArguments());
|
|
||||||
InlineableResult inlineableResult = getInlineableCallReplacement(call, inliningContext);
|
InlineableResult inlineableResult = getInlineableCallReplacement(call, inliningContext);
|
||||||
|
|
||||||
JsStatement inlineableBody = inlineableResult.getInlineableBody();
|
JsStatement inlineableBody = inlineableResult.getInlineableBody();
|
||||||
@@ -256,7 +254,7 @@ public class JsInliner extends JsVisitorWithContextImpl {
|
|||||||
private final FunctionContext functionContext;
|
private final FunctionContext functionContext;
|
||||||
|
|
||||||
JsInliningContext(JsFunction function) {
|
JsInliningContext(JsFunction function) {
|
||||||
functionContext = new FunctionContext(function, this, functionReader) {
|
functionContext = new FunctionContext(function, functionReader) {
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
protected JsFunction lookUpStaticFunction(@Nullable JsName functionName) {
|
protected JsFunction lookUpStaticFunction(@Nullable JsName functionName) {
|
||||||
|
|||||||
@@ -19,29 +19,14 @@ package org.jetbrains.kotlin.js.inline.context
|
|||||||
import com.google.dart.compiler.backend.js.ast.*
|
import com.google.dart.compiler.backend.js.ast.*
|
||||||
import com.google.dart.compiler.backend.js.ast.metadata.staticRef
|
import com.google.dart.compiler.backend.js.ast.metadata.staticRef
|
||||||
|
|
||||||
import org.jetbrains.kotlin.js.inline.util.aliasArgumentsIfNeeded
|
|
||||||
import org.jetbrains.kotlin.js.inline.util.getInnerFunction
|
|
||||||
import org.jetbrains.kotlin.js.inline.util.getSimpleName
|
|
||||||
import org.jetbrains.kotlin.js.inline.util.isCallInvocation
|
|
||||||
import org.jetbrains.kotlin.js.inline.util.isFunctionCreator
|
|
||||||
|
|
||||||
import com.intellij.util.containers.ContainerUtil
|
|
||||||
import java.util.IdentityHashMap
|
|
||||||
import org.jetbrains.kotlin.js.inline.FunctionReader
|
import org.jetbrains.kotlin.js.inline.FunctionReader
|
||||||
import com.google.dart.compiler.backend.js.ast.metadata.descriptor
|
import com.google.dart.compiler.backend.js.ast.metadata.descriptor
|
||||||
|
import org.jetbrains.kotlin.js.inline.util.*
|
||||||
|
|
||||||
abstract class FunctionContext(
|
abstract class FunctionContext(
|
||||||
private val function: JsFunction,
|
private val function: JsFunction,
|
||||||
private val inliningContext: InliningContext,
|
|
||||||
private val functionReader: FunctionReader
|
private val functionReader: FunctionReader
|
||||||
) {
|
) {
|
||||||
/**
|
|
||||||
* Caches function with captured arguments applied.
|
|
||||||
*
|
|
||||||
* @see getFunctionWithClosure
|
|
||||||
*/
|
|
||||||
private val functionsWithClosure = IdentityHashMap<JsInvocation, JsFunction?>()
|
|
||||||
|
|
||||||
protected abstract fun lookUpStaticFunction(functionName: JsName?): JsFunction?
|
protected abstract fun lookUpStaticFunction(functionName: JsName?): JsFunction?
|
||||||
|
|
||||||
fun getFunctionDefinition(call: JsInvocation): JsFunction {
|
fun getFunctionDefinition(call: JsInvocation): JsFunction {
|
||||||
@@ -56,27 +41,6 @@ abstract class FunctionContext(
|
|||||||
return function.scope
|
return function.scope
|
||||||
}
|
}
|
||||||
|
|
||||||
fun declareFunctionConstructorCalls(arguments: List<JsExpression>) {
|
|
||||||
val calls = ContainerUtil.findAll<JsExpression, JsInvocation>(arguments, JsInvocation::class.java)
|
|
||||||
|
|
||||||
for (call in calls) {
|
|
||||||
val callName = getSimpleName(call)
|
|
||||||
if (callName == null) continue
|
|
||||||
|
|
||||||
val staticRef = callName.staticRef
|
|
||||||
if (staticRef !is JsFunction) continue
|
|
||||||
|
|
||||||
val functionCalled = staticRef
|
|
||||||
if (isFunctionCreator(functionCalled)) {
|
|
||||||
declareFunctionConstructorCall(call)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun declareFunctionConstructorCall(call: JsInvocation) {
|
|
||||||
functionsWithClosure.put(call, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets function definition by invocation.
|
* Gets function definition by invocation.
|
||||||
*
|
*
|
||||||
@@ -135,57 +99,9 @@ abstract class FunctionContext(
|
|||||||
/** process cases 2, 3 */
|
/** process cases 2, 3 */
|
||||||
val qualifier = callQualifier
|
val qualifier = callQualifier
|
||||||
return when (qualifier) {
|
return when (qualifier) {
|
||||||
is JsInvocation -> getFunctionWithClosure(qualifier)
|
is JsInvocation -> lookUpStaticFunction(getSimpleName(qualifier)!!)
|
||||||
is JsNameRef -> lookUpStaticFunction(qualifier.name)
|
is JsNameRef -> lookUpStaticFunction(qualifier.name)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets function body with captured args applied,
|
|
||||||
* and stores in cache.
|
|
||||||
*
|
|
||||||
* Function literals and local functions with closure
|
|
||||||
* are translated as function, that returns function.
|
|
||||||
*
|
|
||||||
* For example,
|
|
||||||
* val a = 1
|
|
||||||
* val f = { a * 2 }
|
|
||||||
* `f` becomes
|
|
||||||
* f: function (a) {
|
|
||||||
* return function () { return a * 2 }
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* @returns inner function with captured parameters,
|
|
||||||
* replaced by outer arguments
|
|
||||||
*
|
|
||||||
* For invocation `f(10)()` returns
|
|
||||||
* `function () { return 10 * 2 }`
|
|
||||||
*/
|
|
||||||
private fun getFunctionWithClosure(call: JsInvocation): JsFunction {
|
|
||||||
val constructed = functionsWithClosure.get(call)
|
|
||||||
|
|
||||||
if (constructed is JsFunction) return constructed
|
|
||||||
|
|
||||||
val name = getSimpleName(call)!!
|
|
||||||
val closureCreator = lookUpStaticFunction(name)!!
|
|
||||||
val innerFunction = closureCreator.getInnerFunction()!!
|
|
||||||
|
|
||||||
val withCapturedArgs = applyCapturedArgs(call, innerFunction, closureCreator)
|
|
||||||
functionsWithClosure.put(call, withCapturedArgs)
|
|
||||||
|
|
||||||
return withCapturedArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun applyCapturedArgs(call: JsInvocation, inner: JsFunction, outer: JsFunction): JsFunction {
|
|
||||||
val innerClone = inner.deepCopy()
|
|
||||||
|
|
||||||
val namingContext = inliningContext.newNamingContext()
|
|
||||||
val arguments = call.arguments
|
|
||||||
val parameters = outer.parameters
|
|
||||||
aliasArgumentsIfNeeded(namingContext, arguments, parameters)
|
|
||||||
namingContext.applyRenameTo(innerClone)
|
|
||||||
|
|
||||||
return innerClone
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,6 +209,12 @@ public class InlineJsTestGenerated extends AbstractInlineJsTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("innerOuterThis.kt")
|
||||||
|
public void testInnerOuterThis() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inline/cases/innerOuterThis.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("jsCode.kt")
|
@TestMetadata("jsCode.kt")
|
||||||
public void testJsCode() throws Exception {
|
public void testJsCode() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inline/cases/jsCode.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inline/cases/jsCode.kt");
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
inline fun<T> with1(value: T, p: T.() -> Unit) = value.p()
|
||||||
|
|
||||||
|
class A(val expected: String) {
|
||||||
|
val b = B()
|
||||||
|
|
||||||
|
fun foo(): A {
|
||||||
|
with1(b) {
|
||||||
|
y = expected
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class B() {
|
||||||
|
var y = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun box() = A("OK").foo().b.y
|
||||||
Reference in New Issue
Block a user