All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.resolveTemporaryNames.kt Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
/*
 * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs

import org.jetbrains.kotlin.js.backend.ast.*

fun JsNode.resolveTemporaryNames() {
    val renamings = resolveNames()
    accept(object : RecursiveJsVisitor() {
        override fun visitElement(node: JsNode) {
            super.visitElement(node)
            if (node is HasName) {
                val name = node.name
                if (name != null) {
                    renamings[name]?.let { node.name = it }
                }
            }
        }
    })
}

private fun JsNode.resolveNames(): Map {
    val rootScope = computeScopes().liftUsedNames()
    val replacements = mutableMapOf()
    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 }

        val nextSuffix = mutableMapOf()
        for (temporaryName in scope.declaredNames.asSequence().filter { it.isTemporary }) {
            var resolvedName = temporaryName.ident
            var suffix = nextSuffix.getOrDefault(temporaryName.ident, 0)
            while (resolvedName in JsDeclarationScope.RESERVED_WORDS || !occupiedNames.add(resolvedName)) {
                resolvedName = "${temporaryName.ident}_${suffix++}"
            }
            nextSuffix[temporaryName.ident] = suffix
            replacements[temporaryName] = JsDynamicScope.declareName(resolvedName).apply { copyMetadataFrom(temporaryName) }
            occupiedNames += resolvedName
        }
        scope.children.forEach(::traverse)
    }

    traverse(rootScope)

    return replacements
}

// 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 += child.declaredNames
            traverse(child)
            scope.usedNames += child.usedNames
        }
    }
    traverse(this)
    return this
}

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()
    val usedNames = mutableSetOf()
    val children = mutableSetOf()
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy