KT-12928: decompose invocation like "a.foo(b)" to "$t = a; $s = $t.foo.bind($t); $s(b)" instead of "$t = a.foo; $t(b)", since in the latter case foo won't receive proper this. Add optimization that replaces "foo.bar.bind(baz)(args)" with "baz.bar(args)"
This commit is contained in:
@@ -20,6 +20,7 @@ import com.google.dart.compiler.backend.js.ast.*
|
||||
import com.google.dart.compiler.backend.js.ast.metadata.*
|
||||
import com.intellij.util.SmartList
|
||||
import org.jetbrains.kotlin.js.inline.util.IdentitySet
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.*
|
||||
import org.jetbrains.kotlin.js.translate.utils.jsAstUtils.*
|
||||
|
||||
@@ -240,6 +241,7 @@ internal class ExpressionDecomposer private constructor(
|
||||
|
||||
private abstract class Callable(hasArguments: HasArguments) {
|
||||
abstract var qualifier: JsExpression
|
||||
abstract val applyBindIfNecessary: Boolean
|
||||
val arguments = hasArguments.arguments
|
||||
}
|
||||
|
||||
@@ -247,12 +249,14 @@ internal class ExpressionDecomposer private constructor(
|
||||
override var qualifier: JsExpression
|
||||
get() = invocation.qualifier
|
||||
set(value) { invocation.qualifier = value }
|
||||
override val applyBindIfNecessary = true
|
||||
}
|
||||
|
||||
private class CallableNewAdapter(val jsnew: JsNew) : Callable(jsnew) {
|
||||
override var qualifier: JsExpression
|
||||
get() = jsnew.constructorExpression
|
||||
set(value) { jsnew.constructorExpression = value }
|
||||
override val applyBindIfNecessary = false
|
||||
}
|
||||
|
||||
private fun Callable.process() {
|
||||
@@ -267,13 +271,17 @@ internal class ExpressionDecomposer private constructor(
|
||||
// Qualifier might be a reference to lambda property. See KT-7674
|
||||
// An exception here is `fn.call()`, which are marked as side effect free. Further recognition of such
|
||||
// case in inliner might be quite difficult, so never extract such call (and other calls marked this way).
|
||||
if ((qualifier !in containsNodeWithSideEffect || (qualifier as? HasMetadata)?.sideEffects == SideEffectKind.PURE) &&
|
||||
(callee != null && receiver != null && receiver in containsNodeWithSideEffect)
|
||||
) {
|
||||
if ((qualifier as? HasMetadata)?.sideEffects == SideEffectKind.PURE &&
|
||||
callee != null && receiver != null && receiver in containsNodeWithSideEffect
|
||||
) {
|
||||
val receiverTmp = receiver.extractToTemporary()
|
||||
callee.qualifier = receiverTmp
|
||||
}
|
||||
else {
|
||||
if (receiver != null && callee != null && applyBindIfNecessary) {
|
||||
val receiverTmp = receiver.extractToTemporary()
|
||||
qualifier = JsAstUtils.invokeBind(receiverTmp, pureFqn(callee.ident, receiverTmp))
|
||||
}
|
||||
qualifier = qualifier.extractToTemporary()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ class FunctionPostProcessor(root: JsFunction) {
|
||||
{ TemporaryAssignmentElimination(root.body).apply() },
|
||||
{ RedundantLabelRemoval(root.body).apply() },
|
||||
{ TemporaryVariableElimination(root).apply() },
|
||||
{ RedundantBindElimination(root.body).apply() },
|
||||
{ IfStatementReduction(root.body).apply() },
|
||||
{ DeadCodeElimination(root.body).apply() },
|
||||
{ RedundantVariableDeclarationElimination(root.body).apply() },
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.js.inline.clean
|
||||
|
||||
import com.google.dart.compiler.backend.js.ast.JsBlock
|
||||
import com.google.dart.compiler.backend.js.ast.JsInvocation
|
||||
import com.google.dart.compiler.backend.js.ast.JsNameRef
|
||||
import com.google.dart.compiler.backend.js.ast.RecursiveJsVisitor
|
||||
|
||||
// TODO: this optimization is a little unfair. It tries to recognize pattern like this a.bind(b)(args) and
|
||||
// replace it with b.a(args). However, we can't be completely sure that `a` is a Function.
|
||||
// Using JS-independent AST should solve the issue (as well as many other issues).
|
||||
class RedundantBindElimination(private val root: JsBlock) {
|
||||
private var changed = false
|
||||
|
||||
fun apply(): Boolean {
|
||||
root.accept(object : RecursiveJsVisitor() {
|
||||
override fun visitInvocation(invocation: JsInvocation) {
|
||||
tryEliminate(invocation)
|
||||
super.visitInvocation(invocation)
|
||||
}
|
||||
|
||||
private fun tryEliminate(invocation: JsInvocation) {
|
||||
val qualifier = invocation.qualifier
|
||||
if (qualifier !is JsInvocation) return
|
||||
|
||||
val outerQualifier = qualifier.qualifier
|
||||
if (outerQualifier !is JsNameRef) return
|
||||
val name = outerQualifier.name?.ident ?: outerQualifier.ident
|
||||
if (name != "bind") return
|
||||
|
||||
val qualifierReplacement = outerQualifier.qualifier ?: return
|
||||
invocation.qualifier = qualifierReplacement
|
||||
changed = true
|
||||
}
|
||||
})
|
||||
|
||||
return changed
|
||||
}
|
||||
}
|
||||
+6
@@ -221,6 +221,12 @@ public class InlineEvaluationOrderTestGenerated extends AbstractInlineEvaluation
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("methodDecomposedWithBind.kt")
|
||||
public void testMethodDecomposedWithBind() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inlineEvaluationOrder/cases/methodDecomposedWithBind.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("methodInlineCallQualifierWithSideEffect.kt")
|
||||
public void testMethodInlineCallQualifierWithSideEffect() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inlineEvaluationOrder/cases/methodInlineCallQualifierWithSideEffect.kt");
|
||||
|
||||
+12
@@ -59,6 +59,18 @@ public class InlineMultiModuleTestGenerated extends AbstractInlineMultiModuleTes
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("externalInlineCallDecomposed")
|
||||
public void testExternalInlineCallDecomposed() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inlineMultiModule/cases/externalInlineCallDecomposed/");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("externalInlineNewDecomposed")
|
||||
public void testExternalInlineNewDecomposed() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inlineMultiModule/cases/externalInlineNewDecomposed/");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("lambda")
|
||||
public void testLambda() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inlineMultiModule/cases/lambda/");
|
||||
|
||||
@@ -490,6 +490,11 @@ public final class JsAstUtils {
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JsInvocation invokeBind(@NotNull JsExpression receiver, @NotNull JsExpression method) {
|
||||
return invokeMethod(method, "bind", receiver);
|
||||
}
|
||||
|
||||
public static boolean isUndefinedExpression(JsExpression expression) {
|
||||
if (!(expression instanceof JsUnaryOperation)) return false;
|
||||
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package foo
|
||||
|
||||
fun box(): String {
|
||||
val v = mapOf(1 to "1", 2 to "2").mapValues { it.value.map { it.toString() } }
|
||||
assertEquals(2, v.size)
|
||||
|
||||
return "OK"
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package lib
|
||||
|
||||
var global = ""
|
||||
|
||||
inline fun baz(x: () -> Int) = A(1).bar(x())
|
||||
|
||||
class A(val y: Int) {
|
||||
fun bar(x: Int) = x + y
|
||||
}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
package foo
|
||||
|
||||
import lib.*
|
||||
|
||||
fun qqq(): Int {
|
||||
global += "qqq;"
|
||||
return 23
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
assertEquals(24, baz {
|
||||
global += "before;"
|
||||
val result = qqq()
|
||||
global += "after;"
|
||||
result
|
||||
})
|
||||
assertEquals("before;qqq;after;", global)
|
||||
|
||||
return "OK"
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package lib
|
||||
|
||||
var global = ""
|
||||
|
||||
inline fun baz(x: () -> Int) = ((A(1).B(x()) as Any) as A.B).bar()
|
||||
|
||||
class A(val y: Int) {
|
||||
inner class B(val x: Int) {
|
||||
fun bar() = x + y
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package foo
|
||||
|
||||
import lib.*
|
||||
|
||||
fun qqq(): Int {
|
||||
global += "qqq;"
|
||||
return 23
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
assertEquals(24, baz {
|
||||
global += "before;"
|
||||
val result = qqq()
|
||||
global += "after;"
|
||||
result
|
||||
})
|
||||
assertEquals("before;qqq;after;", global)
|
||||
|
||||
return "OK"
|
||||
}
|
||||
Reference in New Issue
Block a user