JS inline: alias arguments and local variables
This commit is contained in:
committed by
Zalim Bashorov
parent
49718f143b
commit
667a22f84e
@@ -19,11 +19,17 @@ package org.jetbrains.k2js.inline;
|
|||||||
import com.google.dart.compiler.backend.js.ast.*;
|
import com.google.dart.compiler.backend.js.ast.*;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import static org.jetbrains.k2js.inline.InlinePackage.aliasArgumentsIfNeeded;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
class FunctionInlineMutator {
|
class FunctionInlineMutator {
|
||||||
private JsBlock body;
|
private JsBlock body;
|
||||||
private final JsFunction invokedFunction;
|
private final JsFunction invokedFunction;
|
||||||
|
private final List<JsExpression> arguments;
|
||||||
|
private final List<JsParameter> parameters;
|
||||||
|
private final RenamingContext<JsBlock> renamingContext;
|
||||||
|
private final InsertionPoint<JsStatement> insertionPoint;
|
||||||
|
|
||||||
public static InlineableResult getInlineableCallReplacement(
|
public static InlineableResult getInlineableCallReplacement(
|
||||||
@NotNull JsInvocation call,
|
@NotNull JsInvocation call,
|
||||||
@@ -40,9 +46,22 @@ class FunctionInlineMutator {
|
|||||||
FunctionContext functionContext = inliningContext.getFunctionContext();
|
FunctionContext functionContext = inliningContext.getFunctionContext();
|
||||||
invokedFunction = functionContext.getFunctionDefinition(call);
|
invokedFunction = functionContext.getFunctionDefinition(call);
|
||||||
body = invokedFunction.getBody().deepCopy();
|
body = invokedFunction.getBody().deepCopy();
|
||||||
|
arguments = call.getArguments();
|
||||||
|
parameters = invokedFunction.getParameters();
|
||||||
|
renamingContext = inliningContext.getRenamingContext();
|
||||||
|
insertionPoint = inliningContext.getStatementContext().getInsertionPoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void process() {
|
private void process() {
|
||||||
|
aliasArgumentsIfNeeded(renamingContext, arguments, parameters);
|
||||||
|
renameLocals(renamingContext, invokedFunction);
|
||||||
|
applyRenaming();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyRenaming() {
|
||||||
|
RenamingResult<JsBlock> renamingResult = renamingContext.applyRename(body);
|
||||||
|
body = renamingResult.getRenamed();
|
||||||
|
Collection<JsVars> declarations = renamingResult.getDeclarations();
|
||||||
|
insertionPoint.insertAllBefore(declarations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<JsExpression>,
|
||||||
|
parameters: List<JsParameter>
|
||||||
|
) {
|
||||||
|
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<RecursiveJsVisitor>.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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<JsName, JsExpression> replaceMap;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static <T extends JsNode> T rename(T node, IdentityHashMap<JsName, JsExpression> replaceMap) {
|
||||||
|
RenamingVisitor visitor = new RenamingVisitor(replaceMap);
|
||||||
|
return visitor.accept(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RenamingVisitor(IdentityHashMap<JsName, JsExpression> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<JsName> {
|
||||||
|
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<String, JsName> = 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user