blob: 15b9e4a5dddc5196431e24a22c7b043bd2e3ee47 [file] [log] [blame]
/*
* 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.util
import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.imported
import org.jetbrains.kotlin.js.inline.util.collectors.InstanceCollector
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
fun collectReferencedNames(scope: JsNode): Set<JsName> {
val references = mutableSetOf<JsName>()
object : RecursiveJsVisitor() {
override fun visitBreak(x: JsBreak) { }
override fun visitContinue(x: JsContinue) { }
override fun visit(x: JsVars.JsVar) {
val initializer = x.initExpression
if (initializer != null) {
accept(initializer)
}
}
override fun visitNameRef(nameRef: JsNameRef) {
super.visitNameRef(nameRef)
val name = nameRef.name
if (name != null) {
references += name
}
}
}.accept(scope)
return references
}
fun collectUsedNames(scope: JsNode): Set<JsName> {
val references = mutableSetOf<JsName>()
object : RecursiveJsVisitor() {
override fun visitBreak(x: JsBreak) { }
override fun visitContinue(x: JsContinue) { }
override fun visit(x: JsVars.JsVar) {
val initializer = x.initExpression
if (initializer != null) {
accept(initializer)
}
}
override fun visitNameRef(nameRef: JsNameRef) {
super.visitNameRef(nameRef)
val name = nameRef.name
if (name != null && nameRef.qualifier == null) {
references.add(name)
}
}
override fun visitFunction(x: JsFunction) {
references += x.collectFreeVariables()
}
}.accept(scope)
return references
}
fun collectDefinedNames(scope: JsNode): Set<JsName> {
val names = mutableSetOf<JsName>()
object : RecursiveJsVisitor() {
override fun visit(x: JsVars.JsVar) {
val initializer = x.initExpression
if (initializer != null) {
accept(initializer)
}
names += x.name
}
override fun visitExpressionStatement(x: JsExpressionStatement) {
val expression = x.expression
if (expression is JsFunction) {
val name = expression.name
if (name != null) {
names += name
}
}
super.visitExpressionStatement(x)
}
override fun visitCatch(x: JsCatch) {
names += x.parameter.name
super.visitCatch(x)
}
// Skip function expression, since it does not introduce name in scope of containing function.
// The only exception is function statement, that is handled with the code above.
override fun visitFunction(x: JsFunction) { }
}.accept(scope)
return names
}
fun collectDefinedNamesInAllScopes(scope: JsNode): Set<JsName> {
val names = mutableSetOf<JsName>()
object : RecursiveJsVisitor() {
override fun visit(x: JsVars.JsVar) {
super.visit(x)
names += x.name
}
override fun visitFunction(x: JsFunction) {
super.visitFunction(x)
x.name?.let { names += it }
names += x.parameters.map { it.name }
}
}.accept(scope)
return names
}
fun JsFunction.collectFreeVariables() = collectUsedNames(body) - collectDefinedNames(body) - parameters.map { it.name }
fun JsFunction.collectLocalVariables() = collectDefinedNames(body) + parameters.map { it.name }
fun collectNamedFunctions(scope: JsNode) = collectNamedFunctionsAndMetadata(scope).mapValues { it.value.first.function }
fun collectNamedFunctionsOrMetadata(scope: JsNode) = collectNamedFunctionsAndMetadata(scope).mapValues { it.value.second }
fun collectNamedFunctions(fragments: List<JsProgramFragment>): Map<JsName, JsFunction> {
val result = mutableMapOf<JsName, JsFunction>()
for (fragment in fragments) {
result += collectNamedFunctions(fragment.declarationBlock)
result += collectNamedFunctions(fragment.initializerBlock)
}
return result
}
fun collectNamedFunctionsAndWrappers(fragments: List<JsProgramFragment>): Map<JsName, FunctionWithWrapper> {
val result = mutableMapOf<JsName, FunctionWithWrapper>()
for (fragment in fragments) {
result += collectNamedFunctionsAndMetadata(fragment.declarationBlock).mapValues { it.value.first }
result += collectNamedFunctionsAndMetadata(fragment.initializerBlock).mapValues { it.value.first }
}
return result
}
fun collectNamedFunctionsAndMetadata(scope: JsNode): Map<JsName, Pair<FunctionWithWrapper, JsExpression>> {
val namedFunctions = mutableMapOf<JsName, Pair<FunctionWithWrapper, JsExpression>>()
scope.accept(object : RecursiveJsVisitor() {
override fun visitBinaryExpression(x: JsBinaryOperation) {
val assignment = JsAstUtils.decomposeAssignment(x)
if (assignment != null) {
val (left, right) = assignment
if (left is JsNameRef) {
val name = left.name
if (name != null) {
extractFunction(right)?.let { (function, wrapper) ->
namedFunctions[name] = Pair(FunctionWithWrapper(function, wrapper), right)
}
}
}
}
super.visitBinaryExpression(x)
}
override fun visit(x: JsVars.JsVar) {
val initializer = x.initExpression
val name = x.name
if (initializer != null && name != null) {
extractFunction(initializer)?.let { function ->
namedFunctions[name] = Pair(function, initializer)
}
}
super.visit(x)
}
override fun visitFunction(x: JsFunction) {
val name = x.name
if (name != null) {
namedFunctions[name] = Pair(FunctionWithWrapper(x, null), x)
}
super.visitFunction(x)
}
})
return namedFunctions
}
data class FunctionWithWrapper(val function: JsFunction, val wrapperBody: JsBlock?)
fun collectAccessors(scope: JsNode): Map<String, FunctionWithWrapper> {
val accessors = hashMapOf<String, FunctionWithWrapper>()
scope.accept(object : RecursiveJsVisitor() {
override fun visitInvocation(invocation: JsInvocation) {
InlineMetadata.decompose(invocation)?.let {
accessors[it.tag.value] = it.function
}
super.visitInvocation(invocation)
}
})
return accessors
}
fun collectAccessors(fragments: List<JsProgramFragment>): Map<String, FunctionWithWrapper> {
val result = mutableMapOf<String, FunctionWithWrapper>()
for (fragment in fragments) {
result += collectAccessors(fragment.declarationBlock)
}
return result
}
fun collectNameBindings(fragments: List<JsProgramFragment>): Map<JsName, String> {
val result = mutableMapOf<JsName, String>()
for (fragment in fragments) {
for (binding in fragment.nameBindings) {
result[binding.name] = binding.key
}
}
return result
}
fun extractFunction(expression: JsExpression) = when (expression) {
is JsFunction -> FunctionWithWrapper(expression, null)
else -> InlineMetadata.decompose(expression)?.function ?: InlineMetadata.tryExtractFunction(expression)
}
fun <T : JsNode> collectInstances(klass: Class<T>, scope: JsNode): List<T> {
return with(InstanceCollector(klass, visitNestedDeclarations = false)) {
accept(scope)
collected
}
}
fun JsNode.collectBreakContinueTargets(): Map<JsContinue, JsStatement> {
val targets = mutableMapOf<JsContinue, JsStatement>()
accept(object : RecursiveJsVisitor() {
var defaultBreakTarget: JsStatement? = null
var breakTargets = mutableMapOf<JsName, JsStatement?>()
var defaultContinueTarget: JsStatement? = null
var continueTargets = mutableMapOf<JsName, JsStatement?>()
override fun visitLabel(x: JsLabel) {
val inner = x.statement
when (inner) {
is JsDoWhile -> handleLoop(inner, inner.body, x.name)
is JsWhile -> handleLoop(inner, inner.body, x.name)
is JsFor -> handleLoop(inner, inner.body, x.name)
is JsSwitch -> handleSwitch(inner, x.name)
else -> {
withBreakAndContinue(x.name, x.statement, null) {
accept(inner)
}
}
}
}
override fun visitWhile(x: JsWhile) = handleLoop(x, x.body, null)
override fun visitDoWhile(x: JsDoWhile) = handleLoop(x, x.body, null)
override fun visitFor(x: JsFor) = handleLoop(x, x.body, null)
override fun visit(x: JsSwitch) = handleSwitch(x, null)
private fun handleSwitch(statement: JsSwitch, label: JsName?) {
withBreakAndContinue(label, statement) {
statement.cases.forEach { accept(it) }
}
}
private fun handleLoop(loop: JsStatement, body: JsStatement, label: JsName?) {
withBreakAndContinue(label, loop, loop) {
body.accept(this)
}
}
override fun visitBreak(x: JsBreak) {
val targetLabel = x.label?.name
targets[x] = if (targetLabel == null) {
defaultBreakTarget!!
}
else {
breakTargets[targetLabel]!!
}
}
override fun visitContinue(x: JsContinue) {
val targetLabel = x.label?.name
targets[x] = if (targetLabel == null) {
defaultContinueTarget!!
}
else {
continueTargets[targetLabel]!!
}
}
private fun withBreakAndContinue(
label: JsName?,
breakTargetStatement: JsStatement,
continueTargetStatement: JsStatement? = null,
action: () -> Unit
) {
val oldDefaultBreakTarget = defaultBreakTarget
val oldDefaultContinueTarget = defaultContinueTarget
val (oldBreakTarget, oldContinueTarget) = if (label != null) {
Pair(breakTargets[label], continueTargets[label])
}
else {
Pair(null, null)
}
defaultBreakTarget = breakTargetStatement
if (label != null) {
breakTargets[label] = breakTargetStatement
continueTargets[label] = continueTargetStatement
}
if (continueTargetStatement != null) {
defaultContinueTarget = continueTargetStatement
}
action()
defaultBreakTarget = oldDefaultBreakTarget
defaultContinueTarget = oldDefaultContinueTarget
if (label != null) {
breakTargets[label] = oldBreakTarget
continueTargets[label] = oldContinueTarget
}
}
})
return targets
}
fun getImportTag(jsVars: JsVars): String? {
if (jsVars.vars.size == 1) {
val jsVar = jsVars.vars[0]
if (jsVar.name.imported) {
return extractImportTag(jsVar)
}
}
return null
}
fun extractImportTag(jsVar: JsVars.JsVar): String? {
val initExpression = jsVar.initExpression ?: return null
val sb = StringBuilder()
// Handle Long const val import
if (initExpression is JsInvocation || initExpression is JsNew) {
sb.append(jsVar.name.toString()).append(":")
}
return if (extractImportTagImpl(initExpression, sb)) sb.toString() else null
}
private fun extractImportTagImpl(expression: JsExpression, sb: StringBuilder): Boolean {
when (expression) {
is JsNameRef -> {
val nameRef = expression
if (nameRef.qualifier != null) {
if (!extractImportTagImpl(nameRef.qualifier!!, sb)) return false
sb.append('.')
}
sb.append(JsToStringGenerationVisitor.javaScriptString(nameRef.ident))
return true
}
is JsArrayAccess -> {
val arrayAccess = expression
if (!extractImportTagImpl(arrayAccess.arrayExpression, sb)) return false
sb.append(".")
val stringLiteral = arrayAccess.indexExpression as? JsStringLiteral ?: return false
sb.append(JsToStringGenerationVisitor.javaScriptString(stringLiteral.value))
return true
}
is JsInvocation -> {
val invocation = expression
if (!extractImportTagImpl(invocation.qualifier, sb)) return false
if (!appendArguments(invocation.arguments, sb)) return false
return true
}
is JsNew -> {
val newExpr = expression
if (!extractImportTagImpl(newExpr.constructorExpression, sb)) return false
if (!appendArguments(newExpr.arguments, sb)) return false
return true
}
else -> return false
}
}
private fun appendArguments(arguments: List<JsExpression>, sb: StringBuilder): Boolean {
arguments.forEachIndexed { index, arg ->
if (arg !is JsIntLiteral) {
return false
}
sb.append(if (index == 0) "(" else ",")
sb.append(arg.value)
}
sb.append(")")
return true
}