[K/JS] Move ES modules logic to a new transformer with IC

This commit is contained in:
Artem Kobzar
2022-10-13 07:32:44 +00:00
committed by Space Team
parent 54deba63a1
commit de880ce9aa
88 changed files with 2476 additions and 1134 deletions
@@ -123,7 +123,7 @@ class K2JSCompilerArguments : CommonCompilerArguments() {
)
@Argument(
value = "-module-kind",
valueDescription = "{plain|amd|commonjs|umd}",
valueDescription = "{plain|amd|commonjs|umd|es}",
description = "Kind of the JS module generated by the compiler"
)
var moduleKind: String? by NullableStringFreezableVar(K2JsArgumentConstants.MODULE_PLAIN)
@@ -551,7 +551,7 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
var moduleKind: ModuleKind? = if (moduleKindName != null) moduleKindMap[moduleKindName] else ModuleKind.PLAIN
if (moduleKind == null) {
messageCollector.report(
ERROR, "Unknown module kind: $moduleKindName. Valid values are: plain, amd, commonjs, umd", null
ERROR, "Unknown module kind: $moduleKindName. Valid values are: plain, amd, commonjs, umd, es", null
)
moduleKind = ModuleKind.PLAIN
}
@@ -1,459 +0,0 @@
/*
* Copyright 2010-2020 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.codegen
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.LoweredIr
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity.*
import org.jetbrains.kotlin.ir.backend.js.export.*
import org.jetbrains.kotlin.ir.backend.js.lower.JsCodeOutliningLowering
import org.jetbrains.kotlin.ir.backend.js.lower.StaticMembersLowering
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrFileToJsTransformer
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.processClassModels
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.util.file
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.hasInterfaceParent
import org.jetbrains.kotlin.ir.util.isInterface
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.serialization.js.ModuleKind
import kotlin.math.abs
interface CompilerOutputSink {
fun write(module: String, path: String, content: String)
}
class JsGenerationOptions(
val jsExtension: String = "js",
val generatePackageJson: Boolean = false,
val generateTypeScriptDefinitions: Boolean = false,
)
class IrToJs(
private val backendContext: JsIrBackendContext,
private val guid: (IrDeclaration) -> String,
private val outputSink: CompilerOutputSink,
private val mainArguments: List<String>?,
private val granularity: JsGenerationGranularity,
private val mainModuleName: String,
private val options: JsGenerationOptions,
) {
val indexFileName = "index.${options.jsExtension}"
val FileUnit.initFunctionName
get() = "KotlinInit$" + sanitizeName(pathToJsModule(file))
sealed class CodegenUnitReference
object ThisUnitReference : CodegenUnitReference()
inner class OtherUnitReference(
module: IrModuleFragment,
) : CodegenUnitReference() {
// Path to entry point of other module from "top-level", e.g. directory which contains all other modules
val importPath = "./" + module.jsModuleName + "/" + indexFileName
}
abstract class CodegenUnit {
abstract val packageFragments: Iterable<IrPackageFragment>
abstract val externalPackageFragments: Iterable<IrPackageFragment>
abstract fun referenceCodegenUnitOfDeclaration(declaration: IrDeclaration): CodegenUnitReference
abstract val pathToKotlinModulesRoot: String
}
inner class FileUnit(val file: IrFile, val externalFile: IrFile?) : CodegenUnit() {
override val packageFragments =
listOf(file)
override val externalPackageFragments =
listOfNotNull(externalFile)
override fun referenceCodegenUnitOfDeclaration(declaration: IrDeclaration): CodegenUnitReference =
when (val declarationFile = declaration.file) {
file -> ThisUnitReference
else -> OtherUnitReference(declarationFile.module)
}
override val pathToKotlinModulesRoot: String by lazy {
"../".repeat(file.fqName.pathSegments().size + 1)
}
}
inner class ModuleUnit(val module: IrModuleFragment) : CodegenUnit() {
override val packageFragments: Iterable<IrPackageFragment> =
module.files
override val externalPackageFragments: Iterable<IrPackageFragment> =
packageFragments.mapNotNull { backendContext.externalPackageFragment[it.symbol] }
override fun referenceCodegenUnitOfDeclaration(declaration: IrDeclaration): CodegenUnitReference =
when (val declarationModule = declaration.file.module) {
module -> ThisUnitReference
else -> OtherUnitReference(declarationModule)
}
override val pathToKotlinModulesRoot: String = "../"
}
class WholeProgramUnit(
val modules: Iterable<IrModuleFragment>,
val externalModules: Iterable<IrPackageFragment>
) : CodegenUnit() {
override val packageFragments: Iterable<IrPackageFragment> =
modules.flatMap { it.files }
override val externalPackageFragments: Iterable<IrPackageFragment>
get() = externalModules
override fun referenceCodegenUnitOfDeclaration(declaration: IrDeclaration): CodegenUnitReference =
ThisUnitReference
override val pathToKotlinModulesRoot: String
get() = "../"
}
private fun pathToJsModule(file: IrFile): String =
"${fileJsRootModuleName(file)}/${fileJsSubModulePath(file)}"
private fun fileJsRootModuleName(file: IrFile): String =
when (granularity) {
WHOLE_PROGRAM -> mainModuleName
PER_MODULE, PER_FILE -> file.module.jsModuleName
}
private fun fileJsSubModulePath(file: IrFile): String =
when (granularity) {
WHOLE_PROGRAM, PER_MODULE -> indexFileName
PER_FILE -> {
val maybeSingleOpenClass = (file.declarations.singleOrNull() as? IrClass)?.takeIf {
it.modality == Modality.ABSTRACT || it.modality == Modality.OPEN
}
val hash = abs((maybeSingleOpenClass?.let { guid(it) } ?: file.path).hashCode())
val filePrefix = maybeSingleOpenClass?.name?.asString()?.let { sanitizeName(it) + ".class" } ?: file.name
val fileName = "${filePrefix}_$hash.${options.jsExtension}"
val packagePath = file.fqName.pathSegments().joinToString("") { it.identifier + "/" }
"$packagePath$fileName"
}
}
class GeneratedUnit(
val jsStatements: List<JsStatement>,
val exportedDeclarations: List<ExportedDeclaration>,
)
fun generateUnit(unit: CodegenUnit): GeneratedUnit {
val exportedDeclarations: List<ExportedDeclaration> =
with(ExportModelGenerator(backendContext, generateNamespacesForPackages = false)) {
(unit.externalPackageFragments + unit.packageFragments).flatMap { packageFragment ->
generateExport(packageFragment)
}
}
val stableNames: Set<String> = collectStableNames(unit)
val nameGenerator = NewNamerImpl(backendContext, unit, guid, stableNames)
val staticContext = JsStaticContext(
backendContext = backendContext,
irNamer = nameGenerator,
globalNameScope = nameGenerator.staticNames
)
val declarationStatements: List<JsStatement> = unit.packageFragments.flatMap {
StaticMembersLowering(backendContext).lower(it as IrFile)
it.accept(IrFileToJsTransformer(), staticContext).statements
}
val preDeclarationBlock = JsCompositeBlock()
val postDeclarationBlock = JsCompositeBlock()
processClassModels(staticContext.classModels, preDeclarationBlock, postDeclarationBlock)
val statements = mutableListOf<JsStatement>()
statements += nameGenerator.internalImports.values
statements += preDeclarationBlock
statements += declarationStatements
statements += postDeclarationBlock
// Generate module initialization
val initializerBlock = staticContext.initializerBlock
when (unit) {
is WholeProgramUnit, is ModuleUnit -> {
// Run initialization during ES module initialization
statements += initializerBlock
}
is FileUnit -> {
// Postpone initialization by putting it into a separate function
// Will be called later in proper order after class model is initialized
val initFunction = JsFunction(emptyScope, JsBlock(initializerBlock.statements), "init fun")
initFunction.name = JsName(unit.initFunctionName, false)
statements += initFunction.makeStmt()
statements += JsExport(initFunction.name)
}
}
// Generate internal export
val internalExports = mutableListOf<JsExport.Element>()
fun export(declaration: IrDeclarationWithName) {
internalExports += JsExport.Element(nameGenerator.getNameForStaticDeclaration(declaration), JsName(guid(declaration), false))
}
for (fragment in unit.packageFragments) {
for (declaration in fragment.declarations) {
if (declaration is IrDeclarationWithName) {
if (declaration.origin == JsCodeOutliningLowering.OUTLINED_JS_CODE_ORIGIN) continue
export(declaration)
}
// Default implementations of interface methods are nested under interface declarations in IR at this point,
// but they are effectively used as a static declaration and can be directly referenced by other codegen unit,
// thus requiring internal export
declaration.acceptChildrenVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitSimpleFunction(declaration: IrSimpleFunction) {
if (declaration.hasInterfaceParent() && declaration.body != null) {
export(declaration)
}
super.visitSimpleFunction(declaration)
}
})
}
}
statements += JsExport(JsExport.Subject.Elements(internalExports), null)
// Generate external export
val globalNames = NameTable<String>(nameGenerator.staticNames)
val exporter = ExportModelToJsStatements(
staticContext,
declareNewNamespace = { globalNames.declareFreshName(it, it) }
)
exportedDeclarations.forEach {
statements += exporter.generateDeclarationExport(
it,
null,
esModules = true
)
}
return GeneratedUnit(statements, exportedDeclarations)
}
private fun collectStableNames(unit: CodegenUnit): Set<String> {
val newStableStaticNamesCollectorVisitor =
NewStableStaticNamesCollectorVisitor(needToCollectReferences = granularity != WHOLE_PROGRAM)
unit.packageFragments.forEach { it.acceptVoid(newStableStaticNamesCollectorVisitor) }
unit.externalPackageFragments.forEach { it.acceptVoid(newStableStaticNamesCollectorVisitor) }
return newStableStaticNamesCollectorVisitor.collectedStableNames
}
// Returns import statement and call expression
private fun invokeFunctionFromEntryJsFile(
function: IrFunction,
args: List<JsExpression> = emptyList()
): Pair<JsStatement, JsExpression> {
val name = guid(function)
val importPath = if (granularity == WHOLE_PROGRAM) "./$indexFileName" else "../" + pathToJsModule(function.file)
return Pair(
JsImport(importPath, mutableListOf(JsImport.Element(name, null))),
JsInvocation(JsNameRef(name), args)
)
}
private fun invokeFunctionFromEntryJsFileAsStatements(
function: IrFunction,
args: List<JsExpression> = emptyList()
): List<JsStatement> =
invokeFunctionFromEntryJsFile(function, args)
.let { listOf(it.first, it.second.makeStmt()) }
fun generateModules(
mainModule: IrModuleFragment,
allModules: List<IrModuleFragment>
) {
when (granularity) {
WHOLE_PROGRAM ->
generateModule(mainModule, allModules)
PER_MODULE,
PER_FILE ->
allModules.forEach { module ->
generateModule(mainModule = module, allModules = emptyList())
}
}
}
fun generateModuleLevelCode(module: IrModuleFragment, statements: MutableList<JsStatement>) {
if (mainArguments != null) {
val mainFunction = JsMainFunctionDetector(backendContext).getMainFunctionOrNull(module)
if (mainFunction != null) {
val generateArgv = mainFunction.valueParameters.firstOrNull()?.isStringArrayParameter() ?: false
val generateContinuation = mainFunction.isLoweredSuspendFunction(backendContext)
val mainArgumentsArray =
if (generateArgv)
JsArrayLiteral(mainArguments.map { JsStringLiteral(it) })
else
null
val continuation =
if (generateContinuation) {
val (import, invoke) = invokeFunctionFromEntryJsFile(backendContext.coroutineEmptyContinuation.owner.getter!!)
statements += import
invoke
} else
null
statements += invokeFunctionFromEntryJsFileAsStatements(
mainFunction, listOfNotNull(mainArgumentsArray, continuation)
)
}
}
// TODO: tests
// backendContext.testRoots[module]?.let { testContainer ->
// statements += invokeFunctionFromEntryJsFileAsStatements(testContainer)
// }
}
fun generateModule(
mainModule: IrModuleFragment,
allModules: List<IrModuleFragment>,
) {
val moduleName = mainModule.jsModuleName
val indexJsStatements = mutableListOf<JsStatement>()
val exportedDeclarations = mutableListOf<ExportedDeclaration>()
when (granularity) {
PER_FILE -> {
for (file in mainModule.files.sortedBy(::fileInitOrder)) {
if (file.declarations.isEmpty()) continue
val pathToSubModule = fileJsSubModulePath(file)
indexJsStatements += JsExport(JsExport.Subject.All, fromModule = "./$pathToSubModule")
val unit = FileUnit(file, backendContext.externalPackageFragment[file.symbol])
val generatedUnit = generateUnit(unit)
val importElements = JsImport.Element(unit.initFunctionName, null)
indexJsStatements += JsImport("./$pathToSubModule", mutableListOf(importElements))
indexJsStatements += JsInvocation(JsNameRef(JsName(unit.initFunctionName, false))).makeStmt()
exportedDeclarations += generatedUnit.exportedDeclarations
outputSink.write(
file.module.jsModuleName,
pathToSubModule,
"// Kotlin file: ${file.path}\n" + generatedUnit.jsStatements.toJsCodeString()
)
}
generateModuleLevelCode(mainModule, indexJsStatements)
}
PER_MODULE -> {
val generatedUnit = generateUnit(ModuleUnit(mainModule))
indexJsStatements += generatedUnit.jsStatements
generateModuleLevelCode(mainModule, indexJsStatements)
exportedDeclarations += generatedUnit.exportedDeclarations
}
WHOLE_PROGRAM -> {
val generatedUnit = generateUnit(WholeProgramUnit(allModules, backendContext.externalPackageFragment.values))
indexJsStatements += generatedUnit.jsStatements
allModules.forEach {
generateModuleLevelCode(it, indexJsStatements)
}
exportedDeclarations += generatedUnit.exportedDeclarations
}
}
outputSink.write(moduleName, indexFileName, indexJsStatements.toJsCodeString())
if (options.generatePackageJson) {
outputSink.write(moduleName, "package.json", """{ "main": "$indexFileName", "type": "module" }""")
}
if (options.generateTypeScriptDefinitions && exportedDeclarations.isNotEmpty()) {
val dts = ExportedModule(moduleName, moduleKind = ModuleKind.ES, exportedDeclarations).toTypeScript()
outputSink.write(moduleName, "index.d.ts", dts)
}
}
private fun fileInitOrder(file: IrFile): Int =
when (val singleDeclaration = file.declarations.singleOrNull()) {
// Initialize parent classes before child classes
// TODO: Comment about open classes in separate files
is IrClass -> singleDeclaration.getInheritanceChainLength()
// Initialize regular files after all open classes
else -> Int.MAX_VALUE
}
private fun IrClass.getInheritanceChainLength(): Int {
if (symbol == backendContext.irBuiltIns.anyClass)
return 0
// FIXME: Filter out interfaces
superTypes.forEach { superType ->
val superClass: IrClass? = superType.classOrNull?.owner
if (superClass != null && /* !!! */ !superClass.isInterface)
return superClass.getInheritanceChainLength() + 1
}
return 1
}
}
private val IrModuleFragment.jsModuleName: String
get() = name.asString()
.replace("[.:@]".toRegex(), "_")
.dropWhile { it == '<' }
.dropLastWhile { it == '>' }
private fun List<JsStatement>.toJsCodeString(): String =
JsCompositeBlock(this).toString()
enum class JsGenerationGranularity {
WHOLE_PROGRAM,
PER_MODULE,
PER_FILE
}
fun generateEsModules(
ir: LoweredIr,
outputSink: CompilerOutputSink,
mainArguments: List<String>?,
granularity: JsGenerationGranularity,
options: JsGenerationOptions,
) {
// Declaration numeration to create temporary GUID
// TODO: Replace with an actual GUID
val numerator = StaticDeclarationNumerator()
ir.allModules.forEach { numerator.add(it) }
fun guid(declaration: IrDeclaration): String {
val name = sanitizeName((declaration as IrDeclarationWithName).name.toString())
val number = numerator.numeration[declaration]
?: error("Can't find number for declaration ${declaration.fqNameWhenAvailable}")
// TODO: Use shorter names in release mode
return "${name}_GUID_${number}"
}
val ir2js = IrToJs(ir.context, ::guid, outputSink, mainArguments, granularity, ir.mainModule.jsModuleName, options)
ir2js.generateModules(ir.mainModule, ir.allModules)
}
@@ -0,0 +1,12 @@
/*
* Copyright 2010-2020 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.codegen
enum class JsGenerationGranularity {
WHOLE_PROGRAM,
PER_MODULE,
PER_FILE
}
@@ -48,7 +48,7 @@ data class ExportedConstructSignature(
val returnType: ExportedType,
) : ExportedDeclaration()
class ExportedProperty(
data class ExportedProperty(
val name: String,
val type: ExportedType,
val mutable: Boolean = true,
@@ -94,7 +94,7 @@ data class ExportedObject(
override val members: List<ExportedDeclaration>,
override val nestedClasses: List<ExportedClass>,
override val ir: IrClass,
val irGetter: IrFunction
val irGetter: IrSimpleFunction
) : ExportedClass()
class ExportedParameter(
@@ -5,8 +5,16 @@
package org.jetbrains.kotlin.ir.backend.js.export
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.*
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsAstUtils
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.defineProperty
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.jsAssignment
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.prototypeOf
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.jsElementAccess
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
import org.jetbrains.kotlin.ir.util.companionObject
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.util.collectionUtils.filterIsInstanceAnd
@@ -17,8 +25,14 @@ class ExportModelToJsStatements(
) {
private val namespaceToRefMap = mutableMapOf<String, JsNameRef>()
fun generateModuleExport(module: ExportedModule, internalModuleName: JsName): List<JsStatement> {
return module.declarations.flatMap { generateDeclarationExport(it, JsNameRef(internalModuleName), esModules = false) }
fun generateModuleExport(
module: ExportedModule,
internalModuleName: JsName?,
esModules: Boolean
): List<JsStatement> {
return module.declarations.flatMap {
generateDeclarationExport(it, internalModuleName?.makeRef(), esModules)
}
}
fun generateDeclarationExport(
@@ -60,17 +74,12 @@ class ExportModelToJsStatements(
is ExportedFunction -> {
val name = namer.getNameForStaticDeclaration(declaration.ir)
if (esModules) {
listOf(JsExport(name, alias = JsName(declaration.name, false)))
} else {
if (namespace != null) {
listOf(
jsAssignment(
jsElementAccess(declaration.name, namespace),
JsNameRef(name)
).makeStmt()
)
} else emptyList()
when {
namespace != null ->
listOf(jsAssignment(jsElementAccess(declaration.name, namespace), JsNameRef(name)).makeStmt())
esModules -> listOf(JsExport(name, alias = JsName(declaration.name, false)))
else -> emptyList()
}
}
@@ -78,40 +87,77 @@ class ExportModelToJsStatements(
is ExportedConstructSignature -> emptyList()
is ExportedProperty -> {
require(namespace != null) { "Only namespaced properties are allowed" }
val getter = declaration.irGetter?.let { JsNameRef(namer.getNameForStaticDeclaration(it)) }
val setter = declaration.irSetter?.let { JsNameRef(namer.getNameForStaticDeclaration(it)) }
listOf(defineProperty(namespace, declaration.name, getter, setter, namer).makeStmt())
require(namespace != null || esModules) { "Only namespaced properties are allowed" }
val getter = declaration.irGetter?.let { namer.getNameForStaticDeclaration(it) }
val setter = declaration.irSetter?.let { namer.getNameForStaticDeclaration(it) }
if (namespace == null) {
val property = JsVars.JsVar(
JsName(declaration.name, false),
JsObjectLiteral(false).apply {
getter?.let {
val fieldName = when (declaration.irGetter.origin) {
JsLoweredDeclarationOrigin.OBJECT_GET_INSTANCE_FUNCTION -> "getInstance"
else -> "get"
}
propertyInitializers += JsPropertyInitializer(JsStringLiteral(fieldName), it.makeRef())
}
setter?.let { propertyInitializers += JsPropertyInitializer(JsStringLiteral("set"), it.makeRef()) }
}
)
listOf(
JsVars(property),
JsExport(property.name, JsName(declaration.name, false))
)
} else {
listOf(defineProperty(namespace, declaration.name, getter?.makeRef(), setter?.makeRef(), namer).makeStmt())
}
}
is ErrorDeclaration -> emptyList()
is ExportedObject -> {
require(namespace != null) { "Only namespaced properties are allowed" }
val newNameSpace = jsElementAccess(declaration.name, namespace)
val getter = JsNameRef(namer.getNameForStaticDeclaration(declaration.irGetter))
require(namespace != null || esModules) { "Only namespaced properties are allowed" }
val newNameSpace = when {
namespace != null -> jsElementAccess(declaration.name, namespace)
else ->
jsElementAccess(Namer.PROTOTYPE_NAME, namer.getNameForClass(declaration.ir).makeRef())
}
val staticsExport = declaration.nestedClasses.flatMap { generateDeclarationExport(it, newNameSpace, esModules) }
listOf(defineProperty(namespace, declaration.name, getter, null, namer).makeStmt()) + staticsExport
val objectExport = when (namespace) {
null -> generateDeclarationExport(
ExportedProperty(declaration.name, ExportedType.Primitive.Any, irGetter = declaration.irGetter),
namespace,
esModules
)
else -> listOf(
defineProperty(
namespace,
declaration.name,
namer.getNameForStaticDeclaration(declaration.irGetter).makeRef(),
null,
namer
).makeStmt()
)
}
objectExport + staticsExport
}
is ExportedRegularClass -> {
if (declaration.isInterface) return emptyList()
val newNameSpace = if (namespace != null)
jsElementAccess(declaration.name, namespace)
else
prototypeOf(namer.getNameForClass(declaration.ir).makeRef(), namer)
val name = namer.getNameForStaticDeclaration(declaration.ir)
val klassExport =
if (esModules) {
JsExport(name, alias = JsName(declaration.name, false))
} else {
if (namespace != null) {
jsAssignment(
newNameSpace,
JsNameRef(name)
).makeStmt()
} else null
}
val newNameSpace = when {
namespace != null -> jsElementAccess(declaration.name, namespace)
esModules -> name.makeRef()
else -> prototypeOf(namer.getNameForClass(declaration.ir).makeRef(), namer)
}
val klassExport = when {
namespace != null -> jsAssignment(newNameSpace, JsNameRef(name)).makeStmt()
esModules -> JsExport(name, alias = JsName(declaration.name, false))
else -> null
}
// These are only used when exporting secondary constructors annotated with @JsName
val staticFunctions = declaration.members
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.ir.backend.js.export
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.backend.js.utils.getFqNameWithJsNameWhenAvailable
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName
@@ -15,11 +16,14 @@ import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.js.common.isValidES5Identifier
import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import javax.lang.model.type.IntersectionType
import org.jetbrains.kotlin.utils.addToStdlib.runIf
private const val Nullable = "Nullable"
private const val objects = "_objects_"
private const val declare = "declare "
private const val declareExorted = "export $declare"
private const val NonExistent = "__NonExistent"
private const val syntheticObjectNameSeparator = '$'
fun ExportedModule.toTypeScript(): String {
@@ -61,32 +65,36 @@ class ExportModelToTsDeclarations {
return joinToString("\n") {
it.toTypeScript(
indent = moduleKind.indent,
prefix = if (moduleKind == ModuleKind.PLAIN) "" else "export "
prefix = if (moduleKind == ModuleKind.PLAIN) "" else declareExorted,
esModules = moduleKind == ModuleKind.ES
)
} + generateObjectsNamespaceIfNeeded(moduleKind.indent)
} + generateObjectsNamespaceIfNeeded(
indent = moduleKind.indent,
prefix = if (moduleKind == ModuleKind.PLAIN) "" else declare,
)
}
private fun generateObjectsNamespaceIfNeeded(indent: String): String {
private fun generateObjectsNamespaceIfNeeded(indent: String, prefix: String): String {
return if (objectsSyntheticProperties.isEmpty()) {
""
} else {
"\n" + ExportedNamespace(objects, objectsSyntheticProperties).toTypeScript(indent, "")
"\n" + ExportedNamespace(objects, objectsSyntheticProperties).toTypeScript(indent, prefix)
}
}
private fun List<ExportedDeclaration>.toTypeScript(indent: String): String =
joinToString("") { it.toTypeScript(indent) + "\n" }
private fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = ""): String =
private fun ExportedDeclaration.toTypeScript(indent: String, prefix: String = "", esModules: Boolean = false): String =
indent + when (this) {
is ErrorDeclaration -> generateTypeScriptString()
is ExportedNamespace -> generateTypeScriptString(indent, prefix)
is ExportedFunction -> generateTypeScriptString(indent, prefix)
is ExportedConstructor -> generateTypeScriptString(indent)
is ExportedConstructSignature -> generateTypeScriptString(indent)
is ExportedProperty -> generateTypeScriptString(indent, prefix)
is ExportedObject -> generateTypeScriptString(indent, prefix)
is ExportedNamespace -> generateTypeScriptString(indent, prefix)
is ExportedFunction -> generateTypeScriptString(indent, prefix)
is ExportedRegularClass -> generateTypeScriptString(indent, prefix)
is ExportedProperty -> generateTypeScriptString(indent, prefix, esModules)
is ExportedObject -> generateTypeScriptString(indent, prefix, esModules)
}
private fun ErrorDeclaration.generateTypeScriptString(): String {
@@ -107,34 +115,45 @@ class ExportModelToTsDeclarations {
return "new($renderedParameters): ${returnType.toTypeScript(indent)};"
}
private fun ExportedProperty.generateTypeScriptString(indent: String, prefix: String): String {
val visibility = if (isProtected) "protected " else ""
val keyword = when {
isMember -> (if (isAbstract) "abstract " else "")
else -> if (mutable) "let " else "const "
}
val possibleStatic = if (isMember && isStatic) "static " else ""
private fun ExportedProperty.generateTypeScriptString(indent: String, prefix: String, esModules: Boolean = false): String {
val extraIndent = "$indent "
val optional = if (isOptional) "?" else ""
val containsUnresolvedChar = !name.isValidES5Identifier()
val memberName = when {
isMember && containsUnresolvedChar -> "\"$name\""
else -> name
}
val typeToTypeScript = type.toTypeScript(indent)
val memberName = if (containsUnresolvedChar) "\"$name\"" else name
val isObjectGetter = irGetter?.origin == JsLoweredDeclarationOrigin.OBJECT_GET_INSTANCE_FUNCTION
return if (isMember && !isField) {
val getter = "$prefix$visibility$possibleStatic${keyword}get $memberName(): $typeToTypeScript;"
if (!mutable) {
getter
val typeToTypeScript = type.toTypeScript(if (!isMember && esModules && isObjectGetter) extraIndent else indent)
return if (isMember) {
val static = if (isStatic) "static " else ""
val abstract = if (isAbstract) "abstract " else ""
val visibility = if (isProtected) "protected " else ""
if (isField) {
val readonly = if (!mutable) "readonly " else ""
"$prefix$visibility$static$abstract$readonly$memberName$optional: $typeToTypeScript;"
} else {
getter + "\n" + "$indent$prefix$visibility$possibleStatic${keyword}set $memberName(value: $typeToTypeScript);"
val getter = "$prefix$visibility$static${abstract}get $memberName(): $typeToTypeScript;"
val setter = runIf(mutable) { "\n$indent$prefix$visibility$static${abstract}set $memberName(value: $typeToTypeScript);" }
getter + setter.orEmpty()
}
} else {
if (!isMember && containsUnresolvedChar) {
""
} else {
val readonly = if (isMember && !mutable) "readonly " else ""
val optional = if (isOptional) "?" else ""
"$prefix$visibility$possibleStatic$keyword$readonly$memberName$optional: $typeToTypeScript;"
when {
containsUnresolvedChar -> ""
esModules -> {
if (isObjectGetter) {
"${prefix}const $name: {\n${extraIndent}getInstance(): $typeToTypeScript;\n};"
} else {
val getter = "get(): $typeToTypeScript;"
val setter = runIf(mutable) { " set(value: $typeToTypeScript): void;" }
"${prefix}const $name: { $getter${setter.orEmpty()} };"
}
}
else -> {
val keyword = if (mutable) "let " else "const "
"$prefix$keyword$memberName$optional: $typeToTypeScript;"
}
}
}
}
@@ -171,14 +190,19 @@ class ExportModelToTsDeclarations {
return if (!isMember && containsUnresolvedChar) {
""
} else {
"${prefix}$visibility$keyword$escapedName$renderedTypeParameters($renderedParameters): $renderedReturnType;"
"$prefix$visibility$keyword$escapedName$renderedTypeParameters($renderedParameters): $renderedReturnType;"
}
}
private fun ExportedObject.generateTypeScriptString(indent: String, prefix: String): String {
private fun ExportedObject.generateTypeScriptString(indent: String, prefix: String, esModules: Boolean = false): String {
val shouldRenderSeparatedAbstractClass = !couldBeProperty()
var t: ExportedType = ExportedType.InlineInterfaceType(members)
val extraMembers = nestedClasses
.takeIf { !shouldRenderSeparatedAbstractClass }
?.map { it as ExportedObject }
.orEmpty()
var t: ExportedType = ExportedType.InlineInterfaceType(members + extraMembers)
for (superInterface in superClasses + superInterfaces) {
t = ExportedType.IntersectionType(t, superInterface)
@@ -208,13 +232,14 @@ class ExportModelToTsDeclarations {
)
return if (!shouldRenderSeparatedAbstractClass) {
property.generateTypeScriptString(indent, prefix)
property.generateTypeScriptString(indent, prefix, esModules)
} else {
val className = NonExistent.takeIf { esModules }.orEmpty() + name
val propertyRef = "$objects.$propertyName"
val shouldCreateExtraProperty = members.isNotEmpty() || superInterfaces.isNotEmpty() || superClasses.isNotEmpty()
val newSuperClass = ExportedType.ClassType(propertyRef, emptyList(), ir).takeIf { shouldCreateExtraProperty }
ExportedRegularClass(
name = name,
val classForRender = ExportedRegularClass(
name = className,
isInterface = false,
isAbstract = true,
superClasses = listOfNotNull(newSuperClass),
@@ -224,8 +249,14 @@ class ExportModelToTsDeclarations {
nestedClasses = nestedClasses,
ir = ir
)
.generateTypeScriptString(indent, prefix)
.also { if (shouldCreateExtraProperty) objectsSyntheticProperties.add(property) }
if (esModules && !property.isMember) {
property.copy(type = ExportedType.TypeOf(className), name = name)
.generateTypeScriptString(indent, prefix, esModules) + "\n${classForRender.generateTypeScriptString(indent, declare)}"
} else {
classForRender.generateTypeScriptString(indent, prefix)
}
}
}
@@ -272,10 +303,7 @@ class ExportModelToTsDeclarations {
val klassExport =
"$prefix$modifiers$keyword $name$renderedTypeParameters$superClassClause$superInterfacesClause {\n$bodyString}"
val staticsExport =
if (nestedClasses.isNotEmpty()) "\n" + ExportedNamespace(name, nestedClasses).toTypeScript(
indent,
prefix
) else ""
if (nestedClasses.isNotEmpty()) "\n" + ExportedNamespace(name, nestedClasses).toTypeScript(indent, prefix) else ""
return if (name.isValidES5Identifier()) klassExport + staticsExport else ""
}
@@ -42,7 +42,7 @@ class JsExecutableProducer(
val jsMultiModuleCache = JsMultiModuleCache(caches)
val cachedProgram = jsMultiModuleCache.loadProgramHeadersFromCache()
val resolver = CrossModuleDependenciesResolver(cachedProgram.map { it.jsIrHeader })
val resolver = CrossModuleDependenciesResolver(moduleKind, cachedProgram.map { it.jsIrHeader })
val crossModuleReferences = resolver.resolveCrossModuleDependencies(relativeRequirePath)
jsMultiModuleCache.loadRequiredJsIrModules(crossModuleReferences)
@@ -159,7 +159,7 @@ class IrModuleToJsTransformer(
val internalModuleName = ReservedJsNames.makeInternalModuleName()
val globalNames = NameTable<String>(namer.globalNames)
val exportStatements = ExportModelToJsStatements(staticContext) { globalNames.declareFreshName(it, it) }
.generateModuleExport(exportedModule, internalModuleName)
.generateModuleExport(exportedModule, internalModuleName, false)
val (crossModuleImports, importedKotlinModules) = generateCrossModuleImports(nameGenerator, modules, dependencies, { JsName(sanitizeName(it), false) })
val crossModuleExports = generateCrossModuleExports(modules, refInfo, internalModuleName)
@@ -94,6 +94,7 @@ class IrModuleToJsTransformerTmp(
private val mainModuleName = backendContext.configuration[CommonConfigurationKeys.MODULE_NAME]!!
private val moduleKind = backendContext.configuration[JSConfigurationKeys.MODULE_KIND]!!
private val isEsModules = moduleKind == ModuleKind.ES
private val sourceMapInfo = SourceMapsInfo.from(backendContext.configuration)
private class IrAndExportedDeclarations(val fragment: IrModuleFragment, val files: List<Pair<IrFile, List<ExportedDeclaration>>>)
@@ -103,7 +104,7 @@ class IrModuleToJsTransformerTmp(
}
private fun associateIrAndExport(modules: Iterable<IrModuleFragment>): List<IrAndExportedDeclarations> {
val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = true)
val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = !isEsModules)
return modules.map { module ->
val files = module.files.map { file ->
@@ -166,7 +167,7 @@ class IrModuleToJsTransformerTmp(
}
fun generateBinaryAst(files: Collection<IrFile>, allModules: Collection<IrModuleFragment>): List<JsIrFragmentAndBinaryAst> {
val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = true)
val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = !isEsModules)
val exportData = files.map { it to exportModelGenerator.generateExportWithExternals(it) }
@@ -235,12 +236,13 @@ class IrModuleToJsTransformerTmp(
polyfills.statements += backendContext.polyfills.getAllPolyfillsFor(file)
}
val internalModuleName = ReservedJsNames.makeInternalModuleName()
val internalModuleName = ReservedJsNames.makeInternalModuleName().takeIf { !isEsModules }
val globalNames = NameTable<String>(globalNameScope)
val exportStatements =
ExportModelToJsStatements(staticContext, { globalNames.declareFreshName(it, it) }).generateModuleExport(
ExportedModule(mainModuleName, moduleKind, exports),
internalModuleName,
isEsModules
)
result.exports.statements += exportStatements
@@ -379,7 +381,7 @@ private fun generateWrappedModuleBody(
// mutable container allows explicitly remove elements from itself,
// so we are able to help GC to free heavy JsIrModule objects
// TODO: It makes sense to invent something better, because this logic can be easily broken
val moduleToRef = program.asCrossModuleDependencies(relativeRequirePath).toMutableList()
val moduleToRef = program.asCrossModuleDependencies(moduleKind, relativeRequirePath).toMutableList()
val mainModule = moduleToRef.removeLast().let { (main, mainRef) ->
generateSingleWrappedModuleBody(
mainModuleName,
@@ -433,7 +435,7 @@ fun generateSingleWrappedModuleBody(
sourceMapsInfo: SourceMapsInfo?,
generateScriptModule: Boolean,
generateCallToMain: Boolean,
crossModuleReferences: CrossModuleReferences = CrossModuleReferences.Empty,
crossModuleReferences: CrossModuleReferences = CrossModuleReferences.Empty(moduleKind),
outJsProgram: Boolean = true
): CompilationOutputs {
val program = Merger(
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
import org.jetbrains.kotlin.ir.backend.js.utils.toJsIdentifier
import org.jetbrains.kotlin.js.backend.ast.*
import java.io.File
import org.jetbrains.kotlin.serialization.js.ModuleKind
class JsIrProgramFragment(val packageFqn: String) {
val nameBindings = mutableMapOf<String, JsName>()
@@ -57,8 +58,8 @@ class JsIrModuleHeader(
}
class JsIrProgram(private var modules: List<JsIrModule>) {
fun asCrossModuleDependencies(relativeRequirePath: Boolean): List<Pair<JsIrModule, CrossModuleReferences>> {
val resolver = CrossModuleDependenciesResolver(modules.map { it.makeModuleHeader() })
fun asCrossModuleDependencies(moduleKind: ModuleKind, relativeRequirePath: Boolean): List<Pair<JsIrModule, CrossModuleReferences>> {
val resolver = CrossModuleDependenciesResolver(moduleKind, modules.map { it.makeModuleHeader() })
modules = emptyList()
val crossModuleReferences = resolver.resolveCrossModuleDependencies(relativeRequirePath)
return crossModuleReferences.entries.map {
@@ -75,9 +76,12 @@ class JsIrProgram(private var modules: List<JsIrModule>) {
}
}
class CrossModuleDependenciesResolver(private val headers: List<JsIrModuleHeader>) {
class CrossModuleDependenciesResolver(
private val moduleKind: ModuleKind,
private val headers: List<JsIrModuleHeader>
) {
fun resolveCrossModuleDependencies(relativeRequirePath: Boolean): Map<JsIrModuleHeader, CrossModuleReferences> {
val headerToBuilder = headers.associateWith { JsIrModuleCrossModuleReferecenceBuilder(it, relativeRequirePath) }
val headerToBuilder = headers.associateWith { JsIrModuleCrossModuleReferecenceBuilder(moduleKind, it, relativeRequirePath) }
val definitionModule = mutableMapOf<String, JsIrModuleCrossModuleReferecenceBuilder>()
val mainModuleHeader = headers.last()
@@ -110,7 +114,11 @@ private fun String.prettyTag() = takeWhile { c -> c != '|' }
private class CrossModuleRef(val module: JsIrModuleCrossModuleReferecenceBuilder, val tag: String)
private class JsIrModuleCrossModuleReferecenceBuilder(val header: JsIrModuleHeader, val relativeRequirePath: Boolean) {
private class JsIrModuleCrossModuleReferecenceBuilder(
val moduleKind: ModuleKind,
val header: JsIrModuleHeader,
val relativeRequirePath: Boolean
) {
val imports = mutableListOf<CrossModuleRef>()
val exports = mutableSetOf<String>()
var transitiveJsExportFrom = emptyList<JsIrModuleHeader>()
@@ -155,7 +163,13 @@ private class JsIrModuleCrossModuleReferecenceBuilder(val header: JsIrModuleHead
val transitiveExport = transitiveJsExportFrom.mapNotNull {
if (it.hasJsExports) import(it) else null
}
return CrossModuleReferences(importedModules.values.toList(), transitiveExport, exportNames, resultImports)
return CrossModuleReferences(
moduleKind,
importedModules.values.toList(),
transitiveExport,
exportNames,
resultImports
)
}
private fun relativeRequirePath(moduleHeader: JsIrModuleHeader): String? {
@@ -177,6 +191,7 @@ private class JsIrModuleCrossModuleReferecenceBuilder(val header: JsIrModuleHead
class CrossModuleImport(val exportedAs: String, val moduleExporter: JsName)
class CrossModuleReferences(
val moduleKind: ModuleKind,
val importedModules: List<JsImportedModule>, // additional Kotlin imported modules
val transitiveJsExportFrom: List<JsName>, // the list of modules which provide their js exports for transitive export
val exports: Map<String, String>, // tag -> index
@@ -190,12 +205,21 @@ class CrossModuleReferences(
val tagToName = module.fragments.flatMap { it.nameBindings.entries }.associate { it.key to it.value }
jsImports = imports.entries.associate {
val importedAs = tagToName[it.key] ?: error("Internal error: cannot find imported name for symbol ${it.key.prettyTag()}")
val exportRef = JsNameRef(it.value.exportedAs, ReservedJsNames.makeCrossModuleNameRef(it.value.moduleExporter))
val exportRef = JsNameRef(
it.value.exportedAs,
it.value.moduleExporter.let {
if (moduleKind == ModuleKind.ES) {
it.makeRef()
} else {
ReservedJsNames.makeCrossModuleNameRef(it)
}
}
)
it.key to JsVars.JsVar(importedAs, exportRef)
}
}
companion object {
val Empty = CrossModuleReferences(listOf(), emptyList(), emptyMap(), emptyMap())
fun Empty(moduleKind: ModuleKind) = CrossModuleReferences(moduleKind, listOf(), emptyList(), emptyMap(), emptyMap())
}
}
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.utils.DFS
import org.jetbrains.kotlin.utils.addToStdlib.partitionIsInstance
class Merger(
private val moduleName: String,
@@ -20,6 +21,7 @@ class Merger(
private val generateCallToMain: Boolean,
) {
private val isEsModules = moduleKind == ModuleKind.ES
private val importStatements = mutableMapOf<String, JsStatement>()
private val importedModulesMap = mutableMapOf<JsImportedModuleKey, JsImportedModule>()
@@ -65,16 +67,25 @@ class Merger(
if (crossModuleReferences.exports.isNotEmpty()) {
val internalModuleName = ReservedJsNames.makeInternalModuleName()
val createExportBlock = jsAssignment(
ReservedJsNames.makeCrossModuleNameRef(internalModuleName),
JsAstUtils.or(ReservedJsNames.makeCrossModuleNameRef(internalModuleName), JsObjectLiteral())
).makeStmt()
additionalExports += createExportBlock
if (isEsModules) {
val exportedElements = crossModuleReferences.exports.entries.map { (tag, hash) ->
val internalName = nameMap[tag] ?: error("Missing name for declaration '$tag'")
JsExport.Element(internalName, JsName(hash, false))
}
crossModuleReferences.exports.entries.forEach { (tag, hash) ->
val internalName = nameMap[tag] ?: error("Missing name for declaration '$tag'")
val crossModuleRef = ReservedJsNames.makeCrossModuleNameRef(ReservedJsNames.makeInternalModuleName())
additionalExports += jsAssignment(JsNameRef(hash, crossModuleRef), JsNameRef(internalName)).makeStmt()
additionalExports += JsExport(JsExport.Subject.Elements(exportedElements))
} else {
val createExportBlock = jsAssignment(
ReservedJsNames.makeCrossModuleNameRef(internalModuleName),
JsAstUtils.or(ReservedJsNames.makeCrossModuleNameRef(internalModuleName), JsObjectLiteral())
).makeStmt()
additionalExports += createExportBlock
crossModuleReferences.exports.entries.forEach { (tag, hash) ->
val internalName = nameMap[tag] ?: error("Missing name for declaration '$tag'")
val crossModuleRef = ReservedJsNames.makeCrossModuleNameRef(ReservedJsNames.makeInternalModuleName())
additionalExports += jsAssignment(JsNameRef(hash, crossModuleRef), JsNameRef(internalName)).makeStmt()
}
}
}
}
@@ -125,25 +136,41 @@ class Merger(
}
private fun declareAndCallJsExporter(): List<JsStatement> {
val exportBody = JsBlock(fragments.flatMap { it.exports.statements })
if (exportBody.isEmpty) {
return emptyList()
}
if (isEsModules) {
val allExportRelatedStatements = fragments.flatMap { it.exports.statements }
val (allExportStatements, restStatements) = allExportRelatedStatements.partitionIsInstance<JsStatement, JsExport>()
val (currentModuleExportStatements, restExportStatements) = allExportStatements.partition { it.fromModule == null }
val exportedElements = currentModuleExportStatements.takeIf { it.isNotEmpty() }
?.asSequence()
?.flatMap { (it.subject as JsExport.Subject.Elements).elements }
?.distinctBy { (it.alias ?: it.name).ident }
?.map { if (it.name.ident == it.alias?.ident) JsExport.Element(it.name, null) else it }
?.toList()
val internalModuleName = ReservedJsNames.makeInternalModuleName()
val exporterName = ReservedJsNames.makeJsExporterName()
val jsExporterFunction = JsFunction(emptyScope, "js exporter function").apply {
body = exportBody
name = exporterName
parameters.add(JsParameter(internalModuleName))
val oneLargeExportStatement = exportedElements?.let { JsExport(JsExport.Subject.Elements(it)) }
return restStatements + listOfNotNull(oneLargeExportStatement) + restExportStatements
} else {
val exportBody = JsBlock(fragments.flatMap { it.exports.statements })
if (exportBody.isEmpty) {
return emptyList()
}
val internalModuleName = ReservedJsNames.makeInternalModuleName()
val exporterName = ReservedJsNames.makeJsExporterName()
val jsExporterFunction = JsFunction(emptyScope, "js exporter function").apply {
body = exportBody
name = exporterName
parameters.add(JsParameter(internalModuleName))
}
val jsExporterCall = JsInvocation(exporterName.makeRef(), internalModuleName.makeRef())
val result = mutableListOf(jsExporterFunction.makeStmt(), jsExporterCall.makeStmt())
if (!generateCallToMain) {
val exportExporter = jsAssignment(JsNameRef(exporterName, internalModuleName.makeRef()), exporterName.makeRef())
result += exportExporter.makeStmt()
}
return result
}
val jsExporterCall = JsInvocation(exporterName.makeRef(), internalModuleName.makeRef())
val result = mutableListOf(jsExporterFunction.makeStmt(), jsExporterCall.makeStmt())
if (!generateCallToMain) {
val exportExporter = jsAssignment(JsNameRef(exporterName, internalModuleName.makeRef()), exporterName.makeRef())
result += exportExporter.makeStmt()
}
return result
}
private fun transitiveJsExport(): List<JsStatement> {
@@ -214,9 +241,6 @@ class Merger(
if (generateScriptModule) {
with(program.globalBlock) {
if (!generateScriptModule) {
statements += JsStringLiteral("use strict").makeStmt()
}
statements.addWithComment("block: polyfills", polyfillDeclarationBlock.statements)
statements.addWithComment("block: imports", importStatements)
statements += moduleBody
@@ -228,7 +252,7 @@ class Merger(
parameters += JsParameter(internalModuleName)
parameters += (importedJsModules).map { JsParameter(it.internalName) }
with(body) {
if (!generateScriptModule) {
if (!isEsModules) {
statements += JsStringLiteral("use strict").makeStmt()
}
statements.addWithComment("block: imports", importStatements)
@@ -17,7 +17,7 @@ object ModuleWrapperTranslation {
}
fun wrap(
moduleId: String, function: JsExpression, importedModules: List<JsImportedModule>,
moduleId: String, function: JsFunction, importedModules: List<JsImportedModule>,
program: JsProgram, kind: ModuleKind
): List<JsStatement> {
return when (kind) {
@@ -25,7 +25,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 -> error("ES modules are not supported in legacy wrapper")
ModuleKind.ES -> wrapEsModule(function, importedModules)
}
}
@@ -100,6 +100,21 @@ object ModuleWrapperTranslation {
return listOf(invocation.makeStmt())
}
private fun wrapEsModule(function: JsFunction, importedModules: List<JsImportedModule>): List<JsStatement> {
val importStatements = importedModules.zip(function.parameters.drop(1)).map {
JsImport(
it.first.externalName,
if (it.first.plainReference == null) {
JsImport.Target.All(alias = it.second.name)
} else {
JsImport.Target.Default(name = it.second.name)
}
)
}
return importStatements + function.body.statements.dropLast(1)
}
private fun wrapPlain(
moduleId: String, function: JsExpression,
importedModules: List<JsImportedModule>, program: JsProgram
@@ -1,208 +0,0 @@
/*
* Copyright 2010-2020 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.utils
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.codegen.IrToJs
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrDeclarationReference
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.js.backend.ast.JsImport
import org.jetbrains.kotlin.js.backend.ast.JsName
class StaticDeclarationNumerator {
var currentNumber = 0
val numeration = mutableMapOf<IrDeclaration, Int>()
fun add(moduleFragment: IrModuleFragment) {
moduleFragment.files.forEach { add(it) }
}
fun add(declaration: IrDeclaration) {
// TODO: We should not visit declarations multiple times.
// Investigate enum tests in dce-driven mode.
if (declaration !in numeration) {
numeration[declaration] = currentNumber
currentNumber++
}
}
fun add(packageFragment: IrPackageFragment) {
packageFragment.acceptChildrenVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitDeclaration(declaration: IrDeclarationBase) {
if (declaration !is IrVariable) {
add(declaration)
}
super.visitDeclaration(declaration)
}
})
}
}
class NewStableStaticNamesCollectorVisitor(val needToCollectReferences: Boolean) : IrElementVisitorVoid {
val collectedStableNames = mutableSetOf<String>()
init {
collectedStableNames.addAll(RESERVED_IDENTIFIERS)
collectedStableNames.add(Namer.IMPLICIT_RECEIVER_NAME)
}
private fun IrDeclaration.collectStableName() {
collectedStableNames += stableNameForExternalDeclaration(this) ?: return
}
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitDeclaration(declaration: IrDeclarationBase) {
super.visitDeclaration(declaration)
declaration.collectStableName()
}
override fun visitDeclarationReference(expression: IrDeclarationReference) {
super.visitDeclarationReference(expression)
if (needToCollectReferences) {
val declaration = expression.symbol.owner as? IrDeclaration
declaration?.collectStableName()
}
}
}
class NewNamerImpl(
val context: JsIrBackendContext,
val unit: IrToJs.CodegenUnit,
val exportId: (IrDeclarationWithName) -> String,
stableNames: Set<String>,
) : IrNamerBase() {
val staticNames = NameTable<IrDeclaration>(
reserved = stableNames.toMutableSet()
)
val internalImports = mutableMapOf<String, JsImport>()
override fun getNameForMemberFunction(function: IrSimpleFunction): JsName {
require(function.dispatchReceiverParameter != null)
val name = jsFunctionSignature(function, context)
return name.toJsName()
}
override fun getNameForMemberField(field: IrField): JsName {
val fieldName = sanitizeName(
try {
exportId(field)
} catch (e: IllegalStateException) {
// TODO: Fix DCE with inline classes and remove this hack
field.name.asString() + "_LIKELY_ELIMINATED_BY_DCE"
}
)
// TODO: Webpack not minimize member names, it is long name, which is not minimized, so it affects final JS bundle size
// Use shorter names
return JsName("f_$fieldName", false)
}
override fun getNameForStaticDeclaration(declaration: IrDeclarationWithName): JsName {
staticNames.names[declaration]?.let { return JsName(it, false) }
fun registerImport(moduleId: String, importedName: String) {
val fullModuleId = if (moduleId.startsWith(".")) {
unit.pathToKotlinModulesRoot + moduleId
} else {
// TODO: Do we cover this path in tests?
moduleId
}
val import = internalImports.getOrPut(fullModuleId) {
JsImport(fullModuleId)
}
import.elements += JsImport.Element(importedName, staticNames.names[declaration]!!)
}
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 -> {
// TODO: Support jsQualifier
staticNames.declareFreshName(declaration, declaration.name.asString())
registerImport(jsModule, "default")
}
fileJsModule != null -> {
// TODO: Support jsQualifier
staticNames.declareFreshName(declaration, declaration.name.asString())
registerImport(fileJsModule, declaration.getJsNameOrKotlinName().identifier)
}
else -> {
var name = declaration.getJsNameOrKotlinName().identifier
if (jsQualifier != null)
name = "$jsQualifier.$name"
staticNames.declareStableName(declaration, name)
}
}
} else { // Non-external declaration
val name = declaration.nameIfPropertyAccessor() ?: declaration.name.asString()
staticNames.declareFreshName(declaration, name)
val unitReference = unit.referenceCodegenUnitOfDeclaration(declaration)
if (unitReference is IrToJs.OtherUnitReference) {
registerImport(unitReference.importPath, exportId(declaration))
}
}
return JsName(staticNames.names[declaration]!!, false)
}
}
// TODO: Cache?
private fun stableNameForExternalDeclaration(declaration: IrDeclaration): String? {
if (declaration !is IrDeclarationWithName ||
!declaration.hasStaticDispatch() ||
!declaration.isEffectivelyExternal() ||
declaration.isPropertyAccessor ||
declaration.isPropertyField
) {
return null
}
if (declaration is IrConstructor) {
return stableNameForExternalDeclaration(declaration.parentAsClass)
}
val importedFromModuleOnly =
declaration.getJsModule() != null && !declaration.isJsNonModule()
val jsName = declaration.getJsName()
val jsQualifier = declaration.fileOrNull?.getJsQualifier()
return when {
importedFromModuleOnly ->
null
jsQualifier != null ->
jsQualifier.split('1')[0]
jsName != null ->
jsName
else ->
declaration.name.identifier
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ where possible options include:
-libraries <path> Paths to Kotlin libraries with .meta.js and .kjsm files, separated by system path separator
-main {call|noCall} Define whether the `main` function should be called upon execution
-meta-info Generate .meta.js and .kjsm files with metadata. Use to create a library
-module-kind {plain|amd|commonjs|umd}
-module-kind {plain|amd|commonjs|umd|es}
Kind of the JS module generated by the compiler
-no-stdlib Don't automatically include the default Kotlin/JS stdlib into compilation dependencies
-output <filepath> Destination *.js file for the compilation result
@@ -34,8 +34,6 @@ object BinaryArtifacts {
class JsIrArtifact(override val outputFile: File, val compilerResult: CompilerResult, val icCache: Map<String, ByteArray>? = null) : Js()
class JsEsArtifact(override val outputFile: File, val outputDceFile: File?) : Js()
data class IncrementalJsArtifact(val originalArtifact: Js, val recompiledArtifact: Js) : Js() {
override val outputFile: File
get() = unwrap().outputFile