JS: transform coroutines before serializing AST to binary format
This commit is contained in:
@@ -30,7 +30,7 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f
|
||||
private val body = functionWithBody.body
|
||||
private val localVariables = (function.collectLocalVariables() + functionWithBody.collectLocalVariables() -
|
||||
functionWithBody.parameters.last().name).toMutableSet()
|
||||
private val className = function.scope.parent.declareFreshName("Coroutine\$${name ?: "anonymous"}")
|
||||
private val className = JsScope.declareTemporaryName("Coroutine\$${name ?: "anonymous"}")
|
||||
|
||||
fun transform(): List<JsStatement> {
|
||||
val context = CoroutineTransformationContext(function.scope, function)
|
||||
@@ -41,7 +41,7 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f
|
||||
val globalCatchBlockIndex = coroutineBlocks.indexOf(context.globalCatchBlock)
|
||||
|
||||
coroutineBlocks.forEach { it.jsBlock.collectAdditionalLocalVariables() }
|
||||
coroutineBlocks.forEach { it.jsBlock.replaceLocalVariables(function.scope, context, localVariables) }
|
||||
coroutineBlocks.forEach { it.jsBlock.replaceLocalVariables(context, localVariables) }
|
||||
|
||||
val additionalStatements = mutableListOf<JsStatement>()
|
||||
generateDoResume(coroutineBlocks, context, additionalStatements)
|
||||
@@ -67,7 +67,7 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f
|
||||
val lastParameter = parameters.lastOrNull()?.name
|
||||
|
||||
val controllerName = if (context.metadata.hasController) {
|
||||
function.scope.declareFreshName("controller").apply {
|
||||
JsScope.declareTemporaryName("controller").apply {
|
||||
constructor.parameters.add(constructor.parameters.lastIndex, JsParameter(this))
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f
|
||||
}
|
||||
for (localVariable in localVariables) {
|
||||
val value = if (localVariable !in parameterNames) Namer.getUndefinedExpression() else localVariable.makeRef()
|
||||
assignToField(function.scope.getFieldName(localVariable), value)
|
||||
assignToField(context.getFieldName(localVariable), value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,10 +157,10 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f
|
||||
|
||||
instantiation.arguments += parameters.last().name.makeRef()
|
||||
|
||||
val suspendedName = functionWithBody.scope.declareFreshName("suspended")
|
||||
val suspendedName = JsScope.declareTemporaryName("suspended")
|
||||
functionWithBody.parameters += JsParameter(suspendedName)
|
||||
|
||||
val instanceName = functionWithBody.scope.declareFreshName("instance")
|
||||
val instanceName = JsScope.declareTemporaryName("instance")
|
||||
functionWithBody.body.statements += JsAstUtils.newVar(instanceName, instantiation)
|
||||
|
||||
val invokeResume = JsReturn(JsInvocation(JsNameRef(context.metadata.doResumeName, instanceName.makeRef()), JsLiteral.NULL))
|
||||
|
||||
@@ -237,8 +237,9 @@ fun JsBlock.replaceSpecialReferences(context: CoroutineTransformationContext) {
|
||||
visitor.accept(this)
|
||||
}
|
||||
|
||||
fun JsBlock.replaceLocalVariables(scope: JsScope, context: CoroutineTransformationContext, localVariables: Set<JsName>) {
|
||||
fun JsBlock.replaceLocalVariables(context: CoroutineTransformationContext, localVariables: Set<JsName>) {
|
||||
replaceSpecialReferences(context)
|
||||
|
||||
val visitor = object : JsVisitorWithContextImpl() {
|
||||
override fun visit(x: JsFunction, ctx: JsContext<*>): Boolean = false
|
||||
|
||||
@@ -260,14 +261,14 @@ fun JsBlock.replaceLocalVariables(scope: JsScope, context: CoroutineTransformati
|
||||
|
||||
override fun endVisit(x: JsNameRef, ctx: JsContext<in JsNode>) {
|
||||
if (x.qualifier == null && x.name in localVariables) {
|
||||
val fieldName = scope.getFieldName(x.name!!)
|
||||
val fieldName = context.getFieldName(x.name!!)
|
||||
ctx.replaceMe(JsNameRef(fieldName, JsLiteral.THIS))
|
||||
}
|
||||
}
|
||||
|
||||
override fun endVisit(x: JsVars, ctx: JsContext<in JsStatement>) {
|
||||
val assignments = x.vars.mapNotNull {
|
||||
val fieldName = scope.getFieldName(it.name)
|
||||
val fieldName = context.getFieldName(it.name)
|
||||
val initExpression = it.initExpression
|
||||
if (initExpression != null) {
|
||||
JsAstUtils.assignment(JsNameRef(fieldName, JsLiteral.THIS), it.initExpression)
|
||||
@@ -286,6 +287,4 @@ fun JsBlock.replaceLocalVariables(scope: JsScope, context: CoroutineTransformati
|
||||
}
|
||||
}
|
||||
visitor.accept(this)
|
||||
}
|
||||
|
||||
fun JsScope.getFieldName(variableName: JsName) = declareName("local\$${variableName.ident}")
|
||||
}
|
||||
+17
-3
@@ -17,14 +17,28 @@
|
||||
package org.jetbrains.kotlin.js.coroutine
|
||||
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsFunction
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsName
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsScope
|
||||
import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata
|
||||
|
||||
class CoroutineTransformationContext(private val scope: JsScope, function: JsFunction) {
|
||||
private val localVariableNameCache = mutableMapOf<JsName, JsName>()
|
||||
private val usedLocalVariableIds = mutableSetOf<String>()
|
||||
|
||||
val entryBlock = CoroutineBlock()
|
||||
val globalCatchBlock = CoroutineBlock()
|
||||
val metadata = function.coroutineMetadata!!
|
||||
val controllerFieldName by lazy { scope.declareFreshName("\$controller") }
|
||||
val returnValueFieldName by lazy { scope.declareFreshName("\$returnValue") }
|
||||
val receiverFieldName by lazy { scope.declareFreshName("\$this") }
|
||||
val controllerFieldName by lazy { scope.declareName("\$controller") }
|
||||
val returnValueFieldName by lazy { scope.declareName("\$returnValue") }
|
||||
val receiverFieldName by lazy { scope.declareName("\$this") }
|
||||
|
||||
fun getFieldName(variableName: JsName) = localVariableNameCache.getOrPut(variableName) {
|
||||
val baseId = "local\$${variableName.ident}"
|
||||
var suggestedId = baseId
|
||||
var suffix = 0
|
||||
while (!usedLocalVariableIds.add(suggestedId)) {
|
||||
suggestedId = "${baseId}_${suffix++}"
|
||||
}
|
||||
scope.declareName(suggestedId)
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,10 @@ class JsAstDeserializer(private val program: JsProgram) {
|
||||
deserializeString(inlineModuleProto.signatureId) to moduleExpressions[inlineModuleProto.expressionId]
|
||||
}
|
||||
|
||||
for (nameBinding in fragment.nameBindings) {
|
||||
nameBinding.name.imported = nameBinding.key in fragment.imports
|
||||
}
|
||||
|
||||
return fragment
|
||||
}
|
||||
|
||||
|
||||
@@ -3443,6 +3443,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/box/incremental"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS, true);
|
||||
}
|
||||
|
||||
@TestMetadata("coroutines.kt")
|
||||
public void testCoroutines() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/incremental/coroutines.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inline.kt")
|
||||
public void testInline() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/incremental/inline.kt");
|
||||
|
||||
@@ -119,11 +119,23 @@ public final class K2JSTranslator {
|
||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
|
||||
if (hasError(diagnostics)) return new TranslationResult.Fail(diagnostics);
|
||||
|
||||
List<JsProgramFragment> newFragments = translationResult.getFragments();
|
||||
List<JsProgramFragment> allFragments = new ArrayList<JsProgramFragment>(newFragments);
|
||||
List<JsProgramFragment> newFragments = new ArrayList<JsProgramFragment>(translationResult.getNewFragments());
|
||||
List<JsProgramFragment> allFragments = new ArrayList<JsProgramFragment>(translationResult.getFragments());
|
||||
|
||||
JsInliner.process(config, analysisResult.getBindingTrace(), translationResult.getInnerModuleName(), allFragments, newFragments);
|
||||
|
||||
CoroutineTransformer coroutineTransformer = new CoroutineTransformer(translationResult.getProgram());
|
||||
for (JsProgramFragment fragment : newFragments) {
|
||||
coroutineTransformer.accept(fragment.getDeclarationBlock());
|
||||
coroutineTransformer.accept(fragment.getInitializerBlock());
|
||||
}
|
||||
RemoveUnusedImportsKt.removeUnusedImports(translationResult.getProgram());
|
||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
|
||||
if (hasError(diagnostics)) return new TranslationResult.Fail(diagnostics);
|
||||
|
||||
ExpandIsCallsKt.expandIsCalls(newFragments);
|
||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
|
||||
|
||||
Map<KtFile, FileTranslationResult> fileMap = new HashMap<KtFile, FileTranslationResult>();
|
||||
JsAstSerializer serializer = new JsAstSerializer();
|
||||
boolean serializeFragments = config.getConfiguration().get(JSConfigurationKeys.SERIALIZE_FRAGMENTS, false);
|
||||
@@ -152,15 +164,6 @@ public final class K2JSTranslator {
|
||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
|
||||
if (hasError(diagnostics)) return new TranslationResult.Fail(diagnostics);
|
||||
|
||||
CoroutineTransformer coroutineTransformer = new CoroutineTransformer(translationResult.getProgram());
|
||||
coroutineTransformer.accept(translationResult.getProgram());
|
||||
RemoveUnusedImportsKt.removeUnusedImports(translationResult.getProgram());
|
||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
|
||||
if (hasError(diagnostics)) return new TranslationResult.Fail(diagnostics);
|
||||
|
||||
ExpandIsCallsKt.expandIsCalls(newFragments);
|
||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
|
||||
|
||||
List<String> importedModules = new ArrayList<>();
|
||||
for (JsImportedModule module : translationResult.getImportedModuleList()) {
|
||||
importedModules.add(module.getExternalName());
|
||||
|
||||
@@ -28,6 +28,7 @@ class AstGenerationResult(
|
||||
val innerModuleName: JsName,
|
||||
val fragments: List<JsProgramFragment>,
|
||||
val fragmentMap: Map<KtFile, JsProgramFragment>,
|
||||
val newFragments: List<JsProgramFragment>,
|
||||
val fileMemberScopes: Map<KtFile, List<DeclarationDescriptor>>,
|
||||
val importedModuleList: List<JsImportedModule>
|
||||
)
|
||||
@@ -270,6 +270,7 @@ public final class Translation {
|
||||
|
||||
Map<KtFile, JsProgramFragment> fragmentMap = new HashMap<KtFile, JsProgramFragment>();
|
||||
List<JsProgramFragment> fragments = new ArrayList<JsProgramFragment>();
|
||||
List<JsProgramFragment> newFragments = new ArrayList<JsProgramFragment>();
|
||||
|
||||
Map<KtFile, List<DeclarationDescriptor>> fileMemberScopes = new HashMap<KtFile, List<DeclarationDescriptor>>();
|
||||
|
||||
@@ -282,6 +283,7 @@ public final class Translation {
|
||||
List<DeclarationDescriptor> fileMemberScope = new ArrayList<DeclarationDescriptor>();
|
||||
translateFile(context, file, fileMemberScope);
|
||||
fragments.add(staticContext.getFragment());
|
||||
newFragments.add(staticContext.getFragment());
|
||||
fragmentMap.put(file, staticContext.getFragment());
|
||||
fileMemberScopes.put(file, fileMemberScope);
|
||||
merger.addFragment(staticContext.getFragment());
|
||||
@@ -296,6 +298,7 @@ public final class Translation {
|
||||
|
||||
JsProgramFragment testFragment = mayBeGenerateTests(config, bindingTrace, moduleDescriptor);
|
||||
fragments.add(testFragment);
|
||||
newFragments.add(testFragment);
|
||||
merger.addFragment(testFragment);
|
||||
rootFunction.getParameters().add(new JsParameter(internalModuleName));
|
||||
|
||||
@@ -304,6 +307,7 @@ public final class Translation {
|
||||
bindingTrace, config, moduleDescriptor, mainCallParameters.arguments());
|
||||
if (mainCallFragment != null) {
|
||||
fragments.add(mainCallFragment);
|
||||
newFragments.add(mainCallFragment);
|
||||
merger.addFragment(mainCallFragment);
|
||||
}
|
||||
}
|
||||
@@ -333,7 +337,8 @@ public final class Translation {
|
||||
block.getStatements().addAll(wrapIfNecessary(config.getModuleId(), rootFunction, importedModuleList, program,
|
||||
config.getModuleKind()));
|
||||
|
||||
return new AstGenerationResult(program, internalModuleName, fragments, fragmentMap, fileMemberScopes, importedModuleList);
|
||||
return new AstGenerationResult(program, internalModuleName, fragments, fragmentMap, newFragments,
|
||||
fileMemberScopes, importedModuleList);
|
||||
}
|
||||
|
||||
private static boolean isBuiltinModule(@NotNull List<JsProgramFragment> fragments) {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// FILE: a.kt
|
||||
// WITH_RUNTIME
|
||||
import kotlin.coroutines.experimental.*
|
||||
import kotlin.coroutines.experimental.intrinsics.*
|
||||
|
||||
suspend fun suspendThere(v: String): String = suspendCoroutineOrReturn { x ->
|
||||
x.resume(v)
|
||||
COROUTINE_SUSPENDED
|
||||
}
|
||||
|
||||
suspend fun suspendHere(): String = suspendThere("O") + suspendThere("K")
|
||||
|
||||
// FILE: b.kt
|
||||
// RECOMPILE
|
||||
import kotlin.coroutines.experimental.*
|
||||
import kotlin.coroutines.experimental.intrinsics.*
|
||||
|
||||
fun builder(c: suspend () -> Unit) {
|
||||
c.startCoroutine(object : Continuation<Unit> {
|
||||
override val context = EmptyCoroutineContext
|
||||
|
||||
override fun resume(result: Unit) {}
|
||||
|
||||
override fun resumeWithException(exception: Throwable) {}
|
||||
})
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
var result = ""
|
||||
|
||||
builder {
|
||||
result = suspendHere()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user