diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt index 208a880a435..5ae967c5177 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt @@ -223,7 +223,7 @@ class IrModuleToJsTransformer( private val optimizeGeneratedJs = backendContext.configuration.get(JSConfigurationKeys.OPTIMIZE_GENERATED_JS, true) private fun generateProgramFragment(fileExports: IrFileExports, minimizedMemberNames: Boolean): JsIrProgramFragment { - val nameGenerator = JsNameLinkingNamer(backendContext, minimizedMemberNames) + val nameGenerator = JsNameLinkingNamer(backendContext, minimizedMemberNames, isEsModules) val globalNameScope = NameTable() @@ -329,9 +329,9 @@ class IrModuleToJsTransformer( } } - nameGenerator.imports.entries.forEach { (declaration, importExpression) -> + nameGenerator.imports.entries.forEach { (declaration, importStatement) -> val tag = computeTag(declaration) ?: error("No tag for imported declaration ${declaration.render()}") - result.imports[tag] = importExpression + result.imports[tag] = importStatement result.optionalCrossModuleImports += tag } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt index af5fd3186b5..87c5b0ce153 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt @@ -17,7 +17,7 @@ class JsIrProgramFragment(val packageFqn: String) { val declarations = JsCompositeBlock() val exports = JsCompositeBlock() val importedModules = mutableListOf() - val imports = mutableMapOf() + val imports = mutableMapOf() var dts: TypeScriptFragment? = null val classes = mutableMapOf() val initializers = JsCompositeBlock() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsNameLinkingNamer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsNameLinkingNamer.kt index a6a82b55445..5ff0ebb3674 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsNameLinkingNamer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsNameLinkingNamer.kt @@ -19,7 +19,11 @@ 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, private val minimizedMemberNames: Boolean) : IrNamerBase() { +class JsNameLinkingNamer( + private val context: JsIrBackendContext, + private val minimizedMemberNames: Boolean, + private val isEsModules: Boolean +) : IrNamerBase() { val nameMap = mutableMapOf() @@ -33,50 +37,19 @@ class JsNameLinkingNamer(private val context: JsIrBackendContext, private val mi } val importedModules = mutableListOf() - val imports = mutableMapOf() + val imports = mutableMapOf() 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: List? = maybeParentFile?.getJsQualifier()?.split('.')?.map { JsName(it, false) } - when { - jsModule != null -> { - val nameString = if (declaration.isJsNonModule()) { - declaration.getJsNameOrKotlinName().asString() - } else { - val parent = declaration.fqNameWhenAvailable!!.parent() - parent.child(declaration.getJsNameOrKotlinName()).asString() - } - val name = JsName(sanitizeName(nameString), false) - importedModules += JsImportedModule(jsModule, name, name.makeRef()) - return name - } - - fileJsModule != null -> { - if (declaration !in nameMap) { - val moduleName = JsName(sanitizeName("\$module\$$fileJsModule"), true) - importedModules += JsImportedModule(fileJsModule, moduleName, null) - val qualifiedReference = - if (jsQualifier == null) moduleName.makeRef() else (listOf(moduleName) + jsQualifier).makeRef() - imports[declaration] = jsElementAccess(declaration.getJsNameOrKotlinName().identifier, qualifiedReference) - return declaration.getName() - } - } - - else -> { - val name = declaration.getJsNameOrKotlinName().identifier - - if (jsQualifier != null) { - imports[declaration] = jsElementAccess(name, jsQualifier.makeRef()) - return declaration.getName() - } - - return name.toJsName(temporary = false) - } + return when { + jsModule != null -> declaration.generateImportForDeclarationWithJsModule(jsModule) + fileJsModule != null -> declaration.generateImportForDeclarationInFileWithJsModule(fileJsModule, jsQualifier) + else -> declaration.generateRegularQualifiedImport(jsQualifier) } } @@ -104,6 +77,70 @@ class JsNameLinkingNamer(private val context: JsIrBackendContext, private val mi return JsName(field.parentAsClass.fieldData()[field]!!, false) } + private fun IrDeclarationWithName.generateImportForDeclarationWithJsModule(jsModule: String): JsName { + val nameString = if (isJsNonModule()) { + getJsNameOrKotlinName().asString() + } else { + val parent = fqNameWhenAvailable!!.parent() + parent.child(getJsNameOrKotlinName()).asString() + } + val name = JsName(sanitizeName(nameString), true) + + if (isEsModules) { + imports[this] = JsImport(jsModule, JsImport.Target.Default(name.makeRef())) + } else { + importedModules += JsImportedModule(jsModule, name, name.makeRef()) + } + return name + } + + private fun IrDeclarationWithName.generateImportForDeclarationInFileWithJsModule( + fileJsModule: String, + jsQualifier: List? + ): JsName { + if (this in nameMap) return getName() + + val declarationStableName = getJsNameOrKotlinName().identifier + + if (isEsModules) { + val importedName = jsQualifier?.firstOrNull() ?: declarationStableName.toJsName(temporary = false) + val importStatement = JsImport(fileJsModule, JsImport.Element(importedName)) + imports[this] = when (val qualifiedReference = jsQualifier?.makeRef()) { + null -> importStatement + else -> JsCompositeBlock( + listOf( + importStatement, + jsElementAccess(declarationStableName, qualifiedReference).putIntoVariableWitName( + declarationStableName.toJsName() + ) + ) + ) + } + + } else { + val moduleName = JsName(sanitizeName("\$module\$$fileJsModule"), true) + importedModules += JsImportedModule(fileJsModule, moduleName, null) + val qualifiedReference = + if (jsQualifier == null) moduleName.makeRef() else (listOf(moduleName) + jsQualifier).makeRef() + imports[this] = + jsElementAccess(declarationStableName, qualifiedReference).putIntoVariableWitName(declarationStableName.toJsName()) + } + + return getName() + } + + private fun IrDeclarationWithName.generateRegularQualifiedImport(jsQualifier: List?): JsName { + val name = getJsNameOrKotlinName().identifier + + if (jsQualifier != null) { + imports[this] = jsElementAccess(name, jsQualifier.makeRef()).putIntoVariableWitName(name.toJsName()) + return getName() + } + + return name.toJsName(temporary = false) + } + + private fun IrClass.fieldData(): Map { return context.fieldDataCache.getOrPut(this) { val nameCnt = hashMapOf() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt index daa5f841da8..f5eed8d81dd 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt @@ -34,9 +34,14 @@ class Merger( 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)))) + f.imports.entries.forEach { (declaration, importStatement) -> + val importName = nameMap[declaration] + + if (importName == null && !isEsModules) { + error("Missing name for declaration '${declaration}'") + } + + importStatements.putIfAbsent(declaration, rename(importStatement).importStatementWithName(importName)) } val classModels = (mutableMapOf() + f.classes) @@ -60,7 +65,6 @@ class Merger( for ((tag, crossModuleJsImport) in crossModuleReferences.jsImports) { val importName = nameMap[tag] ?: error("Missing name for declaration '$tag'") - importStatements.putIfAbsent(tag, crossModuleJsImport.renameImportedSymbolInternalName(importName)) } @@ -330,4 +334,29 @@ class Merger( this += statements endRegion() } + + private fun JsStatement.importStatementWithName(name: JsName?): JsStatement { + if (name == null) return this + + return when (this) { + is JsVars -> JsVars(JsVars.JsVar(name, vars.single().initExpression)) + is JsImport -> JsImport( + module, + when (target) { + is JsImport.Target.All -> JsImport.Target.All(alias = name.makeRef()) + is JsImport.Target.Default -> JsImport.Target.Default(name = name.makeRef()) + is JsImport.Target.Elements -> JsImport.Target.Elements( + mutableListOf( + JsImport.Element( + elements.single().name, + name.makeRef() + ) + ) + ) + } + ) + is JsCompositeBlock -> JsCompositeBlock(statements.dropLast(1) + statements.last().importStatementWithName(name)) + else -> error("Unexpected import statement ${this::class.qualifiedName}") + } + } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleWrapperTranslation.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleWrapperTranslation.kt index 3e351ed1eae..cd74e8af485 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleWrapperTranslation.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleWrapperTranslation.kt @@ -53,7 +53,7 @@ object ModuleWrapperTranslation { ModuleKind.COMMON_JS -> wrapCommonJs(function, importedModules, program) ModuleKind.UMD -> wrapUmd(moduleId, function, importedModules, program) ModuleKind.PLAIN -> wrapPlain(moduleId, function, importedModules, program) - ModuleKind.ES -> wrapEsModule(function, importedModules) + ModuleKind.ES -> wrapEsModule(function) } } @@ -128,27 +128,19 @@ object ModuleWrapperTranslation { return listOf(invocation.makeStmt()) } - private fun wrapEsModule(function: JsFunction, importedModules: List): List { - val (alreadyPresentedImportStatements, restStatements) = function.body.statements.partitionIsInstance() + private fun wrapEsModule(function: JsFunction): List { + val (alreadyPresentedImportStatements, restStatements) = function.body.statements + .flatMap { if (it is JsCompositeBlock) it.statements else listOf(it) } + .partitionIsInstance() + val (multipleElementsImport, defaultImports) = alreadyPresentedImportStatements.partition { it.target is JsImport.Target.Elements } - val importStatements = importedModules.zip(function.parameters.drop(1)).map { - JsImport( - it.first.getRequireName(true), - if (it.first.plainReference == null) { - JsImport.Target.All(alias = it.second.name.makeRef()) - } else { - JsImport.Target.Default(name = it.second.name.makeRef()) - } - ) - } - - val alreadyPresentedImportStatementsWithoutDuplicates = alreadyPresentedImportStatements + val mergedImports = multipleElementsImport .groupBy { it.module } .map { (module, import) -> - JsImport(module, *import.flatMap { it.elements }.toTypedArray()) + JsImport(module, *import.flatMap { it.elements }.distinctBy { it.name.ident }.toTypedArray()) } - return importStatements + alreadyPresentedImportStatementsWithoutDuplicates + restStatements.dropLast(1) + return mergedImports + defaultImports + restStatements.dropLast(1) } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt index 0f30cb1f3bf..ac014991d9c 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt @@ -63,6 +63,10 @@ fun IrWhen.toJsNode( fun jsElementAccess(name: String, receiver: JsExpression?): JsExpression = jsElementAccess(JsName(name, false), receiver) +fun JsExpression.putIntoVariableWitName(name: JsName): JsVars { + return JsVars(JsVars.JsVar(name, this)) +} + fun jsElementAccess(name: JsName, receiver: JsExpression?): JsExpression = if (receiver == null || name.ident.isValidES5Identifier()) { JsNameRef(name, receiver) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/resolveTemporaryNames.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/resolveTemporaryNames.kt index 49c486db201..90e12b50f2e 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/resolveTemporaryNames.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/resolveTemporaryNames.kt @@ -120,12 +120,8 @@ private fun JsNode.computeScopes(): Scope { when (val target = import.target) { is JsImport.Target.All -> target.alias.name?.let { currentScope.declaredNames += it } is JsImport.Target.Default -> target.name.name?.let { currentScope.declaredNames += it } - is JsImport.Target.Elements -> target.elements.forEach { element -> - if (element.alias != null) { - element.alias?.name?.let { currentScope.declaredNames += it } - } else { - currentScope.declaredNames += element.name - } + is JsImport.Target.Elements -> target.elements.forEach { + currentScope.declaredNames += it.alias?.name ?: it.name } } super.visitImport(import) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstDeserializer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstDeserializer.kt index e34e1aba616..32b10f31d48 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstDeserializer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstDeserializer.kt @@ -86,7 +86,7 @@ private class JsIrAstDeserializer(private val source: ByteArray) { ) } - readRepeated { imports[stringTable[readInt()]] = readExpression() } + readRepeated { imports[stringTable[readInt()]] = readStatement() } readRepeated { declarations.statements += readStatement() } readRepeated { initializers.statements += readStatement() } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstSerializer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstSerializer.kt index 7c82cbfda3b..af555b289e8 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstSerializer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstSerializer.kt @@ -118,9 +118,9 @@ private class JsIrAstSerializer { } } - writeCollection(fragment.imports.entries) { (signatureId, expression) -> + writeCollection(fragment.imports.entries) { (signatureId, statement) -> writeInt(internalizeString(signatureId)) - writeExpression(expression) + writeStatement(statement) } writeCompositeBlock(fragment.declarations) diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsImport.kt b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsImport.kt index c06e7a1fb0a..13e23cdc0a4 100644 --- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsImport.kt +++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsImport.kt @@ -27,7 +27,7 @@ class JsImport( class Element( val name: JsName, - val alias: JsNameRef? + val alias: JsNameRef? = null ) override fun accept(visitor: JsVisitor) {