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 c2e89548701..8f167bb12e3 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 @@ -48,7 +48,8 @@ class JsIrBackendContext( val symbolTable: SymbolTable, irModuleFragment: IrModuleFragment, override val configuration: CompilerConfiguration, - val dependencies: List + val dependencies: List, + val moduleType: ModuleType ) : CommonBackendContext { override val builtIns = module.builtIns @@ -56,6 +57,9 @@ class JsIrBackendContext( val phaseConfig = PhaseConfig(jsPhases, configuration) override var inVerbosePhase: Boolean = false + val packageLevelJsModules = mutableListOf() + val declarationLevelJsModules = mutableListOf() + val internalPackageFragmentDescriptor = KnownPackageFragmentDescriptor(builtIns.builtInsModule, FqName("kotlin.js.internal")) val implicitDeclarationFile by lazy { IrFileImpl(object : SourceManager.FileEntry { @@ -176,9 +180,6 @@ class JsIrBackendContext( val originalModuleIndex = ModuleIndex(irModuleFragment) - lateinit var moduleFragmentCopy: IrModuleFragment - lateinit var jsProgram: JsNode - fun getOperatorByName(name: Name, type: KotlinType) = operatorMap[name]?.get(type) override val ir = object : Ir(this, irModuleFragment) { @@ -291,4 +292,4 @@ class JsIrBackendContext( /*TODO*/ print(message) } -} \ No newline at end of file +} 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 351b22b8cf7..d795a7524cc 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 @@ -10,16 +10,13 @@ import org.jetbrains.kotlin.backend.common.lower.* import org.jetbrains.kotlin.backend.common.phaser.* 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.coroutines.CoroutineIntrinsicLowering import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.SuspendFunctionsLowering import org.jetbrains.kotlin.ir.backend.js.lower.inline.FunctionInlining import org.jetbrains.kotlin.ir.backend.js.lower.inline.RemoveInlineFunctionsWithReifiedTypeParametersLowering import org.jetbrains.kotlin.ir.backend.js.lower.inline.ReturnableBlockLowering import org.jetbrains.kotlin.ir.backend.js.lower.inline.replaceUnboundSymbols -import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrModuleFragment -import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols import org.jetbrains.kotlin.ir.util.patchDeclarationParents private fun DeclarationContainerLoweringPass.runOnFilesPostfix(files: Iterable) = files.forEach { runOnFilePostfix(it) } @@ -68,8 +65,10 @@ private fun makeCustomJsModulePhase( } ) -private val moveBodilessDeclarationsToSeparatePlacePhase = makeJsModulePhase( - ::MoveBodilessDeclarationsToSeparatePlace, +private val moveBodilessDeclarationsToSeparatePlacePhase = makeCustomJsModulePhase( + { context, module -> + moveBodilessDeclarationsToSeparatePlace(context, module) + }, name = "MoveBodilessDeclarationsToSeparatePlace", description = "Move `external` and `built-in` declarations into separate place to make the following lowerings do not care about them" ) @@ -80,12 +79,6 @@ private val expectDeclarationsRemovingPhase = makeJsModulePhase( description = "Remove expect declaration from module fragment" ) -private val coroutineIntrinsicLoweringPhase = makeJsModulePhase( - ::CoroutineIntrinsicLowering, - name = "CoroutineIntrinsicLowering", - description = "Replace common coroutine intrinsics with platform specific ones" -) - private val arrayInlineConstructorLoweringPhase = makeJsModulePhase( ::ArrayInlineConstructorLowering, name = "ArrayInlineConstructorLowering", @@ -98,13 +91,6 @@ private val lateinitLoweringPhase = makeJsModulePhase( description = "Insert checks for lateinit field references" ) -private val moduleCopyingPhase = makeCustomJsModulePhase( - { context, module -> context.moduleFragmentCopy = module.deepCopyWithSymbols() }, - name = "ModuleCopying", - description = " Copy current module to make it accessible from different one", - prerequisite = setOf(lateinitLoweringPhase) -) - private val functionInliningPhase = makeCustomJsModulePhase( { context, module -> FunctionInlining(context).inline(module) @@ -113,7 +99,7 @@ private val functionInliningPhase = makeCustomJsModulePhase( }, name = "FunctionInliningPhase", description = "Perform function inlining", - prerequisite = setOf(moduleCopyingPhase, lateinitLoweringPhase, arrayInlineConstructorLoweringPhase, coroutineIntrinsicLoweringPhase) + prerequisite = setOf(lateinitLoweringPhase, arrayInlineConstructorLoweringPhase) ) private val removeInlineFunctionsWithReifiedTypeParametersLoweringPhase = makeJsModulePhase( @@ -196,7 +182,7 @@ private val suspendFunctionsLoweringPhase = makeJsModulePhase( ::SuspendFunctionsLowering, name = "SuspendFunctionsLowering", description = "Transform suspend functions into CoroutineImpl instance and build state machine", - prerequisite = setOf(unitMaterializationLoweringPhase, coroutineIntrinsicLoweringPhase) + prerequisite = setOf(unitMaterializationLoweringPhase) ) private val privateMembersLoweringPhase = makeJsModulePhase( @@ -347,21 +333,13 @@ private val callsLoweringPhase = makeJsModulePhase( description = "Handle intrinsics" ) -private val irToJsPhase = makeCustomJsModulePhase( - { context, module -> context.jsProgram = IrModuleToJsTransformer(context).let { module.accept(it, null) } }, - name = "IrModuleToJsTransformer", - description = "Generate JsAst from IrTree" -) - val jsPhases = namedIrModulePhase( name = "IrModuleLowering", description = "IR module lowering", lower = moveBodilessDeclarationsToSeparatePlacePhase then expectDeclarationsRemovingPhase then - coroutineIntrinsicLoweringPhase then arrayInlineConstructorLoweringPhase then lateinitLoweringPhase then - moduleCopyingPhase then functionInliningPhase then removeInlineFunctionsWithReifiedTypeParametersLoweringPhase then throwableSuccessorsLoweringPhase then @@ -396,6 +374,5 @@ val jsPhases = namedIrModulePhase( blockDecomposerLoweringPhase then primitiveCompanionLoweringPhase then constLoweringPhase then - callsLoweringPhase then - irToJsPhase -) \ No newline at end of file + callsLoweringPhase +) 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 e8eddd4cea9..d20e379e35e 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 @@ -7,38 +7,57 @@ package org.jetbrains.kotlin.ir.backend.js import com.intellij.openapi.project.Project import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel +import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.languageVersionSettings -import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl +import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.CoroutineIntrinsicLowering +import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer import org.jetbrains.kotlin.ir.declarations.IrModuleFragment import org.jetbrains.kotlin.ir.util.SymbolTable +import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi2ir.Psi2IrTranslator +import org.jetbrains.kotlin.utils.DFS -data class Result(val moduleDescriptor: ModuleDescriptor, val generatedCode: String, val moduleFragment: IrModuleFragment) +enum class ModuleType { + TEST_RUNTIME, + SECONDARY, + MAIN +} + +class CompiledModule( + val moduleName: String, + val generatedCode: String?, + val moduleFragment: IrModuleFragment?, + val moduleType: ModuleType, + val dependencies: List +) { + val descriptor + get() = moduleFragment!!.descriptor as ModuleDescriptorImpl +} fun compile( project: Project, files: List, configuration: CompilerConfiguration, - export: FqName? = null, - dependencies: List = listOf(), - irDependencyModules: List = listOf(), - builtInsModule: ModuleDescriptorImpl? = null -): Result { + export: List = emptyList(), + dependencies: List = emptyList(), + builtInsModule: CompiledModule? = null, + moduleType: ModuleType +): CompiledModule { val analysisResult = TopDownAnalyzerFacadeForJS.analyzeFiles( files, project, configuration, - dependencies.mapNotNull { it as? ModuleDescriptorImpl }, + dependencies.map { it.descriptor }, emptyList(), thisIsBuiltInsModule = builtInsModule == null, - customBuiltInsModule = builtInsModule + customBuiltInsModule = builtInsModule?.descriptor ) ProgressIndicatorAndCompilationCanceledStatus.checkCanceled() @@ -46,7 +65,7 @@ fun compile( TopDownAnalyzerFacadeForJS.checkForErrors(files, analysisResult.bindingContext) val symbolTable = SymbolTable() - irDependencyModules.forEach { symbolTable.loadModule(it)} + dependencies.forEach { symbolTable.loadModule(it.moduleFragment!!) } val psi2IrTranslator = Psi2IrTranslator(configuration.languageVersionSettings) val psi2IrContext = psi2IrTranslator.createGeneratorContext(analysisResult.moduleDescriptor, analysisResult.bindingContext, symbolTable) @@ -59,10 +78,43 @@ fun compile( psi2IrContext.symbolTable, moduleFragment, configuration, - irDependencyModules + dependencies, + moduleType ) + // CoroutineIntrinsicLowering currently have to be applied to dependencies + // before running inline pass for current module. + // Therefore we run it here before producing ir for current module. + // TODO: Find a better way to implement coroutine intrinsics + val coroutineIntrinsicLowering = CoroutineIntrinsicLowering(context) + moduleFragment.files.forEach { file -> coroutineIntrinsicLowering.lower(file) } + + // TODO: Split compilation into two steps: kt -> ir, ir -> js + val moduleName = configuration[CommonConfigurationKeys.MODULE_NAME]!! + var moduleFragmentCopy: IrModuleFragment? = null + when (moduleType) { + ModuleType.MAIN -> { + val moduleDependencies: List = + DFS.topologicalOrder(dependencies, CompiledModule::dependencies) + .filter { it.moduleType == ModuleType.SECONDARY } + + val fileDependencies = moduleDependencies.flatMap { it.moduleFragment!!.files } + + moduleFragment.files.addAll(0, fileDependencies) + } + + ModuleType.SECONDARY -> { + return CompiledModule(moduleName, null, moduleFragment, moduleType, dependencies) + } + + ModuleType.TEST_RUNTIME -> { + moduleFragmentCopy = moduleFragment.deepCopyWithSymbols() + } + } + jsPhases.invokeToplevel(context.phaseConfig, context, moduleFragment) - return Result(analysisResult.moduleDescriptor, context.jsProgram.toString(), context.moduleFragmentCopy) -} \ No newline at end of file + val jsProgram = moduleFragment.accept(IrModuleToJsTransformer(context), null) + + return CompiledModule(moduleName, jsProgram.toString(), moduleFragmentCopy, moduleType, dependencies) +} diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MoveBodilessDeclarationsToSeparatePlace.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MoveBodilessDeclarationsToSeparatePlace.kt index a3efe669fe0..012f9dc8d89 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MoveBodilessDeclarationsToSeparatePlace.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MoveBodilessDeclarationsToSeparatePlace.kt @@ -5,25 +5,22 @@ package org.jetbrains.kotlin.ir.backend.js.lower -import org.jetbrains.kotlin.backend.common.FileLoweringPass import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.lower.inline.addChild -import org.jetbrains.kotlin.ir.declarations.IrClass -import org.jetbrains.kotlin.ir.declarations.IrDeclaration -import org.jetbrains.kotlin.ir.declarations.IrExternalPackageFragment -import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.backend.js.utils.getJsModule +import org.jetbrains.kotlin.ir.backend.js.utils.getJsQualifier +import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl import org.jetbrains.kotlin.ir.symbols.IrExternalPackageFragmentSymbol import org.jetbrains.kotlin.ir.util.isEffectivelyExternal +import org.jetbrains.kotlin.ir.util.transformFlat import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name -class MoveBodilessDeclarationsToSeparatePlace() : FileLoweringPass { +fun moveBodilessDeclarationsToSeparatePlace(context: JsIrBackendContext, module: IrModuleFragment) { - constructor(context: JsIrBackendContext) : this() - - private val builtInClasses = listOf( + val builtInClasses = listOf( "String", "Nothing", "Array", @@ -44,10 +41,10 @@ class MoveBodilessDeclarationsToSeparatePlace() : FileLoweringPass { "Double" ).map { Name.identifier(it) }.toSet() - private fun isBuiltInClass(declaration: IrDeclaration): Boolean = + fun isBuiltInClass(declaration: IrDeclaration): Boolean = declaration is IrClass && declaration.name in builtInClasses - private val packageFragment = IrExternalPackageFragmentImpl(object : IrExternalPackageFragmentSymbol { + val packageFragment = IrExternalPackageFragmentImpl(object : IrExternalPackageFragmentSymbol { override val descriptor: PackageFragmentDescriptor get() = error("Operation is unsupported") @@ -61,16 +58,31 @@ class MoveBodilessDeclarationsToSeparatePlace() : FileLoweringPass { } }, FqName.ROOT) - override fun lower(irFile: IrFile) { + + fun lowerFile(irFile: IrFile): IrFile? { + if (irFile.getJsModule() != null || irFile.getJsQualifier() != null) { + context.packageLevelJsModules.add(irFile) + return null + } + val it = irFile.declarations.iterator() while (it.hasNext()) { val d = it.next() if (d.isEffectivelyExternal() || isBuiltInClass(d)) { + + if (d.getJsModule() != null) + context.declarationLevelJsModules.add(d) + it.remove() packageFragment.addChild(d) } } + return irFile } -} \ No newline at end of file + + module.files.transformFlat { irFile -> + listOfNotNull(lowerFile(irFile)) + } +} 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 209455085c5..5efc6661f1a 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,47 +5,174 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs +import org.jetbrains.kotlin.config.CommonConfigurationKeys +import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext +import org.jetbrains.kotlin.ir.backend.js.ModuleType 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.getJsModule +import org.jetbrains.kotlin.ir.backend.js.utils.getJsQualifier +import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithVisibility import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.jetbrains.kotlin.ir.declarations.IrSymbolOwner +import org.jetbrains.kotlin.ir.util.isEffectivelyExternal import org.jetbrains.kotlin.js.backend.ast.* +import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.utils.DFS class IrModuleToJsTransformer(private val backendContext: JsIrBackendContext) : BaseIrElementToJsNodeTransformer { - override fun visitModuleFragment(declaration: IrModuleFragment, data: Nothing?): JsNode { - val program = JsProgram() - val rootContext = JsGenerationContext(JsRootScope(program), backendContext) + val moduleName = backendContext.configuration[CommonConfigurationKeys.MODULE_NAME]!! + private val moduleKind = backendContext.configuration[JSConfigurationKeys.MODULE_KIND]!! + + private fun generateModuleBody(module: IrModuleFragment, context: JsGenerationContext): List { + val statements = mutableListOf() // TODO: fix it up with new name generator - val anyName = rootContext.getNameForSymbol(backendContext.irBuiltIns.anyClass) - val throwableName = rootContext.getNameForSymbol(backendContext.irBuiltIns.throwableClass) + val anyName = context.getNameForSymbol(backendContext.irBuiltIns.anyClass) + val throwableName = context.getNameForSymbol(backendContext.irBuiltIns.throwableClass) - program.globalBlock.statements += JsVars(JsVars.JsVar(anyName, Namer.JS_OBJECT)) - program.globalBlock.statements += JsVars(JsVars.JsVar(throwableName, Namer.JS_ERROR)) + statements += JsVars(JsVars.JsVar(anyName, Namer.JS_OBJECT)) + statements += JsVars(JsVars.JsVar(throwableName, Namer.JS_ERROR)) - val preDeclarationBlock = JsGlobalBlock() - val postDeclarationBlock = JsGlobalBlock() + val preDeclarationBlock = JsBlock() + val postDeclarationBlock = JsBlock() - program.globalBlock.statements += preDeclarationBlock + statements += preDeclarationBlock - declaration.files.forEach { - program.globalBlock.statements.add(it.accept(IrFileToJsTransformer(), rootContext)) + module.files.forEach { + statements.add(it.accept(IrFileToJsTransformer(), context)) } // sort member forwarding code - processClassModels(rootContext.staticContext.classModels, preDeclarationBlock, postDeclarationBlock) + processClassModels(context.staticContext.classModels, preDeclarationBlock, postDeclarationBlock) - program.globalBlock.statements += postDeclarationBlock - program.globalBlock.statements += rootContext.staticContext.initializerBlock + statements += postDeclarationBlock + statements += context.staticContext.initializerBlock + + return statements + } + + private fun findModuleExports(module: IrModuleFragment, context: JsGenerationContext): Map { + val publicDeclarations = module.files + .flatMap { it.declarations } + .filterIsInstance() + .filter { it.visibility == Visibilities.PUBLIC } + .filter { !it.isEffectivelyExternal() } + .filterIsInstance() + + val publicDeclarationNames = publicDeclarations.map { + context.getNameForSymbol(it.symbol) + } + + return publicDeclarationNames.associate { it to it } + } + + private fun generateModuleInGlobalScope(module: IrModuleFragment): JsProgram { + val program = JsProgram() + val rootContext = JsGenerationContext(JsRootScope(program), backendContext) + + val moduleBody = generateModuleBody(module, rootContext) + program.globalBlock.statements += moduleBody return program } + private fun generateModule(module: IrModuleFragment): JsProgram { + val program = JsProgram() + val rootContext = JsGenerationContext(JsRootScope(program), backendContext) + + val rootFunction = JsFunction(program.rootScope, JsBlock(), "root function") + val internalModuleName = rootFunction.scope.declareName("_") + + rootFunction.parameters += JsParameter(internalModuleName) + + val declarationLevelJsModules = + backendContext.declarationLevelJsModules.map { externalDeclaration -> + val jsModule = externalDeclaration.getJsModule()!! + val name = rootContext.getNameForDeclaration(externalDeclaration) + JsImportedModule(jsModule, name, name.makeRef()) + } + + val packageLevelJsModules = mutableListOf() + val importStatements = mutableListOf() + + for (file in backendContext.packageLevelJsModules) { + val jsModule = file.getJsModule() + val jsQualifier = file.getJsQualifier() + + assert(jsModule != null || jsQualifier != null) + + val qualifiedReference: JsNameRef + + if (jsModule != null) { + val internalName = rootFunction.scope.declareFreshName("moduleImport") + packageLevelJsModules += JsImportedModule(jsModule, internalName, null) + + qualifiedReference = + if (jsQualifier == null) + internalName.makeRef() + else + JsNameRef(jsQualifier, internalName.makeRef()) + } else { + qualifiedReference = JsNameRef(jsQualifier!!) + } + + file.declarations.forEach { declaration -> + val declName = rootContext.getNameForDeclaration(declaration) + importStatements.add( + JsExpressionStatement( + jsAssignment( + declName.makeRef(), + JsNameRef(declName, qualifiedReference) + ) + ) + ) + } + } + + val importedJsModules = declarationLevelJsModules + packageLevelJsModules + rootFunction.parameters += importedJsModules.map { JsParameter(it.internalName) } + + rootFunction.body.statements += importStatements + + val moduleBody = generateModuleBody(module, rootContext) + + val moduleExports = findModuleExports(module, rootContext) + + val exportStatements = moduleExports.map { (from, to) -> + JsExpressionStatement(jsAssignment(JsNameRef(from, internalModuleName.makeRef()), to.makeRef())) + } + + rootFunction.body.statements += moduleBody + + rootFunction.body.statements += exportStatements + + rootFunction.body.statements += JsReturn(internalModuleName.makeRef()) + + program.globalBlock.statements += ModuleWrapperTranslation.wrap( + moduleName, + rootFunction, + importedJsModules, + program, + kind = moduleKind + ) + + return program + } + + override fun visitModuleFragment(declaration: IrModuleFragment, data: Nothing?): JsNode = + when (backendContext.moduleType) { + ModuleType.MAIN -> generateModule(declaration) + ModuleType.SECONDARY -> JsEmpty + ModuleType.TEST_RUNTIME -> generateModuleInGlobalScope(declaration) + } + + private fun processClassModels( classModelMap: Map, - preDeclarationBlock: JsGlobalBlock, - postDeclarationBlock: JsGlobalBlock + preDeclarationBlock: JsBlock, + postDeclarationBlock: JsBlock ) { val declarationHandler = object : DFS.AbstractNodeHandler() { override fun result() {} 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 new file mode 100644 index 00000000000..4d41fdcb216 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleWrapperTranslation.kt @@ -0,0 +1,162 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.js.backend.ast.* +import org.jetbrains.kotlin.serialization.js.ModuleKind + +object ModuleWrapperTranslation { + object Namer { + fun requiresEscaping(name: String) = false + } + + fun wrap( + moduleId: String, function: JsExpression, importedModules: List, + program: JsProgram, kind: ModuleKind + ): List { + return when (kind) { + ModuleKind.AMD -> wrapAmd(function, importedModules, program) + ModuleKind.COMMON_JS -> wrapCommonJs(function, importedModules, program) + ModuleKind.UMD -> wrapUmd(moduleId, function, importedModules, program) + ModuleKind.PLAIN -> wrapPlain(moduleId, function, importedModules, program) + } + } + + private fun wrapUmd( + moduleId: String, function: JsExpression, + importedModules: List, program: JsProgram + ): List { + val scope = program.scope + val defineName = scope.declareName("define") + val exportsName = scope.declareName("exports") + + val adapterBody = JsBlock() + val adapter = JsFunction(program.scope, adapterBody, "Adapter") + val rootName = adapter.scope.declareName("root") + val factoryName = adapter.scope.declareName("factory") + adapter.parameters += JsParameter(rootName) + adapter.parameters += JsParameter(factoryName) + + val amdTest = JsAstUtils.and(JsAstUtils.typeOfIs(defineName.makeRef(), JsStringLiteral("function")), + JsNameRef("amd", defineName.makeRef())) + val commonJsTest = JsAstUtils.typeOfIs(exportsName.makeRef(), JsStringLiteral("object")) + + val amdBody = JsBlock(wrapAmd(factoryName.makeRef(), importedModules, program)) + val commonJsBody = JsBlock(wrapCommonJs(factoryName.makeRef(), importedModules, program)) + val plainInvocation = makePlainInvocation(moduleId, factoryName.makeRef(), importedModules, program) + + val lhs: JsExpression = if (Namer.requiresEscaping(moduleId)) { + JsArrayAccess(rootName.makeRef(), JsStringLiteral(moduleId)) + } + else { + JsNameRef(scope.declareName(moduleId), rootName.makeRef()) + } + + val plainBlock = JsBlock() + for (importedModule in importedModules) { + plainBlock.statements += addModuleValidation(moduleId, program, importedModule) + } + plainBlock.statements += JsAstUtils.assignment(lhs, plainInvocation).makeStmt() + + val selector = JsAstUtils.newJsIf(amdTest, amdBody, JsAstUtils.newJsIf(commonJsTest, commonJsBody, plainBlock)) + adapterBody.statements += selector + + return listOf(JsInvocation(adapter, JsThisRef(), function).makeStmt()) + } + + private fun wrapAmd( + function: JsExpression, + importedModules: List, program: JsProgram + ): List { + val scope = program.scope + val defineName = scope.declareName("define") + val invocationArgs = listOf( + JsArrayLiteral(listOf(JsStringLiteral("exports")) + importedModules.map { JsStringLiteral(it.externalName) }), + function + ) + + val invocation = JsInvocation(defineName.makeRef(), invocationArgs) + return listOf(invocation.makeStmt()) + } + + private fun wrapCommonJs( + function: JsExpression, + importedModules: List, + program: JsProgram + ): List { + val scope = program.scope + val moduleName = scope.declareName("module") + val requireName = scope.declareName("require") + + val invocationArgs = importedModules.map { JsInvocation(requireName.makeRef(), JsStringLiteral(it.externalName)) } + val invocation = JsInvocation(function, listOf(JsNameRef("exports", moduleName.makeRef())) + invocationArgs) + return listOf(invocation.makeStmt()) + } + + private fun wrapPlain( + moduleId: String, function: JsExpression, + importedModules: List, program: JsProgram + ): List { + val invocation = makePlainInvocation(moduleId, function, importedModules, program) + val statements = mutableListOf() + + for (importedModule in importedModules) { + statements += addModuleValidation(moduleId, program, importedModule) + } + + statements += if (Namer.requiresEscaping(moduleId)) { + JsAstUtils.assignment(makePlainModuleRef(moduleId, program), invocation).makeStmt() + } + else { + JsAstUtils.newVar(program.rootScope.declareName(moduleId), invocation) + } + + return statements + } + + private fun addModuleValidation( + currentModuleId: String, + program: JsProgram, + module: JsImportedModule + ): JsStatement { + val moduleRef = makePlainModuleRef(module, program) + val moduleExistsCond = JsAstUtils.typeOfIs(moduleRef, JsStringLiteral("undefined")) + val moduleNotFoundMessage = JsStringLiteral( + "Error loading module '" + currentModuleId + "'. Its dependency '" + module.externalName + "' was not found. " + + "Please, check whether '" + module.externalName + "' is loaded prior to '" + currentModuleId + "'.") + val moduleNotFoundThrow = JsThrow(JsNew(JsNameRef("Error"), listOf(moduleNotFoundMessage))) + return JsIf(moduleExistsCond, JsBlock(moduleNotFoundThrow)) + } + + private fun makePlainInvocation( + moduleId: String, + function: JsExpression, + importedModules: List, + program: JsProgram + ): JsInvocation { + val invocationArgs = importedModules.map { makePlainModuleRef(it, program) } + val moduleRef = makePlainModuleRef(moduleId, program) + val testModuleDefined = JsAstUtils.typeOfIs(moduleRef, JsStringLiteral("undefined")) + val selfArg = JsConditional(testModuleDefined, JsObjectLiteral(false), moduleRef.deepCopy()) + + return JsInvocation(function, listOf(selfArg) + invocationArgs) + } + + private fun makePlainModuleRef(module: JsImportedModule, program: JsProgram): JsExpression { + return module.plainReference ?: makePlainModuleRef(module.externalName, program) + } + + private fun makePlainModuleRef(moduleId: String, program: JsProgram): JsExpression { + // TODO: we could use `this.moduleName` syntax. However, this does not work for `kotlin` module in Rhino, since + // we run kotlin.js in a parent scope. Consider better solution + return if (Namer.requiresEscaping(moduleId)) { + JsArrayAccess(JsThisRef(), JsStringLiteral(moduleId)) + } + else { + program.scope.declareName(moduleId).makeRef() + } + } +} 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 8c7bff148ae..c3c25952190 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 @@ -87,3 +87,107 @@ fun defineProperty(receiver: JsExpression, name: String, value: () -> JsExpressi val objectDefineProperty = JsNameRef("defineProperty", Namer.JS_OBJECT) return JsInvocation(objectDefineProperty, receiver, JsStringLiteral(name), value()) } + +// Partially copied from org.jetbrains.kotlin.js.translate.utils.JsAstUtils +object JsAstUtils { + private fun deBlockIfPossible(statement: JsStatement): JsStatement { + return if (statement is JsBlock && statement.statements.size == 1) { + statement.statements[0] + } else { + statement + } + } + + fun newJsIf( + ifExpression: JsExpression, + thenStatement: JsStatement, + elseStatement: JsStatement? = null + ): JsIf { + var elseStatement = elseStatement + elseStatement = if (elseStatement != null) deBlockIfPossible(elseStatement) else null + return JsIf(ifExpression, deBlockIfPossible(thenStatement), elseStatement) + } + + fun and(op1: JsExpression, op2: JsExpression): JsBinaryOperation { + return JsBinaryOperation(JsBinaryOperator.AND, op1, op2) + } + + fun or(op1: JsExpression, op2: JsExpression): JsBinaryOperation { + return JsBinaryOperation(JsBinaryOperator.OR, op1, op2) + } + + fun equality(arg1: JsExpression, arg2: JsExpression): JsBinaryOperation { + return JsBinaryOperation(JsBinaryOperator.REF_EQ, arg1, arg2) + } + + fun inequality(arg1: JsExpression, arg2: JsExpression): JsBinaryOperation { + return JsBinaryOperation(JsBinaryOperator.REF_NEQ, arg1, arg2) + } + + fun lessThanEq(arg1: JsExpression, arg2: JsExpression): JsBinaryOperation { + return JsBinaryOperation(JsBinaryOperator.LTE, arg1, arg2) + } + + fun lessThan(arg1: JsExpression, arg2: JsExpression): JsBinaryOperation { + return JsBinaryOperation(JsBinaryOperator.LT, arg1, arg2) + } + + fun greaterThan(arg1: JsExpression, arg2: JsExpression): JsBinaryOperation { + return JsBinaryOperation(JsBinaryOperator.GT, arg1, arg2) + } + + fun greaterThanEq(arg1: JsExpression, arg2: JsExpression): JsBinaryOperation { + return JsBinaryOperation(JsBinaryOperator.GTE, arg1, arg2) + } + + fun assignment(left: JsExpression, right: JsExpression): JsBinaryOperation { + return JsBinaryOperation(JsBinaryOperator.ASG, left, right) + } + + fun assignmentToThisField(fieldName: String, right: JsExpression): JsStatement { + return assignment(JsNameRef(fieldName, JsThisRef()), right).source(right.source).makeStmt() + } + + fun decomposeAssignment(expr: JsExpression): Pair? { + if (expr !is JsBinaryOperation) return null + + return if (expr.operator != JsBinaryOperator.ASG) null else Pair(expr.arg1, expr.arg2) + + } + + fun sum(left: JsExpression, right: JsExpression): JsBinaryOperation { + return JsBinaryOperation(JsBinaryOperator.ADD, left, right) + } + + fun addAssign(left: JsExpression, right: JsExpression): JsBinaryOperation { + return JsBinaryOperation(JsBinaryOperator.ASG_ADD, left, right) + } + + fun subtract(left: JsExpression, right: JsExpression): JsBinaryOperation { + return JsBinaryOperation(JsBinaryOperator.SUB, left, right) + } + + fun mul(left: JsExpression, right: JsExpression): JsBinaryOperation { + return JsBinaryOperation(JsBinaryOperator.MUL, left, right) + } + + fun div(left: JsExpression, right: JsExpression): JsBinaryOperation { + return JsBinaryOperation(JsBinaryOperator.DIV, left, right) + } + + fun mod(left: JsExpression, right: JsExpression): JsBinaryOperation { + return JsBinaryOperation(JsBinaryOperator.MOD, left, right) + } + + fun not(expression: JsExpression): JsPrefixOperation { + return JsPrefixOperation(JsUnaryOperator.NOT, expression) + } + + fun typeOfIs(expression: JsExpression, string: JsStringLiteral): JsBinaryOperation { + return equality(JsPrefixOperation(JsUnaryOperator.TYPEOF, expression), string) + } + + fun newVar(name: JsName, expr: JsExpression?): JsVars { + return JsVars(JsVars.JsVar(name, expr)) + } +} \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt new file mode 100644 index 00000000000..f9cce1adf68 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/AnnotationUtils.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.ir.backend.js.utils + +import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrConst +import org.jetbrains.kotlin.ir.util.getAnnotation +import org.jetbrains.kotlin.ir.util.hasAnnotation +import org.jetbrains.kotlin.name.FqName + +object JsAnnotations { + val jsModuleFqn = FqName("kotlin.js.JsModule") + val jsNonModuleFqn = FqName("kotlin.js.JsNonModule") + val jsNameFqn = FqName("kotlin.js.JsName") + val jsQualifierFqn = FqName("kotlin.js.JsQualifier") +} + +private fun IrCall.getSingleConstStringArgument() = + (getValueArgument(0) as IrConst).value + +fun IrAnnotationContainer.getJsModule(): String? = + getAnnotation(JsAnnotations.jsModuleFqn)?.getSingleConstStringArgument() + +fun IrAnnotationContainer.isJsNonModule(): Boolean = + hasAnnotation(JsAnnotations.jsNonModuleFqn) + +fun IrAnnotationContainer.getJsQualifier(): String? = + getAnnotation(JsAnnotations.jsQualifierFqn)?.getSingleConstStringArgument() + +fun IrAnnotationContainer.getJsName(): String? = + getAnnotation(JsAnnotations.jsNameFqn)?.getSingleConstStringArgument() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsGenerationContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsGenerationContext.kt index 16beb49eb2b..9ea4002bc82 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsGenerationContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsGenerationContext.kt @@ -6,8 +6,7 @@ package org.jetbrains.kotlin.ir.backend.js.utils import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext -import org.jetbrains.kotlin.ir.declarations.IrFunction -import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.IrLoop import org.jetbrains.kotlin.ir.symbols.IrSymbol import org.jetbrains.kotlin.ir.types.IrType @@ -44,6 +43,12 @@ class JsGenerationContext { this.currentFunction = func } + fun getNameForDeclaration(declaration: IrDeclaration): JsName = when (declaration) { + is IrSymbolOwner -> getNameForSymbol(declaration.symbol) + is IrProperty -> currentScope.declareName(declaration.name.identifier) + else -> error("Unsupported") + } + fun getNameForSymbol(symbol: IrSymbol): JsName = staticContext.getNameForSymbol(symbol, this) fun getNameForType(type: IrType): JsName = staticContext.getNameForType(type, this) fun getNameForLoop(loop: IrLoop): JsName? = staticContext.getNameForLoop(loop, this) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/SimpleNameGenerator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/SimpleNameGenerator.kt index a3abad84498..4acc76a1aa3 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/SimpleNameGenerator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/SimpleNameGenerator.kt @@ -5,7 +5,6 @@ package org.jetbrains.kotlin.ir.backend.js.utils -import org.jetbrains.kotlin.backend.common.ir.isStatic import org.jetbrains.kotlin.backend.common.ir.isTopLevel import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.ir.declarations.* @@ -13,7 +12,6 @@ import org.jetbrains.kotlin.ir.expressions.IrLoop import org.jetbrains.kotlin.ir.symbols.IrSymbol import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.classifierOrFail -import org.jetbrains.kotlin.ir.types.isUnit import org.jetbrains.kotlin.ir.util.isDynamic import org.jetbrains.kotlin.ir.util.isEffectivelyExternal import org.jetbrains.kotlin.ir.util.isInlined @@ -102,6 +100,11 @@ class SimpleNameGenerator : NameGenerator { return@getOrPut nameDeclarator(declaration.descriptor.name.asString()) } + val jsName = declaration.getJsName() + if (jsName != null) { + return@getOrPut context.currentScope.declareName(jsName) + } + if (declaration.isEffectivelyExternal()) { // TODO: descriptors are still used here due to the corresponding declaration doesn't have enough information yet val descriptorForName = when (descriptor) { diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt index c8010a54bd9..c6cefd3bfc4 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt @@ -379,6 +379,11 @@ tailrec fun IrElement.getPackageFragment(): IrPackageFragment? { } } +fun IrAnnotationContainer.getAnnotation(name: FqName) = + annotations.find { + it.symbol.owner.parentAsClass.descriptor.fqNameSafe == name + } + fun IrAnnotationContainer.hasAnnotation(name: FqName) = annotations.any { it.symbol.owner.parentAsClass.descriptor.fqNameSafe == name diff --git a/compiler/testData/codegen/box/classes/kt2711.kt b/compiler/testData/codegen/box/classes/kt2711.kt index 55f4c972f66..409537ae567 100644 --- a/compiler/testData/codegen/box/classes/kt2711.kt +++ b/compiler/testData/codegen/box/classes/kt2711.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR class IntRange { operator fun contains(a: Int) = (1..2).contains(a) } 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 ef35df735ea..0abadf47c7b 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 @@ -126,6 +126,9 @@ abstract class BasicBoxTest( val testPackage = testFactory.testPackage val testFunction = TEST_FUNCTION + val mainModuleName = if (TEST_MODULE in modules) TEST_MODULE else DEFAULT_MODULE + val mainModule = modules[mainModuleName] ?: error("No module with name \"$mainModuleName\"") + val generatedJsFiles = orderedModules.asReversed().mapNotNull { module -> val dependencies = module.dependencies.map { modules[it]?.outputFileName(outputDir) + ".meta.js" } val friends = module.friends.map { modules[it]?.outputFileName(outputDir) + ".meta.js" } @@ -135,15 +138,12 @@ abstract class BasicBoxTest( file.parent, module, outputFileName, dependencies, friends, modules.size > 1, !SKIP_SOURCEMAP_REMAPPING.matcher(fileContent).find(), outputPrefixFile, outputPostfixFile, actualMainCallParameters, testPackage, testFunction, - runtimeType + runtimeType, isMainModule = (mainModuleName == module.name) ) if (!module.name.endsWith(OLD_MODULE_SUFFIX)) Pair(outputFileName, module) else null } - val mainModuleName = if (TEST_MODULE in modules) TEST_MODULE else DEFAULT_MODULE - val mainModule = modules[mainModuleName] ?: error("No module with name \"$mainModuleName\"") - val globalCommonFiles = JsTestUtils.getFilesInDirectoryByExtension(COMMON_FILES_DIR_PATH, JavaScript.EXTENSION) val localCommonFile = file.parent + "/" + COMMON_FILES_NAME + JavaScript.DOT_EXTENSION val localCommonFiles = if (File(localCommonFile).exists()) listOf(localCommonFile) else emptyList() @@ -315,7 +315,8 @@ abstract class BasicBoxTest( mainCallParameters: MainCallParameters, testPackage: String?, testFunction: String, - runtime: JsIrTestRuntime + runtime: JsIrTestRuntime, + isMainModule: Boolean ) { val kotlinFiles = module.files.filter { it.fileName.endsWith(".kt") } val testFiles = kotlinFiles.map { it.fileName } @@ -337,7 +338,7 @@ abstract class BasicBoxTest( val incrementalData = IncrementalData() translateFiles( psiFiles.map(TranslationUnit::SourceFile), outputFile, config, outputPrefixFile, outputPostfixFile, - mainCallParameters, incrementalData, remap, testPackage, testFunction, runtime + mainCallParameters, incrementalData, remap, testPackage, testFunction, runtime, isMainModule ) if (incrementalCompilationChecksEnabled && module.hasFilesToRecompile) { @@ -385,7 +386,7 @@ abstract class BasicBoxTest( translateFiles( translationUnits, recompiledOutputFile, recompiledConfig, outputPrefixFile, outputPostfixFile, - mainCallParameters, incrementalData, remap, testPackage, testFunction, runtime + mainCallParameters, incrementalData, remap, testPackage, testFunction, runtime, false ) val originalOutput = FileUtil.loadFile(outputFile) @@ -450,7 +451,8 @@ abstract class BasicBoxTest( remap: Boolean, testPackage: String?, testFunction: String, - runtime: JsIrTestRuntime + runtime: JsIrTestRuntime, + isMainModule: Boolean ) { val translator = K2JSTranslator(config) val translationResult = translator.translateUnits(ExceptionThrowingReporter, units, mainCallParameters) @@ -467,18 +469,9 @@ abstract class BasicBoxTest( val outputDir = outputFile.parentFile ?: error("Parent file for output file should not be null, outputFilePath: " + outputFile.path) outputFiles.writeAllTo(outputDir) - if (config.moduleKind == ModuleKind.COMMON_JS) { + if (config.moduleKind != ModuleKind.PLAIN) { val content = FileUtil.loadFile(outputFile, true) - val wrappedContent = "$KOTLIN_TEST_INTERNAL.beginModule();\n" + - "$content\n" + - "$KOTLIN_TEST_INTERNAL.endModule(\"${StringUtil.escapeStringCharacters(config.moduleId)}\");" - FileUtil.writeToFile(outputFile, wrappedContent) - } - else if (config.moduleKind == ModuleKind.AMD || config.moduleKind == ModuleKind.UMD) { - val content = FileUtil.loadFile(outputFile, true) - val wrappedContent = "if (typeof $KOTLIN_TEST_INTERNAL !== \"undefined\") { " + - "$KOTLIN_TEST_INTERNAL.setModuleId(\"${StringUtil.escapeStringCharacters(config.moduleId)}\"); }\n" + - "$content\n" + val wrappedContent = wrapWithModuleEmulationMarkers(content, moduleId = config.moduleId, moduleKind = config.moduleKind) FileUtil.writeToFile(outputFile, wrappedContent) } @@ -496,6 +489,28 @@ abstract class BasicBoxTest( checkSourceMap(outputFile, translationResult.program, remap) } + protected fun wrapWithModuleEmulationMarkers( + content: String, + moduleKind: ModuleKind, + moduleId: String + ): String { + val escapedModuleId = StringUtil.escapeStringCharacters(moduleId) + + return when (moduleKind) { + + ModuleKind.COMMON_JS -> "$KOTLIN_TEST_INTERNAL.beginModule();\n" + + "$content\n" + + "$KOTLIN_TEST_INTERNAL.endModule(\"$escapedModuleId\");" + + ModuleKind.AMD, ModuleKind.UMD -> + "if (typeof $KOTLIN_TEST_INTERNAL !== \"undefined\") { " + + "$KOTLIN_TEST_INTERNAL.setModuleId(\"$escapedModuleId\"); }\n" + + "$content\n" + + ModuleKind.PLAIN -> content + } + } + private fun processJsProgram(program: JsProgram, psiFiles: List) { psiFiles.asSequence() .map { it.text } 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 fc21e7ddf60..23a7af4fe6d 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 @@ -5,20 +5,21 @@ package org.jetbrains.kotlin.js.test +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.text.StringUtil import org.jetbrains.kotlin.config.* -import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl -import org.jetbrains.kotlin.ir.backend.js.Result +import org.jetbrains.kotlin.ir.backend.js.ModuleType +import org.jetbrains.kotlin.ir.backend.js.CompiledModule import org.jetbrains.kotlin.ir.backend.js.compile import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.js.config.JsConfig import org.jetbrains.kotlin.js.facade.MainCallParameters import org.jetbrains.kotlin.js.facade.TranslationUnit import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.test.TargetBackend import java.io.File -private var runtimeResults = mutableMapOf() +private var runtimeResults = mutableMapOf() abstract class BasicIrBoxTest( pathToTestDir: String, @@ -41,7 +42,7 @@ abstract class BasicIrBoxTest( // TODO Design incremental compilation for IR and add test support override val incrementalCompilationChecksEnabled = false - private val compilationCache = mutableMapOf() + private val compilationCache = mutableMapOf() override fun doTest(filePath: String, expectedResult: String, mainCallParameters: MainCallParameters, coroutinesPackage: String) { compilationCache.clear() @@ -59,7 +60,8 @@ abstract class BasicIrBoxTest( remap: Boolean, testPackage: String?, testFunction: String, - runtime: JsIrTestRuntime + runtime: JsIrTestRuntime, + isMainModule: Boolean ) { val filesToCompile = units .map { (it as TranslationUnit.SourceFile).file } @@ -94,14 +96,15 @@ abstract class BasicIrBoxTest( val runtimeFile = File(runtime.path) val runtimeResult = runtimeResults.getOrPut(runtime) { runtimeConfiguration.put(CommonConfigurationKeys.MODULE_NAME, "JS_IR_RUNTIME") - val result = compile(config.project, runtime.sources.map(::createPsiFile), runtimeConfiguration) - runtimeFile.write(result.generatedCode) + val result = compile(config.project, runtime.sources.map(::createPsiFile), runtimeConfiguration, moduleType = ModuleType.TEST_RUNTIME) + runtimeFile.write(result.generatedCode!!) result } val dependencyNames = config.configuration[JSConfigurationKeys.LIBRARIES]!!.map { File(it).name } - val dependencies = listOf(runtimeResult.moduleDescriptor) + dependencyNames.mapNotNull { compilationCache[it]?.moduleDescriptor } - val irDependencies = listOf(runtimeResult.moduleFragment) + compilationCache.values.map { it.moduleFragment } + val dependencies = listOf(runtimeResult) + dependencyNames.mapNotNull { + compilationCache[it] + } // config.configuration.put(CommonConfigurationKeys.PHASES_TO_DUMP_STATE, setOf("UnitMaterializationLowering")) // config.configuration.put(CommonConfigurationKeys.PHASES_TO_DUMP_STATE_BEFORE, setOf("ReturnableBlockLowering")) @@ -112,18 +115,21 @@ abstract class BasicIrBoxTest( config.project, filesToCompile, config.configuration, - FqName((testPackage?.let { "$it." } ?: "") + testFunction), + listOf(FqName((testPackage?.let { "$it." } ?: "") + testFunction)), dependencies, - irDependencies, - runtimeResult.moduleDescriptor as ModuleDescriptorImpl + runtimeResult, + moduleType = if (isMainModule) ModuleType.MAIN else ModuleType.SECONDARY ) compilationCache[outputFile.name.replace(".js", ".meta.js")] = result - // Prefix to help node.js runner find runtime - val runtimePrefix = "// RUNTIME: [\"${runtimeFile.path}\"]\n" - - outputFile.write(runtimePrefix + result.generatedCode) + val generatedCode = result.generatedCode + if (generatedCode != null) { + // Prefix to help node.js runner find runtime + val runtimePrefix = "// RUNTIME: [\"${runtimeFile.path}\"]\n" + val wrappedCode = wrapWithModuleEmulationMarkers(generatedCode, moduleId = config.moduleId, moduleKind = config.moduleKind) + outputFile.write(runtimePrefix + wrappedCode) + } } override fun runGeneratedCode( @@ -137,7 +143,7 @@ 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 - nashornIrJsTestCheckers[runtime]!!.check(jsFiles, null, null, testFunction, expectedResult, false) + nashornIrJsTestCheckers[runtime]!!.check(jsFiles, testModuleName, null, testFunction, expectedResult, withModuleSystem) } } diff --git a/js/js.translator/testData/box/crossModuleRef/inlineJsModule.kt b/js/js.translator/testData/box/crossModuleRef/inlineJsModule.kt index 0f3b3afd64b..350c5931a8f 100644 --- a/js/js.translator/testData/box/crossModuleRef/inlineJsModule.kt +++ b/js/js.translator/testData/box/crossModuleRef/inlineJsModule.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1316 // MODULE: lib1 // FILE: lib1.js diff --git a/js/js.translator/testData/box/crossModuleRef/inlineJsModuleNonIdentifier.kt b/js/js.translator/testData/box/crossModuleRef/inlineJsModuleNonIdentifier.kt index 145dcd73155..c8cd37dab16 100644 --- a/js/js.translator/testData/box/crossModuleRef/inlineJsModuleNonIdentifier.kt +++ b/js/js.translator/testData/box/crossModuleRef/inlineJsModuleNonIdentifier.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1316 // MODULE: lib-1 // FILE: lib-1.js diff --git a/js/js.translator/testData/box/crossModuleRef/inlineJsModulePackage.kt b/js/js.translator/testData/box/crossModuleRef/inlineJsModulePackage.kt index 93a9996c32f..ee5b223e6c1 100644 --- a/js/js.translator/testData/box/crossModuleRef/inlineJsModulePackage.kt +++ b/js/js.translator/testData/box/crossModuleRef/inlineJsModulePackage.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1284 // MODULE: lib1 // FILE: lib1.js diff --git a/js/js.translator/testData/box/incremental/jsModule.kt b/js/js.translator/testData/box/incremental/jsModule.kt index 8be14c25ef5..295ba7ebf08 100644 --- a/js/js.translator/testData/box/incremental/jsModule.kt +++ b/js/js.translator/testData/box/incremental/jsModule.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1283 // MODULE: lib diff --git a/js/js.translator/testData/box/inlineMultiModule/internalNameClash.kt b/js/js.translator/testData/box/inlineMultiModule/internalNameClash.kt index cb9db656dac..01bc78da3ec 100644 --- a/js/js.translator/testData/box/inlineMultiModule/internalNameClash.kt +++ b/js/js.translator/testData/box/inlineMultiModule/internalNameClash.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1284 // MODULE: lib1 // FILE: lib1.kt diff --git a/js/js.translator/testData/box/jsModule/externalClass.kt b/js/js.translator/testData/box/jsModule/externalClass.kt index 39afb0d9008..1c7af34da2f 100644 --- a/js/js.translator/testData/box/jsModule/externalClass.kt +++ b/js/js.translator/testData/box/jsModule/externalClass.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1284 // MODULE_KIND: AMD package foo diff --git a/js/js.translator/testData/box/jsModule/externalFunction.kt b/js/js.translator/testData/box/jsModule/externalFunction.kt index 3b12a332c22..c2076015c7f 100644 --- a/js/js.translator/testData/box/jsModule/externalFunction.kt +++ b/js/js.translator/testData/box/jsModule/externalFunction.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1284 // MODULE_KIND: AMD package foo diff --git a/js/js.translator/testData/box/jsModule/externalFunctionUmd.kt b/js/js.translator/testData/box/jsModule/externalFunctionUmd.kt index 1896644c1a2..7ed7f18aa7a 100644 --- a/js/js.translator/testData/box/jsModule/externalFunctionUmd.kt +++ b/js/js.translator/testData/box/jsModule/externalFunctionUmd.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1286 // MODULE_KIND: UMD package foo diff --git a/js/js.translator/testData/box/jsModule/externalFunctionUmdFallback.kt b/js/js.translator/testData/box/jsModule/externalFunctionUmdFallback.kt index b90b4841bd9..f629e6297de 100644 --- a/js/js.translator/testData/box/jsModule/externalFunctionUmdFallback.kt +++ b/js/js.translator/testData/box/jsModule/externalFunctionUmdFallback.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1288 // MODULE_KIND: UMD // NO_JS_MODULE_SYSTEM diff --git a/js/js.translator/testData/box/jsModule/externalPackageInDifferentFile.kt b/js/js.translator/testData/box/jsModule/externalPackageInDifferentFile.kt index ce8f8caa1b7..3e288d93898 100644 --- a/js/js.translator/testData/box/jsModule/externalPackageInDifferentFile.kt +++ b/js/js.translator/testData/box/jsModule/externalPackageInDifferentFile.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1290 // MODULE_KIND: AMD // FILE: lib.kt diff --git a/js/js.translator/testData/box/jsModule/externalPackageUmdFallback.kt b/js/js.translator/testData/box/jsModule/externalPackageUmdFallback.kt index bd01b1cea06..47837c99d5c 100644 --- a/js/js.translator/testData/box/jsModule/externalPackageUmdFallback.kt +++ b/js/js.translator/testData/box/jsModule/externalPackageUmdFallback.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1290 // FILE: a.kt // MODULE_KIND: UMD diff --git a/js/js.translator/testData/box/jsModule/externalProperty.kt b/js/js.translator/testData/box/jsModule/externalProperty.kt index 017189a9006..48ed746d160 100644 --- a/js/js.translator/testData/box/jsModule/externalProperty.kt +++ b/js/js.translator/testData/box/jsModule/externalProperty.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1284 // MODULE_KIND: AMD package foo diff --git a/js/js.translator/testData/box/jsModule/importCountCommonJS.kt b/js/js.translator/testData/box/jsModule/importCountCommonJS.kt index 95ae91fad94..34a9aa15b54 100644 --- a/js/js.translator/testData/box/jsModule/importCountCommonJS.kt +++ b/js/js.translator/testData/box/jsModule/importCountCommonJS.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1283 // MODULE_KIND: COMMON_JS // FUNCTION_CALLED_TIMES: require count=2 diff --git a/js/js.translator/testData/box/jsQualifier/simple.kt b/js/js.translator/testData/box/jsQualifier/simple.kt index adcb21ab598..cc885e5d7ca 100644 --- a/js/js.translator/testData/box/jsQualifier/simple.kt +++ b/js/js.translator/testData/box/jsQualifier/simple.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1282 // MODULE: lib // FILE: lib.kt diff --git a/js/js.translator/testData/box/jsQualifier/umdFallback.kt b/js/js.translator/testData/box/jsQualifier/umdFallback.kt index 135fe915394..efd915eed0e 100644 --- a/js/js.translator/testData/box/jsQualifier/umdFallback.kt +++ b/js/js.translator/testData/box/jsQualifier/umdFallback.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1284 // MODULE: lib // FILE: lib.kt diff --git a/js/js.translator/testData/box/jsQualifier/withModule.kt b/js/js.translator/testData/box/jsQualifier/withModule.kt index 1d306ef3062..89a5402727a 100644 --- a/js/js.translator/testData/box/jsQualifier/withModule.kt +++ b/js/js.translator/testData/box/jsQualifier/withModule.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1282 // MODULE: lib // FILE: lib.kt diff --git a/js/js.translator/testData/box/multiModuleWrappers/amd/jsModuleOnPackage.kt b/js/js.translator/testData/box/multiModuleWrappers/amd/jsModuleOnPackage.kt index f54ca56f2d8..a16165de573 100644 --- a/js/js.translator/testData/box/multiModuleWrappers/amd/jsModuleOnPackage.kt +++ b/js/js.translator/testData/box/multiModuleWrappers/amd/jsModuleOnPackage.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1289 // MODULE: lib // FILE: lib.kt diff --git a/js/js.translator/testData/box/multiModuleWrappers/amd/moduleWithNonIdentifierName.kt b/js/js.translator/testData/box/multiModuleWrappers/amd/moduleWithNonIdentifierName.kt index 6b3b922e358..9118a506890 100644 --- a/js/js.translator/testData/box/multiModuleWrappers/amd/moduleWithNonIdentifierName.kt +++ b/js/js.translator/testData/box/multiModuleWrappers/amd/moduleWithNonIdentifierName.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1284 // MODULE: module-1 // FILE: bar.kt diff --git a/js/js.translator/testData/box/multiModuleWrappers/amd/simple.kt b/js/js.translator/testData/box/multiModuleWrappers/amd/simple.kt index 7a246100f2b..bfb4f75265a 100644 --- a/js/js.translator/testData/box/multiModuleWrappers/amd/simple.kt +++ b/js/js.translator/testData/box/multiModuleWrappers/amd/simple.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1284 // MODULE: module1 // FILE: bar.kt diff --git a/js/js.translator/testData/box/multiModuleWrappers/common_js/inlineFromModuleWithNonIdentifierName.kt b/js/js.translator/testData/box/multiModuleWrappers/common_js/inlineFromModuleWithNonIdentifierName.kt index cd275ff700f..fc62afbc76a 100644 --- a/js/js.translator/testData/box/multiModuleWrappers/common_js/inlineFromModuleWithNonIdentifierName.kt +++ b/js/js.translator/testData/box/multiModuleWrappers/common_js/inlineFromModuleWithNonIdentifierName.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1284 // MODULE: #my-libr@ry // FILE: bar.kt diff --git a/js/js.translator/testData/box/multiModuleWrappers/common_js/moduleWithNonIdentifierName.kt b/js/js.translator/testData/box/multiModuleWrappers/common_js/moduleWithNonIdentifierName.kt index 6b381a16473..6858b652081 100644 --- a/js/js.translator/testData/box/multiModuleWrappers/common_js/moduleWithNonIdentifierName.kt +++ b/js/js.translator/testData/box/multiModuleWrappers/common_js/moduleWithNonIdentifierName.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1285 // MODULE: module-1 // FILE: bar.kt diff --git a/js/js.translator/testData/box/multiModuleWrappers/common_js/simple.kt b/js/js.translator/testData/box/multiModuleWrappers/common_js/simple.kt index af78c90a13e..fbd85ae0663 100644 --- a/js/js.translator/testData/box/multiModuleWrappers/common_js/simple.kt +++ b/js/js.translator/testData/box/multiModuleWrappers/common_js/simple.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1285 // MODULE: module1 // FILE: bar.kt diff --git a/js/js.translator/testData/box/multiModuleWrappers/umd/moduleWithNonIdentifierName.kt b/js/js.translator/testData/box/multiModuleWrappers/umd/moduleWithNonIdentifierName.kt index d89236bad25..1a97dc4cc78 100644 --- a/js/js.translator/testData/box/multiModuleWrappers/umd/moduleWithNonIdentifierName.kt +++ b/js/js.translator/testData/box/multiModuleWrappers/umd/moduleWithNonIdentifierName.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1286 // MODULE: module-1 // FILE: bar.kt diff --git a/js/js.translator/testData/box/multiModuleWrappers/umd/simple.kt b/js/js.translator/testData/box/multiModuleWrappers/umd/simple.kt index e0869159048..1d64b045a2e 100644 --- a/js/js.translator/testData/box/multiModuleWrappers/umd/simple.kt +++ b/js/js.translator/testData/box/multiModuleWrappers/umd/simple.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1286 // MODULE: module1 // FILE: bar.kt diff --git a/js/js.translator/testData/box/nameClashes/jsNameAndPrivate.kt b/js/js.translator/testData/box/nameClashes/jsNameAndPrivate.kt index 4e96c4de862..5316a9e331f 100644 --- a/js/js.translator/testData/box/nameClashes/jsNameAndPrivate.kt +++ b/js/js.translator/testData/box/nameClashes/jsNameAndPrivate.kt @@ -1,3 +1,4 @@ +// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1284 package foo diff --git a/js/js.translator/testData/box/propertyAccess/defaultAccessorsWithJsName.kt b/js/js.translator/testData/box/propertyAccess/defaultAccessorsWithJsName.kt index 86a661d74fd..00799288a71 100644 --- a/js/js.translator/testData/box/propertyAccess/defaultAccessorsWithJsName.kt +++ b/js/js.translator/testData/box/propertyAccess/defaultAccessorsWithJsName.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1288 package foo diff --git a/js/js.translator/testData/moduleEmulation.js b/js/js.translator/testData/moduleEmulation.js index d20b94df4f1..2b2943fc482 100644 --- a/js/js.translator/testData/moduleEmulation.js +++ b/js/js.translator/testData/moduleEmulation.js @@ -1,4 +1,4 @@ - +var kotlin; var emulatedModules = { kotlin: kotlin }; var module = { exports: {} }; var currentModuleId; diff --git a/js/js.translator/testData/runIrTestInNode.js b/js/js.translator/testData/runIrTestInNode.js index fb02e7b4e59..7ef90b7bfe5 100644 --- a/js/js.translator/testData/runIrTestInNode.js +++ b/js/js.translator/testData/runIrTestInNode.js @@ -59,4 +59,5 @@ allFiles.forEach(function(path) { }) }); -console.log(vm.runInContext("box()", sandbox)); +// TODO: Support multimodule tests +console.log(vm.runInContext("JS_TESTS.box()", sandbox));