diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt index 0d9d47b1d44..4e2720faa09 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt @@ -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 } diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JsArgumentConstants.java b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JsArgumentConstants.java index 376a33f5a4d..770d438a5f8 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JsArgumentConstants.java +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JsArgumentConstants.java @@ -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"; diff --git a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt index c089f361435..58b9f3d1589 100644 --- a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt +++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt @@ -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() { 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() { 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() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt index 1b6a8dcbe7a..ef9d1de58e3 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt @@ -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.* diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt index 6e52dd5f6d7..bce143f297b 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt @@ -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( cleanupLoweringPhase, // Currently broken due to static members lowering making single-open-class // files non-recognizable as single-class files - // moveOpenClassesToSeparatePlaceLowering, validateIrAfterLowering, ) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt index f5d3acf9cc9..83f4ad592f2 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt @@ -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() val esClassToPossibilityForOptimization = DefaultDelegateFactory.newDeclarationToValueMapping>() + // Check if we need concurrency here + val chunkToOriginalFile = WeakHashMap() + val outerThisFieldSymbols = DefaultDelegateFactory.newDeclarationToDeclarationMapping() val innerClassConstructors = DefaultDelegateFactory.newDeclarationToDeclarationMapping() val originalInnerClassPrimaryConstructorByClass = DefaultDelegateFactory.newDeclarationToDeclarationMapping() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/codegen/JsGenerationGranularity.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/codegen/JsGenerationGranularity.kt deleted file mode 100644 index 15badb9851e..00000000000 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/codegen/JsGenerationGranularity.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt index 1a3ba942949..a94ff4609b2 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt @@ -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 diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compilerWithIC.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compilerWithIC.kt index 4d660a81633..1cda7badde2 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compilerWithIC.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compilerWithIC.kt @@ -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, dirtyFiles: Collection, mainArguments: List? - ): List<() -> JsIrProgramFragment> { + ): List<() -> List> { val shouldGeneratePolyfills = context.configuration.getBoolean(JSConfigurationKeys.GENERATE_POLYFILLS) allModules.forEach { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt index 279b92f5d9c..02511255b40 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt @@ -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()) { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt index 6de4b2eca12..48155007c8a 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToJsStatements.kt @@ -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 { - 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 { 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 { + 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()) } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/CacheUpdater.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/CacheUpdater.kt index b3a99dd6b69..5083fd43e67 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/CacheUpdater.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/CacheUpdater.kt @@ -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, dirtyFiles: Collection, mainArguments: List? - ): List<() -> JsIrProgramFragment> + ): List<() -> List> } fun interface JsIrCompilerICInterfaceFactory { @@ -616,7 +616,7 @@ class CacheUpdater( private fun commitCacheAndBuildModuleArtifacts( incrementalCacheArtifacts: Map, moduleNames: Map, - rebuiltFileFragments: KotlinSourceFileMap + rebuiltFileFragments: KotlinSourceFileMap> ): List = stopwatch.measure("Incremental cache - committing artifacts") { incrementalCacheArtifacts.map { (libFile, incrementalCacheArtifact) -> incrementalCacheArtifact.buildModuleArtifactAndCommitCache( @@ -630,7 +630,7 @@ class CacheUpdater( compilerForIC: JsIrCompilerICInterface, loadedFragments: Map, dirtyFiles: Map> - ): MutableList JsIrProgramFragment>> = + ): MutableList List>> = stopwatch.measure("Processing IR - lowering") { val dirtyFilesForCompiling = mutableListOf() val dirtyFilesForRestoring = mutableListOf>() @@ -737,7 +737,7 @@ class CacheUpdater( private data class FragmentGenerators( val incrementalCacheArtifacts: Map, val moduleNames: Map, - val generators: MutableList JsIrProgramFragment>> + val generators: MutableList List>> ) private fun loadIrAndMakeIrFragmentGenerators(): FragmentGenerators { @@ -751,9 +751,9 @@ class CacheUpdater( } private fun generateIrFragments( - generators: MutableList JsIrProgramFragment>> - ): KotlinSourceFileMap = stopwatch.measure("Processing IR - generating program fragments") { - val rebuiltFragments = KotlinSourceFileMutableMap() + generators: MutableList List>> + ): KotlinSourceFileMap> = stopwatch.measure("Processing IR - generating program fragments") { + val rebuiltFragments = KotlinSourceFileMutableMap>() while (generators.isNotEmpty()) { val (libFile, srcFile, fragmentGenerator) = generators.removeFirst() rebuiltFragments[libFile, srcFile] = fragmentGenerator() @@ -783,7 +783,7 @@ fun rebuildCacheForDirtyFiles( exportedDeclarations: Set, mainArguments: List?, es6mode: Boolean -): Pair>> { +): Pair>>> { val internationService = IrInterningService() val emptyMetadata = object : KotlinSourceFileExports() { override val inverseDependencies = KotlinSourceFileMap>(emptyMap()) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/HashCalculatorForIC.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/HashCalculatorForIC.kt index a365f999deb..4488e6551c6 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/HashCalculatorForIC.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/HashCalculatorForIC.kt @@ -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) } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/ICUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/ICUtils.kt index e851e893691..835a71a1ecf 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/ICUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/ICUtils.kt @@ -32,6 +32,17 @@ internal inline fun File.useCodedOutput(f: CodedOutputStream.() -> Unit) { outputStream().useCodedOutput(f) } +internal inline fun CodedOutputStream.ifNotNull(value: T?, write: (T) -> Unit) { + writeBoolNoTag(value != null) + if (value != null) { + write(value) + } +} + +internal inline fun 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" diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/IncrementalCacheArtifact.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/IncrementalCacheArtifact.kt index cda8c9fb9ff..a7cc5f58281 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/IncrementalCacheArtifact.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/IncrementalCacheArtifact.kt @@ -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) { 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, + rebuiltFileFragments: Map>, ): 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) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsExecutableProducer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsExecutableProducer.kt index a8e03c35f29..706681e0988 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsExecutableProducer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsExecutableProducer.kt @@ -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() } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsMultiModuleCache.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsMultiModuleCache.kt index 2d6ac9e8b03..a33b476b7a1 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsMultiModuleCache.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsMultiModuleCache.kt @@ -30,7 +30,8 @@ class JsMultiModuleCache(private val moduleArtifacts: List) { val optionalCrossModuleImports = hashSetOf() 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) { nameBindings[tag] = readString() } } + CachedModuleInfo( artifact = this@fetchModuleInfo, jsIrHeader = JsIrModuleHeader( @@ -52,7 +54,7 @@ class JsMultiModuleCache(private val moduleArtifacts: List) { definitions = definitions, nameBindings = nameBindings, optionalCrossModuleImports = optionalCrossModuleImports, - hasJsExports = hasJsExports, + reexportedInModuleWithName = reexportedInModuleWithName, associatedModule = null ), crossModuleReferencesHash = crossModuleReferencesHash @@ -74,8 +76,13 @@ class JsMultiModuleCache(private val moduleArtifacts: List) { 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) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/ModuleArtifact.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/ModuleArtifact.kt index a2281e8dc20..33d1dc232b1 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/ModuleArtifact.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/ModuleArtifact.kt @@ -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, private val astArtifact: File? = null) { + fun loadJsIrFragments(): List { + 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) } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ES6ConstructorLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ES6ConstructorLowering.kt index 7feb079c2dd..98e8a4e0be3 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ES6ConstructorLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ES6ConstructorLowering.kt @@ -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() { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/JsPropertyAccessorInlineLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/JsPropertyAccessorInlineLowering.kt index 0aaedb2184f..65b69f3b399 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/JsPropertyAccessorInlineLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/JsPropertyAccessorInlineLowering.kt @@ -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 diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MoveOpenClassesToSeparateFiles.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MoveOpenClassesToSeparateFiles.kt deleted file mode 100644 index 033a4a09112..00000000000 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MoveOpenClassesToSeparateFiles.kt +++ /dev/null @@ -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() - 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) } - } -} - diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt index 725116d790e..74cf3cd313c 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt @@ -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 { - 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 { - 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 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) = diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt index 0eb2b518431..08496bfb211 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt @@ -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?, - private val moduleToName: Map = emptyMap(), + moduleToName: Map = 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, val tsDeclarations: TypeScriptFragment?) + private val moduleFragmentToNameMapper = ModuleFragmentToExternalName(moduleToName) + + private class IrFileExports( + val file: IrFile, + val exports: List, + val tsDeclarations: TypeScriptFragment?, + val generatedFrom: IrFile? = null + ) private class IrAndExportedDeclarations(val fragment: IrModuleFragment, val files: List) @@ -172,15 +197,22 @@ class IrModuleToJsTransformer( return makeJsCodeGeneratorFromIr(exportData, mode) } - fun makeIrFragmentsGenerators(files: Collection, allModules: Collection): List<() -> JsIrProgramFragment> { + fun makeIrFragmentsGenerators( + dirtyFiles: Collection, + allModules: Collection + ): List<() -> List> { + 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): List { @@ -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, 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, 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, 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("")) + ) } 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 + ): JsIrProgramFragment { + val globalNames = NameTable(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 { val globalNameScope = NameTable() - - 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(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.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, + 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, + 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, + 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, diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt index ba1675df3d7..cc4acec8ec5 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt @@ -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() - 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() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIntrinsicTransformers.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIntrinsicTransformers.kt index 324422baa92..b5527116965 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIntrinsicTransformers.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIntrinsicTransformers.kt @@ -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().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) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt index 87c5b0ce153..3e5ef6523a0 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt @@ -31,15 +31,16 @@ class JsIrProgramFragment(val packageFqn: String) { class JsIrModule( val moduleName: String, val externalModuleName: String, - val fragments: List + val fragments: List, + val reexportedInModuleWithName: String? = null, ) { fun makeModuleHeader(): JsIrModuleHeader { val nameBindings = mutableMapOf() val definitions = mutableSetOf() val optionalCrossModuleImports = hashSetOf() - 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, val nameBindings: Map, val optionalCrossModuleImports: Set, - val hasJsExports: Boolean, - var associatedModule: JsIrModule? + val reexportedInModuleWithName: String? = null, + var associatedModule: JsIrModule?, ) { val externalNames: Set by lazy(LazyThreadSafetyMode.NONE) { nameBindings.keys - definitions } } @@ -89,19 +90,18 @@ class JsIrProgram(private var modules: List) { } } -class CrossModuleDependenciesResolver( - private val moduleKind: ModuleKind, - private val headers: List -) { +class CrossModuleDependenciesResolver(private val moduleKind: ModuleKind, private val headers: List) { fun resolveCrossModuleDependencies(relativeRequirePath: Boolean): Map { - val headerToBuilder = headers.associateWith { JsIrModuleCrossModuleReferenceBuilder(moduleKind, it, relativeRequirePath) } - val definitionModule = mutableMapOf() - - 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() 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, ) { val imports = mutableListOf() val exports = mutableSetOf() - var transitiveJsExportFrom = emptyList() - private lateinit var exportNames: Map // tag -> index + lateinit var exportNames: Map // 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() @@ -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, // additional Kotlin imported modules - val transitiveJsExportFrom: List, // the list of modules which provide their js exports for transitive export + val transitiveExportFrom: List, // the list of modules which provide their exports for transitive export val exports: Map, // tag -> index val imports: Map, // tag -> import statement ) { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt index f5eed8d81dd..d6fa6420057 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt @@ -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 { 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() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleFragmentToExternalName.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleFragmentToExternalName.kt new file mode 100644 index 00000000000..a3c58afa14b --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleFragmentToExternalName.kt @@ -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) { + 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" + } +} \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt index 8a736a42c91..9d0c28db124 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt @@ -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, _ -> diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/resolveTemporaryNames.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/resolveTemporaryNames.kt index 90e12b50f2e..a6263f56030 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/resolveTemporaryNames.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/resolveTemporaryNames.kt @@ -41,7 +41,7 @@ private fun JsNode.resolveNames(): Map { 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) } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/IrTypeUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/IrTypeUtils.kt index 3f28cd5172a..14681256ffe 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/IrTypeUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/IrTypeUtils.kt @@ -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) } +} \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsStaticContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsStaticContext.kt index 49a16f996d6..bd52fbe754a 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsStaticContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsStaticContext.kt @@ -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, + val mode: TranslationMode, ) : IrNamer by irNamer { val intrinsics = JsIntrinsicTransformers(backendContext) val classModels = mutableMapOf() val initializerBlock = JsCompositeBlock() + + val isPerFile: Boolean get() = mode.granularity === JsGenerationGranularity.PER_FILE } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/Keeper.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/Keeper.kt index c4ede90608c..6b6cc6ddb98 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/Keeper.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/Keeper.kt @@ -14,7 +14,8 @@ class Keeper(private val keep: Set) : IrElementVisitor = 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) { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstDeserializer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstDeserializer.kt index 32b10f31d48..070a5bb83ab 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstDeserializer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstDeserializer.kt @@ -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 { + 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 { + 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() } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstSerializer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstSerializer.kt index af555b289e8..afdb830386c 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstSerializer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstSerializer.kt @@ -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.serializeTo(output: OutputStream) { + JsIrAstSerializer().appendAll(this).saveTo(output) } private class DataWriter { @@ -89,6 +89,12 @@ private class JsIrAstSerializer { private val fileStack: Deque = ArrayDeque() private val importedNames = mutableSetOf() + fun appendAll(fragments: List): 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) diff --git a/compiler/testData/cli/js/perFileWithValidModuleKind.args b/compiler/testData/cli/js/perFileWithValidModuleKind.args new file mode 100644 index 00000000000..abb8af0e440 --- /dev/null +++ b/compiler/testData/cli/js/perFileWithValidModuleKind.args @@ -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 diff --git a/compiler/testData/cli/js/perFileWithValidModuleKind.out b/compiler/testData/cli/js/perFileWithValidModuleKind.out new file mode 100644 index 00000000000..a0aba9318ad --- /dev/null +++ b/compiler/testData/cli/js/perFileWithValidModuleKind.out @@ -0,0 +1 @@ +OK \ No newline at end of file diff --git a/compiler/testData/cli/js/perFileWithWrongModuleKind.args b/compiler/testData/cli/js/perFileWithWrongModuleKind.args new file mode 100644 index 00000000000..7594c96598b --- /dev/null +++ b/compiler/testData/cli/js/perFileWithWrongModuleKind.args @@ -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 diff --git a/compiler/testData/cli/js/perFileWithWrongModuleKind.out b/compiler/testData/cli/js/perFileWithWrongModuleKind.out new file mode 100644 index 00000000000..0c1fcfa2050 --- /dev/null +++ b/compiler/testData/cli/js/perFileWithWrongModuleKind.out @@ -0,0 +1,2 @@ +error: per-file compilation can't be used with any `moduleKind` except `es` (ECMAScript Modules) +COMPILATION_ERROR diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/JsEnvironmentConfigurator.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/JsEnvironmentConfigurator.kt index 4536a7207d0..e33dc4784d5 100644 --- a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/JsEnvironmentConfigurator.kt +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/JsEnvironmentConfigurator.kt @@ -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 { diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/impl/TemporaryDirectoryManagerImpl.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/impl/TemporaryDirectoryManagerImpl.kt index 24eb6913ace..acf19173a04 100644 --- a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/impl/TemporaryDirectoryManagerImpl.kt +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/impl/TemporaryDirectoryManagerImpl.kt @@ -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 diff --git a/compiler/tests-gen/org/jetbrains/kotlin/cli/CliTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/cli/CliTestGenerated.java index a4f51ff2d8e..27187f6bc49 100644 --- a/compiler/tests-gen/org/jetbrains/kotlin/cli/CliTestGenerated.java +++ b/compiler/tests-gen/org/jetbrains/kotlin/cli/CliTestGenerated.java @@ -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"); diff --git a/core/util.runtime/src/org/jetbrains/kotlin/utils/addToStdlib.kt b/core/util.runtime/src/org/jetbrains/kotlin/utils/addToStdlib.kt index ee925e164aa..ef56069fdaa 100644 --- a/core/util.runtime/src/org/jetbrains/kotlin/utils/addToStdlib.kt +++ b/core/util.runtime/src/org/jetbrains/kotlin/utils/addToStdlib.kt @@ -218,6 +218,8 @@ inline fun Iterable.same(extractor: (T) -> R): Boolean { inline fun runIf(condition: Boolean, block: () -> R): R? = if (condition) block() else null inline fun runUnless(condition: Boolean, block: () -> R): R? = if (condition) null else block() +inline fun A.butIf(condition: Boolean, block: (A) -> B): B = if (condition) block(this) else this + inline fun Collection.foldMap(transform: (T) -> R, operation: (R, R) -> R): R { val iterator = iterator() var result = transform(iterator.next()) diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsClass.kt b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsClass.kt index 01cb6ce103a..d54eb006d06 100644 --- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsClass.kt +++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsClass.kt @@ -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 = mutableListOf() ) : JsLiteral(), HasName { diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsExport.kt b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsExport.kt index e8123616981..7896989c272 100644 --- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsExport.kt +++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsExport.kt @@ -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) diff --git a/js/js.tests/test/org/jetbrains/kotlin/incremental/AbstractInvalidationTest.kt b/js/js.tests/test/org/jetbrains/kotlin/incremental/AbstractInvalidationTest.kt index 68fc25f91f4..302ec6f3c77 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/incremental/AbstractInvalidationTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/incremental/AbstractInvalidationTest.kt @@ -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) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrBackendFacade.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrBackendFacade.kt index 4ad3fec7801..149612cb351 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrBackendFacade.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrBackendFacade.kt @@ -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() { - 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)) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrPathReplacer.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrPathReplacer.kt index db5b03edb8e..44fed9935b6 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrPathReplacer.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrPathReplacer.kt @@ -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 val pathReplacement = stringLiteral.getReplacement() ?: return @@ -43,17 +48,24 @@ class JsIrPathReplacer(testServices: TestServices) : DeclarationTransformer { } private fun IrConst.getReplacement(): IrConst? { - 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 { 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() } -} \ No newline at end of file + + 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" + } + } +} diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsArtifactsDumpHandler.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsArtifactsDumpHandler.kt index 1d87c97b59f..8f28dc5bb3f 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsArtifactsDumpHandler.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsArtifactsDumpHandler.kt @@ -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) } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/AbstractJsPartialLinkageTestCase.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/AbstractJsPartialLinkageTestCase.kt index 0764051ea7a..d1fe65253bd 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/AbstractJsPartialLinkageTestCase.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/AbstractJsPartialLinkageTestCase.kt @@ -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()}" diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/JsIrIncrementalDataProvider.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/JsIrIncrementalDataProvider.kt index 48d0e362615..a8a3b48a08f 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/JsIrIncrementalDataProvider.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/JsIrIncrementalDataProvider.kt @@ -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) ) } ) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/RunnerUtils.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/RunnerUtils.kt index 779eeae405c..9e166590603 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/RunnerUtils.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/RunnerUtils.kt @@ -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 { +fun getAdditionalFilePaths(testServices: TestServices, mode: TranslationMode = TranslationMode.FULL_DEV): List { 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 { +fun getAdditionalMainFilePaths(testServices: TestServices, mode: TranslationMode = TranslationMode.FULL_DEV): List { 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) diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java index ed6eabf5e62..d06df3336ec 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java @@ -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 { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java index 77532aee601..dc5fc48e86f 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java @@ -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 { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java index 650b5a1eea8..19a07885c28 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java @@ -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 { diff --git a/js/js.translator/testData/box/esModules/jsExport/perFileExportedApi.kt b/js/js.translator/testData/box/esModules/jsExport/perFileExportedApi.kt new file mode 100644 index 00000000000..9f8c3bc35d6 --- /dev/null +++ b/js/js.translator/testData/box/esModules/jsExport/perFileExportedApi.kt @@ -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¬ bar&foo&ping&pong") { + return "Fail: ${res}" + } + + return "OK" +} diff --git a/js/js.translator/testData/box/esModules/jsExport/perFileExportedApi.mjs b/js/js.translator/testData/box/esModules/jsExport/perFileExportedApi.mjs new file mode 100644 index 00000000000..b8fbad10f67 --- /dev/null +++ b/js/js.translator/testData/box/esModules/jsExport/perFileExportedApi.mjs @@ -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 +}; diff --git a/js/js.translator/testData/incremental/invalidation/typeScriptExports/project.info b/js/js.translator/testData/incremental/invalidation/typeScriptExports/project.info index bd285384327..1355b89bf22 100644 --- a/js/js.translator/testData/incremental/invalidation/typeScriptExports/project.info +++ b/js/js.translator/testData/incremental/invalidation/typeScriptExports/project.info @@ -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 diff --git a/libraries/stdlib/api/js-v1/kotlin.js.kt b/libraries/stdlib/api/js-v1/kotlin.js.kt index 88b996fd708..271be73e316 100644 --- a/libraries/stdlib/api/js-v1/kotlin.js.kt +++ b/libraries/stdlib/api/js-v1/kotlin.js.kt @@ -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 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) diff --git a/libraries/stdlib/api/js/kotlin.js.kt b/libraries/stdlib/api/js/kotlin.js.kt index 1a7c76a73ee..2e968a7d86e 100644 --- a/libraries/stdlib/api/js/kotlin.js.kt +++ b/libraries/stdlib/api/js/kotlin.js.kt @@ -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 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) diff --git a/libraries/stdlib/common/src/kotlin/JsAnnotationsH.kt b/libraries/stdlib/common/src/kotlin/JsAnnotationsH.kt index 6a5aef9d245..56f843a2090 100644 --- a/libraries/stdlib/common/src/kotlin/JsAnnotationsH.kt +++ b/libraries/stdlib/common/src/kotlin/JsAnnotationsH.kt @@ -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. * diff --git a/libraries/stdlib/js-ir-minimal-for-test/build.gradle.kts b/libraries/stdlib/js-ir-minimal-for-test/build.gradle.kts index b7605e8e2d4..e0ed6e532b6 100644 --- a/libraries/stdlib/js-ir-minimal-for-test/build.gradle.kts +++ b/libraries/stdlib/js-ir-minimal-for-test/build.gradle.kts @@ -78,7 +78,7 @@ val jsMainSources by task { 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 { "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", diff --git a/libraries/stdlib/js-ir/runtime/charSequence.kt b/libraries/stdlib/js-ir/runtime/charSequenceJs.kt similarity index 100% rename from libraries/stdlib/js-ir/runtime/charSequence.kt rename to libraries/stdlib/js-ir/runtime/charSequenceJs.kt diff --git a/libraries/stdlib/js-ir/runtime/metadataUtils.kt b/libraries/stdlib/js-ir/runtime/metadataUtils.kt new file mode 100644 index 00000000000..2fb0258362c --- /dev/null +++ b/libraries/stdlib/js-ir/runtime/metadataUtils.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?) -> Metadata, + parent: Ctor?, + interfaces: Array?, + associatedObjectKey: Number?, + associatedObjects: dynamic, + suspendArity: Array? +) { + 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() + 1 + return iid.unsafeCast() +} + + +internal fun interfaceMeta(name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array?): Metadata { + return createMetadata("interface", name, associatedObjectKey, associatedObjects, suspendArity, generateInterfaceId()) +} + +internal fun objectMeta(name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array?): Metadata { + return createMetadata("object", name, associatedObjectKey, associatedObjects, suspendArity, null) +} + +internal fun classMeta(name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array?): 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?, + 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? + val iid: Int? + + var `$kClass$`: dynamic + + var errorInfo: Int? // Bits set for overridden properties: "message" => 0x1, "cause" => 0x2 +} \ No newline at end of file diff --git a/libraries/stdlib/js-ir/runtime/typeCheckUtils.kt b/libraries/stdlib/js-ir/runtime/typeCheckUtils.kt index 970d96d8000..918d873da21 100644 --- a/libraries/stdlib/js-ir/runtime/typeCheckUtils.kt +++ b/libraries/stdlib/js-ir/runtime/typeCheckUtils.kt @@ -5,95 +5,6 @@ package kotlin.js -internal fun setMetadataFor( - ctor: Ctor, - name: String?, - metadataConstructor: (name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array?) -> Metadata, - parent: Ctor?, - interfaces: Array?, - associatedObjectKey: Number?, - associatedObjects: dynamic, - suspendArity: Array? -) { - 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() + 1 - return iid.unsafeCast() -} - - -internal fun interfaceMeta(name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array?): Metadata { - return createMetadata("interface", name, associatedObjectKey, associatedObjects, suspendArity, generateInterfaceId()) -} - -internal fun objectMeta(name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array?): Metadata { - return createMetadata("object", name, associatedObjectKey, associatedObjects, suspendArity, null) -} - -internal fun classMeta(name: String?, associatedObjectKey: Number?, associatedObjects: dynamic, suspendArity: Array?): 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?, - 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? - 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() ?: return false return mask.isBitSet(iface) diff --git a/libraries/stdlib/js-v1/runtime/hacks.kt b/libraries/stdlib/js-v1/runtime/hacks.kt index 7d5d391ca65..121b8619414 100644 --- a/libraries/stdlib/js-v1/runtime/hacks.kt +++ b/libraries/stdlib/js-v1/runtime/hacks.kt @@ -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 diff --git a/libraries/stdlib/js/src/kotlin/annotations.kt b/libraries/stdlib/js/src/kotlin/annotations.kt index a613d6e1e93..eb744371dc2 100644 --- a/libraries/stdlib/js/src/kotlin/annotations.kt +++ b/libraries/stdlib/js/src/kotlin/annotations.kt @@ -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. * diff --git a/libraries/stdlib/js/src/kotlin/collections.kt b/libraries/stdlib/js/src/kotlin/collectionJs.kt similarity index 100% rename from libraries/stdlib/js/src/kotlin/collections.kt rename to libraries/stdlib/js/src/kotlin/collectionJs.kt diff --git a/libraries/stdlib/js/src/kotlin/sequence.kt b/libraries/stdlib/js/src/kotlin/sequenceJs.kt similarity index 100% rename from libraries/stdlib/js/src/kotlin/sequence.kt rename to libraries/stdlib/js/src/kotlin/sequenceJs.kt diff --git a/libraries/stdlib/js/src/kotlin/text/char.kt b/libraries/stdlib/js/src/kotlin/text/charJs.kt similarity index 100% rename from libraries/stdlib/js/src/kotlin/text/char.kt rename to libraries/stdlib/js/src/kotlin/text/charJs.kt diff --git a/libraries/stdlib/js/src/kotlin/text/string.kt b/libraries/stdlib/js/src/kotlin/text/stringJs.kt similarity index 100% rename from libraries/stdlib/js/src/kotlin/text/string.kt rename to libraries/stdlib/js/src/kotlin/text/stringJs.kt diff --git a/libraries/stdlib/src/kotlin/collections/Collections.kt b/libraries/stdlib/src/kotlin/collections/Collections.kt index f49586206b2..feba9cd7b50 100644 --- a/libraries/stdlib/src/kotlin/collections/Collections.kt +++ b/libraries/stdlib/src/kotlin/collections/Collections.kt @@ -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) diff --git a/libraries/stdlib/wasm/src/kotlin/js/annotations.kt b/libraries/stdlib/wasm/src/kotlin/js/annotations.kt index 0905b5e76d3..82c16f3e71c 100644 --- a/libraries/stdlib/wasm/src/kotlin/js/annotations.kt +++ b/libraries/stdlib/wasm/src/kotlin/js/annotations.kt @@ -30,6 +30,7 @@ public actual annotation class JsExport { */ @Retention(AnnotationRetention.BINARY) @Target( + AnnotationTarget.FILE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index b0e9ca7245d..9eee8ad04fb 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -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;