From bad50c03a7f92c160633df9b8b0b6460c8e38ce6 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Mon, 11 Sep 2017 15:07:58 +0300 Subject: [PATCH] JS: support cross-module inlining of suspend functions See KT-18063 --- .../js/ast/DebugJsAstProtoBuf.java | 46 +++++++- .../multiModule/inlineMultiModule.kt | 69 ++++++++++++ .../multiModule/inlineMultiModuleOverride.kt | 80 ++++++++++++++ .../inlineMultiModuleWithController.kt | 77 +++++++++++++ .../ir/IrBlackBoxCodegenTestGenerated.java | 24 +++++ .../codegen/BlackBoxCodegenTestGenerated.java | 24 +++++ .../LightAnalysisModeTestGenerated.java | 24 +++++ .../js/backend/ast/JsVisitorWithContext.java | 19 ++++ .../ast/metadata/metadataProperties.kt | 4 + .../kotlin/js/inline/FunctionReader.kt | 27 +++-- .../jetbrains/kotlin/js/inline/JsInliner.java | 2 + js/js.libraries/src/js/markerFunctions.js | 13 +++ js/js.serializer/src/js-ast.proto | 4 + .../serialization/js/ast/JsAstDeserializer.kt | 4 + .../serialization/js/ast/JsAstProtoBuf.java | 36 +++++++ .../serialization/js/ast/JsAstSerializer.kt | 4 + .../semantics/JsCodegenBoxTestGenerated.java | 18 ++++ .../kotlin/js/translate/context/Namer.java | 24 +---- .../declaration/AbstractDeclarationVisitor.kt | 42 ++++++-- .../declaration/inlineCoroutineUtil.kt | 101 ++++++++++++++++++ .../expression/FunctionTranslator.kt | 2 +- .../js/translate/utils/TranslationUtils.java | 2 +- 22 files changed, 599 insertions(+), 47 deletions(-) create mode 100644 compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModule.kt create mode 100644 compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleOverride.kt create mode 100644 compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleWithController.kt create mode 100644 js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/inlineCoroutineUtil.kt diff --git a/build-common/test/org/jetbrains/kotlin/serialization/js/ast/DebugJsAstProtoBuf.java b/build-common/test/org/jetbrains/kotlin/serialization/js/ast/DebugJsAstProtoBuf.java index 5ee90c76468..8f32bf61ac4 100644 --- a/build-common/test/org/jetbrains/kotlin/serialization/js/ast/DebugJsAstProtoBuf.java +++ b/build-common/test/org/jetbrains/kotlin/serialization/js/ast/DebugJsAstProtoBuf.java @@ -211,6 +211,22 @@ public final class DebugJsAstProtoBuf { * UNBOX_CHAR = 4; */ UNBOX_CHAR(3, 4), + /** + * SUSPEND_CALL = 5; + */ + SUSPEND_CALL(4, 5), + /** + * COROUTINE_RESULT = 6; + */ + COROUTINE_RESULT(5, 6), + /** + * COROUTINE_CONTROLLER = 7; + */ + COROUTINE_CONTROLLER(6, 7), + /** + * COROUTINE_RECEIVER = 8; + */ + COROUTINE_RECEIVER(7, 8), ; /** @@ -229,6 +245,22 @@ public final class DebugJsAstProtoBuf { * UNBOX_CHAR = 4; */ public static final int UNBOX_CHAR_VALUE = 4; + /** + * SUSPEND_CALL = 5; + */ + public static final int SUSPEND_CALL_VALUE = 5; + /** + * COROUTINE_RESULT = 6; + */ + public static final int COROUTINE_RESULT_VALUE = 6; + /** + * COROUTINE_CONTROLLER = 7; + */ + public static final int COROUTINE_CONTROLLER_VALUE = 7; + /** + * COROUTINE_RECEIVER = 8; + */ + public static final int COROUTINE_RECEIVER_VALUE = 8; public final int getNumber() { return value; } @@ -239,6 +271,10 @@ public final class DebugJsAstProtoBuf { case 2: return WRAP_FUNCTION; case 3: return TO_BOXED_CHAR; case 4: return UNBOX_CHAR; + case 5: return SUSPEND_CALL; + case 6: return COROUTINE_RESULT; + case 7: return COROUTINE_CONTROLLER; + case 8: return COROUTINE_RECEIVER; default: return null; } } @@ -49361,10 +49397,12 @@ public final class DebugJsAstProtoBuf { "t.Fragment*@\n\013SideEffects\022\021\n\rAFFECTS_STA" + "TE\020\001\022\024\n\020DEPENDS_ON_STATE\020\002\022\010\n\004PURE\020\003*?\n\016" + "InlineStrategy\022\017\n\013AS_FUNCTION\020\000\022\014\n\010IN_PL" + - "ACE\020\001\022\016\n\nNOT_INLINE\020\002*c\n\017SpecialFunction" + - "\022\032\n\026DEFINE_INLINE_FUNCTION\020\001\022\021\n\rWRAP_FUN" + - "CTION\020\002\022\021\n\rTO_BOXED_CHAR\020\003\022\016\n\nUNBOX_CHAR" + - "\020\004B\024B\022DebugJsAstProtoBuf" + "ACE\020\001\022\016\n\nNOT_INLINE\020\002*\275\001\n\017SpecialFunctio" + + "n\022\032\n\026DEFINE_INLINE_FUNCTION\020\001\022\021\n\rWRAP_FU" + + "NCTION\020\002\022\021\n\rTO_BOXED_CHAR\020\003\022\016\n\nUNBOX_CHA" + + "R\020\004\022\020\n\014SUSPEND_CALL\020\005\022\024\n\020COROUTINE_RESUL" + + "T\020\006\022\030\n\024COROUTINE_CONTROLLER\020\007\022\026\n\022COROUTI" + + "NE_RECEIVER\020\010B\024B\022DebugJsAstProtoBuf" }; org.jetbrains.kotlin.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new org.jetbrains.kotlin.protobuf.Descriptors.FileDescriptor. InternalDescriptorAssigner() { diff --git a/compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModule.kt b/compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModule.kt new file mode 100644 index 00000000000..ad5db0563d9 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModule.kt @@ -0,0 +1,69 @@ +// IGNORE_BACKEND: NATIVE +// WITH_COROUTINES +// WITH_RUNTIME + +// MODULE: lib(support) +// FILE: lib.kt + +import helpers.* +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* + +var continuation: () -> Unit = { } +var log = "" +var finished = false + +suspend fun foo(v: T): T = suspendCoroutineOrReturn { x -> + continuation = { + x.resume(v) + } + log += "foo($v);" + COROUTINE_SUSPENDED +} + +inline suspend fun bar(v: String) { + log += "before bar($v);" + foo("1:$v") + log += "inside bar($v);" + foo("2:$v") + log += "after bar($v);" +} + +fun builder(c: suspend () -> Unit) { + c.startCoroutine(handleResultContinuation { + continuation = { } + finished = true + }) +} + +// MODULE: main(lib) +// FILE: main.kt + +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* + +suspend fun baz() { + bar("A") + log += "between bar;" + bar("B") +} + +val expectedString = + "before bar(A);foo(1:A);@;inside bar(A);foo(2:A);@;after bar(A);" + + "between bar;" + + "before bar(B);foo(1:B);@;inside bar(B);foo(2:B);@;after bar(B);" + +fun box(): String { + builder { + baz() + } + + while (!finished) { + log += "@;" + continuation() + } + + if (log != expectedString) return "fail: $log" + + return "OK" +} diff --git a/compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleOverride.kt b/compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleOverride.kt new file mode 100644 index 00000000000..bc9a47a50f5 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleOverride.kt @@ -0,0 +1,80 @@ +// IGNORE_BACKEND: NATIVE +// IGNORE_BACKEND: JVM +// WITH_COROUTINES +// WITH_RUNTIME + +// MODULE: lib(support) +// FILE: lib.kt + +import helpers.* +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* + +var continuation: () -> Unit = { } +var log = "" +var finished = false + +suspend fun foo(v: T): T = suspendCoroutineOrReturn { x -> + continuation = { + x.resume(v) + } + log += "foo($v);" + COROUTINE_SUSPENDED +} + +interface I { + suspend fun bar() +} + +class A(val v: String) : I { + override inline suspend fun bar() { + log += "before bar($v);" + foo("1:$v") + log += "inside bar($v);" + foo("2:$v") + log += "after bar($v);" + } +} + +fun builder(c: suspend () -> Unit) { + c.startCoroutine(handleResultContinuation { + continuation = { } + finished = true + }) +} + +// MODULE: main(lib) +// FILE: main.kt + +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* + +suspend fun baz() { + val a = A("A") + a.bar() + + log += "between bar;" + + val b: I = A("B") + b.bar() +} + +val expectedString = + "before bar(A);foo(1:A);@;inside bar(A);foo(2:A);@;after bar(A);" + + "between bar;" + + "before bar(B);foo(1:B);@;inside bar(B);foo(2:B);@;after bar(B);" + +fun box(): String { + builder { + baz() + } + + while (!finished) { + log += "@;" + continuation() + } + + if (log != expectedString) return "fail: $log" + + return "OK" +} diff --git a/compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleWithController.kt b/compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleWithController.kt new file mode 100644 index 00000000000..902ef8e991f --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleWithController.kt @@ -0,0 +1,77 @@ +// IGNORE_BACKEND: NATIVE +// WITH_COROUTINES +// WITH_RUNTIME + +// MODULE: lib(support) +// FILE: lib.kt + +import helpers.* +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* + +var continuation: () -> Unit = { } +var log = "" +var finished = false + +class C { + var v: String = "" + + inline suspend fun bar() { + log += "before bar($v);" + foo("1:$v") + log += "inside bar($v);" + foo("2:$v") + log += "after bar($v);" + } +} + +suspend fun foo(v: T): T = suspendCoroutineOrReturn { x -> + continuation = { + x.resume(v) + } + log += "foo($v);" + COROUTINE_SUSPENDED +} + +fun C.builder(c: suspend C.() -> Unit) { + c.startCoroutine(this, handleResultContinuation { + continuation = { } + finished = true + }) +} + +// MODULE: main(lib) +// FILE: main.kt + +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* + +suspend fun C.baz() { + v = "A" + bar() + log += "between bar;" + v = "B" + bar() +} + +val expectedString = + "before bar(A);foo(1:A);@;inside bar(A);foo(2:A);@;after bar(A);" + + "between bar;" + + "before bar(B);foo(1:B);@;inside bar(B);foo(2:B);@;after bar(B);" + +fun box(): String { + var c = C() + + c.builder { + baz() + } + + while (!finished) { + log += "@;" + continuation() + } + + if (log != expectedString) return "fail: $log" + + return "OK" +} diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 432c90eac09..4584c79d688 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -5788,6 +5788,30 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } + @TestMetadata("inlineMultiModule.kt") + public void testInlineMultiModule() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModule.kt"); + doTest(fileName); + } + + @TestMetadata("inlineMultiModuleOverride.kt") + public void testInlineMultiModuleOverride() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleOverride.kt"); + try { + doTest(fileName); + } + catch (Throwable ignore) { + return; + } + throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); + } + + @TestMetadata("inlineMultiModuleWithController.kt") + public void testInlineMultiModuleWithController() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleWithController.kt"); + doTest(fileName); + } + @TestMetadata("simple.kt") public void testSimple() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/simple.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 76893a87724..7ad0b3590e2 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -5788,6 +5788,30 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("inlineMultiModule.kt") + public void testInlineMultiModule() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModule.kt"); + doTest(fileName); + } + + @TestMetadata("inlineMultiModuleOverride.kt") + public void testInlineMultiModuleOverride() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleOverride.kt"); + try { + doTest(fileName); + } + catch (Throwable ignore) { + return; + } + throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); + } + + @TestMetadata("inlineMultiModuleWithController.kt") + public void testInlineMultiModuleWithController() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleWithController.kt"); + doTest(fileName); + } + @TestMetadata("simple.kt") public void testSimple() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/simple.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 93dee7c4d1f..bcac1e48bcb 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -5778,6 +5778,18 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) public static class MultiModule extends AbstractLightAnalysisModeTest { + @TestMetadata("inlineMultiModuleOverride.kt") + public void ignoreInlineMultiModuleOverride() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleOverride.kt"); + try { + doTest(fileName); + } + catch (Throwable ignore) { + return; + } + throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); + } + public void testAllFilesPresentInMultiModule() throws Exception { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/multiModule"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); } @@ -5788,6 +5800,18 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes doTest(fileName); } + @TestMetadata("inlineMultiModule.kt") + public void testInlineMultiModule() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModule.kt"); + doTest(fileName); + } + + @TestMetadata("inlineMultiModuleWithController.kt") + public void testInlineMultiModuleWithController() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleWithController.kt"); + doTest(fileName); + } + @TestMetadata("simple.kt") public void testSimple() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/simple.kt"); diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsVisitorWithContext.java b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsVisitorWithContext.java index 878f4d155f2..95c5dc6945d 100644 --- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsVisitorWithContext.java +++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsVisitorWithContext.java @@ -59,19 +59,26 @@ public abstract class JsVisitorWithContext { doAcceptStatementList(statements); } + public void endVisit(@NotNull JsExpression x, @NotNull JsContext ctx) { + } + public void endVisit(@NotNull JsArrayAccess x, @NotNull JsContext ctx) { + endVisit((JsExpression) x, ctx); } public void endVisit(@NotNull JsArrayLiteral x, @NotNull JsContext ctx) { + endVisit((JsExpression) x, ctx); } public void endVisit(@NotNull JsBinaryOperation x, @NotNull JsContext ctx) { + endVisit((JsExpression) x, ctx); } public void endVisit(@NotNull JsBlock x, @NotNull JsContext ctx) { } public void endVisit(@NotNull JsBooleanLiteral x, @NotNull JsContext ctx) { + endVisit((JsExpression) x, ctx); } public void endVisit(@NotNull JsBreak x, @NotNull JsContext ctx) { @@ -84,6 +91,7 @@ public abstract class JsVisitorWithContext { } public void endVisit(@NotNull JsConditional x, @NotNull JsContext ctx) { + endVisit((JsExpression) x, ctx); } public void endVisit(@NotNull JsContinue x, @NotNull JsContext ctx) { @@ -111,12 +119,14 @@ public abstract class JsVisitorWithContext { } public void endVisit(@NotNull JsFunction x, @NotNull JsContext ctx) { + endVisit((JsExpression) x, ctx); } public void endVisit(@NotNull JsIf x, @NotNull JsContext ctx) { } public void endVisit(@NotNull JsInvocation x, @NotNull JsContext ctx) { + endVisit((JsExpression) x, ctx); } public void endVisit(@NotNull JsLabel x, @NotNull JsContext ctx) { @@ -126,27 +136,33 @@ public abstract class JsVisitorWithContext { } public void endVisit(@NotNull JsNameRef x, @NotNull JsContext ctx) { + endVisit((JsExpression) x, ctx); } public void endVisit(@NotNull JsNew x, @NotNull JsContext ctx) { } public void endVisit(@NotNull JsNullLiteral x, @NotNull JsContext ctx) { + endVisit((JsExpression) x, ctx); } public void endVisit(@NotNull JsNumberLiteral x, @NotNull JsContext ctx) { + endVisit((JsExpression) x, ctx); } public void endVisit(@NotNull JsObjectLiteral x, @NotNull JsContext ctx) { + endVisit((JsExpression) x, ctx); } public void endVisit(@NotNull JsParameter x, @NotNull JsContext ctx) { } public void endVisit(@NotNull JsPostfixOperation x, @NotNull JsContext ctx) { + endVisit((JsExpression) x, ctx); } public void endVisit(@NotNull JsPrefixOperation x, @NotNull JsContext ctx) { + endVisit((JsExpression) x, ctx); } public void endVisit(@NotNull JsProgram x, @NotNull JsContext ctx) { @@ -156,18 +172,21 @@ public abstract class JsVisitorWithContext { } public void endVisit(@NotNull JsRegExp x, @NotNull JsContext ctx) { + endVisit((JsExpression) x, ctx); } public void endVisit(@NotNull JsReturn x, @NotNull JsContext ctx) { } public void endVisit(@NotNull JsStringLiteral x, @NotNull JsContext ctx) { + endVisit((JsExpression) x, ctx); } public void endVisit(@NotNull JsSwitch x, @NotNull JsContext ctx) { } public void endVisit(@NotNull JsThisRef x, @NotNull JsContext ctx) { + endVisit((JsExpression) x, ctx); } public void endVisit(@NotNull JsThrow x, @NotNull JsContext ctx) { diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/metadata/metadataProperties.kt b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/metadata/metadataProperties.kt index 1a54f7ff717..a4e5eb3898d 100644 --- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/metadata/metadataProperties.kt +++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/metadata/metadataProperties.kt @@ -147,6 +147,10 @@ enum class SpecialFunction(val suggestedName: String) { WRAP_FUNCTION("wrapFunction"), TO_BOXED_CHAR("toBoxedChar"), UNBOX_CHAR("unboxChar"), + SUSPEND_CALL("suspendCall"), + COROUTINE_RESULT("coroutineResult"), + COROUTINE_CONTROLLER("coroutineController"), + COROUTINE_RECEIVER("coroutineReceiver") } enum class BoxingKind { diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt index c01ad90a15d..aa1a6f34813 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt @@ -49,7 +49,10 @@ private val JS_IDENTIFIER_PART = "$JS_IDENTIFIER_START\\p{Pc}\\p{Mc}\\p{Mn}\\d" private val JS_IDENTIFIER="[$JS_IDENTIFIER_START][$JS_IDENTIFIER_PART]*" private val DEFINE_MODULE_PATTERN = ("($JS_IDENTIFIER)\\.defineModule\\(\\s*(['\"])([^'\"]+)\\2\\s*,\\s*(\\w+)\\s*\\)").toRegex().toPattern() private val DEFINE_MODULE_FIND_PATTERN = ".defineModule(" -private val WRAP_FUNCTION_PATTERN = Regex("var\\s+($JS_IDENTIFIER)\\s*=\\s*($JS_IDENTIFIER)\\.wrapFunction\\s*;").toPattern() + +private val specialFunctions = enumValues().joinToString("|") { it.suggestedName } +private val specialFunctionsByName = enumValues().associateBy { it.suggestedName } +private val SPECIAL_FUNCTION_PATTERN = Regex("var\\s+($JS_IDENTIFIER)\\s*=\\s*($JS_IDENTIFIER)\\.($specialFunctions)\\s*;").toPattern() class FunctionReader( private val reporter: JsConfig.Reporter, @@ -72,13 +75,15 @@ class FunctionReader( val fileContent: String, val moduleVariable: String, val kotlinVariable: String, - val wrapFunctionVariable: String?, + val specialFunctions: Map, offsetToSourceMappingProvider: () -> OffsetToSourceMapping, val sourceMap: SourceMap? ) { val offsetToSourceMapping by lazy(offsetToSourceMappingProvider) - val wrapFunctionRegex = wrapFunctionVariable?.let { Regex("\\s*$it\\s*\\(\\s*").toPattern() } + val wrapFunctionRegex = specialFunctions.entries + .singleOrNull { (_, v) -> v == SpecialFunction.WRAP_FUNCTION }?.key + ?.let { Regex("\\s*$it\\s*\\(\\s*").toPattern() } } private val moduleNameToInfo by lazy { @@ -100,8 +105,12 @@ class FunctionReader( val moduleVariable = preciseMatcher.group(4) val kotlinVariable = preciseMatcher.group(1) - val wrapFunctionVariable = WRAP_FUNCTION_PATTERN.matcher(content).let { matcher -> - if (matcher.find() && matcher.group(2) == kotlinVariable) matcher.group(1) else null + val matcher = SPECIAL_FUNCTION_PATTERN.matcher(content) + val specialFunctions = mutableMapOf() + while (matcher.find()) { + if (matcher.group(2) == kotlinVariable) { + specialFunctions[matcher.group(1)] = specialFunctionsByName[matcher.group(3)]!! + } } val sourceMap = sourceMapContent?.let { @@ -120,7 +129,7 @@ class FunctionReader( fileContent = content, moduleVariable = moduleVariable, kotlinVariable = kotlinVariable, - wrapFunctionVariable = wrapFunctionVariable, + specialFunctions = specialFunctions, offsetToSourceMappingProvider = { OffsetToSourceMapping(content) }, sourceMap = sourceMap ) @@ -246,9 +255,9 @@ class FunctionReader( function.markInlineArguments(descriptor) markDefaultParams(function) - info.wrapFunctionVariable.let { wrapFunction -> - for (externalName in (collectReferencedNames(function) - allDefinedNames).filter { it.ident == wrapFunction }) { - externalName.specialFunction = SpecialFunction.WRAP_FUNCTION + for (externalName in (collectReferencedNames(function) - allDefinedNames)) { + info.specialFunctions[externalName.ident]?.let { + externalName.specialFunction = it } } diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.java b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.java index 1b88c5d3267..4b73f125329 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.java +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.java @@ -43,6 +43,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import static org.jetbrains.kotlin.js.inline.util.CollectUtilsKt.getImportTag; +import static org.jetbrains.kotlin.js.translate.declaration.InlineCoroutineUtilKt.transformSpecialFunctionsToCoroutineMetadata; import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.flattenStatement; import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.pureFqn; @@ -348,6 +349,7 @@ public class JsInliner extends JsVisitorWithContextImpl { } JsFunction function = functionWithWrapper.getFunction().deepCopy(); + function.setBody(transformSpecialFunctionsToCoroutineMetadata(function.getBody())); if (functionWithWrapper.getWrapperBody() != null) { applyWrapper(functionWithWrapper.getWrapperBody(), function, functionWithWrapper.getFunction(), inliningContext); } diff --git a/js/js.libraries/src/js/markerFunctions.js b/js/js.libraries/src/js/markerFunctions.js index eab19252910..e02aaafb26e 100644 --- a/js/js.libraries/src/js/markerFunctions.js +++ b/js/js.libraries/src/js/markerFunctions.js @@ -61,3 +61,16 @@ Kotlin.andPredicate = function (a, b) { Kotlin.kotlinModuleMetadata = function (abiVersion, moduleName, data) { }; + +Kotlin.suspendCall = function(value) { + return value; +}; + +Kotlin.coroutineResult = function(qualifier) { +}; + +Kotlin.coroutineController = function(qualifier) { +}; + +Kotlin.coroutineReceiver = function(qualifier) { +}; \ No newline at end of file diff --git a/js/js.serializer/src/js-ast.proto b/js/js.serializer/src/js-ast.proto index 6a904c7dee3..9635474d45d 100644 --- a/js/js.serializer/src/js-ast.proto +++ b/js/js.serializer/src/js-ast.proto @@ -435,6 +435,10 @@ enum SpecialFunction { WRAP_FUNCTION = 2; TO_BOXED_CHAR = 3; UNBOX_CHAR = 4; + SUSPEND_CALL = 5; + COROUTINE_RESULT = 6; + COROUTINE_CONTROLLER = 7; + COROUTINE_RECEIVER = 8; } diff --git a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstDeserializer.kt b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstDeserializer.kt index 686dbe9c8c7..06ccb635204 100644 --- a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstDeserializer.kt +++ b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstDeserializer.kt @@ -522,6 +522,10 @@ class JsAstDeserializer(program: JsProgram, private val sourceRoots: Iterable SpecialFunction.WRAP_FUNCTION JsAstProtoBuf.SpecialFunction.TO_BOXED_CHAR -> SpecialFunction.TO_BOXED_CHAR JsAstProtoBuf.SpecialFunction.UNBOX_CHAR -> SpecialFunction.UNBOX_CHAR + JsAstProtoBuf.SpecialFunction.SUSPEND_CALL -> SpecialFunction.SUSPEND_CALL + JsAstProtoBuf.SpecialFunction.COROUTINE_RESULT -> SpecialFunction.COROUTINE_RESULT + JsAstProtoBuf.SpecialFunction.COROUTINE_CONTROLLER -> SpecialFunction.COROUTINE_CONTROLLER + JsAstProtoBuf.SpecialFunction.COROUTINE_RECEIVER -> SpecialFunction.COROUTINE_RECEIVER } private fun withLocation(fileId: Int?, location: Location?, action: () -> T): T { diff --git a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstProtoBuf.java b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstProtoBuf.java index 21c70d3290c..cb5593bd162 100644 --- a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstProtoBuf.java +++ b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstProtoBuf.java @@ -159,6 +159,22 @@ public final class JsAstProtoBuf { * UNBOX_CHAR = 4; */ UNBOX_CHAR(3, 4), + /** + * SUSPEND_CALL = 5; + */ + SUSPEND_CALL(4, 5), + /** + * COROUTINE_RESULT = 6; + */ + COROUTINE_RESULT(5, 6), + /** + * COROUTINE_CONTROLLER = 7; + */ + COROUTINE_CONTROLLER(6, 7), + /** + * COROUTINE_RECEIVER = 8; + */ + COROUTINE_RECEIVER(7, 8), ; /** @@ -177,6 +193,22 @@ public final class JsAstProtoBuf { * UNBOX_CHAR = 4; */ public static final int UNBOX_CHAR_VALUE = 4; + /** + * SUSPEND_CALL = 5; + */ + public static final int SUSPEND_CALL_VALUE = 5; + /** + * COROUTINE_RESULT = 6; + */ + public static final int COROUTINE_RESULT_VALUE = 6; + /** + * COROUTINE_CONTROLLER = 7; + */ + public static final int COROUTINE_CONTROLLER_VALUE = 7; + /** + * COROUTINE_RECEIVER = 8; + */ + public static final int COROUTINE_RECEIVER_VALUE = 8; public final int getNumber() { return value; } @@ -187,6 +219,10 @@ public final class JsAstProtoBuf { case 2: return WRAP_FUNCTION; case 3: return TO_BOXED_CHAR; case 4: return UNBOX_CHAR; + case 5: return SUSPEND_CALL; + case 6: return COROUTINE_RESULT; + case 7: return COROUTINE_CONTROLLER; + case 8: return COROUTINE_RECEIVER; default: return null; } } diff --git a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstSerializer.kt b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstSerializer.kt index 46ac9253136..a6275fc2f62 100644 --- a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstSerializer.kt +++ b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstSerializer.kt @@ -558,6 +558,10 @@ class JsAstSerializer(private val pathResolver: (File) -> String) { SpecialFunction.WRAP_FUNCTION -> JsAstProtoBuf.SpecialFunction.WRAP_FUNCTION SpecialFunction.TO_BOXED_CHAR -> JsAstProtoBuf.SpecialFunction.TO_BOXED_CHAR SpecialFunction.UNBOX_CHAR -> JsAstProtoBuf.SpecialFunction.UNBOX_CHAR + SpecialFunction.SUSPEND_CALL -> JsAstProtoBuf.SpecialFunction.SUSPEND_CALL + SpecialFunction.COROUTINE_RESULT -> JsAstProtoBuf.SpecialFunction.COROUTINE_RESULT + SpecialFunction.COROUTINE_CONTROLLER -> JsAstProtoBuf.SpecialFunction.COROUTINE_CONTROLLER + SpecialFunction.COROUTINE_RECEIVER -> JsAstProtoBuf.SpecialFunction.COROUTINE_RECEIVER } private fun serialize(name: JsName): Int = nameMap.getOrPut(name) { diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index f7196604228..ba10b1f4037 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -6424,6 +6424,24 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { doTest(fileName); } + @TestMetadata("inlineMultiModule.kt") + public void testInlineMultiModule() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModule.kt"); + doTest(fileName); + } + + @TestMetadata("inlineMultiModuleOverride.kt") + public void testInlineMultiModuleOverride() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleOverride.kt"); + doTest(fileName); + } + + @TestMetadata("inlineMultiModuleWithController.kt") + public void testInlineMultiModuleWithController() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleWithController.kt"); + doTest(fileName); + } + @TestMetadata("simple.kt") public void testSimple() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/simple.kt"); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java index b932ba79101..5db760caffe 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java @@ -95,7 +95,6 @@ public final class Namer { public static final JsNameRef IS_ARRAY_FUN_REF = new JsNameRef("isArray", "Array"); public static final String DEFINE_INLINE_FUNCTION = "defineInlineFunction"; - private static final String WRAP_FUNCTION = "wrapFunction"; public static final String DEFAULT_PARAMETER_IMPLEMENTOR_SUFFIX = "$default"; private static final JsNameRef JS_OBJECT = new JsNameRef("Object"); @@ -324,30 +323,9 @@ public final class Namer { return pureFqn("Long", kotlinObject()); } - @NotNull - private static JsNameRef createInlineFunction() { - return pureFqn(DEFINE_INLINE_FUNCTION, kotlinObject()); - } - - @NotNull - private static JsNameRef wrapFunction() { - return pureFqn(WRAP_FUNCTION, kotlinObject()); - } - @NotNull public static JsExpression createSpecialFunction(@NotNull SpecialFunction specialFunction) { - switch (specialFunction) { - case DEFINE_INLINE_FUNCTION: - return createInlineFunction(); - case WRAP_FUNCTION: - return wrapFunction(); - case TO_BOXED_CHAR: - return pureFqn("toBoxedChar", kotlinObject()); - case UNBOX_CHAR: - return pureFqn("unboxChar", kotlinObject()); - default: - throw new IllegalArgumentException("Unknown function: " + specialFunction); - } + return pureFqn(specialFunction.getSuggestedName(), kotlinObject()); } @NotNull diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/AbstractDeclarationVisitor.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/AbstractDeclarationVisitor.kt index 2b8ec1f5e3b..5c9a778fd8d 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/AbstractDeclarationVisitor.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/AbstractDeclarationVisitor.kt @@ -21,15 +21,17 @@ import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.isOverridable import org.jetbrains.kotlin.js.backend.ast.JsExpression +import org.jetbrains.kotlin.js.backend.ast.JsFunction import org.jetbrains.kotlin.js.backend.ast.JsName +import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata +import org.jetbrains.kotlin.js.descriptorUtils.shouldBeExported import org.jetbrains.kotlin.js.translate.context.TranslationContext -import org.jetbrains.kotlin.js.translate.expression.translateAndAliasParameters -import org.jetbrains.kotlin.js.translate.expression.translateFunction -import org.jetbrains.kotlin.js.translate.expression.wrapWithInlineMetadata +import org.jetbrains.kotlin.js.translate.expression.* import org.jetbrains.kotlin.js.translate.general.TranslatorVisitor import org.jetbrains.kotlin.js.translate.utils.* import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.descriptorUtil.isExtensionProperty +import org.jetbrains.kotlin.resolve.source.getPsi abstract class AbstractDeclarationVisitor : TranslatorVisitor() { override fun emptyResult(context: TranslationContext) { } @@ -52,7 +54,7 @@ abstract class AbstractDeclarationVisitor : TranslatorVisitor() { val defaultTranslator = DefaultPropertyTranslator(descriptor, context, getBackingFieldReference(descriptor)) val getter = descriptor.getter!! val getterExpr = if (expression.hasCustomGetter()) { - translateFunction(getter, expression.getter!!, propertyContext) + translateFunction(getter, expression.getter!!, propertyContext).first } else { val function = context.getFunctionObject(getter) @@ -64,7 +66,7 @@ abstract class AbstractDeclarationVisitor : TranslatorVisitor() { val setterExpr = if (descriptor.isVar) { val setter = descriptor.setter!! if (expression.hasCustomSetter()) { - translateFunction(setter, expression.setter!!, propertyContext) + translateFunction(setter, expression.setter!!, propertyContext).first } else { val function = context.getFunctionObject(setter) @@ -88,8 +90,23 @@ abstract class AbstractDeclarationVisitor : TranslatorVisitor() { override fun visitNamedFunction(expression: KtNamedFunction, context: TranslationContext) { val descriptor = BindingUtils.getFunctionDescriptor(context.bindingContext(), expression) - val jsFunction = if (descriptor.modality != Modality.ABSTRACT) translateFunction(descriptor, expression, context) else null - addFunction(descriptor, jsFunction, expression) + val functionAndContext = if (descriptor.modality != Modality.ABSTRACT) { + translateFunction(descriptor, expression, context) + } + else { + null + } + addFunction(descriptor, functionAndContext?.first, expression) + + if (descriptor.isSuspend && descriptor.isInline && descriptor.shouldBeExported(context.config) && functionAndContext != null) { + val innerContext = functionAndContext.second + val inlineFunction = transformCoroutineMetadataToSpecialFunctions(context, functionAndContext.first.deepCopy() as JsFunction) + inlineFunction.name = null + inlineFunction.coroutineMetadata = null + val metadata = InlineMetadata.compose(inlineFunction, descriptor, innerContext) + val functionWithMetadata = metadata.functionWithMetadata(context, descriptor.source.getPsi()) + context.addDeclarationStatement(functionWithMetadata.makeStmt()) + } } override fun visitTypeAlias(typeAlias: KtTypeAlias, data: TranslationContext?) {} @@ -98,7 +115,7 @@ abstract class AbstractDeclarationVisitor : TranslatorVisitor() { descriptor: FunctionDescriptor, expression: KtDeclarationWithBody, context: TranslationContext - ): JsExpression { + ): Pair { val function = context.getFunctionObject(descriptor) function.source = expression.finalElement val innerContext = context.newDeclaration(descriptor).translateAndAliasParameters(descriptor, function.parameters) @@ -113,7 +130,14 @@ abstract class AbstractDeclarationVisitor : TranslatorVisitor() { function.body.statements += FunctionBodyTranslator.setDefaultValueForArguments(descriptor, innerContext) } innerContext.translateFunction(expression, function) - return innerContext.wrapWithInlineMetadata(context, function, descriptor) + val result = if (descriptor.isSuspend && descriptor.shouldBeExported(context.config)) { + function + } + else { + innerContext.wrapWithInlineMetadata(context, function, descriptor) + } + + return Pair(result, innerContext) } // used from kotlinx.serialization diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/inlineCoroutineUtil.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/inlineCoroutineUtil.kt new file mode 100644 index 00000000000..7c14f3749ff --- /dev/null +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/inlineCoroutineUtil.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.js.translate.declaration + +import org.jetbrains.kotlin.js.backend.ast.* +import org.jetbrains.kotlin.js.backend.ast.metadata.* +import org.jetbrains.kotlin.js.translate.context.TranslationContext +import org.jetbrains.kotlin.js.translate.utils.TranslationUtils +import org.jetbrains.kotlin.js.translate.utils.name + +fun transformCoroutineMetadataToSpecialFunctions(context: TranslationContext, node: T): T { + val visitor = object : JsVisitorWithContextImpl() { + override fun endVisit(x: JsNameRef, ctx: JsContext) { + val specialFunction = when { + x.coroutineController -> SpecialFunction.COROUTINE_CONTROLLER + x.coroutineReceiver -> SpecialFunction.COROUTINE_RECEIVER + x.coroutineResult -> SpecialFunction.COROUTINE_RESULT + else -> null + } + if (specialFunction != null) { + val arguments = listOfNotNull(x.qualifier).toTypedArray() + ctx.replaceMe(TranslationUtils.invokeSpecialFunction(context, specialFunction, *arguments).source(x.source)) + } + else { + super.endVisit(x, ctx) + } + } + + override fun endVisit(x: JsExpression, ctx: JsContext) { + if (x.isSuspend) { + x.isSuspend = false + ctx.replaceMe(TranslationUtils.invokeSpecialFunction(context, SpecialFunction.SUSPEND_CALL, x).source(x.source)) + } + } + } + return visitor.accept(node) +} + +fun transformSpecialFunctionsToCoroutineMetadata(node: T): T { + val visitor = object : JsVisitorWithContextImpl() { + override fun endVisit(x: JsInvocation, ctx: JsContext) { + x.qualifier.name?.specialFunction?.let { specialFunction -> + val replacement = when (specialFunction) { + SpecialFunction.COROUTINE_CONTROLLER -> { + JsNameRef("\$\$controller\$\$", x.arguments.getOrNull(0)).apply { + coroutineController = true + } + } + SpecialFunction.COROUTINE_RECEIVER -> { + JsNameRef("\$this\$", x.arguments.getOrNull(0)).apply { + coroutineReceiver = true + } + } + SpecialFunction.COROUTINE_RESULT -> { + JsNameRef("\$result\$", x.arguments.getOrNull(0)).apply { + coroutineResult = true + } + } + SpecialFunction.SUSPEND_CALL -> { + x.arguments[0].apply { + isSuspend = true + } + } + else -> null + } + replacement?.let { ctx.replaceMe(it) } + } + } + } + return visitor.accept(node) +} \ No newline at end of file diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.kt index 73f55169dc5..ae52db9da4a 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/FunctionTranslator.kt @@ -112,7 +112,7 @@ fun TranslationContext.wrapWithInlineMetadata( if (descriptor.shouldBeExported(config)) { val metadata = InlineMetadata.compose(function, descriptor, this) val functionWithMetadata = metadata.functionWithMetadata(outerContext, sourceInfo) - config.configuration[JSConfigurationKeys.INCREMENTAL_RESULTS_CONSUMER]?.apply { + config.configuration[JSConfigurationKeys.INCREMENTAL_RESULTS_CONSUMER]?.apply { val psiFile = (descriptor.source.containingFile as? PsiSourceFile)?.psiFile ?: return@apply val file = VfsUtilCore.virtualToIoFile(psiFile.virtualFile) diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java index 35d98c35268..3cfa71d1a67 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/TranslationUtils.java @@ -526,7 +526,7 @@ public final class TranslationUtils { } @NotNull - private static JsInvocation invokeSpecialFunction( + public static JsInvocation invokeSpecialFunction( @NotNull TranslationContext context, @NotNull SpecialFunction function, @NotNull JsExpression... arguments ) {