diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt index d7838442118..a740535d975 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt @@ -198,7 +198,8 @@ class K2JsIrCompiler : CLICompiler() { return ExitCode.COMPILATION_ERROR } - // TODO: Handle main call parameters + // TODO: Handle non-empty main call arguments + val mainCallArguments = if (K2JsArgumentConstants.NO_CALL == arguments.main) null else emptyList() val dependencies = libraries.flatMap { listOfNotNull(loadIrLibrary(it, messageCollector)) } .distinctBy { it.moduleName } @@ -214,7 +215,8 @@ class K2JsIrCompiler : CLICompiler() { sourcesFiles, configuration, immediateDependencies = dependencies, - allDependencies = dependencies + allDependencies = dependencies, + mainArguments = mainCallArguments ) outputFile.writeText(compiledModule) 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 901f87fe6ba..3fe153320ca 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 @@ -224,6 +224,7 @@ class JsIrBackendContext( } val coroutineGetContextJs = symbolTable.referenceSimpleFunction(getJsInternalFunction(GET_COROUTINE_CONTEXT_NAME)) + val coroutineEmptyContinuation = symbolTable.referenceField(getProperty(FqName.fromSegments(listOf("kotlin", "coroutines", "js", "internal", "EmptyContinuation")))) val coroutineContextProperty: PropertyDescriptor get() { @@ -283,12 +284,18 @@ class JsIrBackendContext( private fun findFunctions(memberScope: MemberScope, name: Name): List = memberScope.getContributedFunctions(name, NoLookupLocation.FROM_BACKEND).toList() + private fun findProperty(memberScope: MemberScope, name: Name): List = + memberScope.getContributedVariables(name, NoLookupLocation.FROM_BACKEND).toList() + internal fun getJsInternalClass(name: String): ClassDescriptor = findClass(internalPackage.memberScope, Name.identifier(name)) internal fun getClass(fqName: FqName): ClassDescriptor = findClass(module.getPackage(fqName.parent()).memberScope, fqName.shortName()) + internal fun getProperty(fqName: FqName): PropertyDescriptor = + findProperty(module.getPackage(fqName.parent()).memberScope, fqName.shortName()).single() + private fun getIrClass(fqName: FqName): IrClassSymbol = symbolTable.referenceClass(getClass(fqName)) internal fun getJsInternalFunction(name: String): SimpleFunctionDescriptor = 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 60252f25d9b..464694faca5 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt @@ -10,6 +10,7 @@ import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer +import org.jetbrains.kotlin.ir.backend.js.utils.JsMainFunctionDetector import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator import org.jetbrains.kotlin.ir.util.patchDeclarationParents import org.jetbrains.kotlin.psi.KtFile @@ -20,13 +21,16 @@ fun compile( configuration: CompilerConfiguration, phaseConfig: PhaseConfig = PhaseConfig(jsPhases), immediateDependencies: List, - allDependencies: List + allDependencies: List, + mainArguments: List? ): String { val (moduleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer) = loadIr(project, files, configuration, immediateDependencies, allDependencies) val moduleDescriptor = moduleFragment.descriptor + val mainFunction = JsMainFunctionDetector.getMainFunctionOrNull(moduleFragment) + val context = JsIrBackendContext(moduleDescriptor, irBuiltIns, symbolTable, moduleFragment, configuration) // Load declarations referenced during `context` initialization @@ -55,6 +59,7 @@ fun compile( jsPhases.invokeToplevel(phaseConfig, context, moduleFragment) - val jsProgram = moduleFragment.accept(IrModuleToJsTransformer(context), null) + val jsProgram = + moduleFragment.accept(IrModuleToJsTransformer(context, mainFunction, mainArguments), null) return jsProgram.toString() } \ No newline at end of file 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 6cd153ab981..0f121dad82f 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 @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs import org.jetbrains.kotlin.config.CommonConfigurationKeys +import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.utils.* @@ -18,7 +19,9 @@ import org.jetbrains.kotlin.utils.DFS import org.jetbrains.kotlin.utils.addIfNotNull class IrModuleToJsTransformer( - private val backendContext: JsIrBackendContext + private val backendContext: JsIrBackendContext, + private val mainFunction: IrSimpleFunction?, + private val mainArguments: List? ) : BaseIrElementToJsNodeTransformer { val moduleName = backendContext.configuration[CommonConfigurationKeys.MODULE_NAME]!! @@ -166,6 +169,7 @@ class IrModuleToJsTransformer( statements += importStatements statements += moduleBody statements += exportStatements + statements += generateCallToMain(rootContext) statements += JsReturn(internalModuleName.makeRef()) } } @@ -181,6 +185,27 @@ class IrModuleToJsTransformer( return program } + private fun generateMainArguments(mainFunction: IrSimpleFunction, rootContext: JsGenerationContext): List { + val mainArguments = this.mainArguments!! + val mainArgumentsArray = + if (mainFunction.valueParameters.isNotEmpty()) JsArrayLiteral(mainArguments.map { JsStringLiteral(it) }) else null + + val continuation = if (mainFunction.isSuspend) { + val emptyContinuationField = backendContext.coroutineEmptyContinuation.owner + rootContext.getNameForField(emptyContinuationField).makeRef() + } else null + + return listOfNotNull(mainArgumentsArray, continuation) + } + + private fun generateCallToMain(rootContext: JsGenerationContext): List { + if (mainArguments == null) return emptyList() // in case `NO_MAIN` and `main(..)` exists + return mainFunction?.let { + val jsName = rootContext.getNameForStaticFunction(it) + listOf(JsInvocation(jsName.makeRef(), generateMainArguments(it, rootContext)).makeStmt()) + } ?: emptyList() + } + private fun generateImportStatements( getNameForExternalDeclaration: (IrDeclarationWithName) -> JsName, declareFreshGlobal: (String) -> JsName diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsMainFunctionDetector.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsMainFunctionDetector.kt new file mode 100644 index 00000000000..261656dc1d0 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsMainFunctionDetector.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.ir.backend.js.utils + +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.types.Variance + +object JsMainFunctionDetector { + + + private fun IrSimpleFunction.isSuitableForMainParametersSize(allowEmptyParameters: Boolean): Boolean = + when (valueParameters.size) { + 1 -> true + 0 -> allowEmptyParameters + else -> false + } + + private fun IrSimpleFunction.isMain(allowEmptyParameters: Boolean): Boolean { + if (typeParameters.isNotEmpty()) return false + if (!isSuitableForMainParametersSize(allowEmptyParameters)) return false + if (!returnType.isUnit()) return false + if (name.asString() != "main") return false + + if (valueParameters.size == 1) { + val parameter = valueParameters.single() + + if (!parameter.type.isArray()) return false + + val type = parameter.type as IrSimpleType + + if (type.arguments.size != 1) return false + + val argument = type.arguments.single() as? IrTypeProjection ?: return false + + if (argument.variance == Variance.IN_VARIANCE) return false + + return argument.type.isString() + } else { + require(allowEmptyParameters) + require(valueParameters.isEmpty()) + + val file = parent as IrFile + + return !file.declarations.filterIsInstance().any { it.isMain(allowEmptyParameters = false) } + } + } + + fun getMainFunctionOrNull(module: IrModuleFragment): IrSimpleFunction? { + + var resultPair: Pair? = null + + module.files.forEach { f -> + val fqn = f.fqName.asString() + + f.declarations.filterIsInstance().singleOrNull { it.isMain(allowEmptyParameters = true) }?.let { + val result = resultPair + if (result == null) { + resultPair = Pair(fqn, it) + } else { + if (fqn < result.first) { + resultPair = Pair(fqn, it) + } + } + } + } + + return resultPair?.second + } +} \ No newline at end of file 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 3ad4cc9ed6b..a489a97aa68 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 @@ -96,7 +96,8 @@ abstract class BasicIrBoxTest( configuration = config.configuration, phaseConfig = config.configuration.get(CLIConfigurationKeys.PHASE_CONFIG) ?: PhaseConfig(jsPhases), immediateDependencies = dependencies, - allDependencies = allDependencies + allDependencies = allDependencies, + mainArguments = mainCallParameters.run { if (shouldBeGenerated()) arguments() else null } ) val wrappedCode = wrapWithModuleEmulationMarkers(jsCode, moduleId = config.moduleId, moduleKind = config.moduleKind) diff --git a/js/js.translator/testData/box/main/incremental.kt b/js/js.translator/testData/box/main/incremental.kt index 4ff73580c84..d8f5ab6ea09 100644 --- a/js/js.translator/testData/box/main/incremental.kt +++ b/js/js.translator/testData/box/main/incremental.kt @@ -1,5 +1,4 @@ // EXPECTED_REACHABLE_NODES: 1281 -// IGNORE_BACKEND: JS_IR // CALL_MAIN // FILE: ok.kt @@ -19,6 +18,16 @@ fun main(args: Array) { ok = "fail: b.b" } +// FILE: 0.kt + +package b + +import ok.* + +fun main(args: Array) { + ok = "fail: b" +} + // FILE: 2.kt package a diff --git a/js/js.translator/testData/box/main/noArgs.kt b/js/js.translator/testData/box/main/noArgs.kt index 4b62dc8b5ef..8f78a5dc47b 100644 --- a/js/js.translator/testData/box/main/noArgs.kt +++ b/js/js.translator/testData/box/main/noArgs.kt @@ -1,5 +1,4 @@ // EXPECTED_REACHABLE_NODES: 1281 -// IGNORE_BACKEND: JS_IR // CALL_MAIN var ok: String = "fail" diff --git a/js/js.translator/testData/box/main/simple.kt b/js/js.translator/testData/box/main/simple.kt index 14678d25b05..f0c7891d221 100644 --- a/js/js.translator/testData/box/main/simple.kt +++ b/js/js.translator/testData/box/main/simple.kt @@ -1,5 +1,4 @@ // EXPECTED_REACHABLE_NODES: 1281 -// IGNORE_BACKEND: JS_IR // CALL_MAIN var ok: String = "fail" diff --git a/js/js.translator/testData/box/main/suspendMain.kt b/js/js.translator/testData/box/main/suspendMain.kt index e6af1cc34ea..31b89021ed0 100644 --- a/js/js.translator/testData/box/main/suspendMain.kt +++ b/js/js.translator/testData/box/main/suspendMain.kt @@ -1,5 +1,4 @@ // EXPECTED_REACHABLE_NODES: 1296 -// IGNORE_BACKEND: JS_IR // CALL_MAIN import kotlin.coroutines.* diff --git a/js/js.translator/testData/box/main/suspendMainNoArgs.kt b/js/js.translator/testData/box/main/suspendMainNoArgs.kt index ef9e5e650fd..708858ee121 100644 --- a/js/js.translator/testData/box/main/suspendMainNoArgs.kt +++ b/js/js.translator/testData/box/main/suspendMainNoArgs.kt @@ -1,5 +1,4 @@ // EXPECTED_REACHABLE_NODES: 1296 -// IGNORE_BACKEND: JS_IR // CALL_MAIN import kotlin.coroutines.*