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 656bdb55c13..4411e41f02b 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 @@ -135,6 +135,9 @@ class K2JSCompilerArguments : CommonCompilerArguments() { ) var irModuleName: String? by NullableStringFreezableVar(null) + @Argument(value = "-Xir-per-module", description = "Splits generated .js per-module") + var irPerModule: Boolean by FreezableVar(false) + @Argument( value = "-Xinclude", valueDescription = "", 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 101c1917d4d..ef9f587fc6d 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 @@ -229,14 +229,19 @@ class K2JsIrCompiler : CLICompiler() { mainArguments = mainCallArguments, generateFullJs = !arguments.irDce, generateDceJs = arguments.irDce, - dceDriven = arguments.irDceDriven + dceDriven = arguments.irDceDriven, + multiModule = arguments.irPerModule, + relativeRequirePath = true ) } catch (e: JsIrCompilationError) { return COMPILATION_ERROR } val jsCode = if (arguments.irDce && !arguments.irDceDriven) compiledModule.dceJsCode!! else compiledModule.jsCode!! - outputFile.writeText(jsCode) + outputFile.writeText(jsCode.mainModule) + jsCode.dependencies.forEach { (name, content) -> + outputFile.resolveSibling("$name.js").writeText(content) + } if (arguments.generateDts) { val dtsFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "d.ts")!! dtsFile.writeText(compiledModule.tsDefinitions ?: error("No ts definitions")) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt index 3e8f29180e8..2bb84fa62c6 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt @@ -8,10 +8,7 @@ package org.jetbrains.kotlin.ir.backend.js import org.jetbrains.kotlin.backend.common.ir.isMemberOfOpenClass import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.backend.js.export.isExported -import org.jetbrains.kotlin.ir.backend.js.utils.associatedObject -import org.jetbrains.kotlin.ir.backend.js.utils.getJsName -import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName -import org.jetbrains.kotlin.ir.backend.js.utils.isAssociatedObjectAnnotatedAnnotation +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.IrClassSymbol @@ -26,11 +23,10 @@ import java.util.* fun eliminateDeadDeclarations( modules: Iterable, - context: JsIrBackendContext, - mainFunction: IrSimpleFunction? + context: JsIrBackendContext ) { - val allRoots = stageController.withInitialIr { buildRoots(modules, context, mainFunction) } + val allRoots = stageController.withInitialIr { buildRoots(modules, context) } val usefulDeclarations = usefulDeclarations(allRoots, context) @@ -43,7 +39,7 @@ private fun IrField.isConstant(): Boolean { return correspondingPropertySymbol?.owner?.isConst ?: false } -private fun buildRoots(modules: Iterable, context: JsIrBackendContext, mainFunction: IrSimpleFunction?): Iterable { +private fun buildRoots(modules: Iterable, context: JsIrBackendContext): Iterable { val rootDeclarations = (modules.flatMap { it.files } + context.packageLevelJsModules + context.externalPackageFragment.values).flatMapTo(mutableListOf()) { file -> file.declarations.flatMap { if (it is IrProperty) listOfNotNull(it.backingField, it.getter, it.setter) else listOf(it) } @@ -56,9 +52,9 @@ private fun buildRoots(modules: Iterable, context: JsIrBackend }.filter { !(it is IrField && it.isConstant() && !it.isExported(context)) } } - if (context.hasTests) rootDeclarations += context.testContainer + rootDeclarations += context.testRoots.values - if (mainFunction != null) { + JsMainFunctionDetector.getMainFunctionOrNull(modules.last())?.let { mainFunction -> rootDeclarations += mainFunction if (mainFunction.isSuspend) { rootDeclarations += context.coroutineEmptyContinuation.owner 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 63cf8955fbd..9d3a911d33c 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 @@ -92,8 +92,12 @@ class JsIrBackendContext( private val internalPackageFragmentDescriptor = EmptyPackageFragmentDescriptor(builtIns.builtInsModule, FqName("kotlin.js.internal")) val implicitDeclarationFile = run { - IrFileImpl(object : SourceManager.FileEntry { - override val name = "" + syntheticFile("implicitDeclarations", irModuleFragment) + } + + private fun syntheticFile(name: String, module: IrModuleFragment): IrFile { + return IrFileImpl(object : SourceManager.FileEntry { + override val name = "<$name>" override val maxOffset = UNDEFINED_OFFSET override fun getSourceRangeInfo(beginOffset: Int, endOffset: Int) = @@ -110,20 +114,24 @@ class JsIrBackendContext( override fun getLineNumber(offset: Int) = UNDEFINED_OFFSET override fun getColumnNumber(offset: Int) = UNDEFINED_OFFSET }, internalPackageFragmentDescriptor).also { - irModuleFragment.files += it + module.files += it } } - private var testContainerField: IrSimpleFunction? = null + private val testContainerFuns = mutableMapOf() - val hasTests get() = testContainerField != null - - val testContainer: IrSimpleFunction - get() = testContainerField ?: JsIrBuilder.buildFunction("test fun", irBuiltIns.unitType, implicitDeclarationFile).apply { - body = JsIrBuilder.buildBlockBody(emptyList()) - testContainerField = this - implicitDeclarationFile.declarations += this + fun createTestContainerFun(module: IrModuleFragment): IrSimpleFunction { + return testContainerFuns.getOrPut(module) { + val file = syntheticFile("tests", module) + JsIrBuilder.buildFunction("test fun", irBuiltIns.unitType, file).apply { + body = JsIrBuilder.buildBlockBody(emptyList()) + file.declarations += this + } } + } + + val testRoots: Map + get() = testContainerFuns override val mapping = JsMapping() override val declarationFactory = JsDeclarationFactory(mapping) 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 0b463dd44f0..8cfbe620fc6 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 @@ -618,12 +618,6 @@ private val callsLoweringPhase = makeBodyLoweringPhase( description = "Handle intrinsics" ) -private val testGenerationPhase = makeJsModulePhase( - ::TestGenerator, - name = "TestGenerationLowering", - description = "Generate invocations to kotlin.test suite and test functions" -).toModuleLowering() - private val staticMembersLoweringPhase = makeDeclarationTransformerPhase( ::StaticMembersLowering, name = "StaticMembersLowering", @@ -657,7 +651,6 @@ private val cleanupLoweringPhase = makeBodyLoweringPhase( val loweringList = listOf( scriptRemoveReceiverLowering, validateIrBeforeLowering, - testGenerationPhase, expectDeclarationsRemovingPhase, stripTypeAliasDeclarationsPhase, arrayConstructorPhase, 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 6d24d4642cb..eab379d30f6 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 @@ -14,12 +14,10 @@ import org.jetbrains.kotlin.config.languageVersionSettings import org.jetbrains.kotlin.ir.backend.js.lower.generateTests import org.jetbrains.kotlin.ir.backend.js.lower.moveBodilessDeclarationsToSeparatePlace import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer -import org.jetbrains.kotlin.ir.backend.js.utils.JsMainFunctionDetector import org.jetbrains.kotlin.ir.backend.js.utils.NameTables import org.jetbrains.kotlin.ir.declarations.IrModuleFragment import org.jetbrains.kotlin.ir.declarations.StageController import org.jetbrains.kotlin.ir.declarations.stageController -import org.jetbrains.kotlin.ir.util.DeclarationStubGenerator import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator import org.jetbrains.kotlin.ir.util.noUnboundLeft import org.jetbrains.kotlin.library.KotlinLibrary @@ -27,11 +25,13 @@ import org.jetbrains.kotlin.library.resolver.KotlinLibraryResolveResult import org.jetbrains.kotlin.name.FqName class CompilerResult( - val jsCode: String?, - val dceJsCode: String?, + val jsCode: JsCode?, + val dceJsCode: JsCode?, val tsDefinitions: String? = null ) +class JsCode(val mainModule: String, val dependencies: Iterable> = emptyList()) + fun compile( project: Project, mainModule: MainModule, @@ -45,7 +45,9 @@ fun compile( generateFullJs: Boolean = true, generateDceJs: Boolean = false, dceDriven: Boolean = false, - es6mode: Boolean = false + es6mode: Boolean = false, + multiModule: Boolean = false, + relativeRequirePath: Boolean = false ): CompilerResult { stageController = object : StageController {} @@ -54,19 +56,17 @@ fun compile( val moduleDescriptor = moduleFragment.descriptor - val mainFunction = JsMainFunctionDetector.getMainFunctionOrNull(moduleFragment) - - val context = JsIrBackendContext(moduleDescriptor, irBuiltIns, symbolTable, moduleFragment, exportedDeclarations, configuration, es6mode = es6mode) - - // Load declarations referenced during `context` initialization - val irProviders = listOf(deserializer) - ExternalDependenciesGenerator(symbolTable, irProviders, configuration.languageVersionSettings).generateUnboundSymbolsAsDependencies() - val allModules = when (mainModule) { is MainModule.SourceFiles -> dependencyModules + listOf(moduleFragment) is MainModule.Klib -> dependencyModules } + val context = JsIrBackendContext(moduleDescriptor, irBuiltIns, symbolTable, allModules.first(), exportedDeclarations, configuration, es6mode = es6mode) + + // Load declarations referenced during `context` initialization + val irProviders = listOf(deserializer) + ExternalDependenciesGenerator(symbolTable, irProviders, configuration.languageVersionSettings).generateUnboundSymbolsAsDependencies() + deserializer.postProcess() symbolTable.noUnboundLeft("Unbound symbols at the end of linker") @@ -74,32 +74,42 @@ fun compile( moveBodilessDeclarationsToSeparatePlace(context, module) } + // TODO should be done incrementally + generateTests(context, allModules.last()) + if (dceDriven) { - - // TODO we should only generate tests for the current module - // TODO should be done incrementally - allModules.forEach { module -> - generateTests(context, module) - } - val controller = MutableController(context, pirLowerings) stageController = controller controller.currentStage = controller.lowerings.size + 1 - eliminateDeadDeclarations(allModules, context, mainFunction) + eliminateDeadDeclarations(allModules, context) // TODO investigate whether this is needed anymore stageController = object : StageController { override val currentStage: Int = controller.currentStage } - val transformer = IrModuleToJsTransformer(context, mainFunction, mainArguments) - return transformer.generateModule(allModules, fullJs = true, dceJs = false) + val transformer = IrModuleToJsTransformer( + context, + mainArguments, + fullJs = true, + dceJs = false, + multiModule = multiModule, + relativeRequirePath = relativeRequirePath + ) + return transformer.generateModule(allModules) } else { jsPhases.invokeToplevel(phaseConfig, context, allModules) - val transformer = IrModuleToJsTransformer(context, mainFunction, mainArguments) - return transformer.generateModule(allModules, generateFullJs, generateDceJs) + val transformer = IrModuleToJsTransformer( + context, + mainArguments, + fullJs = generateFullJs, + dceJs = generateDceJs, + multiModule = multiModule, + relativeRequirePath = relativeRequirePath + ) + return transformer.generateModule(allModules) } } @@ -111,6 +121,6 @@ fun generateJsCode( moveBodilessDeclarationsToSeparatePlace(context, moduleFragment) jsPhases.invokeToplevel(PhaseConfig(jsPhases), context, listOf(moduleFragment)) - val transformer = IrModuleToJsTransformer(context, null, null, true, nameTables) - return transformer.generateModule(listOf(moduleFragment)).jsCode!! + val transformer = IrModuleToJsTransformer(context, null, true, nameTables) + return transformer.generateModule(listOf(moduleFragment)).jsCode!!.mainModule } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt index 5d366f196f9..1bb46710c6a 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModel.kt @@ -8,11 +8,13 @@ package org.jetbrains.kotlin.ir.backend.js.export import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrProperty import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.serialization.js.ModuleKind sealed class ExportedDeclaration data class ExportedModule( val name: String, + val moduleKind: ModuleKind, val declarations: List ) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt index 4d54c83bb1b..9c3eedd13fd 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelGenerator.kt @@ -11,7 +11,6 @@ import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.ir.backend.js.* -import org.jetbrains.kotlin.ir.backend.js.lower.ES6AddInternalParametersToConstructorPhase import org.jetbrains.kotlin.ir.backend.js.lower.ES6AddInternalParametersToConstructorPhase.* import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName import org.jetbrains.kotlin.ir.backend.js.utils.isJsExport @@ -22,11 +21,12 @@ import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.utils.addIfNotNull class ExportModelGenerator(val context: JsIrBackendContext) { - private fun generateExport(file: IrPackageFragment): List { + fun generateExport(file: IrPackageFragment): List { val namespaceFqName = file.fqName val exports = file.declarations.flatMap { declaration -> listOfNotNull(exportDeclaration(declaration)) } return when { @@ -38,7 +38,8 @@ class ExportModelGenerator(val context: JsIrBackendContext) { fun generateExport(modules: Iterable): ExportedModule = ExportedModule( - sanitizeName(context.configuration[CommonConfigurationKeys.MODULE_NAME]!!), + context.configuration[CommonConfigurationKeys.MODULE_NAME]!!, + context.configuration[JSConfigurationKeys.MODULE_KIND]!!, (context.externalPackageFragment.values + modules.flatMap { it.files }).flatMap { generateExport(it) } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt index f87e0f35e8c..00e14949eec 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/export/ExportModelToTsDeclarations.kt @@ -5,12 +5,14 @@ package org.jetbrains.kotlin.ir.backend.js.export +import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName + // TODO: Support module kinds other than plain fun ExportedModule.toTypeScript(): String { val prefix = " type Nullable = T | null | undefined\n" val body = declarations.joinToString("\n") { it.toTypeScript(" ") } - return "declare namespace $name {\n$prefix$body\n}\n" + return "declare namespace ${sanitizeName(name)} {\n$prefix$body\n}\n" } fun List.toTypeScript(indent: String): String = diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PropertyReferenceLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PropertyReferenceLowering.kt index 06828aa637e..41c61f6db0b 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PropertyReferenceLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PropertyReferenceLowering.kt @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionExpressionImpl import org.jetbrains.kotlin.ir.types.IrSimpleType import org.jetbrains.kotlin.ir.types.classOrNull +import org.jetbrains.kotlin.ir.util.file import org.jetbrains.kotlin.ir.util.render import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid @@ -28,7 +29,6 @@ import org.jetbrains.kotlin.name.Name class PropertyReferenceLowering(private val context: JsIrBackendContext) : BodyLoweringPass { - private val implicitDeclarationFile = context.implicitDeclarationFile private val referenceBuilderSymbol = context.kpropertyBuilder private val localDelegateBuilderSymbol = context.klocalDelegateBuilder private val jsClassSymbol = context.intrinsics.jsClass @@ -40,7 +40,11 @@ class PropertyReferenceLowering(private val context: JsIrBackendContext) : BodyL override fun lower(irBody: IrBody, container: IrDeclaration) { newDeclarations.clear() irBody.transformChildrenVoid(PropertyReferenceTransformer()) - implicitDeclarationFile.declarations.addAll(newDeclarations) + if (!newDeclarations.isEmpty()) { + val file = container.file + newDeclarations.forEach { it.parent = file } + file.declarations.addAll(newDeclarations) + } } private inner class PropertyReferenceTransformer : IrElementTransformerVoid() { @@ -55,8 +59,6 @@ class PropertyReferenceLowering(private val context: JsIrBackendContext) : BodyL name = Name.identifier("${property.name.asString()}\$factory") } - factoryDeclaration.parent = implicitDeclarationFile - val boundArguments = listOfNotNull(reference.dispatchReceiver, reference.extensionReceiver) val valueParameters = ArrayList(boundArguments.size) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/TestGenerator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/TestGenerator.kt index 5567986f770..bf4f0a64a57 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/TestGenerator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/TestGenerator.kt @@ -22,14 +22,14 @@ import org.jetbrains.kotlin.ir.util.isEffectivelyExternal import org.jetbrains.kotlin.name.FqName fun generateTests(context: JsIrBackendContext, moduleFragment: IrModuleFragment) { - val generator = TestGenerator(context) + val generator = TestGenerator(context) { context.createTestContainerFun(moduleFragment) } - moduleFragment.files.forEach { + moduleFragment.files.toList().forEach { generator.lower(it) } } -class TestGenerator(val context: JsIrBackendContext) : FileLoweringPass { +class TestGenerator(val context: JsIrBackendContext, val testContainerFactory: () -> IrSimpleFunction) : FileLoweringPass { override fun lower(irFile: IrFile) { irFile.declarations.forEach { @@ -44,7 +44,7 @@ class TestGenerator(val context: JsIrBackendContext) : FileLoweringPass { private val packageSuites = mutableMapOf() private fun suiteForPackage(fqName: FqName) = packageSuites.getOrPut(fqName) { - context.suiteFun!!.createInvocation(fqName.asString(), context.testContainer) + context.suiteFun!!.createInvocation(fqName.asString(), testContainerFactory()) } private data class FunctionWithBody(val function: IrSimpleFunction, val body: IrBlockBody) 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 7b530daeac0..198aa6182b8 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 @@ -5,8 +5,8 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs -import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.ir.backend.js.CompilerResult +import org.jetbrains.kotlin.ir.backend.js.JsCode import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.eliminateDeadDeclarations import org.jetbrains.kotlin.ir.backend.js.export.ExportModelGenerator @@ -26,16 +26,17 @@ import org.jetbrains.kotlin.utils.DFS class IrModuleToJsTransformer( private val backendContext: JsIrBackendContext, - private val mainFunction: IrSimpleFunction?, private val mainArguments: List?, private val generateScriptModule: Boolean = false, - var namer: NameTables = NameTables(emptyList()) + var namer: NameTables = NameTables(emptyList()), + private val fullJs: Boolean = true, + private val dceJs: Boolean = false, + private val multiModule: Boolean = false, + private val relativeRequirePath: Boolean = false ) { - val moduleName = backendContext.configuration[CommonConfigurationKeys.MODULE_NAME]!! - private val moduleKind = backendContext.configuration[JSConfigurationKeys.MODULE_KIND]!! private val generateRegionComments = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_REGION_COMMENTS) - fun generateModule(modules: Iterable, fullJs: Boolean = true, dceJs: Boolean = false): CompilerResult { + fun generateModule(modules: Iterable): CompilerResult { val additionalPackages = with(backendContext) { externalPackageFragment.values + listOf( bodilessBuiltInsPackageFragment, @@ -50,6 +51,10 @@ class IrModuleToJsTransformer( module.files.forEach { StaticMembersLowering(backendContext).lower(it) } } + if (multiModule) { + breakCrossModuleFieldAccess(backendContext, modules) + } + modules.forEach { module -> namer.merge(module.files, additionalPackages) } @@ -57,7 +62,7 @@ class IrModuleToJsTransformer( val jsCode = if (fullJs) generateWrappedModuleBody(modules, exportedModule, namer) else null val dceJsCode = if (dceJs) { - eliminateDeadDeclarations(modules, backendContext, mainFunction) + eliminateDeadDeclarations(modules, backendContext) // Use a fresh namer for DCE so that we could compare the result with DCE-driven // TODO: is this mode relevant for scripting? If yes, refactor so that the external name tables are used here when needed. val namer = NameTables(emptyList()) @@ -68,11 +73,63 @@ class IrModuleToJsTransformer( return CompilerResult(jsCode, dceJsCode, dts) } - private fun generateWrappedModuleBody(modules: Iterable, exportedModule: ExportedModule, namer: NameTables): String { - val program = JsProgram() + private fun generateWrappedModuleBody(modules: Iterable, exportedModule: ExportedModule, namer: NameTables): JsCode { + if (multiModule) { - val nameGenerator = IrNamerImpl( - newNameTables = namer + val refInfo = buildCrossModuleReferenceInfo(modules) + + val rM = modules.reversed() + + val main = rM.first() + val others = rM.drop(1) + + val mainModule = generateWrappedModuleBody2( + listOf(main), + others, + exportedModule, + namer, + refInfo + ) + + val dependencies = others.mapIndexed { index, module -> + val moduleName = sanitizeName(module.safeName) + + val exportedDeclarations = ExportModelGenerator(backendContext).let { module.files.flatMap { file -> it.generateExport(file) } } + + moduleName to generateWrappedModuleBody2( + listOf(module), + others.drop(index + 1), + ExportedModule(moduleName, exportedModule.moduleKind, exportedDeclarations), + namer, + refInfo + ) + }.reversed() + + return JsCode(mainModule, dependencies) + } else { + return JsCode( + generateWrappedModuleBody2( + modules, + emptyList(), + exportedModule, + namer, + EmptyCrossModuleReferenceInfo + ) + ) + } + } + + private fun generateWrappedModuleBody2( + modules: Iterable, + dependencies: Iterable, + exportedModule: ExportedModule, + namer: NameTables, + refInfo: CrossModuleReferenceInfo + ): String { + + val nameGenerator = refInfo.withReferenceTracking( + IrNamerImpl(newNameTables = namer), + modules ) val staticContext = JsStaticContext( backendContext = backendContext, @@ -83,9 +140,6 @@ class IrModuleToJsTransformer( staticContext = staticContext ) - val rootFunction = JsFunction(program.rootScope, JsBlock(), "root function") - val internalModuleName = JsName("_") - val (importStatements, importedJsModules) = generateImportStatements( getNameForExternalDeclaration = { rootContext.getNameForStaticDeclaration(it) }, @@ -93,40 +147,96 @@ class IrModuleToJsTransformer( ) val moduleBody = generateModuleBody(modules, rootContext) - val exportStatements = ExportModelToJsStatements(internalModuleName, namer) - .generateModuleExport(exportedModule) + val internalModuleName = JsName("_") + val exportStatements = ExportModelToJsStatements(internalModuleName, namer).generateModuleExport(exportedModule) + + val callToMain = generateCallToMain(modules, rootContext) + + val (crossModuleImports, importedKotlinModules) = generateCrossModuleImports(nameGenerator, modules, dependencies, { JsName(sanitizeName(it)) }) + val crossModuleExports = generateCrossModuleExports(modules, refInfo, internalModuleName) + + val program = JsProgram() if (generateScriptModule) { with(program.globalBlock) { - statements.addWithComment("block: imports", importStatements) + statements.addWithComment("block: imports", importStatements + crossModuleImports) statements += moduleBody - statements.addWithComment("block: exports", exportStatements) + statements.addWithComment("block: exports", exportStatements + crossModuleExports) } } else { - with(rootFunction) { + val rootFunction = JsFunction(program.rootScope, JsBlock(), "root function").apply { parameters += JsParameter(internalModuleName) - parameters += importedJsModules.map { JsParameter(it.internalName) } + parameters += (importedJsModules + importedKotlinModules).map { JsParameter(it.internalName) } with(body) { - statements.addWithComment("block: imports", importStatements) + statements.addWithComment("block: imports", importStatements + crossModuleImports) statements += moduleBody - statements.addWithComment("block: exports", exportStatements) - statements += generateCallToMain(rootContext) + statements.addWithComment("block: exports", exportStatements + crossModuleExports) + statements += callToMain statements += JsReturn(internalModuleName.makeRef()) } } program.globalBlock.statements += ModuleWrapperTranslation.wrap( - moduleName, + exportedModule.name, rootFunction, - importedJsModules, + importedJsModules + importedKotlinModules, program, - kind = moduleKind + kind = exportedModule.moduleKind ) } return program.toString() } + private fun generateCrossModuleImports( + namerWithImports: IrNamerWithImports, + currentModules: Iterable, + allowedDependencies: Iterable, + declareFreshGlobal: (String) -> JsName + ): Pair, List> { + val imports = mutableListOf() + val modules = mutableListOf() + + namerWithImports.imports().forEach { (module, names) -> + check(module in allowedDependencies) { + val deps = if (names.size > 10) "[${names.take(10).joinToString()}, ...]" else "$names" + "Module ${currentModules.map { it.name.asString() }} depend on module ${module.name.asString()} via $deps" + } + + val moduleName = declareFreshGlobal(module.safeName) + modules += JsImportedModule(moduleName.ident, moduleName, moduleName.makeRef(), relativeRequirePath) + + names.forEach { + imports += JsVars(JsVars.JsVar(JsName(it), JsNameRef(it, JsNameRef("\$crossModule\$", moduleName.makeRef())))) + } + } + + return imports to modules + } + + private fun generateCrossModuleExports( + modules: Iterable, + refInfo: CrossModuleReferenceInfo, + internalModuleName: JsName + ): List { + return modules.flatMap { + refInfo.exports(it).map { + jsAssignment( + JsNameRef(it, JsNameRef("\$crossModule\$", internalModuleName.makeRef())), + JsNameRef(it) + ).makeStmt() + } + }.let { + if (!it.isEmpty()) { + val createExportBlock = jsAssignment( + JsNameRef("\$crossModule\$", internalModuleName.makeRef()), + JsAstUtils.or(JsNameRef("\$crossModule\$", internalModuleName.makeRef()), JsObjectLiteral()) + ).makeStmt() + return listOf(createExportBlock) + it + } else it + } + } + private fun generateModuleBody(modules: Iterable, context: JsGenerationContext): List { val statements = mutableListOf().also { if (!generateScriptModule) it += JsStringLiteral("use strict").makeStmt() @@ -176,10 +286,12 @@ class IrModuleToJsTransformer( statements.addWithComment("block: post-declaration", postDeclarationBlock.statements) statements.addWithComment("block: init", context.staticContext.initializerBlock.statements) - if (backendContext.hasTests) { - statements.startRegion("block: tests") - statements += JsInvocation(context.getNameForStaticFunction(backendContext.testContainer).makeRef()).makeStmt() - statements.endRegion() + modules.forEach { + backendContext.testRoots[it]?.let { testContainer -> + statements.startRegion("block: tests") + statements += JsInvocation(context.getNameForStaticFunction(testContainer).makeRef()).makeStmt() + statements.endRegion() + } } return statements @@ -198,8 +310,9 @@ class IrModuleToJsTransformer( return listOfNotNull(mainArgumentsArray, continuation) } - private fun generateCallToMain(rootContext: JsGenerationContext): List { + private fun generateCallToMain(modules: Iterable, rootContext: JsGenerationContext): List { if (mainArguments == null) return emptyList() // in case `NO_MAIN` and `main(..)` exists + val mainFunction = JsMainFunctionDetector.getMainFunctionOrNull(modules.last()) return mainFunction?.let { val jsName = rootContext.getNameForStaticFunction(it) listOf(JsInvocation(jsName.makeRef(), generateMainArguments(it, rootContext)).makeStmt()) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleWrapperTranslation.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleWrapperTranslation.kt index 1d9aace93e2..5289c69c2a7 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleWrapperTranslation.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleWrapperTranslation.kt @@ -77,7 +77,7 @@ object ModuleWrapperTranslation { val scope = program.scope val defineName = scope.declareName("define") val invocationArgs = listOf( - JsArrayLiteral(listOf(JsStringLiteral("exports")) + importedModules.map { JsStringLiteral(it.externalName) }), + JsArrayLiteral(listOf(JsStringLiteral("exports")) + importedModules.map { JsStringLiteral(it.requireName) }), function ) @@ -94,7 +94,7 @@ object ModuleWrapperTranslation { val moduleName = scope.declareName("module") val requireName = scope.declareName("require") - val invocationArgs = importedModules.map { JsInvocation(requireName.makeRef(), JsStringLiteral(it.externalName)) } + val invocationArgs = importedModules.map { JsInvocation(requireName.makeRef(), JsStringLiteral(it.requireName)) } val invocation = JsInvocation(function, listOf(JsNameRef("exports", moduleName.makeRef())) + invocationArgs) return listOf(invocation.makeStmt()) } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/MultiModuleSupport.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/MultiModuleSupport.kt new file mode 100644 index 00000000000..1b659f29725 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/MultiModuleSupport.kt @@ -0,0 +1,217 @@ +/* + * 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.transformers.irToJs + +import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI +import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET +import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext +import org.jetbrains.kotlin.ir.backend.js.utils.IrNamer +import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName +import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter +import org.jetbrains.kotlin.ir.builders.declarations.buildFun +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrGetField +import org.jetbrains.kotlin.ir.expressions.IrSetField +import org.jetbrains.kotlin.ir.expressions.impl.* +import org.jetbrains.kotlin.ir.util.constructedClass +import org.jetbrains.kotlin.ir.util.isEffectivelyExternal +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.js.backend.ast.* +import org.jetbrains.kotlin.name.Name + +interface CrossModuleReferenceInfo { + fun exports(module: IrModuleFragment): List + + fun withReferenceTracking(namer: IrNamer, excludedModules: Iterable): IrNamerWithImports +} + +interface IrNamerWithImports : IrNamer { + fun imports(): List>> +} + +object EmptyCrossModuleReferenceInfo : CrossModuleReferenceInfo { + override fun exports(module: IrModuleFragment): List = emptyList() + + override fun withReferenceTracking(namer: IrNamer, excludedModules: Iterable): IrNamerWithImports = + object : IrNamerWithImports, IrNamer by namer { + override fun imports() = emptyList>>() + } +} + +private class CrossModuleReferenceInfoImpl(val topLevelDeclarationToModule: Map) : + CrossModuleReferenceInfo { + + private val exportedNames: MutableMap> = mutableMapOf() + + override fun exports(module: IrModuleFragment): List { + return exportedNames[module]?.toList() ?: emptyList() + } + + override fun withReferenceTracking(namer: IrNamer, excludedModules: Iterable): IrNamerWithImports { + val excludedModulesSet = excludedModules.toSet() + + return object : IrNamerWithImports, IrNamer by namer { + + override fun imports(): List>> { + return importedNames.entries.map { (module, names) -> module to names.toList() } + } + + private val importedNames: MutableMap> = mutableMapOf() + + private fun JsName.track(d: IrDeclaration): JsName { + topLevelDeclarationToModule[d]?.let { module -> + if (module !in excludedModulesSet) { + importedNames.getOrPut(module) { mutableSetOf() } += this.ident + exportedNames.getOrPut(module) { mutableSetOf() } += this.ident + } + } + + return this + } + + override fun getNameForConstructor(constructor: IrConstructor): JsName { + return namer.getNameForConstructor(constructor).track(constructor.constructedClass) + } + + override fun getNameForField(field: IrField): JsName { + return namer.getNameForField(field).track(field) + } + + override fun getNameForClass(klass: IrClass): JsName { + return namer.getNameForClass(klass).track(klass) + } + + override fun getNameForStaticFunction(function: IrSimpleFunction): JsName { + return namer.getNameForStaticFunction(function).track(function) + } + + override fun getNameForStaticDeclaration(declaration: IrDeclarationWithName): JsName { + return namer.getNameForStaticDeclaration(declaration).track(declaration) + } + + override fun getNameForProperty(property: IrProperty): JsName { + return namer.getNameForProperty(property).track(property) + } + } + } + +} + +fun buildCrossModuleReferenceInfo(modules: Iterable): CrossModuleReferenceInfo { + val map = mutableMapOf() + + modules.forEach { module -> + module.files.forEach { file -> + file.declarations.forEach { declaration -> + map[declaration] = module + } + } + } + + return CrossModuleReferenceInfoImpl(map) +} + +@OptIn(ObsoleteDescriptorBasedAPI::class) +fun breakCrossModuleFieldAccess( + context: JsIrBackendContext, + modules: Iterable +) { + val fieldToGetter = mutableMapOf() + + fun IrField.getter(): IrSimpleFunction { + return fieldToGetter.getOrPut(this) { + val fieldName = name + val getter = buildFun { + name = Name.identifier("get-$fieldName") + returnType = type + } + getter.body = IrBlockBodyImpl( + UNDEFINED_OFFSET, UNDEFINED_OFFSET, listOf( + IrReturnImpl( + UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, getter.symbol, + IrGetFieldImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, symbol, type) + ) + ) + ) + getter.parent = parent + (parent as IrDeclarationContainer).declarations += getter + + getter + } + } + + val fieldToSetter = mutableMapOf() + + fun IrField.setter(): IrSimpleFunction { + return fieldToSetter.getOrPut(this) { + val fieldName = name + val setter = buildFun { + name = Name.identifier("set-$fieldName") + returnType = context.irBuiltIns.unitType + } + + val param = setter.addValueParameter("value", type) + + setter.body = IrBlockBodyImpl( + UNDEFINED_OFFSET, UNDEFINED_OFFSET, listOf( + IrSetFieldImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, symbol, type).apply { + value = IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, param.symbol) + } + ) + ) + setter.parent = parent + (parent as IrDeclarationContainer).declarations += setter + + setter + } + } + + modules.reversed().forEach { module -> + + val moduleFields = module.files.flatMap { it.declarations.filterIsInstance() }.toSet() + + fun IrField.transformAccess(fn: IrField.() -> IrCall): IrCall? { + if (parent !is IrPackageFragment || isEffectivelyExternal() || this in moduleFields) return null + return fn() + } + + module.transformChildrenVoid(object : IrElementTransformerVoid() { + + override fun visitGetField(expression: IrGetField): IrExpression { + expression.transformChildrenVoid(this) + + return expression.symbol.owner.transformAccess { + val getter = getter() + IrCallImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, getter.returnType, getter.symbol) + } ?: expression + } + + override fun visitSetField(expression: IrSetField): IrExpression { + expression.transformChildrenVoid(this) + + return expression.symbol.owner.transformAccess { + val setter = setter() + IrCallImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, setter.returnType, setter.symbol).apply { + putValueArgument(0, expression.value) + } + } ?: expression + } + }) + } +} + +val IrModuleFragment.safeName: String + get() { + var result = name.asString() + + if (result.startsWith('<')) result = result.substring(1) + if (result.endsWith('>')) result = result.substring(0, result.length - 1) + + return sanitizeName("kotlin-$result") + } \ No newline at end of file diff --git a/compiler/testData/cli/js/jsExtraHelp.out b/compiler/testData/cli/js/jsExtraHelp.out index d45d3e2a390..eeb341e2b8c 100644 --- a/compiler/testData/cli/js/jsExtraHelp.out +++ b/compiler/testData/cli/js/jsExtraHelp.out @@ -11,6 +11,7 @@ where advanced options include: Print declarations' reachability info to stdout during performing DCE -Xir-module-name= Specify a compilation module name for IR backend -Xir-only Disables pre-IR backend + -Xir-per-module Splits generated .js per-module -Xir-produce-js Generates JS file using IR backend. Also disables pre-IR backend -Xir-produce-klib-dir Generate unpacked KLIB into parent directory of output JS file. In combination with -meta-info generates both IR and pre-IR versions of library. diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsImportedModule.kt b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsImportedModule.kt index 1cd5864e8a3..d6ba419530a 100644 --- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsImportedModule.kt +++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsImportedModule.kt @@ -16,8 +16,17 @@ package org.jetbrains.kotlin.js.backend.ast -class JsImportedModule(val externalName: String, var internalName: JsName, val plainReference: JsExpression?) { + +class JsImportedModule @JvmOverloads constructor( + val externalName: String, + var internalName: JsName, + val plainReference: JsExpression?, + val relativeRequirePath: Boolean = false +) { val key = JsImportedModuleKey(externalName, plainReference?.toString()) } +val JsImportedModule.requireName: String + get() = if (relativeRequirePath) "./$externalName.js" else externalName + data class JsImportedModuleKey(val baseName: String, val plainName: String?) \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt index c4761978614..6f429c8e4d7 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt @@ -129,6 +129,7 @@ abstract class BasicBoxTest( val expectActualLinker = EXPECT_ACTUAL_LINKER.matcher(fileContent).find() val skipDceDriven = SKIP_DCE_DRIVEN.matcher(fileContent).find() + val splitPerModule = SPLIT_PER_MODULE.matcher(fileContent).find() TestFileFactoryImpl(coroutinesPackage).use { testFactory -> val inputFiles = TestFiles.createTestFiles( @@ -173,7 +174,7 @@ abstract class BasicBoxTest( testFactory.tmpDir, file.parent, module, outputFileName, dceOutputFileName, pirOutputFileName, dependencies, allDependencies, friends, modules.size > 1, !SKIP_SOURCEMAP_REMAPPING.matcher(fileContent).find(), outputPrefixFile, outputPostfixFile, - actualMainCallParameters, testPackage, testFunction, needsFullIrRuntime, isMainModule, expectActualLinker, skipDceDriven + actualMainCallParameters, testPackage, testFunction, needsFullIrRuntime, isMainModule, expectActualLinker, skipDceDriven, splitPerModule ) when { @@ -395,7 +396,8 @@ abstract class BasicBoxTest( needsFullIrRuntime: Boolean, isMainModule: Boolean, expectActualLinker: Boolean, - skipDceDriven: Boolean + skipDceDriven: Boolean, + splitPerModule: Boolean ) { val kotlinFiles = module.files.filter { it.fileName.endsWith(".kt") } val testFiles = kotlinFiles.map { it.fileName } @@ -419,7 +421,7 @@ abstract class BasicBoxTest( val incrementalData = IncrementalData() translateFiles( psiFiles.map(TranslationUnit::SourceFile), outputFile, dceOutputFile, pirOutputFile, config, outputPrefixFile, outputPostfixFile, - mainCallParameters, incrementalData, remap, testPackage, testFunction, needsFullIrRuntime, isMainModule, skipDceDriven + mainCallParameters, incrementalData, remap, testPackage, testFunction, needsFullIrRuntime, isMainModule, skipDceDriven, splitPerModule ) if (incrementalCompilationChecksEnabled && module.hasFilesToRecompile) { @@ -471,7 +473,7 @@ abstract class BasicBoxTest( translateFiles( translationUnits, recompiledOutputFile, recompiledOutputFile, recompiledOutputFile, recompiledConfig, outputPrefixFile, outputPostfixFile, - mainCallParameters, incrementalData, remap, testPackage, testFunction, needsFullIrRuntime, false, true + mainCallParameters, incrementalData, remap, testPackage, testFunction, needsFullIrRuntime, false, true, false ) val originalOutput = FileUtil.loadFile(outputFile) @@ -544,7 +546,8 @@ abstract class BasicBoxTest( testFunction: String, needsFullIrRuntime: Boolean, isMainModule: Boolean, - skipDceDriven: Boolean + skipDceDriven: Boolean, + splitPerModule: Boolean ) { val translator = K2JSTranslator(config, false) val translationResult = translator.translateUnits(ExceptionThrowingReporter, units, mainCallParameters) @@ -929,6 +932,7 @@ abstract class BasicBoxTest( private val KJS_WITH_FULL_RUNTIME = Pattern.compile("^// *KJS_WITH_FULL_RUNTIME *\$", Pattern.MULTILINE) private val EXPECT_ACTUAL_LINKER = Pattern.compile("^// EXPECT_ACTUAL_LINKER *$", Pattern.MULTILINE) private val SKIP_DCE_DRIVEN = Pattern.compile("^// *SKIP_DCE_DRIVEN *$", Pattern.MULTILINE) + private val SPLIT_PER_MODULE = Pattern.compile("^// *SPLIT_PER_MODULE *$", Pattern.MULTILINE) @JvmStatic protected val runTestInNashorn = getBoolean("kotlin.js.useNashorn") diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt index 36998041d56..27ee9439211 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt @@ -17,6 +17,7 @@ import org.jetbrains.kotlin.js.facade.MainCallParameters import org.jetbrains.kotlin.js.facade.TranslationUnit import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.parsing.parseBoolean +import org.jetbrains.kotlin.serialization.js.ModuleKind import org.jetbrains.kotlin.test.TargetBackend import org.jetbrains.kotlin.utils.fileUtils.withReplacedExtensionOrNull import java.io.File @@ -56,6 +57,8 @@ abstract class BasicIrBoxTest( val runEs6Mode: Boolean = getBoolean("kotlin.js.ir.es6", false) + val perModule: Boolean = getBoolean("kotlin.js.ir.perModule") + private val osName: String = System.getProperty("os.name").toLowerCase() // TODO Design incremental compilation for IR and add test support @@ -63,8 +66,11 @@ abstract class BasicIrBoxTest( private val compilationCache = mutableMapOf() + private val cachedDependencies = mutableMapOf>() + override fun doTest(filePath: String, expectedResult: String, mainCallParameters: MainCallParameters, coroutinesPackage: String) { compilationCache.clear() + cachedDependencies.clear() super.doTest(filePath, expectedResult, mainCallParameters, coroutinesPackage) } @@ -86,7 +92,8 @@ abstract class BasicIrBoxTest( testFunction: String, needsFullIrRuntime: Boolean, isMainModule: Boolean, - skipDceDriven: Boolean + skipDceDriven: Boolean, + splitPerModule: Boolean ) { val filesToCompile = units.map { (it as TranslationUnit.SourceFile).file } @@ -135,18 +142,13 @@ abstract class BasicIrBoxTest( exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))), generateFullJs = true, generateDceJs = runIrDce, - es6mode = runEs6Mode + es6mode = runEs6Mode, + multiModule = splitPerModule || perModule ) - val wrappedCode = - wrapWithModuleEmulationMarkers(compiledModule.jsCode!!, moduleId = config.moduleId, moduleKind = config.moduleKind) - outputFile.write(wrappedCode) + compiledModule.jsCode!!.writeTo(outputFile, config) - compiledModule.dceJsCode?.let { dceJsCode -> - val dceWrappedCode = - wrapWithModuleEmulationMarkers(dceJsCode, moduleId = config.moduleId, moduleKind = config.moduleKind) - dceOutputFile.write(dceWrappedCode) - } + compiledModule.dceJsCode?.writeTo(dceOutputFile, config) if (generateDts) { val dtsFile = outputFile.withReplacedExtensionOrNull("_v5.js", ".d.ts") @@ -166,11 +168,9 @@ abstract class BasicIrBoxTest( mainArguments = mainCallParameters.run { if (shouldBeGenerated()) arguments() else null }, exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))), dceDriven = true, - es6mode = runEs6Mode - ).jsCode!!.let { pirCode -> - val pirWrappedCode = wrapWithModuleEmulationMarkers(pirCode, moduleId = config.moduleId, moduleKind = config.moduleKind) - pirOutputFile.write(pirWrappedCode) - } + es6mode = runEs6Mode, + multiModule = splitPerModule || perModule + ).jsCode!!.writeTo(pirOutputFile, config) } } else { generateKLib( @@ -188,6 +188,23 @@ abstract class BasicIrBoxTest( } } + private fun JsCode.writeTo(outputFile: File, config: JsConfig) { + val wrappedCode = + wrapWithModuleEmulationMarkers(mainModule, moduleId = config.moduleId, moduleKind = config.moduleKind) + outputFile.write(wrappedCode) + + val dependencyPaths = mutableListOf() + + dependencies.forEach { (moduleId, code) -> + val wrappedCode = wrapWithModuleEmulationMarkers(code, config.moduleKind, moduleId) + val dependencyPath = outputFile.absolutePath.replace("_v5.js", "-${moduleId}_v5.js") + dependencyPaths += dependencyPath + File(dependencyPath).write(wrappedCode) + } + + cachedDependencies[outputFile.absolutePath] = dependencyPaths + } + override fun dontRunOnSpecificPlatform(targetBackend: TargetBackend): Boolean { if (targetBackend != TargetBackend.JS_IR_ES6) return false if (!runEs6Mode) return false @@ -210,7 +227,8 @@ abstract class BasicIrBoxTest( // TODO: should we do anything special for module systems? // TODO: return list of js from translateFiles and provide then to this function with other js files - testChecker.check(jsFiles, testModuleName, testPackage, testFunction, expectedResult, withModuleSystem) + val allFiles = jsFiles.flatMap { file -> cachedDependencies[file]?.let { deps -> deps + file } ?: listOf(file) } + testChecker.check(allFiles, testModuleName, testPackage, testFunction, expectedResult, withModuleSystem) } } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java index 63a4f25daeb..37717bda207 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java @@ -797,6 +797,104 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test { } } + @TestMetadata("js/js.translator/testData/box/crossModuleRefIR") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class CrossModuleRefIR extends AbstractIrBoxJsES6Test { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR_ES6, testDataFilePath); + } + + public void testAllFilesPresentInCrossModuleRefIR() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/crossModuleRefIR"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR_ES6, true); + } + + @TestMetadata("callableObjectRef.kt") + public void testCallableObjectRef() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/callableObjectRef.kt"); + } + + @TestMetadata("constructor.kt") + public void testConstructor() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/constructor.kt"); + } + + @TestMetadata("export.kt") + public void testExport() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/export.kt"); + } + + @TestMetadata("inheritance.kt") + public void testInheritance() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/inheritance.kt"); + } + + @TestMetadata("inlineJsModule.kt") + public void testInlineJsModule() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/inlineJsModule.kt"); + } + + @TestMetadata("inlineJsModuleNonIdentifier.kt") + public void testInlineJsModuleNonIdentifier() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/inlineJsModuleNonIdentifier.kt"); + } + + @TestMetadata("inlineJsModulePackage.kt") + public void testInlineJsModulePackage() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/inlineJsModulePackage.kt"); + } + + @TestMetadata("inlineModule.kt") + public void testInlineModule() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/inlineModule.kt"); + } + + @TestMetadata("inlineModuleNonIndentifier.kt") + public void testInlineModuleNonIndentifier() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/inlineModuleNonIndentifier.kt"); + } + + @TestMetadata("lambda.kt") + public void testLambda() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/lambda.kt"); + } + + @TestMetadata("object.kt") + public void testObject() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/object.kt"); + } + + @TestMetadata("objectInInlineClosure.kt") + public void testObjectInInlineClosure() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/objectInInlineClosure.kt"); + } + + @TestMetadata("objectIsObject.kt") + public void testObjectIsObject() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/objectIsObject.kt"); + } + + @TestMetadata("topLevelExtension.kt") + public void testTopLevelExtension() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelExtension.kt"); + } + + @TestMetadata("topLevelFunction.kt") + public void testTopLevelFunction() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelFunction.kt"); + } + + @TestMetadata("topLevelMutableProperty.kt") + public void testTopLevelMutableProperty() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelMutableProperty.kt"); + } + + @TestMetadata("topLevelProperty.kt") + public void testTopLevelProperty() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelProperty.kt"); + } + } + @TestMetadata("js/js.translator/testData/box/dataClass") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java index 047e9eaf67f..febde25d59c 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java @@ -797,6 +797,104 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { } } + @TestMetadata("js/js.translator/testData/box/crossModuleRefIR") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class CrossModuleRefIR extends AbstractIrBoxJsTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath); + } + + public void testAllFilesPresentInCrossModuleRefIR() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/crossModuleRefIR"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true); + } + + @TestMetadata("callableObjectRef.kt") + public void testCallableObjectRef() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/callableObjectRef.kt"); + } + + @TestMetadata("constructor.kt") + public void testConstructor() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/constructor.kt"); + } + + @TestMetadata("export.kt") + public void testExport() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/export.kt"); + } + + @TestMetadata("inheritance.kt") + public void testInheritance() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/inheritance.kt"); + } + + @TestMetadata("inlineJsModule.kt") + public void testInlineJsModule() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/inlineJsModule.kt"); + } + + @TestMetadata("inlineJsModuleNonIdentifier.kt") + public void testInlineJsModuleNonIdentifier() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/inlineJsModuleNonIdentifier.kt"); + } + + @TestMetadata("inlineJsModulePackage.kt") + public void testInlineJsModulePackage() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/inlineJsModulePackage.kt"); + } + + @TestMetadata("inlineModule.kt") + public void testInlineModule() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/inlineModule.kt"); + } + + @TestMetadata("inlineModuleNonIndentifier.kt") + public void testInlineModuleNonIndentifier() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/inlineModuleNonIndentifier.kt"); + } + + @TestMetadata("lambda.kt") + public void testLambda() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/lambda.kt"); + } + + @TestMetadata("object.kt") + public void testObject() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/object.kt"); + } + + @TestMetadata("objectInInlineClosure.kt") + public void testObjectInInlineClosure() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/objectInInlineClosure.kt"); + } + + @TestMetadata("objectIsObject.kt") + public void testObjectIsObject() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/objectIsObject.kt"); + } + + @TestMetadata("topLevelExtension.kt") + public void testTopLevelExtension() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelExtension.kt"); + } + + @TestMetadata("topLevelFunction.kt") + public void testTopLevelFunction() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelFunction.kt"); + } + + @TestMetadata("topLevelMutableProperty.kt") + public void testTopLevelMutableProperty() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelMutableProperty.kt"); + } + + @TestMetadata("topLevelProperty.kt") + public void testTopLevelProperty() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelProperty.kt"); + } + } + @TestMetadata("js/js.translator/testData/box/dataClass") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index 12f017e1dbe..be76ab90f23 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -797,6 +797,104 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { } } + @TestMetadata("js/js.translator/testData/box/crossModuleRefIR") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class CrossModuleRefIR extends AbstractBoxJsTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath); + } + + public void testAllFilesPresentInCrossModuleRefIR() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/crossModuleRefIR"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true); + } + + @TestMetadata("callableObjectRef.kt") + public void testCallableObjectRef() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/callableObjectRef.kt"); + } + + @TestMetadata("constructor.kt") + public void testConstructor() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/constructor.kt"); + } + + @TestMetadata("export.kt") + public void testExport() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/export.kt"); + } + + @TestMetadata("inheritance.kt") + public void testInheritance() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/inheritance.kt"); + } + + @TestMetadata("inlineJsModule.kt") + public void testInlineJsModule() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/inlineJsModule.kt"); + } + + @TestMetadata("inlineJsModuleNonIdentifier.kt") + public void testInlineJsModuleNonIdentifier() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/inlineJsModuleNonIdentifier.kt"); + } + + @TestMetadata("inlineJsModulePackage.kt") + public void testInlineJsModulePackage() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/inlineJsModulePackage.kt"); + } + + @TestMetadata("inlineModule.kt") + public void testInlineModule() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/inlineModule.kt"); + } + + @TestMetadata("inlineModuleNonIndentifier.kt") + public void testInlineModuleNonIndentifier() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/inlineModuleNonIndentifier.kt"); + } + + @TestMetadata("lambda.kt") + public void testLambda() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/lambda.kt"); + } + + @TestMetadata("object.kt") + public void testObject() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/object.kt"); + } + + @TestMetadata("objectInInlineClosure.kt") + public void testObjectInInlineClosure() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/objectInInlineClosure.kt"); + } + + @TestMetadata("objectIsObject.kt") + public void testObjectIsObject() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/objectIsObject.kt"); + } + + @TestMetadata("topLevelExtension.kt") + public void testTopLevelExtension() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelExtension.kt"); + } + + @TestMetadata("topLevelFunction.kt") + public void testTopLevelFunction() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelFunction.kt"); + } + + @TestMetadata("topLevelMutableProperty.kt") + public void testTopLevelMutableProperty() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelMutableProperty.kt"); + } + + @TestMetadata("topLevelProperty.kt") + public void testTopLevelProperty() throws Exception { + runTest("js/js.translator/testData/box/crossModuleRefIR/topLevelProperty.kt"); + } + } + @TestMetadata("js/js.translator/testData/box/dataClass") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.translator/testData/box/crossModuleRefIR/callableObjectRef.kt b/js/js.translator/testData/box/crossModuleRefIR/callableObjectRef.kt new file mode 100644 index 00000000000..566fa235621 --- /dev/null +++ b/js/js.translator/testData/box/crossModuleRefIR/callableObjectRef.kt @@ -0,0 +1,27 @@ +// SPLIT_PER_MODULE +// EXPECTED_REACHABLE_NODES: 1289 +// MODULE: lib +// FILE: lib.kt +package lib + +object O { + operator fun invoke() = "OK" +} + +inline fun callO() = O() + +// MODULE: main(lib) +// FILE: main.kt +package main + +import lib.* + +fun box(): String { + val a = O() + if (a != "OK") return "fail: simple: $a" + + val b = callO() + if (b != "OK") return "fail: inline: $a" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/crossModuleRefIR/constructor.kt b/js/js.translator/testData/box/crossModuleRefIR/constructor.kt new file mode 100644 index 00000000000..1e865b42bc7 --- /dev/null +++ b/js/js.translator/testData/box/crossModuleRefIR/constructor.kt @@ -0,0 +1,72 @@ +// SPLIT_PER_MODULE +// EXPECTED_REACHABLE_NODES: 1291 +// MODULE: lib +// FILE: lib.kt +package lib + +class A(val x: Int) { + constructor(a: Int, b: Int) : this(a + b) +} + +external class B(x: Int) { + constructor(a: Int, b: Int) + + val x: Int +} + +// TODO: may be useful after implementing local classes in inline functions +/* +inline fun foo(p: Int, q: Int, r: Int): Pair { + class C(val x : Int) { + constructor(a: Int, b: Int) : this(a + b) + } + return Pair(C(p).x, C(q, r).x) +} +*/ + +inline fun callPrimaryConstructor(x: Int) = A(x).x + +inline fun callSecondaryConstructor(x: Int, y: Int) = A(x, y).x + +// FILE: lib.js + +function B(x, y) { + this.x = x; + if (typeof y !== 'undefined') { + this.x += y; + } +} + +// MODULE: main(lib) +// FILE: main.kt +package main + +import lib.* + +fun box(): String { + val a = A(23).x + if (a != 23) return "fail: primary constructor: $a" + + val b = A(40, 2).x + if (b != 42) return "fail: secondary constructor: $b" + + val c = B(99).x + if (c != 99) return "fail: native primary constructor: $c" + + val d = B(100, 11).x + if (d != 111) return "fail: native secondary constructor: $d" + + /* + val (e, f) = foo(123, 320, 1) + if (e != 123) return "fail: local primary constructor: $e" + if (f != 321) return "fail: local secondary constructor: $f" + */ + + val g = callPrimaryConstructor(55) + if (g != 55) return "fail: primary constructor from inline function: $g" + + val h = callSecondaryConstructor(990, 9) + if (h != 999) return "fail: secondary constructor from inline function: $h" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/crossModuleRefIR/export.kt b/js/js.translator/testData/box/crossModuleRefIR/export.kt new file mode 100644 index 00000000000..feacd77b909 --- /dev/null +++ b/js/js.translator/testData/box/crossModuleRefIR/export.kt @@ -0,0 +1,32 @@ +// IGNORE_BACKEND: JS +// SPLIT_PER_MODULE +// RUN_PLAIN_BOX_FUNCTION +// EXPECTED_REACHABLE_NODES: 1316 + +// MODULE: lib1 +// FILE: lib1.kt + +@JsExport +fun O(): String = "O" + +// MODULE: lib2(lib1) +// FILE: lib2.kt + +@JsExport +fun K(): String = "K" + + +// MODULE: main(lib1, lib2) +// FILE: main.kt + +@JsExport +fun test() = O() + K() + +// FILE: test.js + +function box() { + + if (main.test() != "OK") return "fail 1"; + + return kotlin_lib1.O() + kotlin_lib2.K(); +} \ No newline at end of file diff --git a/js/js.translator/testData/box/crossModuleRefIR/inheritance.kt b/js/js.translator/testData/box/crossModuleRefIR/inheritance.kt new file mode 100644 index 00000000000..c8c65873e54 --- /dev/null +++ b/js/js.translator/testData/box/crossModuleRefIR/inheritance.kt @@ -0,0 +1,27 @@ +// SPLIT_PER_MODULE +// EXPECTED_REACHABLE_NODES: 1292 +// MODULE: lib +// FILE: lib.kt + +package lib + +open class A { + fun foo() = 23 +} + +// MODULE: main(lib) +// FILE: main.kt + +package main + +import lib.A + +class B : A() { + fun bar() = foo() + 1 +} + +fun box(): String { + val result = B().bar() + if (result != 24) return "fail: $result" + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/crossModuleRefIR/inlineJsModule.kt b/js/js.translator/testData/box/crossModuleRefIR/inlineJsModule.kt new file mode 100644 index 00000000000..8dfc47bea58 --- /dev/null +++ b/js/js.translator/testData/box/crossModuleRefIR/inlineJsModule.kt @@ -0,0 +1,26 @@ +// SPLIT_PER_MODULE +// EXPECTED_REACHABLE_NODES: 1316 +// MODULE: lib1 +// FILE: lib1.js +define("lib1", [], function() { + return function() { + return "OK"; + } +}) + +// MODULE: lib2(lib1) +// FILE: lib2.kt +// MODULE_KIND: AMD +@JsModule("lib1") +external fun foo(): String + +// MODULE: lib3(lib2) +// FILE: lib3.kt +// MODULE_KIND: AMD +inline fun bar() = foo() + +// MODULE: main(lib3) +// FILE: main.kt +// MODULE_KIND: AMD + +fun box() = bar() \ No newline at end of file diff --git a/js/js.translator/testData/box/crossModuleRefIR/inlineJsModuleNonIdentifier.kt b/js/js.translator/testData/box/crossModuleRefIR/inlineJsModuleNonIdentifier.kt new file mode 100644 index 00000000000..6acc237518c --- /dev/null +++ b/js/js.translator/testData/box/crossModuleRefIR/inlineJsModuleNonIdentifier.kt @@ -0,0 +1,26 @@ +// SPLIT_PER_MODULE +// EXPECTED_REACHABLE_NODES: 1316 +// MODULE: lib-1 +// FILE: lib-1.js +define("lib-1", [], function() { + return function() { + return "OK"; + } +}) + +// MODULE: lib2(lib-1) +// FILE: lib2.kt +// MODULE_KIND: AMD +@JsModule("lib-1") +external fun foo(): String + +// MODULE: lib3(lib2) +// FILE: lib3.kt +// MODULE_KIND: AMD +inline fun bar() = foo() + +// MODULE: main(lib3) +// FILE: main.kt +// MODULE_KIND: AMD + +fun box() = bar() \ No newline at end of file diff --git a/js/js.translator/testData/box/crossModuleRefIR/inlineJsModulePackage.kt b/js/js.translator/testData/box/crossModuleRefIR/inlineJsModulePackage.kt new file mode 100644 index 00000000000..68eec4a1c8d --- /dev/null +++ b/js/js.translator/testData/box/crossModuleRefIR/inlineJsModulePackage.kt @@ -0,0 +1,29 @@ +// SPLIT_PER_MODULE +// EXPECTED_REACHABLE_NODES: 1284 +// MODULE: lib1 +// FILE: lib1.js +define("lib1", [], function() { + return { + foo: function() { + return "OK"; + } + }; +}) + +// MODULE: lib2(lib1) +// FILE: lib2.kt +// MODULE_KIND: AMD +@file:JsModule("lib1") + +external fun foo(): String + +// MODULE: lib3(lib2) +// FILE: lib3.kt +// MODULE_KIND: AMD +inline fun bar() = foo() + +// MODULE: main(lib3) +// FILE: main.kt +// MODULE_KIND: AMD + +fun box() = bar() \ No newline at end of file diff --git a/js/js.translator/testData/box/crossModuleRefIR/inlineModule.kt b/js/js.translator/testData/box/crossModuleRefIR/inlineModule.kt new file mode 100644 index 00000000000..7f10e58ed89 --- /dev/null +++ b/js/js.translator/testData/box/crossModuleRefIR/inlineModule.kt @@ -0,0 +1,16 @@ +// SPLIT_PER_MODULE +// EXPECTED_REACHABLE_NODES: 1283 +// MODULE: lib1 +// FILE: lib1.kt + +fun foo() = "OK" + +// MODULE: lib2(lib1) +// FILE: lib2.kt + +inline fun bar() = foo() + +// MODULE: main(lib1, lib2) +// FILE: main.kt + +fun box() = bar() \ No newline at end of file diff --git a/js/js.translator/testData/box/crossModuleRefIR/inlineModuleNonIndentifier.kt b/js/js.translator/testData/box/crossModuleRefIR/inlineModuleNonIndentifier.kt new file mode 100644 index 00000000000..85e41aedc26 --- /dev/null +++ b/js/js.translator/testData/box/crossModuleRefIR/inlineModuleNonIndentifier.kt @@ -0,0 +1,16 @@ +// SPLIT_PER_MODULE +// EXPECTED_REACHABLE_NODES: 1283 +// MODULE: 1 +// FILE: lib1.kt + +fun foo() = "OK" + +// MODULE: 2(1) +// FILE: lib2.kt + +inline fun bar() = foo() + +// MODULE: main(2) +// FILE: main.kt + +fun box() = bar() \ No newline at end of file diff --git a/js/js.translator/testData/box/crossModuleRefIR/lambda.kt b/js/js.translator/testData/box/crossModuleRefIR/lambda.kt new file mode 100644 index 00000000000..99dfb98d71f --- /dev/null +++ b/js/js.translator/testData/box/crossModuleRefIR/lambda.kt @@ -0,0 +1,19 @@ +// SPLIT_PER_MODULE +// EXPECTED_REACHABLE_NODES: 1283 +// MODULE: lib +// FILE: lib.kt +package lib + +fun bar(f: () -> String) = f() + +inline fun foo(): String { + return bar { "OK" } +} + +// MODULE: main(lib) +// FILE: main.kt +package main + +import lib.* + +fun box() = foo() \ No newline at end of file diff --git a/js/js.translator/testData/box/crossModuleRefIR/object.kt b/js/js.translator/testData/box/crossModuleRefIR/object.kt new file mode 100644 index 00000000000..51dec0d7a93 --- /dev/null +++ b/js/js.translator/testData/box/crossModuleRefIR/object.kt @@ -0,0 +1,37 @@ +// SPLIT_PER_MODULE +// EXPECTED_REACHABLE_NODES: 1289 +// MODULE: lib +// FILE: lib.kt +package lib + +var log = "" + +object O { + init { + log += "O.init;" + } + + fun result() = "OK" +} + +fun getResult(): String { + log += "before;" + val result = O.result() + log += "after;" + return result +} + +// MODULE: main(lib) +// FILE: main.kt +package main + +import lib.* + +fun box(): String { + val result = getResult() + if (result != "OK") return "fail: unexpected result: $result" + + if (log != "before;O.init;after;") return "fail: wrong evaluation order: $log" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/crossModuleRefIR/objectInInlineClosure.kt b/js/js.translator/testData/box/crossModuleRefIR/objectInInlineClosure.kt new file mode 100644 index 00000000000..43c364d6dfe --- /dev/null +++ b/js/js.translator/testData/box/crossModuleRefIR/objectInInlineClosure.kt @@ -0,0 +1,27 @@ +// SPLIT_PER_MODULE +// EXPECTED_REACHABLE_NODES: 1382 +// MODULE: lib +// FILE: lib.kt +package lib + +object O { + val result = "OK" + + inline fun foo(): String { + val o = object { + fun bar() = O + } + return fetch(o.bar()) + } +} + +fun fetch(o: O) = o.result + + +// MODULE: main(lib) +// FILE: main.kt +package main + +import lib.* + +fun box() = O.foo() \ No newline at end of file diff --git a/js/js.translator/testData/box/crossModuleRefIR/objectIsObject.kt b/js/js.translator/testData/box/crossModuleRefIR/objectIsObject.kt new file mode 100644 index 00000000000..4b4580c2a0e --- /dev/null +++ b/js/js.translator/testData/box/crossModuleRefIR/objectIsObject.kt @@ -0,0 +1,21 @@ +// SPLIT_PER_MODULE +// EXPECTED_REACHABLE_NODES: 1284 +// MODULE: lib +// FILE: lib.kt +package lib + +object O + +// MODULE: main(lib) +// FILE: main.kt +package main + +import lib.* + +fun box(): String { + var o: Any = O + if (o !is O) return "fail1" + if (!(o is O)) return "fail2" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/crossModuleRefIR/topLevelExtension.kt b/js/js.translator/testData/box/crossModuleRefIR/topLevelExtension.kt new file mode 100644 index 00000000000..003dd1a2a7e --- /dev/null +++ b/js/js.translator/testData/box/crossModuleRefIR/topLevelExtension.kt @@ -0,0 +1,44 @@ +// SPLIT_PER_MODULE +// EXPECTED_REACHABLE_NODES: 1290 +// MODULE: lib +// FILE: lib.kt + +package lib + +class A(val x: Int) + +fun A.foo() = 23 + x + +inline fun A.baz() = 99 + x + +inline fun A.callFoo() = foo() + +inline fun A.buzz(): Int { + val o = object { + fun f() = 111 + x + } + return o.f() +} + +// MODULE: main(lib) +// FILE: main.kt + +package main + +import lib.* + +fun box(): String { + val a = A(1).foo() + if (a != 24) return "fail: simple function: $a" + + val c = A(1).baz() + if (c != 100) return "fail: inline function: $c" + + val d = A(1).buzz() + if (d != 112) return "fail: inline function with object expression: $d" + + val e = A(2).callFoo() + if (e != 25) return "fail: inline function calling another function: $e" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/crossModuleRefIR/topLevelFunction.kt b/js/js.translator/testData/box/crossModuleRefIR/topLevelFunction.kt new file mode 100644 index 00000000000..6322d5ec271 --- /dev/null +++ b/js/js.translator/testData/box/crossModuleRefIR/topLevelFunction.kt @@ -0,0 +1,51 @@ +// SPLIT_PER_MODULE +// EXPECTED_REACHABLE_NODES: 1287 +// MODULE: lib +// FILE: lib.kt + +package lib + +fun foo() = 23 + +external fun bar(): Int = definedExternally + +inline fun baz() = 99 + +inline fun callFoo() = foo() + +inline fun buzz(): Int { + val o = object { + fun f() = 111 + } + return o.f() +} + +// FILE: lib.js + +function bar() { + return 42; +} + +// MODULE: main(lib) +// FILE: main.kt + +package main + +fun box(): String { + val a = lib.foo() + if (a != 23) return "fail: simple function: $a" + + val b = lib.bar() + if (b != 42) return "fail: native function: $b" + + val c = lib.baz() + if (c != 99) return "fail: inline function: $c" + + val d = lib.buzz() + if (d != 111) return "fail: inline function with object expression: $d" + + val e = lib.callFoo() + if (e != 23) return "fail: inline function calling another function: $e" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/crossModuleRefIR/topLevelMutableProperty.kt b/js/js.translator/testData/box/crossModuleRefIR/topLevelMutableProperty.kt new file mode 100644 index 00000000000..f14b85e62e4 --- /dev/null +++ b/js/js.translator/testData/box/crossModuleRefIR/topLevelMutableProperty.kt @@ -0,0 +1,38 @@ +// SPLIT_PER_MODULE +// EXPECTED_REACHABLE_NODES: 1286 +// MODULE: lib +// FILE: lib.kt +package lib + +var foo = 23 + +var bar: Int = 42 + get() = field + set(value) { + field = value + } + +@JsName("faz") var baz = 99 + +// MODULE: main(lib) +// FILE: lib.kt +package main + +import lib.* + +fun box(): String { + if (foo != 23) return "fail: simple property initial value: $foo" + foo = 24 + if (foo != 24) return "fail: simple property new value: $foo" + + if (bar != 42) return "fail: property with accessor initial value: $bar" + bar = 43 + if (bar != 43) return "fail: property with accessor new value: $bar" + + if (baz != 99) return "fail: renamed property initial value: $baz" + baz = 100 + if (baz != 100) return "fail: renamed property new value: $baz" + + + return "OK" +} diff --git a/js/js.translator/testData/box/crossModuleRefIR/topLevelProperty.kt b/js/js.translator/testData/box/crossModuleRefIR/topLevelProperty.kt new file mode 100644 index 00000000000..2b5605f50b7 --- /dev/null +++ b/js/js.translator/testData/box/crossModuleRefIR/topLevelProperty.kt @@ -0,0 +1,48 @@ +// SPLIT_PER_MODULE +// EXPECTED_REACHABLE_NODES: 1287 +// MODULE: lib +// FILE: lib.kt +package lib + +val foo = 23 + +val boo: Int + get() = 42 + +external val bar: Int = definedExternally + +external val far: Int + get() = definedExternally + +// TODO: annotations like this are not serialized properly. Uncomment after KT-14529 gets fixed +/* +val fuzz: Int + @JsName("getBuzz") get() = 55 + */ + +inline fun fetchFoo() = foo + +@JsName("fee") +val tee = 2525 + +// FILE: lib.js + +var bar = 99 +var far = 111 + +// MODULE: main(lib) +// FILE: lib.kt +package main + +import lib.* + +fun box(): String { + if (foo != 23) return "fail: simple property: $foo" + if (boo != 42) return "fail: property with accessor: $boo" + if (bar != 99) return "fail: native property: $bar" + if (far != 111) return "fail: native property with accessor: $far" + //if (fuzz != 55) return "fail: property with JsName on accessor: $fuzz" + if (tee != 2525) return "fail: native property with JsName: $tee" + + return "OK" +}