JS: when resolving temporary functions, don't rely on JsScope hierarchy, build this hierarchy instead, based on scoping rules for function, var, etc expressions

This commit is contained in:
Alexey Andreev
2017-02-10 19:55:45 +03:00
parent a6bb5743db
commit 9530ce6c26
11 changed files with 315 additions and 74 deletions
@@ -14,37 +14,13 @@
* limitations under the License.
*/
/*
* 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.coroutineMetadata
import org.jetbrains.kotlin.js.inline.util.collectReferencedTemporaryNames
fun JsNode.resolveTemporaryNames() {
val allNames = collectReferencedTemporaryNames(this)
val scopeTree = ScopeCollector().let {
it.accept(this)
it.scopeTree
}
val renamings = scopeTree.resolveNames(allNames)
val renamings = resolveNames()
accept(object : RecursiveJsVisitor() {
override fun visitElement(node: JsNode) {
super.visitElement(node)
@@ -66,52 +42,126 @@ fun JsNode.resolveTemporaryNames() {
})
}
private fun Map<JsScope, Set<JsScope>>.resolveNames(knownNames: Set<JsName>): Map<JsName, JsName> {
private fun JsNode.resolveNames(): Map<JsName, JsName> {
val rootScope = computeScopes().liftUsedNames()
val replacements = mutableMapOf<JsName, JsName>()
fun traverse(scope: JsScope) {
for (temporaryName in scope.temporaryNames.filter { it in knownNames }.sortedBy { it.ordinal }) {
replacements[temporaryName] = scope.declareFreshName(temporaryName.ident).apply { copyMetadataFrom(temporaryName) }
fun traverse(scope: Scope) {
// Don't clash with non-temporary names declared in current scope. It's for rare cases like `_` or `Kotlin` names,
// since most of local declarations are temporary.
val occupiedNames = scope.declaredNames.asSequence().filter { !it.isTemporary }.map { it.ident }.toMutableSet()
// Don't clash with non-temporary names used in current scope. It's ok to clash with unused names.
// Don't clash with used temporary names from outer scopes that get their resolved names. For example,
// when function declares temporary name `foo` and inner function both declares temporary name `foo` and uses
// (directly or propagates to inner scope) outer `foo`, we should resolve distinct strings for these `foo` names.
// Outer `foo` resolves first, so when traversing inner scope, we should take it into account.
occupiedNames += scope.usedNames.asSequence().mapNotNull { if (!it.isTemporary) it.ident else replacements[it]?.ident }
for (temporaryName in scope.declaredNames.asSequence().filter { it.isTemporary }) {
var resolvedName = temporaryName.ident
var suffix = 0
while (resolvedName in JsDeclarationScope.RESERVED_WORDS || !occupiedNames.add(resolvedName)) {
resolvedName = "${temporaryName.ident}_${suffix++}"
}
replacements[temporaryName] = JsDynamicScope.declareName(resolvedName).apply { copyMetadataFrom(temporaryName) }
occupiedNames += resolvedName
}
this[scope]!!.forEach(::traverse)
scope.children.forEach(::traverse)
}
val roots = keys.toMutableSet()
values.forEach { roots -= it }
traverse(rootScope)
for (root in roots) {
traverse(root)
}
// Labels in JS are in separate scope from local variables. It's only important that nested labels don't clash.
// Adjacent labels in one function is OK
accept(object : RecursiveJsVisitor() {
var labels = mutableSetOf<String>()
override fun visitLabel(x: JsLabel) {
val addedNames = mutableSetOf<String>()
if (x.name.isTemporary) {
var resolvedName = x.name.ident
var suffix = 0
while (!labels.add(resolvedName)) {
resolvedName = "${x.name.ident}_${suffix++}"
}
replacements[x.name] = JsDynamicScope.declareName(resolvedName)
addedNames += resolvedName
}
super.visitLabel(x)
labels.removeAll(addedNames)
}
override fun visitFunction(x: JsFunction) {
val oldLabels = labels
labels = mutableSetOf<String>()
super.visitFunction(x)
labels = oldLabels
}
})
return replacements
}
private class ScopeCollector : RecursiveJsVisitor() {
val scopeTree = mutableMapOf<JsScope, MutableSet<JsScope>>()
override fun visitCatch(x: JsCatch) {
recordScope(x.scope)
super.visitCatch(x)
}
override fun visitFunction(x: JsFunction) {
recordScope(x.scope)
super.visitFunction(x)
}
override fun visitElement(node: JsNode) {
if (node is HasName) {
node.name?.let { recordScope(it.enclosing) }
// When name is used by inner scope, it's implicitly used by outer scopes
private fun Scope.liftUsedNames(): Scope {
fun traverse(scope: Scope) {
scope.children.forEach { child ->
scope.usedNames += scope.declaredNames
traverse(child)
scope.usedNames += child.usedNames.filter { !it.isTemporary }
}
super.visitElement(node)
}
traverse(this)
return this
}
private fun recordScope(scope: JsScope) {
if (scope !in scopeTree) {
scopeTree[scope] = mutableSetOf()
val parent = scope.parent
if (parent != null) {
recordScope(parent)
scopeTree[parent]!!.add(scope)
private fun JsNode.computeScopes(): Scope {
val rootScope = Scope()
accept(object : RecursiveJsVisitor() {
var currentScope: Scope = rootScope
override fun visitFunction(x: JsFunction) {
x.name?.let { currentScope.declaredNames += it }
val oldScope = currentScope
currentScope = Scope().apply {
parent = currentScope
currentScope.children += this
}
currentScope.declaredNames += x.parameters.map { it.name }
super.visitFunction(x)
currentScope = oldScope
}
}
override fun visitCatch(x: JsCatch) {
currentScope.declaredNames += x.parameter.name
super.visitCatch(x)
}
override fun visit(x: JsVars.JsVar) {
currentScope.declaredNames += x.name
super.visit(x)
}
override fun visitNameRef(nameRef: JsNameRef) {
if (nameRef.qualifier == null) {
val name = nameRef.name
currentScope.usedNames += name ?: JsDynamicScope.declareName(nameRef.ident)
}
super.visitNameRef(nameRef)
}
override fun visitBreak(x: JsBreak) {}
override fun visitContinue(x: JsContinue) {}
})
return rootScope
}
private class Scope {
var parent: Scope? = null
val declaredNames = mutableSetOf<JsName>()
val usedNames = mutableSetOf<JsName>()
val children = mutableSetOf<Scope>()
}
@@ -25,21 +25,6 @@ import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
fun collectFunctionReferencesInside(scope: JsNode): List<JsName> =
collectReferencedNames(scope).filter { it.staticRef is JsFunction }
fun collectReferencedTemporaryNames(scope: JsNode): Set<JsName> {
val references = mutableSetOf<JsName>()
object : RecursiveJsVisitor() {
override fun visitElement(node: JsNode) {
if (node is HasName) {
node.name?.let { references += it }
}
super.visitElement(node)
}
}.accept(scope)
return references
}
private fun collectReferencedNames(scope: JsNode): Set<JsName> {
val references = mutableSetOf<JsName>()