org.jetbrains.kotlin.js.inline.clean.removeUnusedLocalFunctionDeclarations.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-compiler-embeddable Show documentation
Show all versions of kotlin-compiler-embeddable Show documentation
the Kotlin compiler embeddable
/*
* 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 org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.staticRef
import org.jetbrains.kotlin.js.inline.util.collectUsedNames
import org.jetbrains.kotlin.js.inline.util.transitiveStaticRef
/**
* Removes unused local function declarations like:
* var inc = _.foo.f$inc(a)
*
* Declaration can become unused, if inlining happened.
*/
fun removeUnusedLocalFunctionDeclarations(root: JsNode) {
val removable =
with(UnusedInstanceCollector()) {
accept(root)
removableDeclarations
}
NodeRemover(JsStatement::class.java) {
it in removable
}.accept(root)
}
private class UnusedInstanceCollector : JsVisitorWithContextImpl() {
private val tracker = ReferenceTracker()
val removableDeclarations: List
get() = tracker.removable
override fun visit(x: JsVars.JsVar, ctx: JsContext<*>): Boolean {
if (!isLocalFunctionDeclaration(x)) return super.visit(x, ctx)
val name = x.name!!
val statementContext = lastStatementLevelContext
val currentStatement = statementContext.currentNode
tracker.addCandidateForRemoval(name, currentStatement!!)
val references = collectUsedNames(x)
references.forEach { tracker.addRemovableReference(name, it) }
return false
}
override fun visit(x: JsNameRef, ctx: JsContext<*>): Boolean {
var q: JsNameRef? = x
while (q != null) {
q.name?.let { tracker.markReachable(it) }
q = q.qualifier as? JsNameRef
}
return false
}
private fun isLocalFunctionDeclaration(jsVar: JsVars.JsVar): Boolean {
val name = jsVar.name
val expr = jsVar.initExpression
// For the case like this: `b = a; c = b;`, where `a` is a function. In this case we should remove both declaration,
// although second one contains 'usage' of `b`.
// see `inlineEvaluationOrder/cases/lambdaWithClosure.kt`.
if (expr is JsNameRef && (expr.name?.let { tracker.isReferenceToRemovableCandidate(it) } ?: false)) return true
val staticRef = name?.staticRef
return staticRef != null && staticRef == expr && isFunctionReference(expr)
}
}
// For RHS of `var a = b;` checks whether *b* is a reference to a function or a closure instantiation, direct or indirect.
private fun isFunctionReference(expr: JsExpression): Boolean {
val qualifier = when (expr) {
// `var tmp = foo(closure)`, where `foo` is a closure constructor.
is JsInvocation -> expr.qualifier
// Either alias to another variable that holds function or a lambda without closure.
is JsNameRef -> expr
else -> null
}
return qualifier?.transitiveStaticRef is JsFunction
}