diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/FunctionInlineMutator.java b/js/js.inliner/src/org/jetbrains/k2js/inline/FunctionInlineMutator.java index 769bb890523..1a387ac568f 100644 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/FunctionInlineMutator.java +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/FunctionInlineMutator.java @@ -20,22 +20,26 @@ import com.google.dart.compiler.backend.js.ast.*; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; +import static org.jetbrains.k2js.inline.InlinePackage.needToAlias; import static org.jetbrains.k2js.inline.InlinePackage.aliasArgumentsIfNeeded; import static org.jetbrains.k2js.inline.InlinePackage.renameLocals; +import static org.jetbrains.k2js.inline.InlinePackage.replaceThisReference; +import static org.jetbrains.k2js.inline.InlinePackage.hasThisReference; import java.util.*; class FunctionInlineMutator { private static final String BREAK_LABEL = "break_inlined"; private static final String RESULT_LABEL = "result_inlined"; + private static final String THIS_ALIAS = "self"; - private JsBlock body; - private final boolean isResultNeeded; + private final JsInvocation call; + private final InliningContext inliningContext; private final JsFunction invokedFunction; - private final List arguments; - private final List parameters; + private final boolean isResultNeeded; private final RenamingContext renamingContext; private final InsertionPoint insertionPoint; + private JsBlock body; private JsNameRef resultExpr = null; private JsLabel breakLabel = null; @@ -61,23 +65,44 @@ class FunctionInlineMutator { } private FunctionInlineMutator(@NotNull JsInvocation call, @NotNull InliningContext inliningContext) { + this.inliningContext = inliningContext; + this.call = call; + FunctionContext functionContext = inliningContext.getFunctionContext(); invokedFunction = functionContext.getFunctionDefinition(call); body = invokedFunction.getBody().deepCopy(); isResultNeeded = inliningContext.isResultNeeded(call); - arguments = call.getArguments(); - parameters = invokedFunction.getParameters(); renamingContext = inliningContext.getRenamingContext(); insertionPoint = inliningContext.getStatementContext().getInsertionPoint(); } private void process() { - aliasArgumentsIfNeeded(renamingContext, arguments, parameters); + replaceThis(); + aliasArgumentsIfNeeded(renamingContext, getArguments(), getParameters()); renameLocals(renamingContext, invokedFunction); applyRenaming(); replaceReturns(); } + private void replaceThis() { + if (!hasThisReference(body)) { + return; + } + + JsExpression thisReplacement = inliningContext.getThisReplacement(call); + if (thisReplacement == JsLiteral.THIS) { + return; + } + + if (needToAlias(thisReplacement)) { + JsName thisName = renamingContext.getFreshName(THIS_ALIAS); + renamingContext.newVar(thisName, thisReplacement); + thisReplacement = thisName.makeRef(); + } + + replaceThisReference(body, thisReplacement); + } + private void applyRenaming() { RenamingResult renamingResult = renamingContext.applyRename(body); body = renamingResult.getRenamed(); @@ -117,4 +142,14 @@ class FunctionInlineMutator { assert resultExpr == null || resultExpr instanceof JsNameRef; ReplaceReturnVisitor.replaceReturn(body, (JsNameRef) resultExpr, breakLabelRef); } + + @NotNull + private List getArguments() { + return inliningContext.getArguments(call); + } + + @NotNull + private List getParameters() { + return invokedFunction.getParameters(); + } } diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/InliningContext.java b/js/js.inliner/src/org/jetbrains/k2js/inline/InliningContext.java index b1be431d3d6..a5adce441bf 100644 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/InliningContext.java +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/InliningContext.java @@ -19,6 +19,8 @@ package org.jetbrains.k2js.inline; import com.google.dart.compiler.backend.js.ast.*; import org.jetbrains.annotations.NotNull; +import java.util.List; + interface InliningContext { @NotNull RenamingContext getRenamingContext(); @@ -29,5 +31,11 @@ interface InliningContext { @NotNull FunctionContext getFunctionContext(); + @NotNull + JsExpression getThisReplacement(JsInvocation call); + + @NotNull + List getArguments(JsInvocation call); + boolean isResultNeeded(JsInvocation call); } diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/InvocationUtil.java b/js/js.inliner/src/org/jetbrains/k2js/inline/InvocationUtil.java index 3fb68af41a6..a943f97bd17 100644 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/InvocationUtil.java +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/InvocationUtil.java @@ -72,4 +72,42 @@ class InvocationUtil { return (JsFunction) returnExpr; } + + /** + * Tests if invocation is JavaScript call function + * + * @return true if invocation is something like `x.call(thisReplacement)` + * false otherwise + */ + public static boolean isCallInvocation(JsInvocation invocation) { + JsExpression qualifier = invocation.getQualifier(); + if (!(qualifier instanceof JsNameRef)) { + return false; + } + + JsNameRef qualifierNameRef = (JsNameRef) qualifier; + return qualifierNameRef.getIdent().equals("call") + && invocation.getArguments().size() == 1; + } + + public static boolean hasReceiver(@NotNull JsInvocation invocation) { + return getReceiverImpl(invocation) != null; + } + + @NotNull + public static JsExpression getReceiver(@NotNull JsInvocation invocation) { + JsExpression receiver = getReceiverImpl(invocation); + assert receiver != null; + return receiver; + } + + @Nullable + private static JsExpression getReceiverImpl(@NotNull JsInvocation invocation) { + JsExpression qualifier = invocation.getQualifier(); + if (!(qualifier instanceof JsNameRef)) { + return null; + } + + return ((JsNameRef) qualifier).getQualifier(); + } } diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/JsInliner.java b/js/js.inliner/src/org/jetbrains/k2js/inline/JsInliner.java index 3529a044c93..53b06ca6c3a 100644 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/JsInliner.java +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/JsInliner.java @@ -22,6 +22,7 @@ import org.jetbrains.annotations.Nullable; import java.util.IdentityHashMap; import java.util.Stack; +import java.util.List; import static org.jetbrains.k2js.inline.FunctionInlineMutator.getInlineableCallReplacement; @@ -70,6 +71,31 @@ public class JsInliner extends JsVisitorWithContextImpl { return functionContext; } + @NotNull + @Override + public JsExpression getThisReplacement(JsInvocation call) { + if (InvocationUtil.isCallInvocation(call)) { + return call.getArguments().get(0); + } + + if (InvocationUtil.hasReceiver(call)) { + return InvocationUtil.getReceiver(call); + } + + return JsLiteral.THIS; + } + + @NotNull + @Override + public List getArguments(JsInvocation call) { + List arguments = call.getArguments(); + if (InvocationUtil.isCallInvocation(call)) { + return arguments.subList(1, arguments.size()); + } + + return arguments; + } + @Override public boolean isResultNeeded(JsInvocation call) { JsStatement currentStatement = getStatementContext().getCurrentStatement(); diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/ThisReplacing.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/ThisReplacing.kt new file mode 100644 index 00000000000..e51db59654e --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/ThisReplacing.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2010-2014 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.k2js.inline + +import com.google.dart.compiler.backend.js.ast.* + +fun replaceThisReference(node: T, replacement: JsExpression) { + val visitor = ThisReplacingVisitor(replacement) + visitor.accept(node) +} + +fun hasThisReference(node: JsNode): Boolean { + val visitor = ContainsThisVisitor() + visitor.accept(node) + return visitor.containsThis +} + +private class ThisReplacingVisitor(private val thisReplacement: JsExpression) : JsVisitorWithContextImpl() { + override fun endVisit(x: JsLiteral.JsThisRef?, ctx: JsContext?) { + ctx?.replaceMe(thisReplacement) + } + + override fun visit(x: JsFunction?, ctx: JsContext?) = false + + override fun visit(x: JsObjectLiteral?, ctx: JsContext?) = false +} + +private class ContainsThisVisitor(): RecursiveJsVisitor() { + public var containsThis: Boolean = false + private set + + override fun visitElement(node: JsNode?) { + if (!containsThis) { + super.visitElement(node) + } + } + + override fun visitThis(x: JsLiteral.JsThisRef?) { + containsThis = true + } + + + override fun visitFunction(x: JsFunction?) { } + + override fun visitObjectLiteral(x: JsObjectLiteral?) { } +}