From ead9b31e03763845bcc89f0faa34a089d2066098 Mon Sep 17 00:00:00 2001 From: Anton Bannykh Date: Mon, 10 Sep 2018 19:33:04 +0300 Subject: [PATCH] JS: support `[suspend] fun main([args: Array])` (KT-26628, KT-26572 fixed) --- .../jetbrains/kotlin/js/test/BasicBoxTest.kt | 5 ++- .../js/test/semantics/BoxJsTestGenerated.java | 38 +++++++++++++++++++ .../test/semantics/IrBoxJsTestGenerated.java | 38 +++++++++++++++++++ .../js/translate/general/Translation.java | 26 ++++++++++++- js/js.translator/testData/box/main/noArgs.kt | 11 ++++++ js/js.translator/testData/box/main/simple.kt | 14 +++++++ .../testData/box/main/suspendMain.kt | 28 ++++++++++++++ .../testData/box/main/suspendMainNoArgs.kt | 25 ++++++++++++ .../testData/box/main/suspendMainThrows.kt | 30 +++++++++++++++ .../js/internal/EmptyContinuation.kt | 14 +++++++ 10 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 js/js.translator/testData/box/main/noArgs.kt create mode 100644 js/js.translator/testData/box/main/simple.kt create mode 100644 js/js.translator/testData/box/main/suspendMain.kt create mode 100644 js/js.translator/testData/box/main/suspendMainNoArgs.kt create mode 100644 js/js.translator/testData/box/main/suspendMainThrows.kt create mode 100644 libraries/stdlib/coroutines/js/src/kotlin/coroutines/js/internal/EmptyContinuation.kt 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 089d5213b17..d8056f6907d 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 @@ -102,6 +102,8 @@ abstract class BasicBoxTest( fileContent = fileContent.replace("COROUTINES_PACKAGE", coroutinesPackage) } + val actualMainCallParameters = if (CALL_MAIN_PATTERN.matcher(fileContent).find()) MainCallParameters.mainWithArguments(listOf("testArg")) else mainCallParameters + val outputPrefixFile = getOutputPrefixFile(filePath) val outputPostfixFile = getOutputPostfixFile(filePath) @@ -124,7 +126,7 @@ abstract class BasicBoxTest( generateJavaScriptFile( file.parent, module, outputFileName, dependencies, friends, modules.size > 1, !SKIP_SOURCEMAP_REMAPPING.matcher(fileContent).find(), - outputPrefixFile, outputPostfixFile, mainCallParameters, testPackage, testFunction + outputPrefixFile, outputPostfixFile, actualMainCallParameters, testPackage, testFunction ) if (!module.name.endsWith(OLD_MODULE_SUFFIX)) Pair(outputFileName, module) else null @@ -771,6 +773,7 @@ abstract class BasicBoxTest( private val EXPECTED_REACHABLE_NODES = Pattern.compile("^// *$EXPECTED_REACHABLE_NODES_DIRECTIVE: *([0-9]+) *$", Pattern.MULTILINE) private val RECOMPILE_PATTERN = Pattern.compile("^// *RECOMPILE *$", Pattern.MULTILINE) private val SOURCE_MAP_SOURCE_EMBEDDING = Regex("^// *SOURCE_MAP_EMBED_SOURCES: ([A-Z]+)*\$", RegexOption.MULTILINE) + private val CALL_MAIN_PATTERN = Pattern.compile("^// *CALL_MAIN *$", Pattern.MULTILINE) val TEST_MODULE = "JS_TESTS" private val DEFAULT_MODULE = "main" 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 1ef6b5d4485..d1c7d6a5cc0 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 @@ -5493,6 +5493,44 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { } } + @TestMetadata("js/js.translator/testData/box/main") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Main extends AbstractBoxJsTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath); + } + + public void testAllFilesPresentInMain() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/box/main"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS, true); + } + + @TestMetadata("noArgs.kt") + public void testNoArgs() throws Exception { + runTest("js/js.translator/testData/box/main/noArgs.kt"); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + runTest("js/js.translator/testData/box/main/simple.kt"); + } + + @TestMetadata("suspendMain.kt") + public void testSuspendMain() throws Exception { + runTest("js/js.translator/testData/box/main/suspendMain.kt"); + } + + @TestMetadata("suspendMainNoArgs.kt") + public void testSuspendMainNoArgs() throws Exception { + runTest("js/js.translator/testData/box/main/suspendMainNoArgs.kt"); + } + + @TestMetadata("suspendMainThrows.kt") + public void testSuspendMainThrows() throws Exception { + runTest("js/js.translator/testData/box/main/suspendMainThrows.kt"); + } + } + @TestMetadata("js/js.translator/testData/box/multiFile") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrBoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrBoxJsTestGenerated.java index 74a746e87c7..9399ba2f919 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrBoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrBoxJsTestGenerated.java @@ -5493,6 +5493,44 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { } } + @TestMetadata("js/js.translator/testData/box/main") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Main extends AbstractIrBoxJsTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath); + } + + public void testAllFilesPresentInMain() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/box/main"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS_IR, true); + } + + @TestMetadata("noArgs.kt") + public void testNoArgs() throws Exception { + runTest("js/js.translator/testData/box/main/noArgs.kt"); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + runTest("js/js.translator/testData/box/main/simple.kt"); + } + + @TestMetadata("suspendMain.kt") + public void testSuspendMain() throws Exception { + runTest("js/js.translator/testData/box/main/suspendMain.kt"); + } + + @TestMetadata("suspendMainNoArgs.kt") + public void testSuspendMainNoArgs() throws Exception { + runTest("js/js.translator/testData/box/main/suspendMainNoArgs.kt"); + } + + @TestMetadata("suspendMainThrows.kt") + public void testSuspendMainThrows() throws Exception { + runTest("js/js.translator/testData/box/main/suspendMainThrows.kt"); + } + } + @TestMetadata("js/js.translator/testData/box/multiFile") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java index 1f2685423d4..cd074466910 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; import org.jetbrains.kotlin.descriptors.FunctionDescriptor; import org.jetbrains.kotlin.descriptors.ModuleDescriptor; +import org.jetbrains.kotlin.descriptors.PropertyDescriptor; import org.jetbrains.kotlin.idea.MainFunctionDetector; import org.jetbrains.kotlin.js.backend.ast.*; import org.jetbrains.kotlin.js.backend.ast.metadata.MetadataProperties; @@ -41,17 +42,22 @@ import org.jetbrains.kotlin.js.translate.context.TranslationContext; import org.jetbrains.kotlin.js.translate.declaration.FileDeclarationVisitor; import org.jetbrains.kotlin.js.translate.expression.ExpressionVisitor; import org.jetbrains.kotlin.js.translate.expression.PatternTranslator; +import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator; import org.jetbrains.kotlin.js.translate.test.JSTestGenerator; import org.jetbrains.kotlin.js.translate.utils.*; import org.jetbrains.kotlin.js.translate.utils.mutator.AssignToExpressionMutator; +import org.jetbrains.kotlin.name.FqNameUnsafe; +import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.KtDeclaration; import org.jetbrains.kotlin.psi.KtExpression; import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.psi.KtSimpleNameExpression; import org.jetbrains.kotlin.resolve.BindingTrace; +import org.jetbrains.kotlin.resolve.DescriptorUtils; import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilsKt; import org.jetbrains.kotlin.resolve.constants.*; import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator; +import org.jetbrains.kotlin.resolve.scopes.MemberScope; import org.jetbrains.kotlin.serialization.js.ast.JsAstDeserializer; import org.jetbrains.kotlin.types.KotlinType; import org.jetbrains.kotlin.types.TypeUtils; @@ -468,8 +474,24 @@ public final class Translation { if (functionDescriptor == null) { return null; } - JsArrayLiteral argument = new JsArrayLiteral(toStringLiteralList(arguments)); - JsExpression call = CallTranslator.INSTANCE.buildCall(context, functionDescriptor, Collections.singletonList(argument), null); + + int parameterCount = functionDescriptor.getValueParameters().size(); + assert parameterCount <= 1; + + List args = new ArrayList<>(); + if (parameterCount != 0) { + args.add(new JsArrayLiteral(toStringLiteralList(arguments))); + } + + if (functionDescriptor.isSuspend()) { + MemberScope scope = moduleDescriptor.getPackage(new FqNameUnsafe("kotlin.coroutines.js.internal").toSafe()).getMemberScope(); + PropertyDescriptor emptyContinuation = DescriptorUtils.getPropertyByName(scope, Name.identifier("EmptyContinuation")); + + args.add(ReferenceTranslator.translateAsValueReference(emptyContinuation, context)); + args.add(new JsBooleanLiteral(false)); + } + + JsExpression call = CallTranslator.INSTANCE.buildCall(context, functionDescriptor, args, null); context.addTopLevelStatement(call.makeStmt()); return staticContext.getFragment(); } diff --git a/js/js.translator/testData/box/main/noArgs.kt b/js/js.translator/testData/box/main/noArgs.kt new file mode 100644 index 00000000000..4b62dc8b5ef --- /dev/null +++ b/js/js.translator/testData/box/main/noArgs.kt @@ -0,0 +1,11 @@ +// EXPECTED_REACHABLE_NODES: 1281 +// IGNORE_BACKEND: JS_IR +// CALL_MAIN + +var ok: String = "fail" + +fun main() { + ok = "OK" +} + +fun box() = ok \ No newline at end of file diff --git a/js/js.translator/testData/box/main/simple.kt b/js/js.translator/testData/box/main/simple.kt new file mode 100644 index 00000000000..14678d25b05 --- /dev/null +++ b/js/js.translator/testData/box/main/simple.kt @@ -0,0 +1,14 @@ +// EXPECTED_REACHABLE_NODES: 1281 +// IGNORE_BACKEND: JS_IR +// CALL_MAIN + +var ok: String = "fail" + +fun main(args: Array) { + assertEquals(1, args.size) + assertEquals("testArg", args[0]) + + ok = "OK" +} + +fun box() = ok \ No newline at end of file diff --git a/js/js.translator/testData/box/main/suspendMain.kt b/js/js.translator/testData/box/main/suspendMain.kt new file mode 100644 index 00000000000..e6af1cc34ea --- /dev/null +++ b/js/js.translator/testData/box/main/suspendMain.kt @@ -0,0 +1,28 @@ +// EXPECTED_REACHABLE_NODES: 1296 +// IGNORE_BACKEND: JS_IR +// CALL_MAIN + +import kotlin.coroutines.* + +var ok: String = "fail" + +var callback: () -> Unit = {} + +suspend fun main(args: Array) { + assertEquals(1, args.size) + assertEquals("testArg", args[0]) + + suspendCoroutine { cont -> + callback = { + cont.resume(Unit) + } + } + + ok = "OK" +} + +fun box(): String { + assertEquals("fail", ok) + callback() + return ok +} \ No newline at end of file diff --git a/js/js.translator/testData/box/main/suspendMainNoArgs.kt b/js/js.translator/testData/box/main/suspendMainNoArgs.kt new file mode 100644 index 00000000000..ef9e5e650fd --- /dev/null +++ b/js/js.translator/testData/box/main/suspendMainNoArgs.kt @@ -0,0 +1,25 @@ +// EXPECTED_REACHABLE_NODES: 1296 +// IGNORE_BACKEND: JS_IR +// CALL_MAIN + +import kotlin.coroutines.* + +var ok: String = "fail" + +var callback: () -> Unit = {} + +suspend fun main() { + suspendCoroutine { cont -> + callback = { + cont.resume(Unit) + } + } + + ok = "OK" +} + +fun box(): String { + assertEquals("fail", ok) + callback() + return ok +} \ No newline at end of file diff --git a/js/js.translator/testData/box/main/suspendMainThrows.kt b/js/js.translator/testData/box/main/suspendMainThrows.kt new file mode 100644 index 00000000000..9694ee2c739 --- /dev/null +++ b/js/js.translator/testData/box/main/suspendMainThrows.kt @@ -0,0 +1,30 @@ +// EXPECTED_REACHABLE_NODES: 1299 +// IGNORE_BACKEND: JS_IR +// CALL_MAIN + +import kotlin.coroutines.* + +var callback: () -> Unit = {} + +val exception = Exception() + +suspend fun main() { + + suspendCoroutine { cont -> + callback = { + cont.resume(Unit) + } + } + + throw exception +} + +fun box(): String { + try { + callback() + } catch (e: Exception) { + assertTrue(e === exception) + } + + return "OK" +} \ No newline at end of file diff --git a/libraries/stdlib/coroutines/js/src/kotlin/coroutines/js/internal/EmptyContinuation.kt b/libraries/stdlib/coroutines/js/src/kotlin/coroutines/js/internal/EmptyContinuation.kt new file mode 100644 index 00000000000..142286c8530 --- /dev/null +++ b/libraries/stdlib/coroutines/js/src/kotlin/coroutines/js/internal/EmptyContinuation.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2010-2018 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 kotlin.coroutines.js.internal + +import kotlin.coroutines.Continuation +import kotlin.coroutines.EmptyCoroutineContext + +@PublishedApi +internal val EmptyContinuation = Continuation(EmptyCoroutineContext) { result -> + result.getOrThrow() +} \ No newline at end of file