diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/DefaultParameters.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/DefaultParameters.kt deleted file mode 100644 index c44fa4e71a5..00000000000 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/DefaultParameters.kt +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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 java.util.IdentityHashMap -import java.util.Collections - -/** - * Removes initializers for default parameters with non-void0 arguments given - * Expands initializers for default parameters with void0 arguments given - * - * @see isInitializer - */ -fun removeRedundantDefaultInitializers(arguments: List, parameters: List, body: JsBlock) { - val toRemove = getInitializedParametersNames(arguments, parameters) - val toExpand = getUnInitializedParametersNames(arguments, parameters) - - val statements = body.getStatements() - - val newStatements = statements.flatMap { - when { - !isInitializer(it) -> listOf(it) - else -> { - val name = getNameFromInitializer(it) - when { - name in toRemove.keySet() -> listOf() - name in toExpand.keySet() -> expand(it) - else -> listOf(it) - } - } - } - }.filterNotNull() - - statements.clear() - statements.addAll(newStatements) -} - -/** - * Tests if statement is an initializer for parameter with default value. - * - * This check assumes that default parameter initializer is generated like: - * if (defaultParam === void 0) { - * ... - * } - */ -private fun isInitializer(statement: JsStatement): Boolean { - return getNameFromInitializer(statement) != null -} - -private fun getNameFromInitializer(statement: JsStatement): JsName? { - val jsIf = (statement as? JsIf) - val ifExpr = jsIf?.getIfExpression() as? JsBinaryOperation - - return when { - jsIf?.getElseStatement() != null -> null - else -> getNameFromInitializer(ifExpr) - } -} - -private fun getNameFromInitializer(initializerTestExpression: JsExpression?): JsName? { - val binOp = initializerTestExpression as? JsBinaryOperation - val arg1 = binOp?.getArg1() - val arg2 = binOp?.getArg2() - val operator = binOp?.getOperator() - - return when { - operator != JsBinaryOperator.REF_EQ -> null - !isVoid0(arg2) -> null - else -> (arg1 as? JsNameRef)?.getName() - } -} - -private fun isVoid0(expr: JsExpression?): Boolean { - val op = (expr as? JsUnaryOperation)?.getOperator() - return op == JsUnaryOperator.VOID -} - -private fun getInitializedParametersNames(args: List, - params: List): Map { - - val names = IdentityHashMap() - val argParams = (args zip params).stream() - - val initialized = argParams.filter { it.second.hasDefaultValue() } - .filter { !isVoid0(it.first) } - .map { it.second } - - initialized.map { it.getName() } - .forEach { names.put(it, true) } - - return names -} - -private fun getUnInitializedParametersNames(args: List, - params: List): Map { - - val names = IdentityHashMap() - val argParams = (args zip params).stream() - - val void0Params = argParams.filter { it.second.hasDefaultValue() } - .filter { isVoid0(it.first) } - .map { it.second } - - val noArgsParams = params.drop(args.size).stream() - val uninitialized = void0Params.plus(noArgsParams) - - uninitialized.map { it.getName() } - .forEach { names.put(it, true) } - - return names -} - -/** - * Changes initializer check: - * if (arg === void 0) { arg = ... } - * To list of statement(s): - * arg = ... - */ -private fun expand(initializer: JsStatement): Iterable { - val then = (initializer as? JsIf)?.getThenStatement() - - return when { - then is JsBlock -> then.getStatements() - then != null -> listOf(then) - else -> listOf() - } -} \ No newline at end of file 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 532e9f18aeb..7ef48222970 100644 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/FunctionInlineMutator.java +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/FunctionInlineMutator.java @@ -23,6 +23,7 @@ import org.jetbrains.k2js.inline.context.*; import static org.jetbrains.k2js.inline.util.UtilPackage.collectInstances; import static org.jetbrains.k2js.inline.util.UtilPackage.replaceReturns; import static org.jetbrains.k2js.inline.util.UtilPackage.replaceThisReference; +import static org.jetbrains.k2js.inline.clean.CleanPackage.removeDefaultInitializers; import static org.jetbrains.k2js.inline.InlinePackage.*; @@ -78,7 +79,7 @@ class FunctionInlineMutator { List parameters = getParameters(); replaceThis(); - removeRedundantDefaultInitializers(arguments, parameters, body); + removeDefaultInitializers(arguments, parameters, body); aliasArgumentsIfNeeded(namingContext, arguments, parameters); renameLocalNames(namingContext, invokedFunction); 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 579a7fff7b1..252f2391018 100644 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/JsInliner.java +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/JsInliner.java @@ -27,8 +27,8 @@ import java.util.Set; import java.util.Stack; import java.util.List; -import static org.jetbrains.k2js.inline.clean.CleanPackage.removeUnusedLocalFunctionInstances; -import static org.jetbrains.k2js.inline.clean.CleanPackage.removeUnusedLocalFunctions; +import static org.jetbrains.k2js.inline.clean.CleanPackage.removeUnusedLocalFunctionDeclarations; +import static org.jetbrains.k2js.inline.clean.CleanPackage.removeUnusedFunctionDefinitions; import static org.jetbrains.k2js.inline.FunctionInlineMutator.getInlineableCallReplacement; import static org.jetbrains.k2js.inline.util.UtilPackage.IdentitySet; import static org.jetbrains.k2js.inline.util.UtilPackage.collectNamedFunctions; @@ -74,7 +74,7 @@ public class JsInliner extends JsVisitorWithContextImpl { IdentityHashMap functions = collectNamedFunctions(program); JsInliner inliner = new JsInliner(functions); inliner.accept(program); - removeUnusedLocalFunctions(program, functions); + removeUnusedFunctionDefinitions(program, functions); return program; } @@ -95,7 +95,7 @@ public class JsInliner extends JsVisitorWithContextImpl { @Override public void endVisit(JsFunction function, JsContext context) { super.endVisit(function, context); - removeUnusedLocalFunctionInstances(function); + removeUnusedLocalFunctionDeclarations(function); processedFunctions.add(function); assert inProcessFunctions.contains(function); diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/clean/FunctionRemover.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/clean/FunctionRemover.kt deleted file mode 100644 index cd6bc3cd4ff..00000000000 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/clean/FunctionRemover.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.clean - -import com.google.dart.compiler.backend.js.ast.* -import org.jetbrains.k2js.inline.util.IdentitySet - -private class FunctionRemover(removable: Collection = listOf()) : NodeRemovingVisitor(removable) { - - override fun endVisit(x: JsPropertyInitializer?, ctx: JsContext?) { - if (x == null) return - - val value = x.getValueExpr() - if (value is JsFunction && shouldRemove(value)) { - ctx?.removeMe() - } else { - super.endVisit(x, ctx) - } - } -} diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/clean/GenericRemover.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/clean/NodeRemover.kt similarity index 56% rename from js/js.inliner/src/org/jetbrains/k2js/inline/clean/GenericRemover.kt rename to js/js.inliner/src/org/jetbrains/k2js/inline/clean/NodeRemover.kt index 214e375b32e..534af2dc9d6 100644 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/clean/GenericRemover.kt +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/clean/NodeRemover.kt @@ -16,17 +16,24 @@ package org.jetbrains.k2js.inline.clean -import com.google.dart.compiler.backend.js.ast.* +import com.google.dart.compiler.backend.js.ast.JsVisitorWithContextImpl +import com.google.dart.compiler.backend.js.ast.JsNode +import com.google.dart.compiler.backend.js.ast.JsContext -private class GenericRemover(removable: Collection = listOf()) : NodeRemovingVisitor(removable) { +private class NodeRemover(val klass: Class, val predicate: (T) -> Boolean): JsVisitorWithContextImpl() { override fun doTraverse(node: T?, ctx: JsContext?) { - if (node == null) return + if (node == null || ctx == null) return - if (shouldRemove(node)) { - ctx?.removeMe() - } else { - super.doTraverse(node, ctx) + if (klass.isInstance(node)) { + val instance = klass.cast(node)!! + + if (predicate(instance)) { + ctx.removeMe() + return + } } + + super.doTraverse(node, ctx) } } diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/clean/NodeRemovingVisitor.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/clean/NodeRemovingVisitor.kt deleted file mode 100644 index 3290a8d2c74..00000000000 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/clean/NodeRemovingVisitor.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.clean - -import com.google.dart.compiler.backend.js.ast.* -import org.jetbrains.k2js.inline.util.IdentitySet - -import java.util.IdentityHashMap -import org.jetbrains.k2js.inline.util.toIdentitySet - -private abstract class NodeRemovingVisitor(removable: Collection) : JsVisitorWithContextImpl() { - private val removeSet: Set = removable.toIdentitySet() - - protected fun shouldRemove(node: R): Boolean { - return removeSet.contains(node) - } -} diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/clean/ReferenceTracker.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/clean/ReferenceTracker.kt index e0fdb59a1b0..94eca05864c 100644 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/clean/ReferenceTracker.kt +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/clean/ReferenceTracker.kt @@ -25,7 +25,7 @@ import java.util.ArrayList private class ReferenceTracker { private val reachable = IdentityHashMap() private val removableCandidates = IdentityHashMap() - private val refereceFromTo = IdentityHashMap>() + private val refereceFromTo = IdentityHashMap>() private val visited = IdentitySet() public val removable: List @@ -65,8 +65,8 @@ private class ReferenceTracker { reachable[reference] = true } - private fun getReferencedBy(referer: Reference): MutableList { - return refereceFromTo.getOrPut(referer, { ArrayList() }) + private fun getReferencedBy(referer: Reference): MutableSet { + return refereceFromTo.getOrPut(referer, { IdentitySet() }) } private fun isKnown(ref: Reference): Boolean { diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/clean/referenceCollectionUtils.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/clean/referenceCollectionUtils.kt deleted file mode 100644 index 4646aa42a3e..00000000000 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/clean/referenceCollectionUtils.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.clean - -import com.google.dart.compiler.backend.js.ast.* -import org.jetbrains.k2js.inline.util.IdentitySet - -import java.util.ArrayList - -private fun collectFunctionReferencesInside(scope: JsNode): List { - return with(ReferenceNameCollector()) { - accept(scope) - references.filter { it.getStaticRef() is JsFunction } - } -} - -private fun collectReferencesInside(scope: JsNode): List { - return with(ReferenceNameCollector()) { - accept(scope) - references - } -} - -private class ReferenceNameCollector : JsVisitorWithContextImpl() { - private val referenceSet = IdentitySet() - - public val references: List - get() = referenceSet.toList() - - override fun endVisit(x: JsNameRef?, ctx: JsContext?) { - val name = x?.getName() - if (name != null) { - referenceSet.add(name) - } - } -} \ No newline at end of file diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/clean/removeDefaultInitializers.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/clean/removeDefaultInitializers.kt new file mode 100644 index 00000000000..ca5505b83dc --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/clean/removeDefaultInitializers.kt @@ -0,0 +1,126 @@ +/* + * 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.clean + +import com.google.dart.compiler.backend.js.ast.* + +import org.jetbrains.k2js.inline.util.toIdentitySet +import org.jetbrains.k2js.inline.util.zipWithDefault +import org.jetbrains.k2js.translate.context.Namer +import org.jetbrains.k2js.translate.context.Namer.isUndefined +import org.jetbrains.k2js.translate.utils.JsAstUtils.flattenStatement + +import java.util.IdentityHashMap +import java.util.Collections +import org.jetbrains.k2js.translate.utils.JsAstUtils + +/** + * Removes initializers for default parameters with defined arguments given + * Expands initializers for default parameters with undefined arguments given + * + * @see isInitializer + */ +public fun removeDefaultInitializers(arguments: List, parameters: List, body: JsBlock) { + val toRemove = getDefaultParamsNames(arguments, parameters, initialized = true) + val toExpand = getDefaultParamsNames(arguments, parameters, initialized = false) + + val statements = body.getStatements() + val newStatements = statements.flatMap { + val name = getNameFromInitializer(it) + if (name != null && !isNameInitialized(name, it)) { + throw AssertionError("Unexpected initializer structure") + } + + when { + name in toRemove -> + listOf() + name in toExpand -> + flattenStatement((it as JsIf).getThenStatement()!!) + else -> + listOf(it) + } + } + + statements.clear() + statements.addAll(newStatements) +} + +private fun getNameFromInitializer(statement: JsStatement): JsName? { + val ifStmt = (statement as? JsIf) + val testExpr = ifStmt?.getIfExpression() + + val elseStmt = ifStmt?.getElseStatement() + if (elseStmt == null && testExpr is JsBinaryOperation) + return getNameFromInitializer(testExpr) + + return null +} + +private fun getNameFromInitializer(isInitializedExpr: JsBinaryOperation): JsName? { + val arg1 = isInitializedExpr.getArg1() + val arg2 = isInitializedExpr.getArg2() + val op = isInitializedExpr.getOperator() + + if (arg1 == null || arg2 == null || op == null) { + return null + } + + if (op == JsBinaryOperator.REF_EQ && isUndefined(arg2)) { + return (arg1 as? JsNameRef)?.getName() + } + + return null +} + +/** + * Tests if the last statement of initializer + * is name assignment. + */ +private fun isNameInitialized( + name: JsName, + initializer: JsStatement +): Boolean { + val thenStmt = (initializer as JsIf).getThenStatement()!! + val lastThenStmt = flattenStatement(thenStmt).last + + val expr = (lastThenStmt as? JsExpressionStatement)?.getExpression() + if (expr !is JsBinaryOperation) return false + + val op = expr.getOperator()!! + if (!op.isAssignment()) return false + + val arg1 = expr.getArg1() + if (arg1 is HasName && arg1.getName() identityEquals name) return true + + return false +} + +private fun getDefaultParamsNames( + args: List, + params: List, + initialized: Boolean +): Set { + + val argsParams = args.zipWithDefault(params, Namer.UNDEFINED_EXPRESSION) + val relevantParams = argsParams.stream() + .filter { it.second.hasDefaultValue() } + .filter { initialized == !isUndefined(it.first) } + + val names = relevantParams.map { it.second.getName() } + return names.toIdentitySet() +} + diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/clean/LocalFunctions.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/clean/removeUnusedFunctionDefinitions.kt similarity index 83% rename from js/js.inliner/src/org/jetbrains/k2js/inline/clean/LocalFunctions.kt rename to js/js.inliner/src/org/jetbrains/k2js/inline/clean/removeUnusedFunctionDefinitions.kt index 507d857a913..29e1962a8fd 100644 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/clean/LocalFunctions.kt +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/clean/removeUnusedFunctionDefinitions.kt @@ -21,17 +21,28 @@ import org.jetbrains.k2js.inline.util.IdentitySet import com.intellij.util.containers.Stack import java.util.IdentityHashMap +import org.jetbrains.k2js +import org.jetbrains.k2js.inline.util.collectReferencesInside +import org.jetbrains.k2js.inline.util.collectFunctionReferencesInside -public fun removeUnusedLocalFunctions(root: JsNode, functions: Map) { +/** + * Removes unused function definitions: + * f: function() { return 10 } + * + * At now, it only removes unused local functions and function literals, + * because named functions can be referenced from another module. + */ +public fun removeUnusedFunctionDefinitions(root: JsNode, functions: Map) { val removable = with(UnusedLocalFunctionsCollector(functions)) { process() accept(root) removableFunctions } - with(FunctionRemover(removable)) { - accept(root) - } + NodeRemover(javaClass()) { + val function = it.getValueExpr() as? JsFunction + function in removable + }.accept(root) } private class UnusedLocalFunctionsCollector(functions: Map) : JsVisitorWithContextImpl() { diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/clean/LocalFunctionInstances.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/clean/removeUnusedLocalFunctionDeclarations.kt similarity index 76% rename from js/js.inliner/src/org/jetbrains/k2js/inline/clean/LocalFunctionInstances.kt rename to js/js.inliner/src/org/jetbrains/k2js/inline/clean/removeUnusedLocalFunctionDeclarations.kt index 73c4d436022..4d179820ba2 100644 --- a/js/js.inliner/src/org/jetbrains/k2js/inline/clean/LocalFunctionInstances.kt +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/clean/removeUnusedLocalFunctionDeclarations.kt @@ -20,27 +20,36 @@ import com.google.dart.compiler.backend.js.ast.* import java.util.ArrayList import java.util.IdentityHashMap +import org.jetbrains.k2js -public fun removeUnusedLocalFunctionInstances(root: JsNode) { +/** + * Removes unused local function declarations like: + * var inc = _.foo.f$inc(a) + * + * Declaration can become unused, if inlining happened. + */ +public fun removeUnusedLocalFunctionDeclarations(root: JsNode) { val removable = with(UnusedInstanceCollector()) { accept(root) - removableInstances + removableDeclarations } - GenericRemover(removable).accept(root) + NodeRemover(javaClass()) { + it in removable + }.accept(root) } private class UnusedInstanceCollector : JsVisitorWithContextImpl() { private val tracker = ReferenceTracker() - public val removableInstances: List + public val removableDeclarations: List get() = tracker.removable override fun visit(x: JsVars.JsVar?, ctx: JsContext?): Boolean { if (x == null) return false - if (!isLocalFunctionInstance(x)) return super.visit(x, ctx) + if (!isLocalFunctionDeclaration(x)) return super.visit(x, ctx) val name = x.getName()!! val statementContext = getLastStatementLevelContext() @@ -49,7 +58,7 @@ private class UnusedInstanceCollector : JsVisitorWithContextImpl() { val currentStatement = currentNode as JsStatement tracker.addCandidateForRemoval(name, currentStatement) - val references = collectReferencesInside(x) + val references = k2js.inline.util.collectReferencesInside(x) references.filterNotNull() .forEach { tracker.addRemovableReference(name, it) } @@ -66,7 +75,7 @@ private class UnusedInstanceCollector : JsVisitorWithContextImpl() { return false } - private fun isLocalFunctionInstance(jsVar: JsVars.JsVar): Boolean { + private fun isLocalFunctionDeclaration(jsVar: JsVars.JsVar): Boolean { val name = jsVar.getName() val expr = jsVar.getInitExpression() val staticRef = name?.getStaticRef()