JS: support cross-module inlining of suspend functions

See KT-18063
This commit is contained in:
Alexey Andreev
2017-09-11 15:07:58 +03:00
parent 992cc61abc
commit bad50c03a7
22 changed files with 599 additions and 47 deletions
@@ -211,6 +211,22 @@ public final class DebugJsAstProtoBuf {
* <code>UNBOX_CHAR = 4;</code>
*/
UNBOX_CHAR(3, 4),
/**
* <code>SUSPEND_CALL = 5;</code>
*/
SUSPEND_CALL(4, 5),
/**
* <code>COROUTINE_RESULT = 6;</code>
*/
COROUTINE_RESULT(5, 6),
/**
* <code>COROUTINE_CONTROLLER = 7;</code>
*/
COROUTINE_CONTROLLER(6, 7),
/**
* <code>COROUTINE_RECEIVER = 8;</code>
*/
COROUTINE_RECEIVER(7, 8),
;
/**
@@ -229,6 +245,22 @@ public final class DebugJsAstProtoBuf {
* <code>UNBOX_CHAR = 4;</code>
*/
public static final int UNBOX_CHAR_VALUE = 4;
/**
* <code>SUSPEND_CALL = 5;</code>
*/
public static final int SUSPEND_CALL_VALUE = 5;
/**
* <code>COROUTINE_RESULT = 6;</code>
*/
public static final int COROUTINE_RESULT_VALUE = 6;
/**
* <code>COROUTINE_CONTROLLER = 7;</code>
*/
public static final int COROUTINE_CONTROLLER_VALUE = 7;
/**
* <code>COROUTINE_RECEIVER = 8;</code>
*/
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() {
@@ -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 <T> 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"
}
@@ -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 <T> 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"
}
@@ -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 <T> 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"
}
@@ -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");
@@ -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");
@@ -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");
@@ -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) {
@@ -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 {
@@ -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<SpecialFunction>().joinToString("|") { it.suggestedName }
private val specialFunctionsByName = enumValues<SpecialFunction>().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<String, SpecialFunction>,
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<String, SpecialFunction>()
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
}
}
@@ -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);
}
+13
View File
@@ -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) {
};
+4
View File
@@ -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;
}
@@ -522,6 +522,10 @@ class JsAstDeserializer(program: JsProgram, private val sourceRoots: Iterable<Fi
JsAstProtoBuf.SpecialFunction.WRAP_FUNCTION -> 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 <T : JsNode> withLocation(fileId: Int?, location: Location?, action: () -> T): T {
@@ -159,6 +159,22 @@ public final class JsAstProtoBuf {
* <code>UNBOX_CHAR = 4;</code>
*/
UNBOX_CHAR(3, 4),
/**
* <code>SUSPEND_CALL = 5;</code>
*/
SUSPEND_CALL(4, 5),
/**
* <code>COROUTINE_RESULT = 6;</code>
*/
COROUTINE_RESULT(5, 6),
/**
* <code>COROUTINE_CONTROLLER = 7;</code>
*/
COROUTINE_CONTROLLER(6, 7),
/**
* <code>COROUTINE_RECEIVER = 8;</code>
*/
COROUTINE_RECEIVER(7, 8),
;
/**
@@ -177,6 +193,22 @@ public final class JsAstProtoBuf {
* <code>UNBOX_CHAR = 4;</code>
*/
public static final int UNBOX_CHAR_VALUE = 4;
/**
* <code>SUSPEND_CALL = 5;</code>
*/
public static final int SUSPEND_CALL_VALUE = 5;
/**
* <code>COROUTINE_RESULT = 6;</code>
*/
public static final int COROUTINE_RESULT_VALUE = 6;
/**
* <code>COROUTINE_CONTROLLER = 7;</code>
*/
public static final int COROUTINE_CONTROLLER_VALUE = 7;
/**
* <code>COROUTINE_RECEIVER = 8;</code>
*/
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;
}
}
@@ -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) {
@@ -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");
@@ -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
@@ -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<Unit>() {
override fun emptyResult(context: TranslationContext) { }
@@ -52,7 +54,7 @@ abstract class AbstractDeclarationVisitor : TranslatorVisitor<Unit>() {
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<Unit>() {
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<Unit>() {
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<Unit>() {
descriptor: FunctionDescriptor,
expression: KtDeclarationWithBody,
context: TranslationContext
): JsExpression {
): Pair<JsExpression, TranslationContext> {
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<Unit>() {
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
@@ -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 <T : JsNode> transformCoroutineMetadataToSpecialFunctions(context: TranslationContext, node: T): T {
val visitor = object : JsVisitorWithContextImpl() {
override fun endVisit(x: JsNameRef, ctx: JsContext<in JsExpression>) {
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<in JsExpression>) {
if (x.isSuspend) {
x.isSuspend = false
ctx.replaceMe(TranslationUtils.invokeSpecialFunction(context, SpecialFunction.SUSPEND_CALL, x).source(x.source))
}
}
}
return visitor.accept(node)
}
fun <T : JsNode> transformSpecialFunctionsToCoroutineMetadata(node: T): T {
val visitor = object : JsVisitorWithContextImpl() {
override fun endVisit(x: JsInvocation, ctx: JsContext<in JsExpression>) {
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)
}
@@ -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)
@@ -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
) {