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 0e756fd8764..5f39070e644 100644 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/FunctionInlineMutator.java +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/FunctionInlineMutator.java @@ -19,11 +19,17 @@ package org.jetbrains.k2js.inline; import com.google.dart.compiler.backend.js.ast.*; import org.jetbrains.annotations.NotNull; +import static org.jetbrains.k2js.inline.InlinePackage.aliasArgumentsIfNeeded; + import java.util.*; class FunctionInlineMutator { private JsBlock body; private final JsFunction invokedFunction; + private final List arguments; + private final List parameters; + private final RenamingContext renamingContext; + private final InsertionPoint insertionPoint; public static InlineableResult getInlineableCallReplacement( @NotNull JsInvocation call, @@ -40,9 +46,22 @@ class FunctionInlineMutator { FunctionContext functionContext = inliningContext.getFunctionContext(); invokedFunction = functionContext.getFunctionDefinition(call); body = invokedFunction.getBody().deepCopy(); + arguments = call.getArguments(); + parameters = invokedFunction.getParameters(); + renamingContext = inliningContext.getRenamingContext(); + insertionPoint = inliningContext.getStatementContext().getInsertionPoint(); } private void process() { + aliasArgumentsIfNeeded(renamingContext, arguments, parameters); + renameLocals(renamingContext, invokedFunction); + applyRenaming(); + } + private void applyRenaming() { + RenamingResult renamingResult = renamingContext.applyRename(body); + body = renamingResult.getRenamed(); + Collection declarations = renamingResult.getDeclarations(); + insertionPoint.insertAllBefore(declarations); } } diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/Renaming.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/Renaming.kt new file mode 100644 index 00000000000..eac92190bf2 --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/Renaming.kt @@ -0,0 +1,133 @@ +/* + * 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.* +import com.google.dart.compiler.backend.js.ast.JsVars.JsVar +import org.jetbrains.k2js.inline.util.collectLocalNames + +import java.util.ArrayList +import java.util.HashMap +import java.util.IdentityHashMap + +import kotlin.test.assertTrue + +fun aliasArgumentsIfNeeded( + context: RenamingContext<*>, + arguments: List, + parameters: List +) { + assertTrue { arguments.size <= parameters.size } + + for ((arg, param) in arguments zip parameters) { + val paramName = param.getName() + val replacement = + if (needToAlias(arg)) { + val freshName = context.getFreshName(paramName) + context.newVar(freshName, arg) + freshName.makeRef() + } else { + arg + } + + context.replaceName(paramName, replacement) + } + + val defaultParams = parameters.subList(arguments.size, parameters.size) + for (defaultParam in defaultParams) { + val paramName = defaultParam.getName() + val freshName = context.getFreshName(paramName) + context.newVar(freshName) + + context.replaceName(paramName, freshName.makeRef()) + } +} + +/** + * Makes function local names fresh in context + */ +fun renameLocalNames( + context: RenamingContext<*>, + function: JsFunction +) { + for (name in collectLocalNames(function)) { + val freshName = context.getFreshName(name) + context.replaceName(name, freshName.makeRef()) + } +} + +private fun isLambdaConstructor(x: JsInvocation): Boolean { + val staticRef = (x.getQualifier() as? HasName)?.getName()?.getStaticRef() + return when (staticRef) { + is JsFunction -> isLambdaConstructor(staticRef) + else -> false + } +} + +private fun isLambdaConstructor(x: JsFunction): Boolean { + return InvocationUtil.getInnerFunction(x) != null; +} + +private fun needToAlias(x: JsExpression): Boolean { + val visitor = ShouldBeAliasedVisitor() + visitor.accept(x) + return visitor.shouldBeAliased +} + +private class ShouldBeAliasedVisitor(): RecursiveJsVisitor() { + public var shouldBeAliased: Boolean = false + private set + + override fun visitElement(node: JsNode?) { + if (!shouldBeAliased) { + super.visitElement(node) + } + } + override fun visitBinaryExpression(x: JsBinaryOperation?) { + shouldBeAliased = true + } + + override fun visitInvocation(invocation: JsInvocation?) { + if (invocation != null && !isLambdaConstructor(invocation)) { + shouldBeAliased = true + } + } + + override fun visitPostfixOperation(x: JsPostfixOperation?) { + shouldBeAliased = true + } + + override fun visitPrefixOperation(x: JsPrefixOperation?) { + shouldBeAliased = true + } + + override fun visitObjectLiteral(x: JsObjectLiteral?) { + shouldBeAliased = true + } + + override fun visitNew(x: JsNew?) { + shouldBeAliased = true + } + + override fun visitThis(x: JsLiteral.JsThisRef?) { + shouldBeAliased = true + } + + override fun visitArray(x: JsArrayLiteral?) { + shouldBeAliased = true + } +} diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/RenamingVisitor.java b/js/js.inliner/src/org/jetbrains/k2js/inline/RenamingVisitor.java new file mode 100644 index 00000000000..a023b0fa653 --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/RenamingVisitor.java @@ -0,0 +1,72 @@ +/* + * 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.*; +import org.jetbrains.annotations.NotNull; + +import java.util.IdentityHashMap; + +public class RenamingVisitor extends JsVisitorWithContextImpl { + private final IdentityHashMap replaceMap; + + @NotNull + public static T rename(T node, IdentityHashMap replaceMap) { + RenamingVisitor visitor = new RenamingVisitor(replaceMap); + return visitor.accept(node); + } + + private RenamingVisitor(IdentityHashMap replaceMap) { + this.replaceMap = replaceMap; + } + + @Override + public void endVisit(JsNameRef x, JsContext ctx) { + JsExpression replacement = replaceMap.get(x.getName()); + + if (replacement == null) { + return; + } + + ctx.replaceMe(replacement); + } + + @Override + public void endVisit(JsVars.JsVar x, JsContext ctx) { + JsExpression replacement = replaceMap.get(x.getName()); + if (replacement instanceof HasName) { + JsName replacementName = ((HasName) replacement).getName(); + JsVars.JsVar replacementVar = new JsVars.JsVar(replacementName, x.getInitExpression()); + ctx.replaceMe(replacementVar); + } + } + + @Override + public void endVisit(JsLabel x, JsContext ctx) { + JsExpression replacement = replaceMap.get(x.getName()); + if (replacement instanceof HasName) { + JsName replacementName = ((HasName) replacement).getName(); + JsLabel replacementLabel = new JsLabel(replacementName, x.getStatement()); + ctx.replaceMe(replacementLabel); + } + } + + private static boolean hasStaticRef(@NotNull HasName hasName) { + JsName name = hasName.getName(); + return name != null && name.getStaticRef() != null; + } +} diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/util/nameUtils.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/util/nameUtils.kt new file mode 100644 index 00000000000..1abe5055e9e --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/util/nameUtils.kt @@ -0,0 +1,61 @@ +/* + * 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.util + +import com.google.dart.compiler.backend.js.ast.* + +import java.util.ArrayList +import java.util.HashMap + +public fun collectLocalNames(function: JsFunction): List { + val functionScope = function.getScope() + + return with (NameCollector(functionScope)) { + accept(function.getBody()) + names.values().toList() + } +} + +class NameCollector(private val scope: JsScope) : RecursiveJsVisitor() { + public val names: MutableMap = HashMap() + + override fun visit(x: JsVars.JsVar?) { + super.visit(x) + addNameIfNeeded(x) + } + + override fun visitFunction(x: JsFunction?) { } + + override fun visitLabel(label: JsLabel?) { + super.visitLabel(label) + addNameIfNeeded(label) + } + + private fun addNameIfNeeded(hasName: HasName?) { + val name = hasName?.getName() + val ident = name?.getIdent() + + if (name == null || ident == null) return + + val nameCollected = names.get(ident) + assert(nameCollected == null || nameCollected identityEquals name) { "ambiguous identifier for $hasName" } + assert(scope.hasOwnName(ident)) { "non-local name was added $hasName" } + + names.put(ident, name) + } +} +