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.google.dart.compiler.backend.js.ast.metadata.*
|
||||||
import com.intellij.util.SmartList
|
import com.intellij.util.SmartList
|
||||||
import org.jetbrains.kotlin.js.inline.util.IdentitySet
|
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.*
|
||||||
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) {
|
private abstract class Callable(hasArguments: HasArguments) {
|
||||||
abstract var qualifier: JsExpression
|
abstract var qualifier: JsExpression
|
||||||
|
abstract val applyBindIfNecessary: Boolean
|
||||||
val arguments = hasArguments.arguments
|
val arguments = hasArguments.arguments
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,12 +249,14 @@ internal class ExpressionDecomposer private constructor(
|
|||||||
override var qualifier: JsExpression
|
override var qualifier: JsExpression
|
||||||
get() = invocation.qualifier
|
get() = invocation.qualifier
|
||||||
set(value) { invocation.qualifier = value }
|
set(value) { invocation.qualifier = value }
|
||||||
|
override val applyBindIfNecessary = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CallableNewAdapter(val jsnew: JsNew) : Callable(jsnew) {
|
private class CallableNewAdapter(val jsnew: JsNew) : Callable(jsnew) {
|
||||||
override var qualifier: JsExpression
|
override var qualifier: JsExpression
|
||||||
get() = jsnew.constructorExpression
|
get() = jsnew.constructorExpression
|
||||||
set(value) { jsnew.constructorExpression = value }
|
set(value) { jsnew.constructorExpression = value }
|
||||||
|
override val applyBindIfNecessary = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Callable.process() {
|
private fun Callable.process() {
|
||||||
@@ -267,13 +271,17 @@ internal class ExpressionDecomposer private constructor(
|
|||||||
// Qualifier might be a reference to lambda property. See KT-7674
|
// 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
|
// 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).
|
// 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) &&
|
if ((qualifier as? HasMetadata)?.sideEffects == SideEffectKind.PURE &&
|
||||||
(callee != null && receiver != null && receiver in containsNodeWithSideEffect)
|
callee != null && receiver != null && receiver in containsNodeWithSideEffect
|
||||||
) {
|
) {
|
||||||
val receiverTmp = receiver.extractToTemporary()
|
val receiverTmp = receiver.extractToTemporary()
|
||||||
callee.qualifier = receiverTmp
|
callee.qualifier = receiverTmp
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if (receiver != null && callee != null && applyBindIfNecessary) {
|
||||||
|
val receiverTmp = receiver.extractToTemporary()
|
||||||
|
qualifier = JsAstUtils.invokeBind(receiverTmp, pureFqn(callee.ident, receiverTmp))
|
||||||
|
}
|
||||||
qualifier = qualifier.extractToTemporary()
|
qualifier = qualifier.extractToTemporary()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class FunctionPostProcessor(root: JsFunction) {
|
|||||||
{ TemporaryAssignmentElimination(root.body).apply() },
|
{ TemporaryAssignmentElimination(root.body).apply() },
|
||||||
{ RedundantLabelRemoval(root.body).apply() },
|
{ RedundantLabelRemoval(root.body).apply() },
|
||||||
{ TemporaryVariableElimination(root).apply() },
|
{ TemporaryVariableElimination(root).apply() },
|
||||||
|
{ RedundantBindElimination(root.body).apply() },
|
||||||
{ IfStatementReduction(root.body).apply() },
|
{ IfStatementReduction(root.body).apply() },
|
||||||
{ DeadCodeElimination(root.body).apply() },
|
{ DeadCodeElimination(root.body).apply() },
|
||||||
{ RedundantVariableDeclarationElimination(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);
|
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")
|
@TestMetadata("methodInlineCallQualifierWithSideEffect.kt")
|
||||||
public void testMethodInlineCallQualifierWithSideEffect() throws Exception {
|
public void testMethodInlineCallQualifierWithSideEffect() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inlineEvaluationOrder/cases/methodInlineCallQualifierWithSideEffect.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inlineEvaluationOrder/cases/methodInlineCallQualifierWithSideEffect.kt");
|
||||||
|
|||||||
+12
@@ -59,6 +59,18 @@ public class InlineMultiModuleTestGenerated extends AbstractInlineMultiModuleTes
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("lambda")
|
||||||
public void testLambda() throws Exception {
|
public void testLambda() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inlineMultiModule/cases/lambda/");
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inlineMultiModule/cases/lambda/");
|
||||||
|
|||||||
@@ -490,6 +490,11 @@ public final class JsAstUtils {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static JsInvocation invokeBind(@NotNull JsExpression receiver, @NotNull JsExpression method) {
|
||||||
|
return invokeMethod(method, "bind", receiver);
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isUndefinedExpression(JsExpression expression) {
|
public static boolean isUndefinedExpression(JsExpression expression) {
|
||||||
if (!(expression instanceof JsUnaryOperation)) return false;
|
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