[JS IR] generate JS separately for each file and link at the end
Name clashes are handled as a post-processing step
This commit is contained in:
committed by
teamcityserver
parent
936383254a
commit
95ab5e1b7e
+61
-327
@@ -14,7 +14,6 @@ import org.jetbrains.kotlin.ir.backend.js.export.*
|
|||||||
import org.jetbrains.kotlin.ir.backend.js.lower.StaticMembersLowering
|
import org.jetbrains.kotlin.ir.backend.js.lower.StaticMembersLowering
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.*
|
import org.jetbrains.kotlin.ir.backend.js.utils.*
|
||||||
import org.jetbrains.kotlin.ir.declarations.*
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
|
||||||
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
|
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
|
||||||
import org.jetbrains.kotlin.ir.util.isInterface
|
import org.jetbrains.kotlin.ir.util.isInterface
|
||||||
import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor
|
import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor
|
||||||
@@ -27,7 +26,6 @@ import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver
|
|||||||
import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder
|
import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder
|
||||||
import org.jetbrains.kotlin.js.sourceMap.SourceMapBuilderConsumer
|
import org.jetbrains.kotlin.js.sourceMap.SourceMapBuilderConsumer
|
||||||
import org.jetbrains.kotlin.js.util.TextOutputImpl
|
import org.jetbrains.kotlin.js.util.TextOutputImpl
|
||||||
import org.jetbrains.kotlin.utils.DFS
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class IrModuleToJsTransformer(
|
class IrModuleToJsTransformer(
|
||||||
@@ -62,25 +60,12 @@ class IrModuleToJsTransformer(
|
|||||||
module.files.forEach { StaticMembersLowering(backendContext).lower(it) }
|
module.files.forEach { StaticMembersLowering(backendContext).lower(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val additionalPackages = with(backendContext) {
|
val jsCode = if (fullJs) generateWrappedModuleBody(mainModuleName, modules, generateProgramFragments(modules, exportData)) else null
|
||||||
externalPackageFragment.values + listOf(
|
|
||||||
bodilessBuiltInsPackageFragment,
|
|
||||||
) + packageLevelJsModules
|
|
||||||
}
|
|
||||||
|
|
||||||
modules.forEach { module ->
|
|
||||||
namer.merge(module.files, additionalPackages)
|
|
||||||
}
|
|
||||||
|
|
||||||
val jsCode = if (fullJs) generateWrappedModuleBody(modules, generateProgramFragments(modules, exportData, namer), namer) else null
|
|
||||||
|
|
||||||
val dceJsCode = if (dceJs) {
|
val dceJsCode = if (dceJs) {
|
||||||
eliminateDeadDeclarations(modules, backendContext, removeUnusedAssociatedObjects)
|
eliminateDeadDeclarations(modules, backendContext, removeUnusedAssociatedObjects)
|
||||||
// Use a fresh namer for DCE so that we could compare the result with DCE-driven
|
|
||||||
// TODO: is this mode relevant for scripting? If yes, refactor so that the external name tables are used here when needed.
|
generateWrappedModuleBody(mainModuleName, modules, generateProgramFragments(modules, exportData))
|
||||||
val namer = NameTables(emptyList(), context = backendContext)
|
|
||||||
namer.merge(modules.flatMap { it.files }, additionalPackages)
|
|
||||||
generateWrappedModuleBody(modules, generateProgramFragments(modules, exportData, namer), namer)
|
|
||||||
} else null
|
} else null
|
||||||
|
|
||||||
return CompilerResult(jsCode, dceJsCode, dts)
|
return CompilerResult(jsCode, dceJsCode, dts)
|
||||||
@@ -89,33 +74,13 @@ class IrModuleToJsTransformer(
|
|||||||
private fun generateProgramFragments(
|
private fun generateProgramFragments(
|
||||||
modules: Iterable<IrModuleFragment>,
|
modules: Iterable<IrModuleFragment>,
|
||||||
exportData: Map<IrModuleFragment, Map<IrFile, List<ExportedDeclaration>>>,
|
exportData: Map<IrModuleFragment, Map<IrFile, List<ExportedDeclaration>>>,
|
||||||
namer: NameTables,
|
|
||||||
): Map<IrFile, JsIrProgramFragment> {
|
): Map<IrFile, JsIrProgramFragment> {
|
||||||
|
|
||||||
val nameGenerator = IrNamerImpl(newNameTables = namer, backendContext)
|
|
||||||
|
|
||||||
val fragments = mutableMapOf<IrFile, JsIrProgramFragment>()
|
val fragments = mutableMapOf<IrFile, JsIrProgramFragment>()
|
||||||
modules.forEach { m ->
|
modules.forEach { m ->
|
||||||
m.files.forEach { f ->
|
m.files.forEach { f ->
|
||||||
val fragment = JsIrProgramFragment(f.fqName.asString())
|
|
||||||
|
|
||||||
val exports = exportData[m]!![f]!! // TODO
|
val exports = exportData[m]!![f]!! // TODO
|
||||||
|
fragments[f] = generateProgramFragment(f, exports)
|
||||||
val internalModuleName = JsName("_", false)
|
|
||||||
val globalNames = NameTable<String>(namer.globalNames)
|
|
||||||
val exportStatements =
|
|
||||||
ExportModelToJsStatements(nameGenerator, { globalNames.declareFreshName(it, it) }).generateModuleExport(
|
|
||||||
ExportedModule(mainModuleName, moduleKind, exports),
|
|
||||||
internalModuleName,
|
|
||||||
)
|
|
||||||
|
|
||||||
fragment.exports.statements += exportStatements
|
|
||||||
|
|
||||||
if (exports.isNotEmpty()) {
|
|
||||||
fragment.dts = exports.toTypeScript(moduleKind)
|
|
||||||
}
|
|
||||||
|
|
||||||
fragments[f] = fragment
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,123 +88,19 @@ class IrModuleToJsTransformer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun generateWrappedModuleBody(
|
private fun generateWrappedModuleBody(
|
||||||
modules: Iterable<IrModuleFragment>,
|
|
||||||
fragments: Map<IrFile, JsIrProgramFragment>,
|
|
||||||
namer: NameTables
|
|
||||||
): CompilationOutputs {
|
|
||||||
if (multiModule) {
|
|
||||||
|
|
||||||
val refInfo = buildCrossModuleReferenceInfo(modules)
|
|
||||||
|
|
||||||
val rM = modules.reversed()
|
|
||||||
|
|
||||||
val main = rM.first()
|
|
||||||
val others = rM.drop(1)
|
|
||||||
|
|
||||||
val mainModule = generateWrappedModuleBody2(
|
|
||||||
mainModuleName,
|
|
||||||
listOf(main),
|
|
||||||
others,
|
|
||||||
fragments,
|
|
||||||
namer,
|
|
||||||
refInfo,
|
|
||||||
generateMainCall = true
|
|
||||||
)
|
|
||||||
|
|
||||||
val dependencies = others.mapIndexed { index, module ->
|
|
||||||
val moduleName = module.externalModuleName()
|
|
||||||
|
|
||||||
moduleName to generateWrappedModuleBody2(
|
|
||||||
moduleName,
|
|
||||||
listOf(module),
|
|
||||||
others.drop(index + 1),
|
|
||||||
fragments,
|
|
||||||
namer,
|
|
||||||
refInfo,
|
|
||||||
generateMainCall = false
|
|
||||||
)
|
|
||||||
}.reversed()
|
|
||||||
|
|
||||||
return CompilationOutputs(mainModule.jsCode, mainModule.jsProgram, mainModule.sourceMap, dependencies)
|
|
||||||
} else {
|
|
||||||
return generateWrappedModuleBody2(
|
|
||||||
mainModuleName,
|
|
||||||
modules,
|
|
||||||
emptyList(),
|
|
||||||
fragments,
|
|
||||||
namer,
|
|
||||||
EmptyCrossModuleReferenceInfo
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun generateWrappedModuleBody2(
|
|
||||||
moduleName: String,
|
moduleName: String,
|
||||||
modules: Iterable<IrModuleFragment>,
|
modules: Iterable<IrModuleFragment>,
|
||||||
dependencies: Iterable<IrModuleFragment>,
|
|
||||||
fragments: Map<IrFile, JsIrProgramFragment>,
|
fragments: Map<IrFile, JsIrProgramFragment>,
|
||||||
namer: NameTables,
|
|
||||||
refInfo: CrossModuleReferenceInfo,
|
|
||||||
generateMainCall: Boolean = true
|
|
||||||
): CompilationOutputs {
|
): CompilationOutputs {
|
||||||
|
val program = Merger(
|
||||||
|
moduleName,
|
||||||
|
moduleKind,
|
||||||
|
modules.map { it.files.map { fragments[it]!! } },
|
||||||
|
generateScriptModule,
|
||||||
|
generateRegionComments
|
||||||
|
).merge()
|
||||||
|
|
||||||
val nameGenerator = refInfo.withReferenceTracking(
|
program.resolveTemporaryNames()
|
||||||
IrNamerImpl(newNameTables = namer, backendContext),
|
|
||||||
modules
|
|
||||||
)
|
|
||||||
val staticContext = JsStaticContext(
|
|
||||||
backendContext = backendContext,
|
|
||||||
irNamer = nameGenerator,
|
|
||||||
globalNameScope = namer.globalNames
|
|
||||||
)
|
|
||||||
|
|
||||||
val (importStatements, importedJsModules) =
|
|
||||||
generateImportStatements(
|
|
||||||
getNameForExternalDeclaration = { staticContext.getNameForStaticDeclaration(it) },
|
|
||||||
declareFreshGlobal = { JsName(sanitizeName(it), false) } // TODO: Declare fresh name
|
|
||||||
)
|
|
||||||
|
|
||||||
val (moduleBody, callToMain, exportStatements) = generateModuleBody(modules, staticContext, fragments)
|
|
||||||
|
|
||||||
val internalModuleName = JsName("_", false)
|
|
||||||
|
|
||||||
val (crossModuleImports, importedKotlinModules) = generateCrossModuleImports(
|
|
||||||
nameGenerator,
|
|
||||||
modules,
|
|
||||||
dependencies,
|
|
||||||
{ JsName(sanitizeName(it), false) })
|
|
||||||
val crossModuleExports = generateCrossModuleExports(modules, refInfo, internalModuleName)
|
|
||||||
|
|
||||||
val program = JsProgram()
|
|
||||||
if (generateScriptModule) {
|
|
||||||
with(program.globalBlock) {
|
|
||||||
statements.addWithComment("block: imports", importStatements + crossModuleImports)
|
|
||||||
statements += moduleBody
|
|
||||||
statements.addWithComment("block: exports", exportStatements + crossModuleExports)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val rootFunction = JsFunction(program.rootScope, JsBlock(), "root function").apply {
|
|
||||||
parameters += JsParameter(internalModuleName)
|
|
||||||
parameters += (importedJsModules + importedKotlinModules).map { JsParameter(it.internalName) }
|
|
||||||
with(body) {
|
|
||||||
statements.addWithComment("block: imports", importStatements + crossModuleImports)
|
|
||||||
statements += moduleBody
|
|
||||||
statements.addWithComment("block: exports", exportStatements + crossModuleExports)
|
|
||||||
if (generateMainCall && callToMain != null) {
|
|
||||||
statements += callToMain
|
|
||||||
}
|
|
||||||
statements += JsReturn(internalModuleName.makeRef())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
program.globalBlock.statements += ModuleWrapperTranslation.wrap(
|
|
||||||
moduleName,
|
|
||||||
rootFunction,
|
|
||||||
importedJsModules + importedKotlinModules,
|
|
||||||
program,
|
|
||||||
kind = moduleKind
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val jsCode = TextOutputImpl()
|
val jsCode = TextOutputImpl()
|
||||||
|
|
||||||
@@ -279,79 +140,35 @@ class IrModuleToJsTransformer(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun IrModuleFragment.externalModuleName(): String {
|
|
||||||
return moduleToName[this] ?: sanitizeName(safeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun generateCrossModuleImports(
|
|
||||||
namerWithImports: IrNamerWithImports,
|
|
||||||
currentModules: Iterable<IrModuleFragment>,
|
|
||||||
allowedDependencies: Iterable<IrModuleFragment>,
|
|
||||||
declareFreshGlobal: (String) -> JsName
|
|
||||||
): Pair<MutableList<JsStatement>, List<JsImportedModule>> {
|
|
||||||
val imports = mutableListOf<JsStatement>()
|
|
||||||
val modules = mutableListOf<JsImportedModule>()
|
|
||||||
|
|
||||||
namerWithImports.imports().forEach { (module, names) ->
|
|
||||||
check(module in allowedDependencies) {
|
|
||||||
val deps = if (names.size > 10) "[${names.take(10).joinToString()}, ...]" else "$names"
|
|
||||||
"Module ${currentModules.map { it.name.asString() }} depend on module ${module.name.asString()} via $deps"
|
|
||||||
}
|
|
||||||
|
|
||||||
val moduleName = declareFreshGlobal(module.safeName)
|
|
||||||
modules += JsImportedModule(module.externalModuleName(), moduleName, null, relativeRequirePath)
|
|
||||||
|
|
||||||
names.forEach {
|
|
||||||
imports += JsVars(JsVars.JsVar(JsName(it, false), JsNameRef(it, JsNameRef("\$crossModule\$", moduleName.makeRef()))))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return imports to modules
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun generateCrossModuleExports(
|
|
||||||
modules: Iterable<IrModuleFragment>,
|
|
||||||
refInfo: CrossModuleReferenceInfo,
|
|
||||||
internalModuleName: JsName
|
|
||||||
): List<JsStatement> {
|
|
||||||
return modules.flatMap {
|
|
||||||
refInfo.exports(it).map {
|
|
||||||
jsAssignment(
|
|
||||||
JsNameRef(it, JsNameRef("\$crossModule\$", internalModuleName.makeRef())),
|
|
||||||
JsNameRef(it)
|
|
||||||
).makeStmt()
|
|
||||||
}
|
|
||||||
}.let {
|
|
||||||
if (!it.isEmpty()) {
|
|
||||||
val createExportBlock = jsAssignment(
|
|
||||||
JsNameRef("\$crossModule\$", internalModuleName.makeRef()),
|
|
||||||
JsAstUtils.or(JsNameRef("\$crossModule\$", internalModuleName.makeRef()), JsObjectLiteral())
|
|
||||||
).makeStmt()
|
|
||||||
return listOf(createExportBlock) + it
|
|
||||||
} else it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private data class ModuleBody(val statements: List<JsStatement>, val mainFunction: JsStatement?, val exportStatements: List<JsStatement>)
|
|
||||||
|
|
||||||
private fun generateModuleBody(
|
|
||||||
modules: Iterable<IrModuleFragment>,
|
|
||||||
staticContext: JsStaticContext,
|
|
||||||
fragments: Map<IrFile, JsIrProgramFragment>
|
|
||||||
): ModuleBody {
|
|
||||||
val thisModuleFragments = modules.map { it.files.map { fragments[it]!!.fillProgramFragment(it, staticContext) } }
|
|
||||||
|
|
||||||
return merge(thisModuleFragments, staticContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val generateFilePaths = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_COMMENTS_WITH_FILE_PATH)
|
private val generateFilePaths = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_COMMENTS_WITH_FILE_PATH)
|
||||||
private val pathPrefixMap = backendContext.configuration.getMap(JSConfigurationKeys.FILE_PATHS_PREFIX_MAP)
|
private val pathPrefixMap = backendContext.configuration.getMap(JSConfigurationKeys.FILE_PATHS_PREFIX_MAP)
|
||||||
|
|
||||||
private fun JsIrProgramFragment.fillProgramFragment(file: IrFile, staticContext: JsStaticContext): JsIrProgramFragment {
|
private fun generateProgramFragment(file: IrFile, exports: List<ExportedDeclaration>): JsIrProgramFragment {
|
||||||
require(staticContext.classModels.isEmpty())
|
val nameGenerator = JsNameLinkingNamer(backendContext)
|
||||||
require(staticContext.initializerBlock.statements.isEmpty())
|
|
||||||
|
val staticContext = JsStaticContext(
|
||||||
|
backendContext = backendContext,
|
||||||
|
irNamer = nameGenerator,
|
||||||
|
globalNameScope = namer.globalNames
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = JsIrProgramFragment(file.fqName.asString())
|
||||||
|
|
||||||
|
val internalModuleName = JsName("_", false)
|
||||||
|
val globalNames = NameTable<String>(namer.globalNames)
|
||||||
|
val exportStatements = ExportModelToJsStatements(staticContext, { globalNames.declareFreshName(it, it) }).generateModuleExport(
|
||||||
|
ExportedModule(mainModuleName, moduleKind, exports),
|
||||||
|
internalModuleName,
|
||||||
|
)
|
||||||
|
|
||||||
|
result.exports.statements += exportStatements
|
||||||
|
|
||||||
|
if (exports.isNotEmpty()) {
|
||||||
|
result.dts = exports.toTypeScript(moduleKind)
|
||||||
|
}
|
||||||
|
|
||||||
|
val statements = result.declarations.statements
|
||||||
|
|
||||||
val statements = this.declarations.statements
|
|
||||||
|
|
||||||
val fileStatements = file.accept(IrFileToJsTransformer(), staticContext).statements
|
val fileStatements = file.accept(IrFileToJsTransformer(), staticContext).statements
|
||||||
if (fileStatements.isNotEmpty()) {
|
if (fileStatements.isNotEmpty()) {
|
||||||
@@ -376,84 +193,51 @@ class IrModuleToJsTransformer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
statements.addAll(fileStatements)
|
statements.addAll(fileStatements)
|
||||||
statements.endRegion()
|
if (generateRegionComments) {
|
||||||
|
statements += JsSingleLineComment("endregion")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.classes += staticContext.classModels
|
staticContext.classModels.entries.forEach { (symbol, model) ->
|
||||||
this.initializers.statements += staticContext.initializerBlock.statements
|
result.classes[nameGenerator.getNameForClass(symbol.owner)] = model
|
||||||
|
}
|
||||||
|
|
||||||
|
result.initializers.statements += staticContext.initializerBlock.statements
|
||||||
|
|
||||||
if (mainArguments != null) {
|
if (mainArguments != null) {
|
||||||
JsMainFunctionDetector(backendContext).getMainFunctionOrNull(file)?.let {
|
JsMainFunctionDetector(backendContext).getMainFunctionOrNull(file)?.let {
|
||||||
val jsName = staticContext.getNameForStaticFunction(it)
|
val jsName = staticContext.getNameForStaticFunction(it)
|
||||||
val generateArgv = it.valueParameters.firstOrNull()?.isStringArrayParameter() ?: false
|
val generateArgv = it.valueParameters.firstOrNull()?.isStringArrayParameter() ?: false
|
||||||
val generateContinuation = it.isLoweredSuspendFunction(backendContext)
|
val generateContinuation = it.isLoweredSuspendFunction(backendContext)
|
||||||
this.mainFunction = JsInvocation(jsName.makeRef(), generateMainArguments(generateArgv, generateContinuation, staticContext)).makeStmt()
|
result.mainFunction = JsInvocation(jsName.makeRef(), generateMainArguments(generateArgv, generateContinuation, staticContext)).makeStmt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
backendContext.testFunsPerFile[file]?.let {
|
backendContext.testFunsPerFile[file]?.let {
|
||||||
this.testFunInvocation = JsInvocation(staticContext.getNameForStaticFunction(it).makeRef()).makeStmt()
|
result.testFunInvocation = JsInvocation(staticContext.getNameForStaticFunction(it).makeRef()).makeStmt()
|
||||||
|
result.suiteFn = staticContext.getNameForStaticFunction(backendContext.suiteFun!!.owner)
|
||||||
}
|
}
|
||||||
|
|
||||||
staticContext.classModels.clear()
|
|
||||||
staticContext.initializerBlock.statements.clear()
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun merge(fragments: List<List<JsIrProgramFragment>>, staticContext: JsStaticContext): ModuleBody {
|
result.importedModules += nameGenerator.importedModules
|
||||||
val statements = mutableListOf<JsStatement>().also {
|
|
||||||
if (!generateScriptModule) it += JsStringLiteral("use strict").makeStmt()
|
fun computeTag(declaration: IrDeclaration): String {
|
||||||
|
// TODO proper tags
|
||||||
|
return System.identityHashCode(declaration).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
val preDeclarationBlock = JsGlobalBlock()
|
nameGenerator.nameMap.entries.forEach { (declaration, name) ->
|
||||||
val postDeclarationBlock = JsGlobalBlock()
|
val tag = computeTag(declaration)
|
||||||
|
result.nameBindings[tag] = name
|
||||||
statements.addWithComment("block: pre-declaration", preDeclarationBlock)
|
|
||||||
|
|
||||||
val classModels = mutableMapOf<IrClassSymbol, JsIrClassModel>()
|
|
||||||
val initializerBlock = JsGlobalBlock()
|
|
||||||
fragments.forEach {
|
|
||||||
it.forEach {
|
|
||||||
statements += it.declarations.statements
|
|
||||||
classModels += it.classes
|
|
||||||
initializerBlock.statements += it.initializers.statements
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort member forwarding code
|
nameGenerator.imports.entries.forEach { (declaration, importExpression) ->
|
||||||
processClassModels(classModels, preDeclarationBlock, postDeclarationBlock)
|
val tag = computeTag(declaration)
|
||||||
|
result.imports[tag] = importExpression
|
||||||
statements.addWithComment("block: post-declaration", postDeclarationBlock.statements)
|
|
||||||
statements.addWithComment("block: init", initializerBlock.statements)
|
|
||||||
|
|
||||||
val lastModuleFragments = fragments.last()
|
|
||||||
|
|
||||||
// Merge test function invocations
|
|
||||||
if (lastModuleFragments.any { it.testFunInvocation != null }) {
|
|
||||||
val testFunBody = JsBlock()
|
|
||||||
val testFun = JsFunction(emptyScope, testFunBody, "root test fun")
|
|
||||||
val suiteFunRef = staticContext.getNameForStaticFunction(backendContext.suiteFun!!.owner).makeRef()
|
|
||||||
|
|
||||||
val tests = lastModuleFragments.filter { it.testFunInvocation != null }
|
|
||||||
.groupBy({ it.packageFqn }) { it.testFunInvocation } // String -> [IrSimpleFunction]
|
|
||||||
|
|
||||||
for ((pkg, testCalls) in tests) {
|
|
||||||
val pkgTestFun = JsFunction(emptyScope, JsBlock(), "test fun for $pkg")
|
|
||||||
pkgTestFun.body.statements += testCalls
|
|
||||||
testFun.body.statements += JsInvocation(suiteFunRef, JsStringLiteral(pkg), JsBooleanLiteral(false), pkgTestFun).makeStmt()
|
|
||||||
}
|
|
||||||
|
|
||||||
statements.startRegion("block: tests")
|
|
||||||
statements += JsInvocation(testFun).makeStmt()
|
|
||||||
statements.endRegion()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val callToMain = lastModuleFragments.sortedBy { it.packageFqn }.firstNotNullOfOrNull { it.mainFunction }
|
return result
|
||||||
|
|
||||||
val exportStatements = fragments.flatMap { it.flatMap { it.exports.statements } }
|
|
||||||
|
|
||||||
return ModuleBody(statements, callToMain, exportStatements)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateMainArguments(
|
private fun generateMainArguments(
|
||||||
@@ -525,54 +309,4 @@ class IrModuleToJsTransformer(
|
|||||||
val importedJsModules = (declarationLevelJsModules + packageLevelJsModules).distinctBy { it.key }
|
val importedJsModules = (declarationLevelJsModules + packageLevelJsModules).distinctBy { it.key }
|
||||||
return Pair(importStatements, importedJsModules)
|
return Pair(importStatements, importedJsModules)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun MutableList<JsStatement>.startRegion(description: String = "") {
|
|
||||||
if (generateRegionComments) {
|
|
||||||
this += JsSingleLineComment("region $description")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun MutableList<JsStatement>.endRegion() {
|
|
||||||
if (generateRegionComments) {
|
|
||||||
this += JsSingleLineComment("endregion")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun MutableList<JsStatement>.addWithComment(regionDescription: String = "", block: JsBlock) {
|
|
||||||
startRegion(regionDescription)
|
|
||||||
this += block
|
|
||||||
endRegion()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun MutableList<JsStatement>.addWithComment(regionDescription: String = "", statements: List<JsStatement>) {
|
|
||||||
if (statements.isEmpty()) return
|
|
||||||
|
|
||||||
startRegion(regionDescription)
|
|
||||||
this += statements
|
|
||||||
endRegion()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun processClassModels(
|
|
||||||
classModelMap: Map<IrClassSymbol, JsIrClassModel>,
|
|
||||||
preDeclarationBlock: JsBlock,
|
|
||||||
postDeclarationBlock: JsBlock
|
|
||||||
) {
|
|
||||||
val declarationHandler = object : DFS.AbstractNodeHandler<IrClassSymbol, Unit>() {
|
|
||||||
override fun result() {}
|
|
||||||
override fun afterChildren(current: IrClassSymbol) {
|
|
||||||
classModelMap[current]?.let {
|
|
||||||
preDeclarationBlock.statements += it.preDeclarationBlock.statements
|
|
||||||
postDeclarationBlock.statements += it.postDeclarationBlock.statements
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DFS.dfs(
|
|
||||||
classModelMap.keys,
|
|
||||||
{ klass -> classModelMap[klass]?.superClasses ?: emptyList() },
|
|
||||||
declarationHandler
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
+2
-4
@@ -32,7 +32,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
|
|||||||
}
|
}
|
||||||
private val classPrototypeRef = prototypeOf(classNameRef)
|
private val classPrototypeRef = prototypeOf(classNameRef)
|
||||||
private val classBlock = JsGlobalBlock()
|
private val classBlock = JsGlobalBlock()
|
||||||
private val classModel = JsIrClassModel(irClass)
|
private val classModel = JsIrClassModel(irClass.superTypes.map { context.getNameForClass((it.classifierOrFail as IrClassSymbol).owner) })
|
||||||
|
|
||||||
private val es6mode = context.staticContext.backendContext.es6mode
|
private val es6mode = context.staticContext.backendContext.es6mode
|
||||||
|
|
||||||
@@ -364,9 +364,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
|
|||||||
private val IrClassifierSymbol.isInterface get() = (owner as? IrClass)?.isInterface == true
|
private val IrClassifierSymbol.isInterface get() = (owner as? IrClass)?.isInterface == true
|
||||||
private val IrClassifierSymbol.isEffectivelyExternal get() = (owner as? IrDeclaration)?.isEffectivelyExternal() == true
|
private val IrClassifierSymbol.isEffectivelyExternal get() = (owner as? IrDeclaration)?.isEffectivelyExternal() == true
|
||||||
|
|
||||||
class JsIrClassModel(val klass: IrClass) {
|
class JsIrClassModel(val superClasses: List<JsName>) {
|
||||||
val superClasses = klass.superTypes.map { it.classifierOrFail as IrClassSymbol }
|
|
||||||
|
|
||||||
val preDeclarationBlock = JsGlobalBlock()
|
val preDeclarationBlock = JsGlobalBlock()
|
||||||
val postDeclarationBlock = JsGlobalBlock()
|
val postDeclarationBlock = JsGlobalBlock()
|
||||||
}
|
}
|
||||||
|
|||||||
+229
-3
@@ -5,16 +5,242 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||||
import org.jetbrains.kotlin.js.backend.ast.JsGlobalBlock
|
import org.jetbrains.kotlin.js.backend.ast.*
|
||||||
import org.jetbrains.kotlin.js.backend.ast.JsStatement
|
import org.jetbrains.kotlin.serialization.js.ModuleKind
|
||||||
|
import org.jetbrains.kotlin.utils.DFS
|
||||||
|
|
||||||
class JsIrProgramFragment(val packageFqn: String) {
|
class JsIrProgramFragment(val packageFqn: String) {
|
||||||
|
val nameBindings = mutableMapOf<String, JsName>()
|
||||||
val declarations = JsGlobalBlock()
|
val declarations = JsGlobalBlock()
|
||||||
val exports = JsGlobalBlock()
|
val exports = JsGlobalBlock()
|
||||||
|
val importedModules = mutableListOf<JsImportedModule>()
|
||||||
|
val imports = mutableMapOf<String, JsExpression>()
|
||||||
var dts: String? = null
|
var dts: String? = null
|
||||||
val classes = mutableMapOf<IrClassSymbol, JsIrClassModel>()
|
val classes = mutableMapOf<JsName, JsIrClassModel>()
|
||||||
val initializers = JsGlobalBlock()
|
val initializers = JsGlobalBlock()
|
||||||
var mainFunction: JsStatement? = null
|
var mainFunction: JsStatement? = null
|
||||||
var testFunInvocation: JsStatement? = null
|
var testFunInvocation: JsStatement? = null
|
||||||
|
var suiteFn: JsName? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
class Merger(
|
||||||
|
private val moduleName: String,
|
||||||
|
private val moduleKind: ModuleKind,
|
||||||
|
private val fragments: List<List<JsIrProgramFragment>>,
|
||||||
|
private val generateScriptModule: Boolean,
|
||||||
|
private val generateRegionComments: Boolean,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val importStatements = mutableMapOf<String, JsStatement>()
|
||||||
|
private val importedModulesMap = mutableMapOf<JsImportedModuleKey, JsImportedModule>()
|
||||||
|
|
||||||
|
private fun linkJsNames() {
|
||||||
|
val nameMap = mutableMapOf<String, JsName>()
|
||||||
|
|
||||||
|
fragments.flatMap { it }.forEach { f ->
|
||||||
|
f.buildRenames(nameMap).run {
|
||||||
|
rename(f.declarations)
|
||||||
|
rename(f.exports)
|
||||||
|
|
||||||
|
f.imports.entries.forEach { (declaration, importExpression) ->
|
||||||
|
val importName = nameMap[declaration] ?: error("Missing name for declaration '${declaration}'")
|
||||||
|
importStatements.putIfAbsent(declaration, JsVars(JsVars.JsVar(importName, rename(importExpression))))
|
||||||
|
}
|
||||||
|
|
||||||
|
val classModels = mutableMapOf<JsName, JsIrClassModel>() + f.classes
|
||||||
|
f.classes.clear()
|
||||||
|
classModels.entries.forEach { (name, model) ->
|
||||||
|
f.classes[rename(name)] = JsIrClassModel(model.superClasses.map { rename(it) }).also {
|
||||||
|
it.preDeclarationBlock.statements += model.preDeclarationBlock.statements
|
||||||
|
it.postDeclarationBlock.statements += model.postDeclarationBlock.statements
|
||||||
|
rename(it.preDeclarationBlock)
|
||||||
|
rename(it.postDeclarationBlock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rename(f.initializers)
|
||||||
|
f.mainFunction?.let { rename(it) }
|
||||||
|
f.testFunInvocation?.let { rename(it) }
|
||||||
|
f.suiteFn?.let { f.suiteFn = rename(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JsIrProgramFragment.buildRenames(nameMap: MutableMap<String, JsName>): Map<JsName, JsName> {
|
||||||
|
val result = mutableMapOf<JsName, JsName>()
|
||||||
|
|
||||||
|
this.importedModules.forEach { module ->
|
||||||
|
val existingModule = importedModulesMap.getOrPut(module.key) { module }
|
||||||
|
if (existingModule !== module) {
|
||||||
|
result[module.internalName] = existingModule.internalName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nameBindings.entries.forEach { (tag, name) ->
|
||||||
|
val existingName = nameMap.getOrPut(tag) { name }
|
||||||
|
if (existingName !== name) {
|
||||||
|
result[name] = existingName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Map<JsName, JsName>.rename(name: JsName): JsName = getOrElse(name) { name }
|
||||||
|
|
||||||
|
private fun <T : JsNode> Map<JsName, JsName>.rename(rootNode: T): T {
|
||||||
|
rootNode.accept(object : RecursiveJsVisitor() {
|
||||||
|
override fun visitElement(node: JsNode) {
|
||||||
|
super.visitElement(node)
|
||||||
|
if (node is HasName) {
|
||||||
|
node.name = node.name?.let { rename(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return rootNode
|
||||||
|
}
|
||||||
|
|
||||||
|
fun merge(): JsProgram {
|
||||||
|
linkJsNames()
|
||||||
|
|
||||||
|
val moduleBody = mutableListOf<JsStatement>().also {
|
||||||
|
if (!generateScriptModule) it += JsStringLiteral("use strict").makeStmt()
|
||||||
|
}
|
||||||
|
|
||||||
|
val preDeclarationBlock = JsGlobalBlock()
|
||||||
|
val postDeclarationBlock = JsGlobalBlock()
|
||||||
|
|
||||||
|
moduleBody.addWithComment("block: pre-declaration", preDeclarationBlock)
|
||||||
|
|
||||||
|
val classModels = mutableMapOf<JsName, JsIrClassModel>()
|
||||||
|
val initializerBlock = JsGlobalBlock()
|
||||||
|
fragments.forEach {
|
||||||
|
it.forEach {
|
||||||
|
moduleBody += it.declarations.statements
|
||||||
|
classModels += it.classes
|
||||||
|
initializerBlock.statements += it.initializers.statements
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort member forwarding code
|
||||||
|
processClassModels(classModels, preDeclarationBlock, postDeclarationBlock)
|
||||||
|
|
||||||
|
moduleBody.addWithComment("block: post-declaration", postDeclarationBlock.statements)
|
||||||
|
moduleBody.addWithComment("block: init", initializerBlock.statements)
|
||||||
|
|
||||||
|
val lastModuleFragments = fragments.last()
|
||||||
|
|
||||||
|
// Merge test function invocations
|
||||||
|
if (lastModuleFragments.any { it.testFunInvocation != null }) {
|
||||||
|
val testFunBody = JsBlock()
|
||||||
|
val testFun = JsFunction(emptyScope, testFunBody, "root test fun")
|
||||||
|
val suiteFunRef = lastModuleFragments.firstNotNullOf { it.suiteFn }.makeRef()
|
||||||
|
|
||||||
|
val tests = lastModuleFragments.filter { it.testFunInvocation != null }
|
||||||
|
.groupBy({ it.packageFqn }) { it.testFunInvocation } // String -> [IrSimpleFunction]
|
||||||
|
|
||||||
|
for ((pkg, testCalls) in tests) {
|
||||||
|
val pkgTestFun = JsFunction(emptyScope, JsBlock(), "test fun for $pkg")
|
||||||
|
pkgTestFun.body.statements += testCalls
|
||||||
|
testFun.body.statements += JsInvocation(suiteFunRef, JsStringLiteral(pkg), JsBooleanLiteral(false), pkgTestFun).makeStmt()
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleBody.startRegion("block: tests")
|
||||||
|
moduleBody += JsInvocation(testFun).makeStmt()
|
||||||
|
moduleBody.endRegion()
|
||||||
|
}
|
||||||
|
|
||||||
|
val callToMain = lastModuleFragments.sortedBy { it.packageFqn }.firstNotNullOfOrNull { it.mainFunction }
|
||||||
|
|
||||||
|
val exportStatements = fragments.flatMap { it.flatMap { it.exports.statements } }
|
||||||
|
|
||||||
|
val importedJsModules = this.importedModulesMap.values.toList()
|
||||||
|
val importStatements = this.importStatements.values.toList()
|
||||||
|
|
||||||
|
val internalModuleName = JsName("_", false)
|
||||||
|
|
||||||
|
val program = JsProgram()
|
||||||
|
|
||||||
|
if (generateScriptModule) {
|
||||||
|
with(program.globalBlock) {
|
||||||
|
this.statements.addWithComment("block: imports", importStatements)
|
||||||
|
this.statements += moduleBody
|
||||||
|
this.statements.addWithComment("block: exports", exportStatements)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val rootFunction = JsFunction(program.rootScope, JsBlock(), "root function").apply {
|
||||||
|
parameters += JsParameter(internalModuleName)
|
||||||
|
parameters += (importedJsModules).map { JsParameter(it.internalName) }
|
||||||
|
with(body) {
|
||||||
|
this.statements.addWithComment("block: imports", importStatements)
|
||||||
|
this.statements += moduleBody
|
||||||
|
this.statements.addWithComment("block: exports", exportStatements)
|
||||||
|
callToMain?.let { this.statements += it }
|
||||||
|
this.statements += JsReturn(internalModuleName.makeRef())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
program.globalBlock.statements += ModuleWrapperTranslation.wrap(
|
||||||
|
moduleName,
|
||||||
|
rootFunction,
|
||||||
|
importedJsModules,
|
||||||
|
program,
|
||||||
|
kind = moduleKind
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return program
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun processClassModels(
|
||||||
|
classModelMap: Map<JsName, JsIrClassModel>,
|
||||||
|
preDeclarationBlock: JsBlock,
|
||||||
|
postDeclarationBlock: JsBlock
|
||||||
|
) {
|
||||||
|
val declarationHandler = object : DFS.AbstractNodeHandler<JsName, Unit>() {
|
||||||
|
override fun result() {}
|
||||||
|
override fun afterChildren(current: JsName) {
|
||||||
|
classModelMap[current]?.let {
|
||||||
|
preDeclarationBlock.statements += it.preDeclarationBlock.statements
|
||||||
|
postDeclarationBlock.statements += it.postDeclarationBlock.statements
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DFS.dfs(
|
||||||
|
classModelMap.keys,
|
||||||
|
{ klass -> classModelMap[klass]?.superClasses ?: emptyList() },
|
||||||
|
declarationHandler
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MutableList<JsStatement>.startRegion(description: String = "") {
|
||||||
|
if (generateRegionComments) {
|
||||||
|
this += JsSingleLineComment("region $description")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MutableList<JsStatement>.endRegion() {
|
||||||
|
if (generateRegionComments) {
|
||||||
|
this += JsSingleLineComment("endregion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MutableList<JsStatement>.addWithComment(regionDescription: String = "", block: JsBlock) {
|
||||||
|
startRegion(regionDescription)
|
||||||
|
this += block
|
||||||
|
endRegion()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MutableList<JsStatement>.addWithComment(regionDescription: String = "", statements: List<JsStatement>) {
|
||||||
|
if (statements.isEmpty()) return
|
||||||
|
|
||||||
|
startRegion(regionDescription)
|
||||||
|
this += statements
|
||||||
|
endRegion()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+125
@@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* 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.ir.backend.js.JsIrBackendContext
|
||||||
|
import org.jetbrains.kotlin.ir.backend.js.utils.*
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
|
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||||
|
import org.jetbrains.kotlin.ir.types.IrSimpleType
|
||||||
|
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
|
||||||
|
import org.jetbrains.kotlin.ir.util.parentAsClass
|
||||||
|
import org.jetbrains.kotlin.ir.util.render
|
||||||
|
import org.jetbrains.kotlin.js.backend.ast.*
|
||||||
|
import org.jetbrains.kotlin.utils.DFS
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||||
|
|
||||||
|
class JsNameLinkingNamer(private val context: JsIrBackendContext) : IrNamerBase() {
|
||||||
|
|
||||||
|
val nameMap = mutableMapOf<IrDeclaration, JsName>()
|
||||||
|
|
||||||
|
private fun IrDeclarationWithName.getName(prefix: String = ""): JsName {
|
||||||
|
return nameMap.getOrPut(this) { JsName(sanitizeName(prefix + getJsNameOrKotlinName().asString()), true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val importedModules = mutableListOf<JsImportedModule>()
|
||||||
|
val imports = mutableMapOf<IrDeclaration, JsExpression>()
|
||||||
|
|
||||||
|
override fun getNameForStaticDeclaration(declaration: IrDeclarationWithName): JsName {
|
||||||
|
|
||||||
|
if (declaration.isEffectivelyExternal()) {
|
||||||
|
val jsModule: String? = declaration.getJsModule()
|
||||||
|
val maybeParentFile: IrFile? = declaration.parent as? IrFile
|
||||||
|
val fileJsModule: String? = maybeParentFile?.getJsModule()
|
||||||
|
val jsQualifier: String? = maybeParentFile?.getJsQualifier()
|
||||||
|
|
||||||
|
when {
|
||||||
|
jsModule != null -> {
|
||||||
|
val name = JsName(declaration.getJsNameOrKotlinName().asString(), false)
|
||||||
|
importedModules += JsImportedModule(jsModule, name, name.makeRef())
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
fileJsModule != null -> {
|
||||||
|
if (declaration !in nameMap) {
|
||||||
|
val moduleName = JsName("\$module\$$jsModule", true)
|
||||||
|
importedModules += JsImportedModule(fileJsModule, moduleName, null)
|
||||||
|
val qualifiedReference =
|
||||||
|
if (jsQualifier == null) moduleName.makeRef() else JsNameRef(jsQualifier, moduleName.makeRef())
|
||||||
|
imports[declaration] = JsNameRef(declaration.getJsNameOrKotlinName().identifier, qualifiedReference)
|
||||||
|
return declaration.getName()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
var name = declaration.getJsNameOrKotlinName().identifier
|
||||||
|
if (jsQualifier != null)
|
||||||
|
name = "$jsQualifier.$name"
|
||||||
|
|
||||||
|
return name.toJsName(temporary = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return declaration.getName()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getNameForMemberFunction(function: IrSimpleFunction): JsName {
|
||||||
|
require(function.dispatchReceiverParameter != null)
|
||||||
|
val signature = jsFunctionSignature(function, context)
|
||||||
|
return signature.toJsName()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getNameForMemberField(field: IrField): JsName {
|
||||||
|
require(!field.isStatic)
|
||||||
|
// TODO this looks funny. Rethink.
|
||||||
|
return JsName(field.parentAsClass.fieldData().pickFieldName(field), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FieldData(private val nameCnt: Map<String, Int>) {
|
||||||
|
fun pickFieldName(field: IrField): String {
|
||||||
|
val fieldName = field.safeName()
|
||||||
|
val cnt = nameCnt[fieldName] ?: error("Unexpected field: ${field.render()}")
|
||||||
|
return fieldName + "_$cnt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val fieldDataCache = mutableMapOf<IrClass, FieldData>()
|
||||||
|
|
||||||
|
private fun IrClass.fieldData(): FieldData {
|
||||||
|
return fieldDataCache.getOrPut(this) {
|
||||||
|
val nameCnt = mutableMapOf<String, Int>()
|
||||||
|
|
||||||
|
val allClasses = DFS.topologicalOrder(listOf(this)) { node ->
|
||||||
|
node.superTypes.mapNotNull {
|
||||||
|
it.safeAs<IrSimpleType>()?.classifier.safeAs<IrClassSymbol>()?.owner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allClasses.forEach {
|
||||||
|
it.declarations.forEach {
|
||||||
|
when {
|
||||||
|
it is IrField -> {
|
||||||
|
val safeName = it.safeName()
|
||||||
|
nameCnt[safeName] = nameCnt.getOrDefault(safeName, 0) + 1
|
||||||
|
}
|
||||||
|
it is IrFunction && it.dispatchReceiverParameter != null -> {
|
||||||
|
nameCnt[jsFunctionSignature(it, context)] = 1 // avoid clashes with member functions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FieldData(nameCnt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IrField.safeName(): String {
|
||||||
|
return sanitizeName(name.asString()).let {
|
||||||
|
if (it.lastOrNull()!!.isDigit()) it + "_" else it // Avoid name clashes
|
||||||
|
}
|
||||||
|
}
|
||||||
+119
@@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* 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<JsName, JsName> {
|
||||||
|
val rootScope = computeScopes().liftUsedNames()
|
||||||
|
val replacements = mutableMapOf<JsName, JsName>()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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<JsName>()
|
||||||
|
val usedNames = mutableSetOf<JsName>()
|
||||||
|
val children = mutableSetOf<Scope>()
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ abstract class IrNamerBase : IrNamer {
|
|||||||
abstract override fun getNameForMemberField(field: IrField): JsName
|
abstract override fun getNameForMemberField(field: IrField): JsName
|
||||||
abstract override fun getNameForStaticDeclaration(declaration: IrDeclarationWithName): JsName
|
abstract override fun getNameForStaticDeclaration(declaration: IrDeclarationWithName): JsName
|
||||||
|
|
||||||
protected fun String.toJsName() = JsName(this)
|
protected fun String.toJsName(temporary: Boolean = true) = JsName(this, temporary)
|
||||||
|
|
||||||
override fun getNameForStaticFunction(function: IrSimpleFunction): JsName =
|
override fun getNameForStaticFunction(function: IrSimpleFunction): JsName =
|
||||||
getNameForStaticDeclaration(function)
|
getNameForStaticDeclaration(function)
|
||||||
|
|||||||
Reference in New Issue
Block a user