[K/JS] Add file-to-file compilation ^KT-6168 Fixed

This commit is contained in:
Artem Kobzar
2023-06-22 18:23:45 +00:00
committed by Space Team
parent a44b5e4562
commit 64158a8a2f
74 changed files with 947 additions and 608 deletions
@@ -657,6 +657,13 @@ class K2JSCompilerArguments : CommonCompilerArguments() {
collector.deprecationWarn(irBaseClassInMetadata, false, "-Xir-base-class-in-metadata")
collector.deprecationWarn(irNewIr2Js, true, "-Xir-new-ir2js")
if (irPerFile && moduleKind != MODULE_ES) {
collector.report(
CompilerMessageSeverity.ERROR,
"Per-file compilation can't be used with any `moduleKind` except `es` (ECMAScript Modules)"
)
}
return super.configureAnalysisFlags(collector, languageVersion).also {
it[allowFullyQualifiedNameInKClass] = wasm && wasmKClassFqn //Only enabled WASM BE supports this flag
}
@@ -26,6 +26,10 @@ public interface K2JsArgumentConstants {
String MODULE_UMD = "umd";
String MODULE_ES = "es";
String GRANULARITY_WHOLE_PROGRAM = "whole-program";
String GRANULARITY_PER_MODULE = "per-module";
String GRANULARITY_PER_FILE = "per-file";
String SOURCE_MAP_SOURCE_CONTENT_ALWAYS = "always";
String SOURCE_MAP_SOURCE_CONTENT_NEVER = "never";
String SOURCE_MAP_SOURCE_CONTENT_INLINING = "inlining";
@@ -41,13 +41,9 @@ import org.jetbrains.kotlin.incremental.js.IncrementalDataProvider
import org.jetbrains.kotlin.incremental.js.IncrementalNextRoundChecker
import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer
import org.jetbrains.kotlin.ir.backend.js.*
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.dce.dumpDeclarationIrSizesIfNeed
import org.jetbrains.kotlin.ir.backend.js.ic.*
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.CompilationOutputsBuilt
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsCodeGenerator
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.*
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC
import org.jetbrains.kotlin.ir.linkage.partial.setupPartialLinkageConfig
@@ -71,8 +67,8 @@ import java.io.IOException
private val K2JSCompilerArguments.granularity: JsGenerationGranularity
get() = when {
this.irPerModule -> JsGenerationGranularity.PER_MODULE
this.irPerFile -> JsGenerationGranularity.PER_FILE
this.irPerModule -> JsGenerationGranularity.PER_MODULE
else -> JsGenerationGranularity.WHOLE_PROGRAM
}
@@ -129,7 +125,7 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
val ir = lowerIr()
val transformer = IrModuleToJsTransformer(ir.context, mainCallArguments, ir.moduleFragmentToUniqueName)
val mode = TranslationMode.fromFlags(arguments.irDce, arguments.irPerModule, arguments.irMinimizedMemberNames)
val mode = TranslationMode.fromFlags(arguments.irDce, arguments.granularity, arguments.irMinimizedMemberNames)
return transformer.makeJsCodeGenerator(ir.allModules, mode)
}
@@ -303,7 +299,7 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
relativeRequirePath = true
)
val (outputs, rebuiltModules) = jsExecutableProducer.buildExecutable(arguments.irPerModule, outJsProgram = false)
val (outputs, rebuiltModules) = jsExecutableProducer.buildExecutable(arguments.granularity, outJsProgram = false)
outputs.writeAll(outputDir, outputName, arguments.generateDts, moduleName, moduleKind)
icCaches.cacheGuard.release()
@@ -22,9 +22,9 @@ import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.lower.JsInnerClassesSupport
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsPolyfills
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.translateJsCodeIntoStatementList
import org.jetbrains.kotlin.ir.backend.js.utils.*
@@ -16,7 +16,6 @@ import org.jetbrains.kotlin.backend.common.lower.inline.LocalClassesInInlineLamb
import org.jetbrains.kotlin.backend.common.lower.loops.ForLoopsLowering
import org.jetbrains.kotlin.backend.common.lower.optimizations.FoldConstantLowering
import org.jetbrains.kotlin.backend.common.phaser.*
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.lower.*
import org.jetbrains.kotlin.ir.backend.js.lower.calls.CallsLowering
import org.jetbrains.kotlin.ir.backend.js.lower.cleanup.CleanupLowering
@@ -24,6 +23,7 @@ import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.AddContinuationToFunc
import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.JsSuspendArityStoreLowering
import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.JsSuspendFunctionsLowering
import org.jetbrains.kotlin.ir.backend.js.lower.inline.*
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsGenerationGranularity
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
@@ -840,15 +840,6 @@ private val cleanupLoweringPhase = makeBodyLoweringPhase(
description = "Clean up IR before codegen"
)
private val moveOpenClassesToSeparatePlaceLowering = makeCustomJsModulePhase(
{ context, module ->
if (context.granularity == JsGenerationGranularity.PER_FILE)
moveOpenClassesToSeparateFiles(module)
},
name = "MoveOpenClassesToSeparateFiles",
description = "Move open classes to separate files"
).toModuleLowering()
private val jsSuspendArityStorePhase = makeDeclarationTransformerPhase(
::JsSuspendArityStoreLowering,
name = "JsSuspendArityStoreLowering",
@@ -962,7 +953,6 @@ val loweringList = listOf<Lowering>(
cleanupLoweringPhase,
// Currently broken due to static members lowering making single-open-class
// files non-recognizable as single-class files
// moveOpenClassesToSeparatePlaceLowering,
validateIrAfterLowering,
)
@@ -9,11 +9,15 @@ import org.jetbrains.kotlin.backend.common.DefaultDelegateFactory
import org.jetbrains.kotlin.backend.common.DefaultMapping
import org.jetbrains.kotlin.ir.backend.js.utils.MutableReference
import org.jetbrains.kotlin.ir.declarations.*
import java.util.WeakHashMap
class JsMapping : DefaultMapping() {
val esClassWhichNeedBoxParameters = DefaultDelegateFactory.newDeclarationToValueMapping<IrClass, Boolean>()
val esClassToPossibilityForOptimization = DefaultDelegateFactory.newDeclarationToValueMapping<IrClass, MutableReference<Boolean>>()
// Check if we need concurrency here
val chunkToOriginalFile = WeakHashMap<IrFile, IrFile>()
val outerThisFieldSymbols = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrField>()
val innerClassConstructors = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrConstructor, IrConstructor>()
val originalInnerClassPrimaryConstructorByClass = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrConstructor>()
@@ -1,12 +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
enum class JsGenerationGranularity {
WHOLE_PROGRAM,
PER_MODULE,
PER_FILE
}
@@ -10,12 +10,12 @@ import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.lower.collectNativeImplementations
import org.jetbrains.kotlin.ir.backend.js.lower.generateJsTests
import org.jetbrains.kotlin.ir.backend.js.lower.moveBodilessDeclarationsToSeparatePlace
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrLinker
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.CompilationOutputs
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode
import org.jetbrains.kotlin.ir.declarations.IrFactory
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
@@ -9,7 +9,6 @@ import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.common.phaser.PhaserState
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.ic.JsIrCompilerICInterface
import org.jetbrains.kotlin.ir.backend.js.lower.collectNativeImplementations
import org.jetbrains.kotlin.ir.backend.js.lower.generateJsTests
@@ -53,7 +52,7 @@ class JsIrCompilerWithIC(
allModules: Collection<IrModuleFragment>,
dirtyFiles: Collection<IrFile>,
mainArguments: List<String>?
): List<() -> JsIrProgramFragment> {
): List<() -> List<JsIrProgramFragment>> {
val shouldGeneratePolyfills = context.configuration.getBoolean(JSConfigurationKeys.GENERATE_POLYFILLS)
allModules.forEach {
@@ -159,7 +159,7 @@ internal class JsUsefulDeclarationProcessor(
context.intrinsics.jsPrototypeOfSymbol.owner.enqueue(irClass, "interface default implementation")
}
if ((!context.es6mode || !isEsModules) && (irClass.isInner || irClass.isObject)) {
if (irClass.isInner || irClass.isObject) {
context.intrinsics.jsDefinePropertySymbol.owner.enqueue(irClass, "object lazy initialization")
}
@@ -185,8 +185,6 @@ internal class JsUsefulDeclarationProcessor(
irFunction.parentClassOrNull?.takeIf { it.isInterface }?.enqueue(irFunction, "interface default method is used")
}
if (context.es6mode && isEsModules) return
val property = irFunction.correspondingPropertySymbol?.owner ?: return
if (property.isExported(context) || property.getJsName() != null || property.isOverriddenExternal()) {
@@ -93,16 +93,11 @@ class ExportModelToJsStatements(
is ExportedProperty -> {
require(namespace != null || esModules) { "Only namespaced properties are allowed" }
when {
namespace == null -> {
when (namespace) {
null -> {
val property = declaration.generateTopLevelGetters()
listOf(JsVars(property), JsExport(property.name.makeRef(), JsName(declaration.name, false)))
}
es6mode && declaration.isMember -> {
val jsClass = parentClass?.getCorrespondingJsClass() ?: error("Expect to have parentClass at this point")
jsClass.members += declaration.generateClassMembers()
listOf(JsEmpty)
}
else -> {
val getter = declaration.irGetter?.let { staticContext.getNameForStaticDeclaration(it) }
val setter = declaration.irSetter?.let { staticContext.getNameForStaticDeclaration(it) }
@@ -123,10 +118,11 @@ class ExportModelToJsStatements(
is ExportedObject -> {
require(namespace != null || esModules) { "Only namespaced properties are allowed" }
val (name, objectClassInitialization) = declaration.getNameAndInitialization()
val newNameSpace = when {
namespace != null -> jsElementAccess(declaration.name, namespace)
else ->
jsElementAccess(Namer.PROTOTYPE_NAME, staticContext.getNameForClass(declaration.ir).makeRef())
jsElementAccess(Namer.PROTOTYPE_NAME, name.makeRef())
}
val staticsExport =
declaration.nestedClasses.flatMap { generateDeclarationExport(it, newNameSpace, esModules, declaration.ir) }
@@ -155,19 +151,19 @@ class ExportModelToJsStatements(
)
}
objectExport + staticsExport
listOfNotNull(objectClassInitialization.takeIf { staticsExport.isNotEmpty() }) + objectExport + staticsExport
}
is ExportedRegularClass -> {
if (declaration.isInterface) return emptyList()
val name = staticContext.getNameForStaticDeclaration(declaration.ir)
val (name, classInitialization) = declaration.getNameAndInitialization()
val newNameSpace = when {
namespace != null -> jsElementAccess(declaration.name, namespace)
esModules -> name.makeRef()
else -> prototypeOf(staticContext.getNameForClass(declaration.ir).makeRef(), staticContext)
else -> prototypeOf(name.makeRef(), staticContext)
}
val klassExport = when {
namespace != null -> jsAssignment(newNameSpace, JsNameRef(name)).makeStmt()
namespace != null -> jsAssignment(newNameSpace, name.makeRef()).makeStmt()
esModules -> JsExport(name.makeRef(), alias = JsName(declaration.name, false))
else -> null
}
@@ -181,12 +177,12 @@ class ExportModelToJsStatements(
val innerClassesAssignments = declaration.nestedClasses
.filter { it.ir.isInner }
.map { it.generateInnerClassAssignment(declaration) }
.map { it.generateInnerClassAssignment(name) }
val staticsExport = (staticFunctions + enumEntries + declaration.nestedClasses)
.flatMap { generateDeclarationExport(it, newNameSpace, esModules, declaration.ir) }
listOfNotNull(klassExport) + staticsExport + innerClassesAssignments
listOfNotNull(classInitialization, klassExport) + staticsExport + innerClassesAssignments
}
}
}
@@ -210,43 +206,9 @@ class ExportModelToJsStatements(
)
}
private fun ExportedProperty.generateClassMembers(): List<JsFunction> {
val getter = irGetter?.let { staticContext.getNameForStaticDeclaration(it) }
val setter = irSetter?.let { staticContext.getNameForStaticDeclaration(it) }
return buildList {
if (getter != null) {
add(JsFunction(emptyScope, "").also {
it.name = JsName(name, false)
if (isStatic) {
it.modifiers.add(JsFunction.Modifier.STATIC)
}
it.modifiers.add(JsFunction.Modifier.GET)
it.body = JsBlock().apply {
statements.add(JsReturn(JsInvocation(getter.makeRef())))
}
})
}
if (setter != null) {
add(JsFunction(emptyScope, "").also {
val value = JsName("value", true)
it.name = JsName(name, false)
it.parameters.add(JsParameter(value))
if (isStatic) {
it.modifiers.add(JsFunction.Modifier.STATIC)
}
it.modifiers.add(JsFunction.Modifier.SET)
it.body = JsBlock().apply {
statements.add(JsExpressionStatement(JsInvocation(setter.makeRef(), value.makeRef())))
}
})
}
}
}
private fun ExportedClass.generateInnerClassAssignment(outerClass: ExportedClass): JsStatement {
val innerClassRef = staticContext.getNameForStaticDeclaration(ir).makeRef()
val outerClassRef = staticContext.getNameForStaticDeclaration(outerClass.ir).makeRef()
private fun ExportedClass.generateInnerClassAssignment(outerClassName: JsName): JsStatement {
val innerClassRef = ir.getClassRef(staticContext)
val outerClassRef = outerClassName.makeRef()
val companionObject = ir.companionObject()
val secondaryConstructors = members.filterIsInstanceAnd<ExportedFunction> { it.isStatic }
val bindConstructor = JsName("__bind_constructor_", false)
@@ -283,30 +245,27 @@ class ExportModelToJsStatements(
blockStatements.add(JsReturn(bindConstructor.makeRef()))
val innerClassGetter = JsFunction(emptyScope, JsBlock(*blockStatements.toTypedArray()), "inner class '$name' getter")
return if (es6mode) {
outerClass.ir.getCorrespondingJsClass().members += innerClassGetter.also {
it.name = JsName(name, false)
it.modifiers.add(JsFunction.Modifier.GET)
return defineProperty(
prototypeOf(outerClassRef, staticContext),
name,
innerClassGetter,
null,
staticContext
).makeStmt()
}
private fun ExportedClass.getNameAndInitialization(): Pair<JsName, JsStatement?> {
return when (val classRef = ir.getClassRef(staticContext)) {
!is JsNameRef -> {
val stableName = JsName(name, true)
stableName to JsVars(JsVars.JsVar(stableName, classRef))
}
JsEmpty
} else {
defineProperty(
prototypeOf(outerClassRef, staticContext),
name,
innerClassGetter,
null,
staticContext
).makeStmt()
else -> classRef.name!! to null
}
}
private fun IrClass.getCorrespondingJsClass(): JsClass {
val jsClassModel = staticContext.classModels[symbol] ?: error("Class with name '$name' was not found")
return (jsClassModel.preDeclarationBlock.statements.first() as? JsExpressionStatement)?.expression as? JsClass
?: error("Expect to have JsClass as a first statement inside JsIrClassModel")
}
private fun JsNameRef.bindToThis(bindTo: JsExpression): JsInvocation {
private fun JsExpression.bindToThis(bindTo: JsExpression): JsInvocation {
return JsInvocation(JsNameRef("bind", this), bindTo, JsThisRef())
}
}
@@ -10,7 +10,7 @@ import org.jetbrains.kotlin.backend.common.serialization.IrInterningService
import org.jetbrains.kotlin.backend.common.serialization.cityHash64
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.ir.backend.js.*
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramFragment
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.util.*
@@ -33,7 +33,7 @@ fun interface JsIrCompilerICInterface {
allModules: Collection<IrModuleFragment>,
dirtyFiles: Collection<IrFile>,
mainArguments: List<String>?
): List<() -> JsIrProgramFragment>
): List<() -> List<JsIrProgramFragment>>
}
fun interface JsIrCompilerICInterfaceFactory {
@@ -616,7 +616,7 @@ class CacheUpdater(
private fun commitCacheAndBuildModuleArtifacts(
incrementalCacheArtifacts: Map<KotlinLibraryFile, IncrementalCacheArtifact>,
moduleNames: Map<KotlinLibraryFile, String>,
rebuiltFileFragments: KotlinSourceFileMap<JsIrProgramFragment>
rebuiltFileFragments: KotlinSourceFileMap<List<JsIrProgramFragment>>
): List<ModuleArtifact> = stopwatch.measure("Incremental cache - committing artifacts") {
incrementalCacheArtifacts.map { (libFile, incrementalCacheArtifact) ->
incrementalCacheArtifact.buildModuleArtifactAndCommitCache(
@@ -630,7 +630,7 @@ class CacheUpdater(
compilerForIC: JsIrCompilerICInterface,
loadedFragments: Map<KotlinLibraryFile, IrModuleFragment>,
dirtyFiles: Map<KotlinLibraryFile, Set<KotlinSourceFile>>
): MutableList<Triple<KotlinLibraryFile, KotlinSourceFile, () -> JsIrProgramFragment>> =
): MutableList<Triple<KotlinLibraryFile, KotlinSourceFile, () -> List<JsIrProgramFragment>>> =
stopwatch.measure("Processing IR - lowering") {
val dirtyFilesForCompiling = mutableListOf<IrFile>()
val dirtyFilesForRestoring = mutableListOf<Pair<KotlinLibraryFile, KotlinSourceFile>>()
@@ -737,7 +737,7 @@ class CacheUpdater(
private data class FragmentGenerators(
val incrementalCacheArtifacts: Map<KotlinLibraryFile, IncrementalCacheArtifact>,
val moduleNames: Map<KotlinLibraryFile, String>,
val generators: MutableList<Triple<KotlinLibraryFile, KotlinSourceFile, () -> JsIrProgramFragment>>
val generators: MutableList<Triple<KotlinLibraryFile, KotlinSourceFile, () -> List<JsIrProgramFragment>>>
)
private fun loadIrAndMakeIrFragmentGenerators(): FragmentGenerators {
@@ -751,9 +751,9 @@ class CacheUpdater(
}
private fun generateIrFragments(
generators: MutableList<Triple<KotlinLibraryFile, KotlinSourceFile, () -> JsIrProgramFragment>>
): KotlinSourceFileMap<JsIrProgramFragment> = stopwatch.measure("Processing IR - generating program fragments") {
val rebuiltFragments = KotlinSourceFileMutableMap<JsIrProgramFragment>()
generators: MutableList<Triple<KotlinLibraryFile, KotlinSourceFile, () -> List<JsIrProgramFragment>>>
): KotlinSourceFileMap<List<JsIrProgramFragment>> = stopwatch.measure("Processing IR - generating program fragments") {
val rebuiltFragments = KotlinSourceFileMutableMap<List<JsIrProgramFragment>>()
while (generators.isNotEmpty()) {
val (libFile, srcFile, fragmentGenerator) = generators.removeFirst()
rebuiltFragments[libFile, srcFile] = fragmentGenerator()
@@ -783,7 +783,7 @@ fun rebuildCacheForDirtyFiles(
exportedDeclarations: Set<FqName>,
mainArguments: List<String>?,
es6mode: Boolean
): Pair<IrModuleFragment, List<Pair<IrFile, JsIrProgramFragment>>> {
): Pair<IrModuleFragment, List<Pair<IrFile, List<JsIrProgramFragment>>>> {
val internationService = IrInterningService()
val emptyMetadata = object : KotlinSourceFileExports() {
override val inverseDependencies = KotlinSourceFileMap<Set<IdSignature>>(emptyMap())
@@ -211,7 +211,7 @@ internal fun CrossModuleReferences.crossModuleReferencesHashForIC() = HashCalcul
update(importedModule.relativeRequirePath ?: "")
}
updateForEach(transitiveJsExportFrom) { transitiveExport ->
updateForEach(transitiveExportFrom) { transitiveExport ->
update(transitiveExport.internalName.toString())
update(transitiveExport.externalName)
}
@@ -32,6 +32,17 @@ internal inline fun File.useCodedOutput(f: CodedOutputStream.() -> Unit) {
outputStream().useCodedOutput(f)
}
internal inline fun <T> CodedOutputStream.ifNotNull(value: T?, write: (T) -> Unit) {
writeBoolNoTag(value != null)
if (value != null) {
write(value)
}
}
internal inline fun <T> CodedInputStream.ifTrue(then: () -> T): T? {
return if (readBool()) then() else null
}
internal fun icError(what: String, libFile: KotlinLibraryFile? = null, srcFile: KotlinSourceFile? = null): Nothing {
val filePath = listOfNotNull(libFile?.path, srcFile?.path).joinToString(":") { File(it).name }
val msg = if (filePath.isEmpty()) what else "$what for $filePath"
@@ -15,10 +15,10 @@ import java.io.File
internal sealed class SourceFileCacheArtifact(val srcFile: KotlinSourceFile, val binaryAstFile: File) {
abstract fun commitMetadata()
fun commitBinaryAst(fragment: JsIrProgramFragment) {
fun commitBinaryAst(fragments: List<JsIrProgramFragment>) {
binaryAstFile.parentFile?.mkdirs()
BufferedOutputStream(binaryAstFile.outputStream()).use {
fragment.serializeTo(it)
fragments.serializeTo(it)
}
}
@@ -59,7 +59,7 @@ internal class IncrementalCacheArtifact(
fun buildModuleArtifactAndCommitCache(
moduleName: String,
rebuiltFileFragments: Map<KotlinSourceFile, JsIrProgramFragment>,
rebuiltFileFragments: Map<KotlinSourceFile, List<JsIrProgramFragment>>,
): ModuleArtifact {
val fileArtifacts = srcCacheActions.map { srcFileAction ->
val rebuiltFileFragment = rebuiltFileFragments[srcFileAction.srcFile]
@@ -67,7 +67,7 @@ internal class IncrementalCacheArtifact(
srcFileAction.commitBinaryAst(rebuiltFileFragment)
}
srcFileAction.commitMetadata()
SrcFileArtifact(srcFileAction.srcFile.path, rebuiltFileFragment, srcFileAction.binaryAstFile)
SrcFileArtifact(srcFileAction.srcFile.path, rebuiltFileFragment ?: emptyList(), srcFileAction.binaryAstFile)
}
return ModuleArtifact(moduleName, fileArtifacts, artifactsDir, forceRebuildJs, externalModuleName)
@@ -26,11 +26,11 @@ class JsExecutableProducer(
}
}
fun buildExecutable(multiModule: Boolean, outJsProgram: Boolean) = if (multiModule) {
buildMultiModuleExecutable(outJsProgram)
} else {
buildSingleModuleExecutable(outJsProgram)
}
fun buildExecutable(granularity: JsGenerationGranularity, outJsProgram: Boolean) =
when (granularity) {
JsGenerationGranularity.WHOLE_PROGRAM -> buildSingleModuleExecutable(outJsProgram)
JsGenerationGranularity.PER_MODULE, JsGenerationGranularity.PER_FILE -> buildMultiModuleExecutable(outJsProgram)
}
private fun buildSingleModuleExecutable(outJsProgram: Boolean): BuildResult {
val modules = caches.map { cacheArtifact -> cacheArtifact.loadJsIrModule() }
@@ -30,7 +30,8 @@ class JsMultiModuleCache(private val moduleArtifacts: List<ModuleArtifact>) {
val optionalCrossModuleImports = hashSetOf<String>()
val crossModuleReferencesHash = ICHash.fromProtoStream(this)
val hasJsExports = readBool()
val reexportedInModuleWithName = ifTrue { readString() }
repeat(readInt32()) {
val tag = readString()
val mask = readInt32()
@@ -44,6 +45,7 @@ class JsMultiModuleCache(private val moduleArtifacts: List<ModuleArtifact>) {
nameBindings[tag] = readString()
}
}
CachedModuleInfo(
artifact = this@fetchModuleInfo,
jsIrHeader = JsIrModuleHeader(
@@ -52,7 +54,7 @@ class JsMultiModuleCache(private val moduleArtifacts: List<ModuleArtifact>) {
definitions = definitions,
nameBindings = nameBindings,
optionalCrossModuleImports = optionalCrossModuleImports,
hasJsExports = hasJsExports,
reexportedInModuleWithName = reexportedInModuleWithName,
associatedModule = null
),
crossModuleReferencesHash = crossModuleReferencesHash
@@ -74,8 +76,13 @@ class JsMultiModuleCache(private val moduleArtifacts: List<ModuleArtifact>) {
names[tag] = ((maskAndName?.first ?: 0) or NameType.DEFINITIONS.typeMask) to maskAndName?.second
}
crossModuleReferencesHash.toProtoStream(this)
writeBoolNoTag(jsIrHeader.hasJsExports)
ifNotNull(jsIrHeader.reexportedInModuleWithName) {
writeStringNoTag(it)
}
writeInt32NoTag(names.size)
for ((tag, maskAndName) in names) {
writeStringNoTag(tag)
writeInt32NoTag(maskAndName.first)
@@ -11,17 +11,17 @@ import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.safeModuleName
import org.jetbrains.kotlin.ir.backend.js.utils.serialization.deserializeJsIrProgramFragment
import java.io.File
class SrcFileArtifact(val srcFilePath: String, private val fragment: JsIrProgramFragment?, private val astArtifact: File? = null) {
fun loadJsIrFragment(): JsIrProgramFragment? {
if (fragment != null) {
return fragment
class SrcFileArtifact(val srcFilePath: String, private val fragments: List<JsIrProgramFragment>, private val astArtifact: File? = null) {
fun loadJsIrFragments(): List<JsIrProgramFragment> {
if (fragments.isNotEmpty()) {
return fragments
}
return astArtifact?.ifExists { readBytes() }?.let {
deserializeJsIrProgramFragment(it)
}
} ?: emptyList()
}
fun isModified() = fragment != null
fun isModified() = fragments.isNotEmpty()
}
class ModuleArtifact(
@@ -35,7 +35,7 @@ class ModuleArtifact(
val moduleExternalName = externalModuleName ?: moduleSafeName
fun loadJsIrModule(): JsIrModule {
val fragments = fileArtifacts.sortedBy { it.srcFilePath }.mapNotNull { it.loadJsIrFragment() }
val fragments = fileArtifacts.sortedBy { it.srcFilePath }.flatMap { it.loadJsIrFragments() }
return JsIrModule(moduleSafeName, moduleExternalName, fragments)
}
}
@@ -226,7 +226,7 @@ class ES6ConstructorLowering(val context: JsIrBackendContext) : DeclarationTrans
constructor.isEffectivelyExternal() ->
JsIrBuilder.buildCall(context.intrinsics.jsCreateExternalThisSymbol)
.apply {
putValueArgument(0, irClass.getCurrentConstructorReference(constructorReplacement))
putValueArgument(0, getCurrentConstructorReference(constructorReplacement))
putValueArgument(1, expression.symbol.owner.parentAsClass.jsConstructorReference(context))
putValueArgument(2, irAnyArray(expression.valueArguments.memoryOptimizedMap { it ?: context.getVoid() }))
putValueArgument(3, boxParameterGetter)
@@ -234,7 +234,7 @@ class ES6ConstructorLowering(val context: JsIrBackendContext) : DeclarationTrans
constructor.parentAsClass.symbol == context.irBuiltIns.anyClass ->
JsIrBuilder.buildCall(context.intrinsics.jsCreateThisSymbol)
.apply {
putValueArgument(0, irClass.getCurrentConstructorReference(constructorReplacement))
putValueArgument(0, getCurrentConstructorReference(constructorReplacement))
putValueArgument(1, boxParameterGetter)
}
else ->
@@ -263,12 +263,8 @@ class ES6ConstructorLowering(val context: JsIrBackendContext) : DeclarationTrans
}
}
private fun IrClass.getCurrentConstructorReference(currentFactoryFunction: IrSimpleFunction): IrExpression {
return if (isFinalClass) {
jsConstructorReference(context)
} else {
JsIrBuilder.buildGetValue(currentFactoryFunction.dispatchReceiverParameter!!.symbol)
}
private fun getCurrentConstructorReference(currentFactoryFunction: IrSimpleFunction): IrExpression {
return JsIrBuilder.buildGetValue(currentFactoryFunction.dispatchReceiverParameter!!.symbol)
}
private fun IrDeclaration.excludeFromExport() {
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.lower.optimizations.PropertyAccessorInlineLowering
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.util.fileOrNull
@@ -1,62 +0,0 @@
/*
* Copyright 2010-2019 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.lower
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclarationContainer
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrFileSymbolImpl
import org.jetbrains.kotlin.utils.memoryOptimizedPlus
import org.jetbrains.kotlin.ir.util.transformDeclarationsFlat
import org.jetbrains.kotlin.ir.util.transformFlat
fun moveOpenClassesToSeparateFiles(moduleFragment: IrModuleFragment) {
fun createFile(file: IrFile, klass: IrClass): IrFile =
IrFileImpl(fileEntry = file.fileEntry, fqName = file.packageFqName, symbol = IrFileSymbolImpl(), module = file.module).also {
it.annotations = it.annotations memoryOptimizedPlus file.annotations
it.declarations += klass
klass.parent = it
}
moduleFragment.files.transformFlat { file ->
// We don't have to split declarations with a single class
if (file.declarations.size <= 1)
return@transformFlat null
val openClasses = mutableListOf<IrClass>()
fun removeAndCollectOpenClasses(container: IrDeclarationContainer) {
container.transformDeclarationsFlat { declaration ->
if (declaration is IrDeclarationContainer) {
removeAndCollectOpenClasses(declaration)
}
if (
declaration is IrClass &&
(declaration.modality == Modality.OPEN || declaration.modality == Modality.ABSTRACT) &&
declaration.kind == ClassKind.CLASS &&
declaration.visibility != DescriptorVisibilities.PRIVATE &&
declaration.visibility != DescriptorVisibilities.LOCAL
) {
openClasses += declaration
emptyList()
} else {
null
}
}
}
removeAndCollectOpenClasses(file)
return@transformFlat if (openClasses.isEmpty())
null
else
openClasses.mapTo(mutableListOf(file)) { createFile(file, it) }
}
}
@@ -8,9 +8,7 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
import org.jetbrains.kotlin.backend.common.compilationException
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins
import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.types.isString
@@ -169,7 +167,7 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
}
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall, context: JsGenerationContext): JsExpression {
val classNameRef = context.getNameForConstructor(expression.symbol.owner).makeRef()
val classNameRef = expression.symbol.owner.getConstructorRef(context.staticContext)
val callFuncRef = JsNameRef(Namer.CALL_FUNCTION, classNameRef)
val fromPrimary = context.currentFunction is IrConstructor
val thisRef =
@@ -202,7 +200,7 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
return when {
klass.isEffectivelyExternal() -> {
val refForExternalClass = context.getRefForExternalClass(klass)
val refForExternalClass = klass.getClassRef(context.staticContext)
val varargParameterIndex = expression.symbol.owner.varargParameterIndex()
if (varargParameterIndex == -1) {
JsNew(refForExternalClass, arguments)
@@ -225,8 +223,7 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
}
}
else -> {
val ref = context.getNameForClass(klass).makeRef()
JsNew(ref, arguments)
JsNew(klass.getClassRef(context.staticContext), arguments)
}
}.withSource(expression, context)
}
@@ -320,14 +317,14 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
override fun visitRawFunctionReference(expression: IrRawFunctionReference, data: JsGenerationContext): JsExpression {
val name = when (val function = expression.symbol.owner) {
is IrConstructor -> data.getNameForConstructor(function)
is IrSimpleFunction -> data.getNameForStaticFunction(function)
is IrConstructor -> function.getConstructorRef(data.staticContext)
is IrSimpleFunction -> data.getNameForStaticFunction(function).makeRef()
else -> compilationException(
"Unexpected function kind",
expression
)
}
return JsNameRef(name).withSource(expression, data)
return name.withSource(expression, data)
}
private fun prefixOperation(operator: JsUnaryOperator, expression: IrDynamicOperatorExpression, data: JsGenerationContext) =
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.backend.common.serialization.checkIsFunctionInterfac
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.ir.backend.js.*
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.lower.isBuiltInClass
import org.jetbrains.kotlin.ir.backend.js.utils.*
@@ -45,34 +46,51 @@ val String.safeModuleName: String
val IrModuleFragment.safeName: String
get() = name.asString().safeModuleName
enum class JsGenerationGranularity {
WHOLE_PROGRAM,
PER_MODULE,
PER_FILE
}
enum class TranslationMode(
val production: Boolean,
val perModule: Boolean,
val granularity: JsGenerationGranularity,
val minimizedMemberNames: Boolean,
) {
FULL_DEV(production = false, perModule = false, minimizedMemberNames = false),
FULL_PROD(production = true, perModule = false, minimizedMemberNames = false),
FULL_PROD_MINIMIZED_NAMES(production = true, perModule = false, minimizedMemberNames = true),
PER_MODULE_DEV(production = false, perModule = true, minimizedMemberNames = false),
PER_MODULE_PROD(production = true, perModule = true, minimizedMemberNames = false),
PER_MODULE_PROD_MINIMIZED_NAMES(production = true, perModule = true, minimizedMemberNames = true);
FULL_DEV(production = false, granularity = JsGenerationGranularity.WHOLE_PROGRAM, minimizedMemberNames = false),
FULL_PROD(production = true, granularity = JsGenerationGranularity.WHOLE_PROGRAM, minimizedMemberNames = false),
FULL_PROD_MINIMIZED_NAMES(production = true, granularity = JsGenerationGranularity.WHOLE_PROGRAM, minimizedMemberNames = true),
PER_MODULE_DEV(production = false, granularity = JsGenerationGranularity.PER_MODULE, minimizedMemberNames = false),
PER_MODULE_PROD(production = true, granularity = JsGenerationGranularity.PER_MODULE, minimizedMemberNames = false),
PER_MODULE_PROD_MINIMIZED_NAMES(production = true, granularity = JsGenerationGranularity.PER_MODULE, minimizedMemberNames = true),
PER_FILE_DEV(production = false, granularity = JsGenerationGranularity.PER_FILE, minimizedMemberNames = false),
PER_FILE_PROD(production = true, granularity = JsGenerationGranularity.PER_FILE, minimizedMemberNames = false),
PER_FILE_PROD_MINIMIZED_NAMES(production = true, granularity = JsGenerationGranularity.PER_FILE, minimizedMemberNames = true);
companion object {
fun fromFlags(
production: Boolean,
perModule: Boolean,
granularity: JsGenerationGranularity,
minimizedMemberNames: Boolean
): TranslationMode {
return if (perModule) {
if (production) {
if (minimizedMemberNames) PER_MODULE_PROD_MINIMIZED_NAMES
else PER_MODULE_PROD
} else PER_MODULE_DEV
} else {
if (production) {
if (minimizedMemberNames) FULL_PROD_MINIMIZED_NAMES
else FULL_PROD
} else FULL_DEV
return when (granularity) {
JsGenerationGranularity.PER_MODULE ->
if (production) {
if (minimizedMemberNames) PER_MODULE_PROD_MINIMIZED_NAMES
else PER_MODULE_PROD
} else PER_MODULE_DEV
JsGenerationGranularity.PER_FILE ->
if (production) {
if (minimizedMemberNames) PER_FILE_PROD_MINIMIZED_NAMES
else PER_FILE_PROD
} else PER_FILE_DEV
JsGenerationGranularity.WHOLE_PROGRAM ->
if (production) {
if (minimizedMemberNames) FULL_PROD_MINIMIZED_NAMES
else FULL_PROD
} else FULL_DEV
}
}
}
@@ -80,14 +98,14 @@ enum class TranslationMode(
class JsCodeGenerator(
private val program: JsIrProgram,
private val multiModule: Boolean,
private val granularity: JsGenerationGranularity,
private val mainModuleName: String,
private val moduleKind: ModuleKind,
private val sourceMapsInfo: SourceMapsInfo?
) {
fun generateJsCode(relativeRequirePath: Boolean, outJsProgram: Boolean): CompilationOutputsBuilt {
return generateWrappedModuleBody(
multiModule,
granularity,
mainModuleName,
moduleKind,
program,
@@ -101,7 +119,7 @@ class JsCodeGenerator(
class IrModuleToJsTransformer(
private val backendContext: JsIrBackendContext,
private val mainArguments: List<String>?,
private val moduleToName: Map<IrModuleFragment, String> = emptyMap(),
moduleToName: Map<IrModuleFragment, String> = emptyMap(),
private val removeUnusedAssociatedObjects: Boolean = true,
) {
private val shouldGeneratePolyfills = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_POLYFILLS)
@@ -113,7 +131,14 @@ class IrModuleToJsTransformer(
private val isEsModules = moduleKind == ModuleKind.ES
private val sourceMapInfo = SourceMapsInfo.from(backendContext.configuration)
private class IrFileExports(val file: IrFile, val exports: List<ExportedDeclaration>, val tsDeclarations: TypeScriptFragment?)
private val moduleFragmentToNameMapper = ModuleFragmentToExternalName(moduleToName)
private class IrFileExports(
val file: IrFile,
val exports: List<ExportedDeclaration>,
val tsDeclarations: TypeScriptFragment?,
val generatedFrom: IrFile? = null
)
private class IrAndExportedDeclarations(val fragment: IrModuleFragment, val files: List<IrFileExports>)
@@ -172,15 +197,22 @@ class IrModuleToJsTransformer(
return makeJsCodeGeneratorFromIr(exportData, mode)
}
fun makeIrFragmentsGenerators(files: Collection<IrFile>, allModules: Collection<IrModuleFragment>): List<() -> JsIrProgramFragment> {
fun makeIrFragmentsGenerators(
dirtyFiles: Collection<IrFile>,
allModules: Collection<IrModuleFragment>
): List<() -> List<JsIrProgramFragment>> {
val files = dirtyFiles + backendContext.mapping.chunkToOriginalFile.keys
val exportModelGenerator = ExportModelGenerator(backendContext, generateNamespacesForPackages = !isEsModules)
val exportData = exportModelGenerator.generateExportWithExternals(files)
val mode = TranslationMode.fromFlags(production = false, backendContext.granularity, minimizedMemberNames = false)
doStaticMembersLowering(allModules)
return exportData.map {
{ generateProgramFragment(it, minimizedMemberNames = false) }
}
return exportData
.groupBy { it.generatedFrom ?: it.file }
.map {
{ it.value.flatMap { generateProgramFragment(it, mode) } }
}
}
private fun ExportModelGenerator.generateExportWithExternals(irFiles: Collection<IrFile>): List<IrFileExports> {
@@ -191,47 +223,123 @@ class IrModuleToJsTransformer(
val tsDeclarations = runIf(shouldGenerateTypeScriptDefinitions) {
allExports.ifNotEmpty { toTypeScriptFragment(moduleKind) }
}
IrFileExports(irFile, allExports, tsDeclarations)
IrFileExports(irFile, allExports, tsDeclarations, context.mapping.chunkToOriginalFile[irFile])
}
}
private fun IrModuleFragment.externalModuleName(): String {
return moduleToName[this] ?: sanitizeName(safeName)
}
private fun makeJsCodeGeneratorFromIr(exportData: List<IrAndExportedDeclarations>, mode: TranslationMode): JsCodeGenerator {
if (mode.minimizedMemberNames) {
backendContext.fieldDataCache.clear()
backendContext.minimizedNameGenerator.clear()
}
val program = JsIrProgram(
val program = when (mode.granularity) {
JsGenerationGranularity.WHOLE_PROGRAM, JsGenerationGranularity.PER_MODULE -> generateJsIrProgramPerModule(exportData, mode)
JsGenerationGranularity.PER_FILE -> generateJsIrProgramPerFile(exportData, mode)
}
return JsCodeGenerator(program, mode.granularity, mainModuleName, moduleKind, sourceMapInfo)
}
private fun generateJsIrProgramPerModule(exportData: List<IrAndExportedDeclarations>, mode: TranslationMode): JsIrProgram {
val mainModule = exportData.last()
return JsIrProgram(
exportData.map { data ->
JsIrModule(
data.fragment.safeName,
data.fragment.externalModuleName(),
data.files.map { generateProgramFragment(it, mode.minimizedMemberNames) }
moduleFragmentToNameMapper.getExternalNameFor(data.fragment),
data.files.flatMap { generateProgramFragment(it, mode) },
mainModule.fragment.safeName.takeIf { !isEsModules && data != mainModule }
)
}
)
}
return JsCodeGenerator(program, mode.perModule, mainModuleName, moduleKind, sourceMapInfo)
private fun generateJsIrProgramPerFile(exportData: List<IrAndExportedDeclarations>, mode: TranslationMode): JsIrProgram {
val modulesPerFile = buildList {
for (module in exportData) {
var hasFileWithJsExportedDeclaration = false
for (fileExports in module.files) {
if (fileExports.file.couldBeSkipped()) continue
val (programFragment, exportProgramFragment) = generateProgramFragment(fileExports, mode)
add(fileExports.toJsIrModule(mode, programFragment))
if (fileExports.exports.isNotEmpty()) {
add(fileExports.toJsIrModuleForExport(module, mode, exportProgramFragment))
hasFileWithJsExportedDeclaration = true
}
}
if (hasFileWithJsExportedDeclaration) {
add(module.toJsIrProxyModule())
}
}
}
return JsIrProgram(modulesPerFile)
}
private fun IrFileExports.toJsIrModule(mode: TranslationMode, programFragment: JsIrProgramFragment): JsIrModule {
return JsIrModule(
moduleFragmentToNameMapper.getSafeNameFor(file),
moduleFragmentToNameMapper.getExternalNameFor(file, mode.granularity),
listOf(programFragment),
)
}
private fun IrFileExports.toJsIrModuleForExport(
module: IrAndExportedDeclarations,
mode: TranslationMode,
programFragment: JsIrProgramFragment
): JsIrModule {
return JsIrModule(
moduleFragmentToNameMapper.getSafeNameExporterFor(file),
moduleFragmentToNameMapper.getExternalNameForExporterFile(file, mode.granularity),
listOf(programFragment),
module.fragment.safeName
)
}
private fun IrAndExportedDeclarations.toJsIrProxyModule(): JsIrModule {
return JsIrModule(
fragment.safeName,
moduleFragmentToNameMapper.getExternalNameFor(fragment),
listOf(JsIrProgramFragment("<proxy-file>"))
)
}
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 optimizeGeneratedJs = backendContext.configuration.get(JSConfigurationKeys.OPTIMIZE_GENERATED_JS, true)
private fun generateProgramFragment(fileExports: IrFileExports, minimizedMemberNames: Boolean): JsIrProgramFragment {
val nameGenerator = JsNameLinkingNamer(backendContext, minimizedMemberNames, isEsModules)
private fun IrFileExports.generateProgramFragmentForExport(
mode: TranslationMode,
nameScope: NameTable<IrDeclaration>
): JsIrProgramFragment {
val globalNames = NameTable<String>(nameScope)
val nameGenerator = JsNameLinkingNamer(backendContext, mode.minimizedMemberNames, isEsModules)
val internalModuleName = ReservedJsNames.makeInternalModuleName().takeIf { !isEsModules }
val staticContext = JsStaticContext(backendContext, nameGenerator, nameScope, mode)
return JsIrProgramFragment(file.packageFqName.asString()).apply {
dts = tsDeclarations
exports.statements += ExportModelToJsStatements(staticContext, backendContext.es6mode, { globalNames.declareFreshName(it, it) })
.generateModuleExport(
ExportedModule(mainModuleName, moduleKind, this@generateProgramFragmentForExport.exports),
internalModuleName,
isEsModules
)
computeAndSaveNameBindings(emptySet(), nameGenerator)
}
}
private fun generateProgramFragment(fileExports: IrFileExports, mode: TranslationMode): List<JsIrProgramFragment> {
val globalNameScope = NameTable<IrDeclaration>()
val staticContext = JsStaticContext(
backendContext = backendContext,
irNamer = nameGenerator,
globalNameScope = globalNameScope
)
val nameGenerator = JsNameLinkingNamer(backendContext, mode.minimizedMemberNames, isEsModules)
val staticContext = JsStaticContext(backendContext, nameGenerator, globalNameScope, mode)
val result = JsIrProgramFragment(fileExports.file.packageFqName.asString()).apply {
if (shouldGeneratePolyfills) {
@@ -239,21 +347,9 @@ class IrModuleToJsTransformer(
}
}
val internalModuleName = ReservedJsNames.makeInternalModuleName().takeIf { !isEsModules }
val globalNames = NameTable<String>(globalNameScope)
val statements = result.declarations.statements
val fileStatements = fileExports.file.accept(IrFileToJsTransformer(useBareParameterNames = true), staticContext).statements
val exportStatements =
ExportModelToJsStatements(staticContext, backendContext.es6mode, { globalNames.declareFreshName(it, it) }).generateModuleExport(
ExportedModule(mainModuleName, moduleKind, fileExports.exports),
internalModuleName,
isEsModules
)
result.exports.statements += exportStatements
result.dts = fileExports.tsDeclarations
val exportFragment = fileExports.generateProgramFragmentForExport(mode, globalNameScope)
if (fileStatements.isNotEmpty()) {
var startComment = ""
@@ -310,50 +406,69 @@ class IrModuleToJsTransformer(
val definitionSet = fileExports.file.declarations.toSet()
fun computeTag(declaration: IrDeclaration): String? {
val tag = (backendContext.irFactory as IdSignatureRetriever).declarationSignature(declaration)?.toString()
if (tag == null && declaration !in definitionSet) {
error("signature for ${declaration.render()} not found")
}
return tag
}
nameGenerator.nameMap.entries.forEach { (declaration, name) ->
computeTag(declaration)?.let { tag ->
result.nameBindings[tag] = name
if (isBuiltInClass(declaration) || checkIsFunctionInterface(declaration.symbol.signature)) {
result.optionalCrossModuleImports += tag
}
}
}
nameGenerator.imports.entries.forEach { (declaration, importStatement) ->
val tag = computeTag(declaration) ?: error("No tag for imported declaration ${declaration.render()}")
result.imports[tag] = importStatement
result.optionalCrossModuleImports += tag
}
fileExports.file.declarations.forEach {
computeTag(it)?.let { tag ->
result.definitions += tag
}
if (it is IrClass && it.isInterface) {
it.declarations.forEach {
computeTag(it)?.let { tag ->
result.definitions += tag
}
}
}
}
result.computeAndSaveNameBindings(definitionSet, nameGenerator)
result.computeAndSaveImports(definitionSet, nameGenerator)
result.computeAndSaveDefinitions(definitionSet, fileExports)
if (optimizeGeneratedJs) {
optimizeFragmentByJsAst(result)
}
return result
return listOf(result, exportFragment)
}
private fun Set<IrDeclaration>.computeTag(declaration: IrDeclaration): String? {
val tag = (backendContext.irFactory as IdSignatureRetriever).declarationSignature(declaration)?.toString()
if (tag == null && !contains(declaration)) {
error("signature for ${declaration.render()} not found")
}
return tag
}
private fun JsIrProgramFragment.computeAndSaveNameBindings(
definitions: Set<IrDeclaration>,
nameGenerator: JsNameLinkingNamer
) {
nameGenerator.nameMap.entries.forEach { (declaration, name) ->
definitions.computeTag(declaration)?.let { tag ->
nameBindings[tag] = name
if (isBuiltInClass(declaration) || checkIsFunctionInterface(declaration.symbol.signature)) {
optionalCrossModuleImports += tag
}
}
}
}
private fun JsIrProgramFragment.computeAndSaveImports(
definitions: Set<IrDeclaration>,
nameGenerator: JsNameLinkingNamer
) {
nameGenerator.imports.entries.forEach { (declaration, importExpression) ->
val tag = definitions.computeTag(declaration) ?: error("No tag for imported declaration ${declaration.render()}")
imports[tag] = importExpression
optionalCrossModuleImports += tag
}
}
private fun JsIrProgramFragment.computeAndSaveDefinitions(
definitions: Set<IrDeclaration>,
fileExports: IrFileExports,
) {
fileExports.file.declarations.forEach {
definitions.computeTag(it)?.let { tag ->
this.definitions += tag
}
if (it is IrClass && it.isInterface) {
it.declarations.forEach {
definitions.computeTag(it)?.let { tag ->
this.definitions += tag
}
}
}
}
}
private fun generateMainArguments(
@@ -374,10 +489,12 @@ class IrModuleToJsTransformer(
return listOfNotNull(mainArgumentsArray, continuation)
}
private fun IrFile.couldBeSkipped(): Boolean = declarations.all { it.origin == JsCodeOutliningLowering.OUTLINED_JS_CODE_ORIGIN }
}
private fun generateWrappedModuleBody(
multiModule: Boolean,
granularity: JsGenerationGranularity,
mainModuleName: String,
moduleKind: ModuleKind,
program: JsIrProgram,
@@ -385,44 +502,8 @@ private fun generateWrappedModuleBody(
relativeRequirePath: Boolean,
outJsProgram: Boolean
): CompilationOutputsBuilt {
if (multiModule) {
// 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(moduleKind, relativeRequirePath).toMutableList()
val mainModule = moduleToRef.removeLast().let { (main, mainRef) ->
generateSingleWrappedModuleBody(
mainModuleName,
moduleKind,
main.fragments,
sourceMapsInfo,
generateCallToMain = true,
mainRef,
outJsProgram
)
}
mainModule.dependencies = buildList(moduleToRef.size) {
while (moduleToRef.isNotEmpty()) {
moduleToRef.removeFirst().let { (module, moduleRef) ->
val moduleName = module.externalModuleName
val moduleCompilationOutput = generateSingleWrappedModuleBody(
moduleName,
moduleKind,
module.fragments,
sourceMapsInfo,
generateCallToMain = false,
moduleRef,
outJsProgram
)
add(moduleName to moduleCompilationOutput)
}
}
}
return mainModule
} else {
return generateSingleWrappedModuleBody(
return when (granularity) {
JsGenerationGranularity.WHOLE_PROGRAM -> generateSingleWrappedModuleBody(
mainModuleName,
moduleKind,
program.asFragments(),
@@ -430,9 +511,64 @@ private fun generateWrappedModuleBody(
generateCallToMain = true,
outJsProgram = outJsProgram
)
JsGenerationGranularity.PER_FILE,
JsGenerationGranularity.PER_MODULE -> generateMultiWrappedModuleBody(
mainModuleName,
moduleKind,
program,
sourceMapsInfo,
relativeRequirePath,
outJsProgram
)
}
}
private fun generateMultiWrappedModuleBody(
mainModuleName: String,
moduleKind: ModuleKind,
program: JsIrProgram,
sourceMapsInfo: SourceMapsInfo?,
relativeRequirePath: Boolean,
outJsProgram: Boolean
): CompilationOutputsBuilt {
// 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(moduleKind, relativeRequirePath).toMutableList()
val mainModule = moduleToRef.removeLast().let { (main, mainRef) ->
generateSingleWrappedModuleBody(
mainModuleName,
moduleKind,
main.fragments,
sourceMapsInfo,
generateCallToMain = true,
mainRef,
outJsProgram
)
}
mainModule.dependencies = buildList(moduleToRef.size) {
while (moduleToRef.isNotEmpty()) {
moduleToRef.removeFirst().let { (module, moduleRef) ->
val moduleName = module.externalModuleName
val moduleCompilationOutput = generateSingleWrappedModuleBody(
moduleName,
moduleKind,
module.fragments,
sourceMapsInfo,
generateCallToMain = false,
moduleRef,
outJsProgram
)
add(moduleName to moduleCompilationOutput)
}
}
}
return mainModule
}
fun generateSingleWrappedModuleBody(
moduleName: String,
moduleKind: ModuleKind,
@@ -20,55 +20,96 @@ import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.JsVars.JsVar
import org.jetbrains.kotlin.js.common.isValidES5Identifier
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.butIf
import org.jetbrains.kotlin.utils.memoryOptimizedMap
import org.jetbrains.kotlin.utils.toSmartList
class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationContext) {
private val perFile = context.staticContext.isPerFile
private val es6mode = context.staticContext.backendContext.es6mode
private val className = context.getNameForClass(irClass)
private val classNameRef = className.makeRef()
private val baseClass: IrType? = irClass.superTypes.firstOrNull { !it.classifierOrFail.isInterface }
private val classNameUsedInsideDeclarationStatements = when {
perFile -> JsName("$", true)
else -> className
}
private val classNameRef = classNameUsedInsideDeclarationStatements.makeRef()
private val classPrototypeRef by lazy(LazyThreadSafetyMode.NONE) { prototypeOf(classNameRef, context.staticContext) }
private val baseClassRef by lazy(LazyThreadSafetyMode.NONE) { // Lazy in case was not collected by namer during JsClassGenerator construction
if (baseClass != null && !baseClass.isAny()) baseClass.getClassRef(context) else null
if (baseClass != null && !baseClass.isAny()) baseClass.getClassRef(context.staticContext) else null
}
private val classBlock = JsCompositeBlock()
private val classModel = JsIrClassModel(irClass)
private val classBlock = JsCompositeBlock()
private val es6mode = context.staticContext.backendContext.es6mode
private val interfaceDefaultsBlock = when {
perFile -> JsCompositeBlock()
else -> classModel.preDeclarationBlock
}
private val jsUndefined by lazy(LazyThreadSafetyMode.NONE) { jsUndefined(context.staticContext) }
fun generate(): JsStatement {
return generateClassBlock().butIf(perFile) { it.wrapInFunction() }
}
private fun JsCompositeBlock.wrapInFunction(): JsStatement {
val classHolder = JsVar(JsName("${className.ident}Class", true))
val functionWrapper = JsFunction(emptyScope, JsBlock(), "lazy wrapper for classes in per-file").apply {
name = className
with(body.statements) {
add(
JsIf(
JsAstUtils.equality(classHolder.name.makeRef(), jsUndefined),
JsBlock(
classModel.preDeclarationBlock.statements + statements + classModel.postDeclarationBlock.statements +
JsAstUtils.assignment(classHolder.name.makeRef(), classNameRef).makeStmt()
)
)
)
add(JsReturn(classHolder.name.makeRef()))
}
}
return JsCompositeBlock(interfaceDefaultsBlock.statements + listOf(JsVars(classHolder), functionWrapper.makeStmt())).also {
classModel.preDeclarationBlock.statements.clear()
classModel.postDeclarationBlock.statements.clear()
}
}
private fun generateClassBlock(): JsCompositeBlock {
assert(!irClass.isExpect)
if (!es6mode) maybeGeneratePrimaryConstructor()
val transformer = IrDeclarationToJsTransformer()
// Properties might be lowered out of classes
// We'll use IrSimpleFunction::correspondingProperty to collect them into set
val properties = mutableSetOf<IrProperty>()
val jsClass = JsClass(name = className, baseClass = baseClassRef)
val jsClass = JsClass(name = classNameUsedInsideDeclarationStatements, baseClass = baseClassRef)
if (baseClass != null && !baseClass.isAny()) {
jsClass.baseClass = baseClassRef
}
if (es6mode) classModel.preDeclarationBlock.statements += jsClass.makeStmt()
if (es6mode) {
classModel.preDeclarationBlock.statements += jsClass.makeStmt()
}
for (declaration in irClass.declarations) {
when (declaration) {
is IrConstructor -> {
val constructor = declaration.accept(IrFunctionToJsTransformer(), context)
if (es6mode) {
declaration.accept(IrFunctionToJsTransformer(), context).let { fn ->
if (fn.body.statements.any { it !is JsEmpty && !it.isSimpleSuperCall(fn) }) {
jsClass.constructor = fn
}
}
jsClass.constructor = constructor.apply { name = null }
} else {
classBlock.statements += declaration.accept(transformer, context)
classBlock.statements += constructor.apply { name = classNameUsedInsideDeclarationStatements }.makeStmt()
}
}
is IrSimpleFunction -> {
@@ -275,7 +316,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
assert(!declaration.isStaticMethodOfClass)
if (irClass.isInterface) {
classModel.preDeclarationBlock.statements += translatedFunction.makeStmt()
interfaceDefaultsBlock.statements += translatedFunction.makeStmt()
return Pair(memberName, null)
}
@@ -323,7 +364,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
private fun maybeGeneratePrimaryConstructor() {
if (!irClass.declarations.any { it is IrConstructor }) {
val func = JsFunction(emptyScope, JsBlock(), "Ctor for ${irClass.name}")
func.name = className
func.name = classNameUsedInsideDeclarationStatements
classBlock.statements += func.makeStmt()
}
}
@@ -352,12 +393,12 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
}
private fun IrType.asConstructorRef(): JsNameRef? {
private fun IrType.asConstructorRef(): JsExpression? {
val ownerSymbol = classOrNull?.takeIf {
!isAny() && !isFunctionType() && !it.owner.isEffectivelyExternal()
} ?: return null
return JsNameRef(context.getNameForClass(ownerSymbol.owner))
return ownerSymbol.owner.getClassRef(context.staticContext)
}
private fun IrType.isFunctionType() = isFunctionOrKFunction() || isSuspendFunctionOrKFunction()
@@ -7,10 +7,7 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
import org.jetbrains.kotlin.backend.common.compilationException
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
import org.jetbrains.kotlin.ir.backend.js.utils.getClassRef
import org.jetbrains.kotlin.ir.backend.js.utils.invokeFunForLambda
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
@@ -91,13 +88,13 @@ class JsIntrinsicTransformers(backendContext: JsIrBackendContext) {
add(intrinsics.jsObjectCreateSymbol) { call, context ->
val classToCreate = call.getTypeArgument(0)!!.classifierOrFail.owner as IrClass
val className = context.getNameForClass(classToCreate)
objectCreate(prototypeOf(className.makeRef(), context.staticContext), context.staticContext)
val className = classToCreate.getClassRef(context.staticContext)
objectCreate(prototypeOf(className, context.staticContext), context.staticContext)
}
add(intrinsics.jsClass) { call, context ->
val typeArgument = call.getTypeArgument(0)
typeArgument?.getClassRef(context)
typeArgument?.getClassRef(context.staticContext)
?: compilationException(
"Type argument of jsClass must be statically known class",
typeArgument
@@ -180,7 +177,8 @@ class JsIntrinsicTransformers(backendContext: JsIrBackendContext) {
val arg = translateCallArguments(call, context).single()
val inlineClass = icUtils.getInlinedClass(call.getTypeArgument(0)!!)!!
val constructor = inlineClass.declarations.filterIsInstance<IrConstructor>().single { it.isPrimary }
JsNew(context.getNameForConstructor(constructor).makeRef(), listOf(arg))
JsNew(constructor.getConstructorRef(context.staticContext), listOf(arg))
.apply { isInlineClassBoxing = true }
}
@@ -209,7 +207,7 @@ class JsIntrinsicTransformers(backendContext: JsIrBackendContext) {
is IrFunctionReference -> {
val superClass = call.superQualifierSymbol!!
val functionName = context.getNameForMemberFunction(target.symbol.owner as IrSimpleFunction)
val superName = context.getNameForClass(superClass.owner).makeRef()
val superName = superClass.owner.getClassRef(context.staticContext)
JsNameRef(functionName, prototypeOf(superName, context.staticContext))
}
is IrFunctionExpression -> target.accept(IrElementToJsExpressionTransformer(), context)
@@ -31,15 +31,16 @@ class JsIrProgramFragment(val packageFqn: String) {
class JsIrModule(
val moduleName: String,
val externalModuleName: String,
val fragments: List<JsIrProgramFragment>
val fragments: List<JsIrProgramFragment>,
val reexportedInModuleWithName: String? = null,
) {
fun makeModuleHeader(): JsIrModuleHeader {
val nameBindings = mutableMapOf<String, String>()
val definitions = mutableSetOf<String>()
val optionalCrossModuleImports = hashSetOf<String>()
var hasJsExports = false
var hasDeclarationsToReexport = false
for (fragment in fragments) {
hasJsExports = hasJsExports || !fragment.exports.isEmpty
hasDeclarationsToReexport = hasDeclarationsToReexport || !fragment.exports.isEmpty
for ((tag, name) in fragment.nameBindings.entries) {
nameBindings[tag] = name.toString()
}
@@ -52,7 +53,7 @@ class JsIrModule(
definitions = definitions,
nameBindings = nameBindings,
optionalCrossModuleImports = optionalCrossModuleImports,
hasJsExports = hasJsExports,
reexportedInModuleWithName = reexportedInModuleWithName.takeIf { hasDeclarationsToReexport },
associatedModule = this
)
}
@@ -64,8 +65,8 @@ class JsIrModuleHeader(
val definitions: Set<String>,
val nameBindings: Map<String, String>,
val optionalCrossModuleImports: Set<String>,
val hasJsExports: Boolean,
var associatedModule: JsIrModule?
val reexportedInModuleWithName: String? = null,
var associatedModule: JsIrModule?,
) {
val externalNames: Set<String> by lazy(LazyThreadSafetyMode.NONE) { nameBindings.keys - definitions }
}
@@ -89,19 +90,18 @@ class JsIrProgram(private var modules: List<JsIrModule>) {
}
}
class CrossModuleDependenciesResolver(
private val moduleKind: ModuleKind,
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 { JsIrModuleCrossModuleReferenceBuilder(moduleKind, it, relativeRequirePath) }
val definitionModule = mutableMapOf<String, JsIrModuleCrossModuleReferenceBuilder>()
if (moduleKind != ModuleKind.ES) {
val mainModuleHeader = headers.last()
val otherModuleHeaders = headers.dropLast(1)
headerToBuilder[mainModuleHeader]!!.transitiveJsExportFrom = otherModuleHeaders
val reexportModuleToHeader = headers.groupBy { it.reexportedInModuleWithName }
val headerToBuilder = headers.associateWith {
JsIrModuleCrossModuleReferenceBuilder(
moduleKind,
it,
relativeRequirePath,
reexportModuleToHeader[it.moduleName] ?: emptyList(),
)
}
val definitionModule = mutableMapOf<String, JsIrModuleCrossModuleReferenceBuilder>()
for (header in headers) {
val builder = headerToBuilder[header]!!
@@ -115,6 +115,7 @@ class CrossModuleDependenciesResolver(
val builder = headerToBuilder[header]!!
for (tag in header.externalNames) {
val fromModuleBuilder = definitionModule[tag]
if (fromModuleBuilder == null) {
if (tag in header.optionalCrossModuleImports) {
continue
@@ -128,6 +129,8 @@ class CrossModuleDependenciesResolver(
}
}
headerToBuilder.forEach { it.value.buildExportNames() }
return headers.associateWith { headerToBuilder[it]!!.buildCrossModuleRefs() }
}
}
@@ -137,21 +140,20 @@ private class CrossModuleRef(val module: JsIrModuleCrossModuleReferenceBuilder,
private class JsIrModuleCrossModuleReferenceBuilder(
val moduleKind: ModuleKind,
val header: JsIrModuleHeader,
val relativeRequirePath: Boolean
val relativeRequirePath: Boolean,
val transitiveExportFrom: List<JsIrModuleHeader>,
) {
val imports = mutableListOf<CrossModuleRef>()
val exports = mutableSetOf<String>()
var transitiveJsExportFrom = emptyList<JsIrModuleHeader>()
private lateinit var exportNames: Map<String, String> // tag -> index
lateinit var exportNames: Map<String, String> // tag -> index
private fun buildExportNames() {
var index = 0
fun buildExportNames(startIndex: Int = 0) {
var index = startIndex
exportNames = exports.sorted().associateWith { index++.toJsIdentifier() }
}
fun buildCrossModuleRefs(): CrossModuleReferences {
buildExportNames()
val isImportOptional = moduleKind == ModuleKind.ES
val importedModules = mutableMapOf<JsIrModuleHeader, JsImportedModule>()
@@ -165,18 +167,16 @@ private class JsIrModuleCrossModuleReferenceBuilder(
val resultImports = imports.associate { crossModuleRef ->
val tag = crossModuleRef.tag
require(crossModuleRef.module::exportNames.isInitialized) {
// This situation appears in case of a dependent module redefine a symbol (function) from their dependency
"Cross module dependency resolution failed due to signature '$tag' redefinition"
}
val exportedAs = crossModuleRef.module.exportNames[tag]!!
val importedModule = import(crossModuleRef.module.header)
tag to CrossModuleImport(exportedAs, importedModule)
}
val transitiveExport = transitiveJsExportFrom.mapNotNull {
if (!it.hasJsExports) null else CrossModuleTransitiveExport(import(it).internalName, it.externalModuleName)
val transitiveExport = transitiveExportFrom.mapNotNull {
it.reexportedInModuleWithName?.run {
CrossModuleTransitiveExport(import(it).internalName, relativeRequirePath(it) ?: it.externalModuleName)
}
}
return CrossModuleReferences(
moduleKind,
@@ -208,8 +208,7 @@ private class JsIrModuleCrossModuleReferenceBuilder(
.toRelativeString(parentMain)
.replace(File.separator, "/")
return relativePath.takeIf { it.startsWith("../") }
?: "./$relativePath"
return relativePath.takeIf { it.startsWith("../") } ?: "./$relativePath"
}
}
@@ -222,7 +221,7 @@ fun CrossModuleTransitiveExport.getRequireEsmName() = "$externalName$ESM_EXTENSI
class CrossModuleReferences(
val moduleKind: ModuleKind,
val importedModules: List<JsImportedModule>, // additional Kotlin imported modules
val transitiveJsExportFrom: List<CrossModuleTransitiveExport>, // the list of modules which provide their js exports for transitive export
val transitiveExportFrom: List<CrossModuleTransitiveExport>, // the list of modules which provide their exports for transitive export
val exports: Map<String, String>, // tag -> index
val imports: Map<String, CrossModuleImport>, // tag -> import statement
) {
@@ -148,7 +148,6 @@ class Merger(
?.asSequence()
?.flatMap { (it.subject as JsExport.Subject.Elements).elements }
?.distinctBy { it.alias?.ident ?: it.name.ident }
?.map { if (it.name.ident == it.alias?.ident) JsExport.Element(it.name, null) else it }
?.toList()
val oneLargeExportStatement = exportedElements?.let { JsExport(JsExport.Subject.Elements(it)) }
@@ -179,14 +178,14 @@ class Merger(
private fun transitiveJsExport(): List<JsStatement> {
return if (isEsModules) {
crossModuleReferences.transitiveJsExportFrom.map {
crossModuleReferences.transitiveExportFrom.map {
JsExport(JsExport.Subject.All, it.getRequireEsmName())
}
} else {
val internalModuleName = ReservedJsNames.makeInternalModuleName()
val exporterName = ReservedJsNames.makeJsExporterName()
crossModuleReferences.transitiveJsExportFrom.map {
crossModuleReferences.transitiveExportFrom.map {
JsInvocation(
JsNameRef(exporterName, it.internalName.makeRef()),
internalModuleName.makeRef()
@@ -0,0 +1,51 @@
/*
* Copyright 2010-2023 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.utils.getJsName
import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.name
import org.jetbrains.kotlin.ir.declarations.path
private const val EXPORTER_FILE_POSTFIX = ".export"
class ModuleFragmentToExternalName(private val jsOutputNamesMapping: Map<IrModuleFragment, String>) {
fun getExternalNameFor(file: IrFile, granularity: JsGenerationGranularity): String {
assert(granularity == JsGenerationGranularity.PER_FILE) { "This method should be used only for PER_FILE granularity" }
return file.module.getJsOutputName().getExternalModuleNameForPerFile(file)
}
fun getExternalNameForExporterFile(file: IrFile, granularity: JsGenerationGranularity): String {
return "${getExternalNameFor(file, granularity)}$EXPORTER_FILE_POSTFIX"
}
fun getSafeNameFor(file: IrFile): String {
return "${file.module.safeName}/${file.stableFileName}"
}
fun getSafeNameExporterFor(file: IrFile): String {
return "${getSafeNameFor(file)}$EXPORTER_FILE_POSTFIX"
}
fun getExternalNameFor(module: IrModuleFragment): String {
return module.getJsOutputName()
}
private fun IrModuleFragment.getJsOutputName(): String {
return jsOutputNamesMapping[this] ?: sanitizeName(safeName)
}
private fun String.getExternalModuleNameForPerFile(file: IrFile) = "$this/${file.stableFileName}"
private val IrFile.stableFileName: String
get() {
val prefix = packageFqName.asString().replace('.', '/')
val fileName = getJsName() ?: name.substringBefore(".kt")
return "$prefix${if (prefix.isNotEmpty()) "/" else ""}$fileName"
}
}
@@ -10,7 +10,6 @@ import org.jetbrains.kotlin.backend.common.lower.BOUND_VALUE_PARAMETER
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrFileEntry
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins
import org.jetbrains.kotlin.ir.backend.js.lower.*
@@ -19,7 +18,6 @@ import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.types.isSubtypeOfClass
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
@@ -31,6 +29,7 @@ import org.jetbrains.kotlin.js.config.SourceMapNamesPolicy
import org.jetbrains.kotlin.js.config.SourceMapSourceEmbedding
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.runIf
import org.jetbrains.kotlin.utils.memoryOptimizedMap
import org.jetbrains.kotlin.utils.memoryOptimizedPlus
import java.io.FileInputStream
@@ -38,8 +37,8 @@ import java.io.IOException
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
fun jsUndefined(context: IrNamer, backendContext: JsIrBackendContext): JsExpression {
return when (val void = backendContext.getVoid()) {
fun jsUndefined(context: JsStaticContext): JsExpression {
return when (val void = context.backendContext.getVoid()) {
is IrGetField -> context.getNameForField(void.symbol.owner).makeRef()
else -> JsNullLiteral()
}
@@ -93,15 +92,11 @@ fun objectCreate(prototype: JsExpression, context: JsStaticContext) =
)
fun defineProperty(obj: JsExpression, name: String, getter: JsExpression?, setter: JsExpression?, context: JsStaticContext): JsExpression {
val undefined by lazy(LazyThreadSafetyMode.NONE) { jsUndefined(context, context.backendContext) }
return JsInvocation(
context
.getNameForStaticFunction(context.backendContext.intrinsics.jsDefinePropertySymbol.owner)
.makeRef(),
obj,
JsStringLiteral(name),
getter ?: undefined,
setter ?: undefined
listOfNotNull(obj, JsStringLiteral(name), getter ?: runIf(setter != null) { jsUndefined(context) }, setter)
)
}
@@ -213,7 +208,7 @@ fun translateCall(
val nameForStaticDeclaration = context.getNameForStaticDeclaration(target)
JsNameRef(Namer.CALL_FUNCTION, JsNameRef(nameForStaticDeclaration))
} else {
val qualifierName = context.getNameForClass(klass).makeRef()
val qualifierName = klass.getClassRef(context.staticContext)
val targetName = context.getNameForMemberFunction(target)
val qPrototype = JsNameRef(targetName, prototypeOf(qualifierName, context.staticContext))
JsNameRef(Namer.CALL_FUNCTION, qPrototype)
@@ -399,7 +394,7 @@ fun translateCallArguments(
val varargParameterIndex = function.realOverrideTarget.varargParameterIndex()
val validWithNullArgs = expression.validWithNullArgs()
val jsUndefined by lazy(LazyThreadSafetyMode.NONE) { jsUndefined(context, context.staticContext.backendContext) }
val jsUndefined by lazy(LazyThreadSafetyMode.NONE) { jsUndefined(context.staticContext) }
val arguments = (0 until size)
.mapIndexedTo(ArrayList(size)) { i, _ ->
@@ -41,7 +41,7 @@ private fun JsNode.resolveNames(): Map<JsName, JsName> {
for (temporaryName in scope.declaredNames.asSequence().filter { it.isTemporary }) {
var resolvedName = temporaryName.ident
var suffix = nextSuffix.getOrDefault(temporaryName.ident, 0)
while (resolvedName in JsDeclarationScope.RESERVED_WORDS || !occupiedNames.add(resolvedName)) {
while (resolvedName in JsDeclarationScope.RESERVED_WORDS || resolvedName in occupiedNames) {
resolvedName = "${temporaryName.ident}_${suffix++}"
}
nextSuffix[temporaryName.ident] = suffix
@@ -79,6 +79,7 @@ private fun JsNode.computeScopes(): Scope {
// We need it to not rename methods and fields inside class body
// Because if they are in clash with something, it means overriding
x.constructor?.accept(this)
x.baseClass?.accept(this)
x.members.forEach { visitFunction(it, shouldReserveName = false) }
}
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.ir.backend.js.utils
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
@@ -14,10 +15,12 @@ import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
import org.jetbrains.kotlin.js.backend.ast.JsExpression
import org.jetbrains.kotlin.js.backend.ast.JsInvocation
import org.jetbrains.kotlin.ir.util.unexpectedSymbolKind
import org.jetbrains.kotlin.js.backend.ast.JsNameRef
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
import org.jetbrains.kotlin.utils.addToStdlib.butIf
fun IrType.asString(): String = when (this) {
// TODO: should each IrErrorType have own string representation?
@@ -54,13 +57,24 @@ tailrec fun erase(type: IrType): IrClass? = when (val classifier = type.classifi
is IrScriptSymbol -> null
}
fun IrType.getClassRef(context: JsGenerationContext): JsNameRef =
when (val klass = classifierOrFail.owner) {
is IrClass ->
if (klass.isEffectivelyExternal())
context.getRefForExternalClass(klass)
else
context.getNameForClass(klass).makeRef()
fun IrType.getClassRef(context: JsStaticContext): JsExpression {
return when (val klass = classifierOrFail.owner) {
is IrClass -> klass.getClassRef(context)
else -> context.getNameForStaticDeclaration(klass as IrDeclarationWithName).makeRef()
}
}
fun IrClass.getClassRef(context: JsStaticContext): JsExpression {
return when {
isEffectivelyExternal() -> context.getRefForExternalClass(this)
else -> context.getNameForClass(this)
.makeRef()
.butIf(context.isPerFile) { JsInvocation(it) }
}
}
fun IrConstructor.getConstructorRef(context: JsStaticContext): JsExpression {
return context.getNameForConstructor(this)
.makeRef()
.butIf(context.isPerFile && !isEffectivelyExternal()) { JsInvocation(it) }
}
@@ -6,8 +6,10 @@
package org.jetbrains.kotlin.ir.backend.js.utils
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIntrinsicTransformers
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrClassModel
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.js.backend.ast.JsCompositeBlock
@@ -17,9 +19,12 @@ class JsStaticContext(
val backendContext: JsIrBackendContext,
private val irNamer: IrNamer,
val globalNameScope: NameTable<IrDeclaration>,
val mode: TranslationMode,
) : IrNamer by irNamer {
val intrinsics = JsIntrinsicTransformers(backendContext)
val classModels = mutableMapOf<IrClassSymbol, JsIrClassModel>()
val initializerBlock = JsCompositeBlock()
val isPerFile: Boolean get() = mode.granularity === JsGenerationGranularity.PER_FILE
}
@@ -14,7 +14,8 @@ class Keeper(private val keep: Set<String>) : IrElementVisitor<Unit, Keeper.Keep
private val keptDeclarations: MutableSet<IrDeclaration> = mutableSetOf()
fun shouldKeep(declaration: IrDeclaration): Boolean {
return declaration in keptDeclarations
return declaration in keptDeclarations ||
(declaration is IrOverridableDeclaration<*> && declaration.overriddenSymbols.any { shouldKeep(it.owner as IrDeclaration) })
}
override fun visitElement(element: IrElement, data: KeepData) {
@@ -14,8 +14,8 @@ import org.jetbrains.kotlin.js.backend.ast.metadata.*
import java.nio.ByteBuffer
import java.util.*
fun deserializeJsIrProgramFragment(input: ByteArray): JsIrProgramFragment {
return JsIrAstDeserializer(input).readFragment()
fun deserializeJsIrProgramFragment(input: ByteArray): List<JsIrProgramFragment> {
return JsIrAstDeserializer(input).readFragments()
}
private class JsIrAstDeserializer(private val source: ByteArray) {
@@ -76,6 +76,10 @@ private class JsIrAstDeserializer(private val source: ByteArray) {
return if (readBoolean()) then() else null
}
fun readFragments(): List<JsIrProgramFragment> {
return readList { readFragment() }
}
fun readFragment(): JsIrProgramFragment {
return JsIrProgramFragment(readString()).apply {
readRepeated {
@@ -320,7 +324,7 @@ private class JsIrAstDeserializer(private val source: ByteArray) {
CLASS -> {
JsClass(
ifTrue { nameTable[readInt()] },
ifTrue { nameTable[readInt()].makeRef() },
ifTrue { readExpression() },
ifTrue { readFunction() },
).apply {
readRepeated { members += readFunction() }
@@ -15,8 +15,8 @@ import java.io.DataOutputStream
import java.io.OutputStream
import java.util.*
fun JsIrProgramFragment.serializeTo(output: OutputStream) {
JsIrAstSerializer().append(this).saveTo(output)
fun List<JsIrProgramFragment>.serializeTo(output: OutputStream) {
JsIrAstSerializer().appendAll(this).saveTo(output)
}
private class DataWriter {
@@ -89,6 +89,12 @@ private class JsIrAstSerializer {
private val fileStack: Deque<String> = ArrayDeque()
private val importedNames = mutableSetOf<JsName>()
fun appendAll(fragments: List<JsIrProgramFragment>): JsIrAstSerializer {
fragmentSerializer.writeInt(fragments.size)
fragments.forEach(::append)
return this
}
fun append(fragment: JsIrProgramFragment): JsIrAstSerializer {
importedNames += fragment.imports.map { fragment.nameBindings[it.key]!! }
fragmentSerializer.writeFragment(fragment)
@@ -437,9 +443,8 @@ private class JsIrAstSerializer {
ifNotNull(x.name) {
writeInt(internalizeName(it))
}
// TODO: add more complex JsNameRef parsing in future when we will support `class` expressions inside a `js` call
ifNotNull(x.baseClass?.name) {
writeInt(internalizeName(it))
ifNotNull(x.baseClass) {
writeExpression(it)
}
ifNotNull(x.constructor) {
writeFunction(it)
@@ -0,0 +1,9 @@
$TESTDATA_DIR$/simple2js.kt
-Xir-only
-ir-output-dir
$TEMP_DIR$
-ir-output-name
out
-module-kind
es
-Xir-per-file
@@ -0,0 +1 @@
OK
@@ -0,0 +1,9 @@
$TESTDATA_DIR$/simple2js.kt
-Xir-only
-ir-output-dir
$TEMP_DIR$
-ir-output-name
out
-module-kind
commonjs
-Xir-per-file
@@ -0,0 +1,2 @@
error: per-file compilation can't be used with any `moduleKind` except `es` (ECMAScript Modules)
COMPILATION_ERROR
@@ -58,7 +58,9 @@ class JsEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigu
TranslationMode.FULL_DEV to "out",
TranslationMode.FULL_PROD_MINIMIZED_NAMES to "outMin",
TranslationMode.PER_MODULE_DEV to "outPm",
TranslationMode.PER_MODULE_PROD_MINIMIZED_NAMES to "outPmMin"
TranslationMode.PER_MODULE_PROD_MINIMIZED_NAMES to "outPmMin",
TranslationMode.PER_FILE_DEV to "outPf",
TranslationMode.PER_FILE_PROD_MINIMIZED_NAMES to "outPfMin"
)
private const val OUTPUT_KLIB_DIR_NAME = "outputKlibDir"
@@ -86,11 +88,15 @@ class JsEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigu
}
fun getJsModuleArtifactPath(testServices: TestServices, moduleName: String, translationMode: TranslationMode = TranslationMode.FULL_DEV): String {
return getJsArtifactsOutputDir(testServices, translationMode).absolutePath + File.separator + getJsArtifactSimpleName(testServices, moduleName) + "_v5"
return getJsArtifactsOutputDir(testServices, translationMode).absolutePath + File.separator + getJsModuleArtifactName(testServices, moduleName)
}
fun getRecompiledJsModuleArtifactPath(testServices: TestServices, moduleName: String, translationMode: TranslationMode = TranslationMode.FULL_DEV): String {
return getJsArtifactsRecompiledOutputDir(testServices, translationMode).absolutePath + File.separator + getJsArtifactSimpleName(testServices, moduleName) + "_v5"
return getJsArtifactsRecompiledOutputDir(testServices, translationMode).absolutePath + File.separator + getJsModuleArtifactName(testServices, moduleName)
}
fun getJsModuleArtifactName(testServices: TestServices, moduleName: String): String {
return getJsArtifactSimpleName(testServices, moduleName) + "_v5"
}
fun getJsKlibArtifactPath(testServices: TestServices, moduleName: String): String {
@@ -26,10 +26,9 @@ class TemporaryDirectoryManagerImpl(testServices: TestServices) : TemporaryDirec
// This code will simplify directory name for windows. This is needed because there can occur errors due to long name
val lastDot = className.lastIndexOf('.')
val packageName = className.substring(0, lastDot + 1)
val simplifiedClassName = className.substring(lastDot + 1).getOnlyUpperCaseSymbols()
val simplifiedMethodName = methodName.getOnlyUpperCaseSymbols()
KtTestUtil.tmpDirForTest(packageName + simplifiedClassName, "test$simplifiedMethodName")
KtTestUtil.tmpDirForTest(simplifiedClassName, simplifiedMethodName)
}
override val rootDir: File
@@ -1403,6 +1403,16 @@ public class CliTestGenerated extends AbstractCliTest {
runTest("compiler/testData/cli/js/outputPrefixFileNotFound.args");
}
@TestMetadata("perFileWithValidModuleKind.args")
public void testPerFileWithValidModuleKind() throws Exception {
runTest("compiler/testData/cli/js/perFileWithValidModuleKind.args");
}
@TestMetadata("perFileWithWrongModuleKind.args")
public void testPerFileWithWrongModuleKind() throws Exception {
runTest("compiler/testData/cli/js/perFileWithWrongModuleKind.args");
}
@TestMetadata("reifiedIntersectionType.args")
public void testReifiedIntersectionType() throws Exception {
runTest("compiler/testData/cli/js/reifiedIntersectionType.args");
@@ -218,6 +218,8 @@ inline fun <T, R> Iterable<T>.same(extractor: (T) -> R): Boolean {
inline fun <R> runIf(condition: Boolean, block: () -> R): R? = if (condition) block() else null
inline fun <R> runUnless(condition: Boolean, block: () -> R): R? = if (condition) null else block()
inline fun <A : B, B> A.butIf(condition: Boolean, block: (A) -> B): B = if (condition) block(this) else this
inline fun <T, R> Collection<T>.foldMap(transform: (T) -> R, operation: (R, R) -> R): R {
val iterator = iterator()
var result = transform(iterator.next())
@@ -9,7 +9,7 @@ import org.jetbrains.kotlin.js.common.Symbol
class JsClass(
private var name: JsName? = null,
var baseClass: JsNameRef? = null,
var baseClass: JsExpression? = null,
var constructor: JsFunction? = null,
val members: MutableList<JsFunction> = mutableListOf()
) : JsLiteral(), HasName {
@@ -18,10 +18,7 @@ class JsExport(
object All : Subject()
}
class Element(
val name: JsNameRef,
val alias: JsName?
)
class Element(val name: JsNameRef, val alias: JsName? = null)
override fun accept(visitor: JsVisitor) {
visitor.visitExport(this)
@@ -23,11 +23,11 @@ import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.ir.backend.js.JsIrCompilerWithIC
import org.jetbrains.kotlin.ir.backend.js.SourceMapsInfo
import org.jetbrains.kotlin.ir.backend.js.WholeWorldStageController
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.ic.*
import org.jetbrains.kotlin.ir.backend.js.jsPhases
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.CompilationOutputs
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.extension
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.safeModuleName
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
@@ -361,7 +361,7 @@ abstract class AbstractInvalidationTest(private val targetBackend: TargetBackend
relativeRequirePath = true
)
val (jsOutput, rebuiltModules) = jsExecutableProducer.buildExecutable(multiModule = true, outJsProgram = true)
val (jsOutput, rebuiltModules) = jsExecutableProducer.buildExecutable(granularity = JsGenerationGranularity.PER_MODULE, outJsProgram = true)
val writtenFiles = writeJsCode(projStep.id, mainModuleName, jsOutput)
verifyJsExecutableProducerBuildModules(projStep.id, rebuiltModules, projStep.dirtyJS)
@@ -13,11 +13,11 @@ import org.jetbrains.kotlin.cli.common.isWindows
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.ir.backend.js.*
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.ic.JsExecutableProducer
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerDesc
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.*
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.backend.js.SourceMapsInfo
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC
import org.jetbrains.kotlin.ir.linkage.partial.PartialLinkageConfig
import org.jetbrains.kotlin.ir.linkage.partial.PartialLinkageLogLevel
@@ -54,10 +54,10 @@ class JsIrBackendFacade(
val testServices: TestServices,
private val firstTimeCompilation: Boolean
) : AbstractTestFacade<BinaryArtifacts.KLib, BinaryArtifacts.Js>() {
override val inputKind: ArtifactKinds.KLib
get() = ArtifactKinds.KLib
override val outputKind: ArtifactKinds.Js
get() = ArtifactKinds.Js
override val inputKind: ArtifactKinds.KLib get() = ArtifactKinds.KLib
override val outputKind: ArtifactKinds.Js get() = ArtifactKinds.Js
private val jsIrPathReplacer by lazy { JsIrPathReplacer(testServices) }
constructor(testServices: TestServices) : this(testServices, firstTimeCompilation = true)
@@ -90,8 +90,8 @@ class JsIrBackendFacade(
val granularity = when {
!firstTimeCompilation -> JsGenerationGranularity.WHOLE_PROGRAM
splitPerFile || module.kind == ModuleKind.ES -> JsGenerationGranularity.PER_FILE
splitPerModule || perModule -> JsGenerationGranularity.PER_MODULE
splitPerFile -> JsGenerationGranularity.PER_FILE
else -> JsGenerationGranularity.WHOLE_PROGRAM
}
@@ -116,7 +116,7 @@ class JsIrBackendFacade(
caches = testServices.jsIrIncrementalDataProvider.getCaches(),
relativeRequirePath = false
)
jsExecutableProducer.buildExecutable(it.perModule, true).compilationOut
jsExecutableProducer.buildExecutable(it.granularity, true).compilationOut
}
)
return BinaryArtifacts.Js.JsIrArtifact(
@@ -168,6 +168,7 @@ class JsIrBackendFacade(
val runIrDce = JsEnvironmentConfigurationDirectives.RUN_IR_DCE in module.directives
val onlyIrDce = JsEnvironmentConfigurationDirectives.ONLY_IR_DCE in module.directives
val perModuleOnly = JsEnvironmentConfigurationDirectives.SPLIT_PER_MODULE in module.directives
val perFileOnly = JsEnvironmentConfigurationDirectives.SPLIT_PER_FILE in module.directives
val isEsModules = JsEnvironmentConfigurationDirectives.ES_MODULES in module.directives ||
module.directives[JsEnvironmentConfigurationDirectives.MODULE_KIND].contains(ModuleKind.ES)
@@ -182,7 +183,7 @@ class JsIrBackendFacade(
mainArguments,
moduleToName = runIf(isEsModules) {
loweredIr.allModules.associateWith {
"./${getJsArtifactSimpleName(testServices, it.safeName)}_v5.mjs".minifyIfNeed()
"./${getJsArtifactSimpleName(testServices, it.safeName)}_v5".minifyIfNeed()
}
} ?: emptyMap()
)
@@ -190,17 +191,21 @@ class JsIrBackendFacade(
// If perModuleOnly then skip whole program
// (it.dce => runIrDce) && (perModuleOnly => it.perModule)
val translationModes = TranslationMode.values()
.filter { (it.production || !onlyIrDce) && (!it.production || runIrDce) && (!perModuleOnly || it.perModule) }
.filter {
(it.production || !onlyIrDce) &&
(!it.production || runIrDce) &&
(!perModuleOnly || it.granularity == JsGenerationGranularity.PER_MODULE) &&
(!perFileOnly || it.granularity == JsGenerationGranularity.PER_FILE)
}
.filter { it.production == it.minimizedMemberNames }
.filter { isEsModules || it.granularity != JsGenerationGranularity.PER_FILE }
.toSet()
val compilationOut = transformer.generateModule(loweredIr.allModules, translationModes, false)
val compilationOut = transformer.generateModule(loweredIr.allModules, translationModes, isEsModules)
return BinaryArtifacts.Js.JsIrArtifact(outputFile, compilationOut).dump(module)
}
private fun IrModuleFragment.resolveTestPaths() {
JsIrPathReplacer(testServices).let {
files.forEach(it::lower)
}
files.forEach(jsIrPathReplacer::lower)
}
private fun loadIrFromKlib(module: TestModule, configuration: CompilerConfiguration): IrModuleInfo {
@@ -268,8 +273,9 @@ class JsIrBackendFacade(
return this
}
fun File.fixJsFile(newJsTarget: File, moduleId: String, moduleKind: ModuleKind) {
fun File.fixJsFile(rootDir: File, newJsTarget: File, moduleId: String, moduleKind: ModuleKind) {
val newJsCode = ClassicJsBackendFacade.wrapWithModuleEmulationMarkers(readText(), moduleKind, moduleId)
val jsCodeWithCorrectImportPath = jsIrPathReplacer.replacePathTokensWithRealPath(newJsCode, newJsTarget, rootDir)
val oldJsMap = File("$absolutePath.map")
val jsCodeMap = (moduleKind == ModuleKind.PLAIN && oldJsMap.exists()).ifTrue { oldJsMap.readText() }
@@ -277,12 +283,13 @@ class JsIrBackendFacade(
this.delete()
oldJsMap.delete()
newJsTarget.write(newJsCode)
newJsTarget.write(jsCodeWithCorrectImportPath)
jsCodeMap?.let { File("${newJsTarget.absolutePath}.map").write(it) }
}
private fun CompilationOutputs.writeTo(outputFile: File, moduleId: String, moduleKind: ModuleKind) {
val tmpBuildDir = outputFile.parentFile.resolve("tmp-build")
val rootDir = outputFile.parentFile
val tmpBuildDir = rootDir.resolve("tmp-build")
// CompilationOutputs keeps the `outputDir` clean by removing all outdated JS and other unknown files.
// To ensure that useful files around `outputFile`, such as irdump, are not removed, use `tmpBuildDir` instead.
val allJsFiles = writeAll(tmpBuildDir, outputFile.nameWithoutExtension, false, moduleId, moduleKind).filter {
@@ -290,11 +297,11 @@ class JsIrBackendFacade(
}
val mainModuleFile = allJsFiles.last()
mainModuleFile.fixJsFile(outputFile, moduleId, moduleKind)
mainModuleFile.fixJsFile(rootDir, outputFile, moduleId, moduleKind)
dependencies.map { it.first }.zip(allJsFiles.dropLast(1)).forEach { (depModuleId, builtJsFilePath) ->
val newFile = outputFile.augmentWithModuleName(depModuleId)
builtJsFilePath.fixJsFile(newFile, depModuleId, moduleKind)
builtJsFilePath.fixJsFile(rootDir, newFile, depModuleId, moduleKind)
}
tmpBuildDir.deleteRecursively()
}
@@ -347,8 +354,9 @@ fun String.minifyIfNeed(): String {
if (fileName.length <= 80) return this
val fileExtension = fileFullName.substringAfterLast('.')
val extensionPart = if (fileExtension.isEmpty()) "" else ".$fileExtension"
return "$directoryPath$delimiter${fileName.cityHash64().toULong().toString(16)}.$fileExtension"
return "$directoryPath$delimiter${fileName.cityHash64().toULong().toString(16)}$extensionPart"
}
fun File.augmentWithModuleName(moduleName: String): File = File(absolutePath.augmentWithModuleName(moduleName))
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2023 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.
*/
@@ -18,6 +18,10 @@ import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.isJsFile
import org.jetbrains.kotlin.test.services.isMjsFile
import org.jetbrains.kotlin.test.services.moduleStructure
import java.io.File
import kotlin.io.invariantSeparatorsPath
private const val PATH_TO_ROOT_TOKEN = "@PATH_TO_ROOT"
class JsIrPathReplacer(testServices: TestServices) : DeclarationTransformer {
private val replacements = testServices.collectReplacementsMap()
@@ -35,6 +39,7 @@ class JsIrPathReplacer(testServices: TestServices) : DeclarationTransformer {
private fun IrAnnotationContainer.replaceJsModulePath() {
val jsModuleAnnotation = getAnnotation(JsAnnotations.jsModuleFqn) ?: return
@Suppress("UNCHECKED_CAST")
val stringLiteral = jsModuleAnnotation.getValueArgument(0) as IrConst<String>
val pathReplacement = stringLiteral.getReplacement() ?: return
@@ -43,17 +48,24 @@ class JsIrPathReplacer(testServices: TestServices) : DeclarationTransformer {
}
private fun IrConst<String>.getReplacement(): IrConst<String>? {
val replacement = replacements[value] ?: replacements[value.replace("./", "")] ?: return null
return IrConstImpl.string(startOffset, endOffset, type, "./" + replacement.replace("./", ""))
return IrConstImpl.string(startOffset, endOffset, type, replacements[value] ?: return null)
}
private fun TestServices.collectReplacementsMap(): Map<String, String> {
return moduleStructure.modules.asSequence()
.map { module -> module to module.files.filter { it.isJsFile || it.isMjsFile } }
.filter { (_, files) -> files.isNotEmpty() }
.flatMap { (module, files) -> files.map { it.relativePath to module.getNameFor(it, this) } }
.plus(getAdditionalFiles(this).map { it.name to it.name })
.plus(getAdditionalMainFiles(this).map { it.name to it.name })
.flatMap { (module, files) -> files.map { "./${it.relativePath}" to "$PATH_TO_ROOT_TOKEN/${module.getNameFor(it, this)}" } }
.plus(getAdditionalFiles(this).map { "./${it.name}" to "$PATH_TO_ROOT_TOKEN/${it.name}" })
.plus(getAdditionalMainFiles(this).map { "./${it.name}" to "$PATH_TO_ROOT_TOKEN/${it.name}" })
.toMap()
}
}
fun replacePathTokensWithRealPath(content: String, file: File, rootDir: File): String {
val relativePathToRootDir = rootDir.relativeTo(file.parentFile).invariantSeparatorsPath.ifEmpty { "." }
return content.replace("from '$PATH_TO_ROOT_TOKEN/(.+?)';${'$'}".toRegex(RegexOption.MULTILINE)) { result ->
val importPath = result.groups[1]?.value ?: error("Unexpected import path")
"from '$relativePathToRootDir/$importPath'\n"
}
}
}
@@ -31,17 +31,23 @@ class JsArtifactsDumpHandler(testServices: TestServices) : AfterAnalysisChecker(
val testGroupOutputDirForMinification = File(pathToRootOutputDir + "out-min/" + testGroupOutputDirPrefix)
val testGroupOutputDirForPerModuleCompilation = File(pathToRootOutputDir + "out-per-module/" + testGroupOutputDirPrefix)
val testGroupOutputDirForPerModuleMinification = File(pathToRootOutputDir + "out-per-module-min/" + testGroupOutputDirPrefix)
val testGroupOutputDirForPerFileCompilation = File(pathToRootOutputDir + "out-per-file/" + testGroupOutputDirPrefix)
val testGroupOutputDirForPerFileMinification = File(pathToRootOutputDir + "out-per-file-min/" + testGroupOutputDirPrefix)
val outputDir = getOutputDir(originalFile, testGroupOutputDirForCompilation, stopFile)
val dceOutputDir = getOutputDir(originalFile, testGroupOutputDirForMinification, stopFile)
val perModuleOutputDir = getOutputDir(originalFile, testGroupOutputDirForPerModuleCompilation, stopFile)
val preModuleDceOutputDir = getOutputDir(originalFile, testGroupOutputDirForPerModuleMinification, stopFile)
val perModuleDceOutputDir = getOutputDir(originalFile, testGroupOutputDirForPerModuleMinification, stopFile)
val perFileOutputDir = getOutputDir(originalFile, testGroupOutputDirForPerFileCompilation, stopFile)
val perFileDceOutputDir = getOutputDir(originalFile, testGroupOutputDirForPerFileMinification, stopFile)
val minOutputDir = File(dceOutputDir, originalFile.nameWithoutExtension)
copy(JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices), outputDir)
copy(JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices, TranslationMode.FULL_PROD_MINIMIZED_NAMES), dceOutputDir)
copy(JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices, TranslationMode.PER_MODULE_DEV), perModuleOutputDir)
copy(JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices, TranslationMode.PER_MODULE_PROD_MINIMIZED_NAMES), preModuleDceOutputDir)
copy(JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices, TranslationMode.PER_MODULE_PROD_MINIMIZED_NAMES), perModuleDceOutputDir)
copy(JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices, TranslationMode.PER_FILE_DEV), perFileOutputDir)
copy(JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices, TranslationMode.PER_FILE_PROD_MINIMIZED_NAMES), perFileDceOutputDir)
copy(JsEnvironmentConfigurator.getMinificationJsArtifactsOutputDir(testServices), minOutputDir)
}
@@ -23,11 +23,11 @@ import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticReporterFactory
import org.jetbrains.kotlin.ir.backend.js.*
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.ic.CacheUpdater
import org.jetbrains.kotlin.ir.backend.js.ic.JsExecutableProducer
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.CompilationOutputs
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
@@ -346,7 +346,7 @@ abstract class AbstractJsPartialLinkageTestCase(val compilerType: CompilerType)
relativeRequirePath = true
)
return jsExecutableProducer.buildExecutable(multiModule = true, outJsProgram = true).compilationOut
return jsExecutableProducer.buildExecutable(granularity = JsGenerationGranularity.PER_MODULE, outJsProgram = true).compilationOut
}
private val IrModuleFragment.exportName get() = "kotlin_${name.asStringStripSpecialMarkers()}"
@@ -35,7 +35,7 @@ private class TestArtifactCache(val moduleName: String, val binaryAsts: MutableM
// TODO: It will be better to use saved fragments, but it doesn't work
// Merger.merge() + JsNode.resolveTemporaryNames() modify fragments,
// therefore the sequential calls produce different results
fragment = deserializeJsIrProgramFragment(it.value)
fragments = deserializeJsIrProgramFragment(it.value)
)
}
)
@@ -50,7 +50,7 @@ private fun extractJsFiles(
fun copyInputJsFile(module: TestModule, inputJsFile: TestFile): String {
val newName = module.getNameFor(inputJsFile, testServices)
val targetFile = File(outputDir, newName)
targetFile.writeText(inputJsFile.originalContent)
targetFile.writeText(inputJsFile.originalContent.trim())
return targetFile.absolutePath
}
@@ -68,7 +68,7 @@ private fun extractJsFiles(
return before to after
}
fun getAdditionalFilePathes(testServices: TestServices, mode: TranslationMode = TranslationMode.FULL_DEV): List<String> {
fun getAdditionalFilePaths(testServices: TestServices, mode: TranslationMode = TranslationMode.FULL_DEV): List<String> {
return getAdditionalFiles(testServices, mode, true).map { it.absolutePath }
}
@@ -100,7 +100,7 @@ fun getAdditionalFiles(
return additionalFiles
}
fun getAdditionalMainFilePathes(testServices: TestServices, mode: TranslationMode = TranslationMode.FULL_DEV): List<String> {
fun getAdditionalMainFilePaths(testServices: TestServices, mode: TranslationMode = TranslationMode.FULL_DEV): List<String> {
return getAdditionalMainFiles(testServices, mode, shouldCopyFiles = true).map { it.absolutePath }
}
@@ -157,12 +157,13 @@ fun getAllFilesForRunner(
val outputFile = getModeOutputFilePath(testServices, module, mode)
val (inputJsFilesBefore, inputJsFilesAfter) = extractJsFiles(testServices, testServices.moduleStructure.modules, mode)
val additionalFiles = getAdditionalFilePathes(testServices, mode)
val additionalMainFiles = getAdditionalMainFilePathes(testServices, mode)
val additionalFiles = getAdditionalFilePaths(testServices, mode)
val additionalMainFiles = getAdditionalMainFilePaths(testServices, mode)
outputs.dependencies.forEach { (moduleId, _) ->
paths += outputFile.augmentWithModuleName(moduleId)
}
paths += outputFile
result[mode] = additionalFiles + inputJsFilesBefore + paths + commonFiles + additionalMainFiles + inputJsFilesAfter
@@ -171,8 +172,8 @@ fun getAllFilesForRunner(
return result
} else {
val (inputJsFilesBefore, inputJsFilesAfter) = extractJsFiles(testServices, testServices.moduleStructure.modules)
val additionalFiles = getAdditionalFilePathes(testServices)
val additionalMainFiles = getAdditionalMainFilePathes(testServices)
val additionalFiles = getAdditionalFilePaths(testServices)
val additionalMainFiles = getAdditionalMainFilePaths(testServices)
// Old BE
val outputDir = JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices)
val dceOutputDir = JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices, TranslationMode.FULL_PROD_MINIMIZED_NAMES)
@@ -2421,6 +2421,12 @@ public class FirJsBoxTestGenerated extends AbstractFirJsBoxTest {
runTest("js/js.translator/testData/box/esModules/jsExport/jsExportInClass.kt");
}
@Test
@TestMetadata("perFileExportedApi.kt")
public void testPerFileExportedApi() throws Exception {
runTest("js/js.translator/testData/box/esModules/jsExport/perFileExportedApi.kt");
}
@Test
@TestMetadata("recursiveExport.kt")
public void testRecursiveExport() throws Exception {
@@ -2527,6 +2527,12 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test {
runTest("js/js.translator/testData/box/esModules/jsExport/jsExportInClass.kt");
}
@Test
@TestMetadata("perFileExportedApi.kt")
public void testPerFileExportedApi() throws Exception {
runTest("js/js.translator/testData/box/esModules/jsExport/perFileExportedApi.kt");
}
@Test
@TestMetadata("recursiveExport.kt")
public void testRecursiveExport() throws Exception {
@@ -2421,6 +2421,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
runTest("js/js.translator/testData/box/esModules/jsExport/jsExportInClass.kt");
}
@Test
@TestMetadata("perFileExportedApi.kt")
public void testPerFileExportedApi() throws Exception {
runTest("js/js.translator/testData/box/esModules/jsExport/perFileExportedApi.kt");
}
@Test
@TestMetadata("recursiveExport.kt")
public void testRecursiveExport() throws Exception {
@@ -0,0 +1,39 @@
// DONT_TARGET_EXACT_BACKEND: JS
// SKIP_MINIFICATION
// ES_MODULES
// SPLIT_PER_FILE
// FILE: base.kt
@JsExport
open class Base() {
var bar = "bar"
fun foo() = "foo"
}
// FILE: a.kt
@JsExport
class A : Base() {
val pong = "pong"
fun ping() = "ping"
}
// FILE: function.kt
@JsExport
fun create() = A()
// FILE: main.kt
@JsModule("./perFileExportedApi.mjs")
external fun jsBox(): String
fun box(): String {
val res = jsBox()
if (res != "bar&not bar&foo&ping&pong") {
return "Fail: ${res}"
}
return "OK"
}
@@ -0,0 +1,15 @@
import { create } from "./perFileExportedApi-kotlin_main_v5/function.export.mjs";
export default function() {
let result = ""
const a = create()
result += a.bar
a.bar = "not bar"
result += `&${a.bar}`
result += `&${a.foo()}`
result += `&${a.ping()}`
result += `&${a.pong}`
return result
};
@@ -1,8 +1,11 @@
MODULES: lib1, lib2, main
STEP 0..1:
STEP 0:
libs: lib1, lib2, main
dirty js: lib1, lib2, main
STEP 1:
libs: lib1, lib2, main
dirty js: lib1, lib2
STEP 2:
libs: lib1, lib2, main
dirty js: lib1, lib2
@@ -11,7 +14,7 @@ STEP 3:
dirty js: lib2, main
STEP 4:
libs: lib1, lib2, main
dirty js: lib1, lib2, main
dirty js: lib1, lib2
STEP 5..6:
libs: lib1, lib2, main
dirty js: lib2, main
+16 -1
View File
@@ -210,6 +210,13 @@ public final annotation class ExperimentalJsExport : kotlin.Annotation {
public constructor ExperimentalJsExport()
}
@kotlin.RequiresOptIn(level = Level.WARNING)
@kotlin.annotation.MustBeDocumented
@kotlin.annotation.Retention(value = AnnotationRetention.BINARY)
public final annotation class ExperimentalJsFileName : kotlin.Annotation {
public constructor ExperimentalJsFileName()
}
public external object JSON {
public final fun <T> parse(text: kotlin.String): T
@@ -266,6 +273,14 @@ public final annotation class JsExternalInheritorsOnly : kotlin.Annotation {
public constructor JsExternalInheritorsOnly()
}
@kotlin.annotation.Retention(value = AnnotationRetention.BINARY)
@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.FILE})
public final annotation class JsFileName : kotlin.Annotation {
public constructor JsFileName(name: kotlin.String)
public final val name: kotlin.String { get; }
}
@kotlin.annotation.Retention(value = AnnotationRetention.BINARY)
@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.FILE})
public final annotation class JsModule : kotlin.Annotation {
@@ -275,7 +290,7 @@ public final annotation class JsModule : kotlin.Annotation {
}
@kotlin.annotation.Retention(value = AnnotationRetention.BINARY)
@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER})
@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.FILE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER})
public final annotation class JsName : kotlin.Annotation {
public constructor JsName(name: kotlin.String)
+16 -1
View File
@@ -209,6 +209,13 @@ public final annotation class ExperimentalJsExport : kotlin.Annotation {
public constructor ExperimentalJsExport()
}
@kotlin.RequiresOptIn(level = Level.WARNING)
@kotlin.annotation.MustBeDocumented
@kotlin.annotation.Retention(value = AnnotationRetention.BINARY)
public final annotation class ExperimentalJsFileName : kotlin.Annotation {
public constructor ExperimentalJsFileName()
}
public external object JSON {
public final fun <T> parse(text: kotlin.String): T
@@ -265,6 +272,14 @@ public final annotation class JsExternalInheritorsOnly : kotlin.Annotation {
public constructor JsExternalInheritorsOnly()
}
@kotlin.annotation.Retention(value = AnnotationRetention.BINARY)
@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.FILE})
public final annotation class JsFileName : kotlin.Annotation {
public constructor JsFileName(name: kotlin.String)
public final val name: kotlin.String { get; }
}
@kotlin.annotation.Retention(value = AnnotationRetention.BINARY)
@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.FILE})
public final annotation class JsModule : kotlin.Annotation {
@@ -274,7 +289,7 @@ public final annotation class JsModule : kotlin.Annotation {
}
@kotlin.annotation.Retention(value = AnnotationRetention.BINARY)
@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER})
@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.FILE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER})
public final annotation class JsName : kotlin.Annotation {
public constructor JsName(name: kotlin.String)
@@ -10,10 +10,34 @@ import kotlin.annotation.AnnotationTarget.*
/**
* Gives a declaration (a function, a property or a class) specific name in JavaScript.
*/
@Target(CLASS, FUNCTION, PROPERTY, CONSTRUCTOR, PROPERTY_GETTER, PROPERTY_SETTER)
@Target(FILE, CLASS, FUNCTION, PROPERTY, CONSTRUCTOR, PROPERTY_GETTER, PROPERTY_SETTER)
@OptionalExpectation
public expect annotation class JsName(val name: String)
/**
* Marks experimental [JsFileName] annotation.
*
* Note that behavior of these annotations will likely be changed in the future.
*
* Usages of such annotations will be reported as warnings unless an explicit opt-in with
* the [OptIn] annotation, e.g. `@OptIn(ExperimentalJsFileName::class)`,
* or with the `-opt-in=kotlin.js.ExperimentalJsFileName` compiler option is given.
*/
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@MustBeDocumented
@Retention(AnnotationRetention.BINARY)
public annotation class ExperimentalJsFileName
/**
* Specifies the name of the compiled file produced from the annotated source file instead of the default one.
*
* This annotation can be applied only to files and only when the compilation granularity is `PER_FILE`.
*/
@Target(FILE)
@OptionalExpectation
@ExperimentalJsFileName
public expect annotation class JsFileName(val name: String)
/**
* Marks experimental JS export annotations.
*
@@ -78,7 +78,7 @@ val jsMainSources by task<Sync> {
listOf(
"libraries/stdlib/js/src/org.w3c/**",
"libraries/stdlib/js/src/kotlin/char.kt",
"libraries/stdlib/js/src/kotlin/collections.kt",
"libraries/stdlib/js/src/kotlin/collectionJs.kt",
"libraries/stdlib/js/src/kotlin/collections/**",
"libraries/stdlib/js/src/kotlin/time/**",
"libraries/stdlib/js/src/kotlin/console.kt",
@@ -90,7 +90,7 @@ val jsMainSources by task<Sync> {
"libraries/stdlib/js/src/kotlin/json.kt",
"libraries/stdlib/js/src/kotlin/promise.kt",
"libraries/stdlib/js/src/kotlin/regexp.kt",
"libraries/stdlib/js/src/kotlin/sequence.kt",
"libraries/stdlib/js/src/kotlin/sequenceJs.kt",
"libraries/stdlib/js/src/kotlin/throwableExtensions.kt",
"libraries/stdlib/js/src/kotlin/text/**",
"libraries/stdlib/js/src/kotlin/reflect/KTypeHelpers.kt",
@@ -0,0 +1,95 @@
/*
* Copyright 2010-2023 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 kotlin.js
internal fun setMetadataFor(
ctor: Ctor,
name: String?,
metadataConstructor: (name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array<Int>?) -> Metadata,
parent: Ctor?,
interfaces: Array<dynamic>?,
associatedObjectKey: Number?,
associatedObjects: dynamic,
suspendArity: Array<Int>?
) {
if (parent != null) {
js("""
ctor.prototype = Object.create(parent.prototype)
ctor.prototype.constructor = ctor;
""")
}
val metadata = metadataConstructor(name, associatedObjectKey, associatedObjects, suspendArity ?: js("[]"))
ctor.`$metadata$` = metadata
if (interfaces != null) {
val receiver = if (metadata.iid != null) ctor else ctor.prototype
receiver.`$imask$` = implement(interfaces)
}
}
// There was a problem with per-module compilation (KT-55758) when the top-level state (iid) was reinitialized during stdlib module initialization
// As a result we miss already incremented iid and had the same iids in two different modules
// So, to keep the state consistent it was moved into the variable without initializer and function
@Suppress("MUST_BE_INITIALIZED")
private var iid: dynamic
private fun generateInterfaceId(): Int {
if (iid === VOID) {
iid = 0
}
iid = iid.unsafeCast<Int>() + 1
return iid.unsafeCast<Int>()
}
internal fun interfaceMeta(name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array<Int>?): Metadata {
return createMetadata("interface", name, associatedObjectKey, associatedObjects, suspendArity, generateInterfaceId())
}
internal fun objectMeta(name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array<Int>?): Metadata {
return createMetadata("object", name, associatedObjectKey, associatedObjects, suspendArity, null)
}
internal fun classMeta(name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array<Int>?): Metadata {
return createMetadata("class", name, associatedObjectKey, associatedObjects, suspendArity, null)
}
// Seems like we need to disable this check if variables are used inside js annotation
@Suppress("UNUSED_PARAMETER", "UNUSED_VARIABLE")
private fun createMetadata(
kind: String,
name: String?,
associatedObjectKey: Number?,
associatedObjects: dynamic,
suspendArity: Array<Int>?,
iid: Int?
): Metadata {
val undef = VOID
return js("""({
kind: kind,
simpleName: name,
associatedObjectKey: associatedObjectKey,
associatedObjects: associatedObjects,
suspendArity: suspendArity,
${'$'}kClass$: undef,
iid: iid
})""")
}
internal external interface Metadata {
val kind: String
// This field gives fast access to the prototype of metadata owner (Object.getPrototypeOf())
// Can be pre-initialized or lazy initialized and then should be immutable
val simpleName: String?
val associatedObjectKey: Number?
val associatedObjects: dynamic
val suspendArity: Array<Int>?
val iid: Int?
var `$kClass$`: dynamic
var errorInfo: Int? // Bits set for overridden properties: "message" => 0x1, "cause" => 0x2
}
@@ -5,95 +5,6 @@
package kotlin.js
internal fun setMetadataFor(
ctor: Ctor,
name: String?,
metadataConstructor: (name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array<Int>?) -> Metadata,
parent: Ctor?,
interfaces: Array<dynamic>?,
associatedObjectKey: Number?,
associatedObjects: dynamic,
suspendArity: Array<Int>?
) {
if (parent != null) {
js("""
ctor.prototype = Object.create(parent.prototype)
ctor.prototype.constructor = ctor;
""")
}
val metadata = metadataConstructor(name, associatedObjectKey, associatedObjects, suspendArity ?: js("[]"))
ctor.`$metadata$` = metadata
if (interfaces != null) {
val receiver = if (metadata.iid != null) ctor else ctor.prototype
receiver.`$imask$` = implement(interfaces)
}
}
// There was a problem with per-module compilation (KT-55758) when the top-level state (iid) was reinitialized during stdlib module initialization
// As a result we miss already incremented iid and had the same iids in two different modules
// So, to keep the state consistent it was moved into the next lateinit variable and function
private lateinit var iid: Any
private fun generateInterfaceId(): Int {
if (!::iid.isInitialized) {
iid = 0
}
iid = iid.unsafeCast<Int>() + 1
return iid.unsafeCast<Int>()
}
internal fun interfaceMeta(name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array<Int>?): Metadata {
return createMetadata("interface", name, associatedObjectKey, associatedObjects, suspendArity, generateInterfaceId())
}
internal fun objectMeta(name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array<Int>?): Metadata {
return createMetadata("object", name, associatedObjectKey, associatedObjects, suspendArity, null)
}
internal fun classMeta(name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array<Int>?): Metadata {
return createMetadata("class", name, associatedObjectKey, associatedObjects, suspendArity, null)
}
// Seems like we need to disable this check if variables are used inside js annotation
@Suppress("UNUSED_PARAMETER", "UNUSED_VARIABLE")
private fun createMetadata(
kind: String,
name: String?,
associatedObjectKey: Number?,
associatedObjects: dynamic,
suspendArity: Array<Int>?,
iid: Int?
): Metadata {
val undef = VOID
return js("""({
kind: kind,
simpleName: name,
associatedObjectKey: associatedObjectKey,
associatedObjects: associatedObjects,
suspendArity: suspendArity,
${'$'}kClass$: undef,
iid: iid
})""")
}
internal external interface Metadata {
val kind: String
// This field gives fast access to the prototype of metadata owner (Object.getPrototypeOf())
// Can be pre-initialized or lazy initialized and then should be immutable
val simpleName: String?
val associatedObjectKey: Number?
val associatedObjects: dynamic
val suspendArity: Array<Int>?
val iid: Int?
var `$kClass$`: dynamic
var errorInfo: Int? // Bits set for overridden properties: "message" => 0x1, "cause" => 0x2
}
internal external interface Ctor {
var `$imask$`: BitMask?
var `$metadata$`: Metadata
@@ -128,22 +39,6 @@ internal fun calculateErrorInfo(proto: dynamic): Int {
private fun getPrototypeOf(obj: dynamic) = JsObject.getPrototypeOf(obj)
private fun searchForMetadata(obj: dynamic): Metadata? {
if (obj == null) {
return null
}
var metadata: Metadata? = obj.`$metadata$`
var currentObject = getPrototypeOf(obj)
while (metadata == null && currentObject != null) {
val currentConstructor = currentObject.constructor
metadata = currentConstructor.`$metadata$`
currentObject = getPrototypeOf(currentObject)
}
return metadata
}
private fun isInterfaceImpl(obj: dynamic, iface: Int): Boolean {
val mask: BitMask = obj.`$imask$`.unsafeCast<BitMask?>() ?: return false
return mask.isBitSet(iface)
+1 -1
View File
@@ -8,7 +8,7 @@ package kotlin.js
import kotlin.annotation.AnnotationTarget.*
@Retention(AnnotationRetention.BINARY)
@Target(CLASS, FUNCTION, PROPERTY, CONSTRUCTOR, PROPERTY_GETTER, PROPERTY_SETTER)
@Target(FILE, CLASS, FUNCTION, PROPERTY, CONSTRUCTOR, PROPERTY_GETTER, PROPERTY_SETTER)
internal external annotation class JsName(val name: String)
internal external annotation class native
+10 -1
View File
@@ -58,9 +58,18 @@ internal annotation class marker
*
*/
@Retention(AnnotationRetention.BINARY)
@Target(CLASS, FUNCTION, PROPERTY, CONSTRUCTOR, PROPERTY_GETTER, PROPERTY_SETTER)
@Target(FILE, CLASS, FUNCTION, PROPERTY, CONSTRUCTOR, PROPERTY_GETTER, PROPERTY_SETTER)
public actual annotation class JsName(actual val name: String)
/**
* Specifies the name of the compiled file produced from the annotated source file instead of the default one.
*
* This annotation can be applied only to files and only when the compilation granularity is `PER_FILE`.
*/
@Retention(AnnotationRetention.BINARY)
@Target(FILE)
public actual annotation class JsFileName(actual val name: String)
/**
* Denotes an `external` declaration that must be imported from native JavaScript library.
*
@@ -3,6 +3,9 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
// TODO: Suppress will be until the next bootstrapping, after the bootstrapping the annotation should be replaced with [kotlin.js.JsFileName]
@file:Suppress("ANNOTATION_IS_NOT_APPLICABLE_TO_MULTIFILE_CLASSES")
@file:kotlin.js.JsName("CollectionsKt")
@file:kotlin.jvm.JvmMultifileClass
@file:kotlin.jvm.JvmName("CollectionsKt")
@file:OptIn(kotlin.experimental.ExperimentalTypeInference::class)
@@ -30,6 +30,7 @@ public actual annotation class JsExport {
*/
@Retention(AnnotationRetention.BINARY)
@Target(
AnnotationTarget.FILE,
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY,
@@ -3384,6 +3384,9 @@ public final class kotlin/jdk7/AutoCloseableKt {
public abstract interface annotation class kotlin/js/ExperimentalJsExport : java/lang/annotation/Annotation {
}
public abstract interface annotation class kotlin/js/ExperimentalJsFileName : java/lang/annotation/Annotation {
}
public final class kotlin/jvm/JvmClassMappingKt {
public static final fun getAnnotationClass (Ljava/lang/annotation/Annotation;)Lkotlin/reflect/KClass;
public static final fun getJavaClass (Ljava/lang/Object;)Ljava/lang/Class;