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
|
||||
|
||||
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 org.jetbrains.kotlin.js.inline.clean.removeDefaultInitializers
|
||||
import org.jetbrains.kotlin.js.inline.context.InliningContext
|
||||
@@ -37,18 +38,16 @@ private constructor(
|
||||
private val currentStatement = inliningContext.statementContext.currentNode
|
||||
|
||||
init {
|
||||
|
||||
val functionContext = inliningContext.functionContext
|
||||
invokedFunction = functionContext.getFunctionDefinition(call)
|
||||
body = invokedFunction.body.deepCopy()
|
||||
namingContext = inliningContext.newNamingContext()
|
||||
val functionContext = inliningContext.functionContext
|
||||
invokedFunction = uncoverClosure(functionContext.getFunctionDefinition(call).deepCopy())
|
||||
body = invokedFunction.body
|
||||
}
|
||||
|
||||
private fun process() {
|
||||
val arguments = getArguments()
|
||||
val parameters = getParameters()
|
||||
|
||||
replaceThis()
|
||||
removeDefaultInitializers(arguments, parameters, body)
|
||||
aliasArgumentsIfNeeded(namingContext, arguments, parameters)
|
||||
renameLocalNames(namingContext, invokedFunction)
|
||||
@@ -60,8 +59,41 @@ private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun replaceThis() {
|
||||
if (!hasThisReference(body)) return
|
||||
private fun uncoverClosure(invokedFunction: JsFunction): JsFunction {
|
||||
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)
|
||||
if (thisReplacement == null || thisReplacement is JsLiteral.JsThisRef) return
|
||||
@@ -72,7 +104,7 @@ private constructor(
|
||||
thisReplacement = thisName.makeRef()
|
||||
}
|
||||
|
||||
replaceThisReference(body, thisReplacement)
|
||||
replaceThisReference(block, thisReplacement)
|
||||
}
|
||||
|
||||
private fun processReturns() {
|
||||
|
||||
@@ -175,8 +175,6 @@ public class JsInliner extends JsVisitorWithContextImpl {
|
||||
|
||||
private void inline(@NotNull JsInvocation call, @NotNull JsContext context) {
|
||||
JsInliningContext inliningContext = getInliningContext();
|
||||
FunctionContext functionContext = getFunctionContext();
|
||||
functionContext.declareFunctionConstructorCalls(call.getArguments());
|
||||
InlineableResult inlineableResult = getInlineableCallReplacement(call, inliningContext);
|
||||
|
||||
JsStatement inlineableBody = inlineableResult.getInlineableBody();
|
||||
@@ -256,7 +254,7 @@ public class JsInliner extends JsVisitorWithContextImpl {
|
||||
private final FunctionContext functionContext;
|
||||
|
||||
JsInliningContext(JsFunction function) {
|
||||
functionContext = new FunctionContext(function, this, functionReader) {
|
||||
functionContext = new FunctionContext(function, functionReader) {
|
||||
@Nullable
|
||||
@Override
|
||||
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.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 com.google.dart.compiler.backend.js.ast.metadata.descriptor
|
||||
import org.jetbrains.kotlin.js.inline.util.*
|
||||
|
||||
abstract class FunctionContext(
|
||||
private val function: JsFunction,
|
||||
private val inliningContext: InliningContext,
|
||||
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?
|
||||
|
||||
fun getFunctionDefinition(call: JsInvocation): JsFunction {
|
||||
@@ -56,27 +41,6 @@ abstract class FunctionContext(
|
||||
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.
|
||||
*
|
||||
@@ -135,57 +99,9 @@ abstract class FunctionContext(
|
||||
/** process cases 2, 3 */
|
||||
val qualifier = callQualifier
|
||||
return when (qualifier) {
|
||||
is JsInvocation -> getFunctionWithClosure(qualifier)
|
||||
is JsInvocation -> lookUpStaticFunction(getSimpleName(qualifier)!!)
|
||||
is JsNameRef -> lookUpStaticFunction(qualifier.name)
|
||||
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);
|
||||
}
|
||||
|
||||
@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")
|
||||
public void testJsCode() throws Exception {
|
||||
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