JS: fix translation of safe calls if suspend functions. See KT-15892

This commit is contained in:
Alexey Andreev
2017-01-23 13:13:32 +03:00
parent 2adaaf5e9f
commit bf55744e64
9 changed files with 39 additions and 52 deletions
@@ -1,6 +1,5 @@
// WITH_RUNTIME
// WITH_COROUTINES
// IGNORE_BACKEND: JS
import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.*
@@ -1,6 +1,5 @@
// WITH_RUNTIME
// WITH_COROUTINES
// IGNORE_BACKEND: JS
import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.*
@@ -73,7 +73,7 @@ var HasMetadata.sideEffects: SideEffectKind by MetadataProperty(default = SideEf
* Denotes a suspension call-site that is to be processed by coroutine transformer.
* More clearly, denotes invocation that should immediately return from coroutine state machine
*/
var JsInvocation.isSuspend: Boolean by MetadataProperty(default = false)
var JsExpression.isSuspend: Boolean by MetadataProperty(default = false)
/**
* Denotes a reference to coroutine's `result` field that contains result of
@@ -332,29 +332,22 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
}
private fun handleExpression(expression: JsExpression): JsExpression? {
return if (expression is JsInvocation) {
if (handleInvocation(expression)) null else expression
}
else {
val assignment = JsAstUtils.decomposeAssignment(expression)
if (assignment != null) {
(assignment.second as? JsInvocation)?.let { return if (handleInvocation(it)) null else expression }
val assignment = JsAstUtils.decomposeAssignment(expression)
if (assignment != null) {
val rhs = assignment.second
if (rhs.isSuspend) {
handleSuspend(expression)
return null
}
expression
}
}
private fun handleInvocation(expression: JsInvocation): Boolean {
return if (expression.isSuspend) {
else if (expression.isSuspend) {
handleSuspend(expression)
true
}
else {
false
return null
}
return expression
}
private fun handleSuspend(invocation: JsInvocation) {
private fun handleSuspend(invocation: JsExpression) {
val nextBlock = CoroutineBlock()
currentStatements += state(nextBlock)
@@ -29,12 +29,19 @@ fun JsNode.collectNodesToSplit(breakContinueTargets: Map<JsContinue, JsStatement
var childrenInSet = false
var finallyLevel = 0
override fun visitInvocation(invocation: JsInvocation) {
super.visitInvocation(invocation)
if (invocation.isSuspend) {
nodes += invocation
override fun visitExpressionStatement(x: JsExpressionStatement) {
super.visitExpressionStatement(x)
if (x.expression.isSuspend) {
nodes += x.expression
childrenInSet = true
}
else {
val assignment = JsAstUtils.decomposeAssignment(x.expression)
if (assignment != null && assignment.second.isSuspend) {
nodes += assignment.second
childrenInSet = true
}
}
}
override fun visitReturn(x: JsReturn) {
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.js.inline.clean
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.SideEffectKind
import org.jetbrains.kotlin.js.backend.ast.metadata.isSuspend
import org.jetbrains.kotlin.js.backend.ast.metadata.sideEffects
import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic
import org.jetbrains.kotlin.js.inline.util.collectLocalVariables
@@ -35,7 +36,7 @@ class RedundantStatementElimination(private val root: JsFunction) {
private fun process() {
object : JsVisitorWithContextImpl() {
override fun visit(x: JsExpressionStatement, ctx: JsContext<JsNode>): Boolean {
if (x.synthetic || x.expression.synthetic) {
if (!x.expression.isSuspend && (x.synthetic || x.expression.synthetic)) {
val replacement = replace(x.expression)
if (replacement.size != 1 || replacement[0] != x.expression) {
hasChanges = true
@@ -47,7 +48,7 @@ class RedundantStatementElimination(private val root: JsFunction) {
}
override fun visit(x: JsBinaryOperation, ctx: JsContext<JsNode>): Boolean {
if (x.operator == JsBinaryOperator.COMMA) {
if (!x.isSuspend && x.operator == JsBinaryOperator.COMMA) {
val expressions = replace(x.arg1)
val replacement = if (expressions.isEmpty()) {
x.arg2
@@ -5565,25 +5565,13 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
@TestMetadata("suspensionInsideSafeCall.kt")
public void testSuspensionInsideSafeCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspensionInsideSafeCall.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
doTest(fileName);
}
@TestMetadata("suspensionInsideSafeCallWithElvis.kt")
public void testSuspensionInsideSafeCallWithElvis() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspensionInsideSafeCallWithElvis.kt");
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
doTest(fileName);
}
@TestMetadata("tryCatchFinallyWithHandleResult.kt")
@@ -16,13 +16,13 @@
package org.jetbrains.kotlin.js.translate.callTranslator
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.jetbrains.kotlin.js.backend.ast.JsBlock
import org.jetbrains.kotlin.js.backend.ast.JsConditional
import org.jetbrains.kotlin.js.backend.ast.JsExpression
import org.jetbrains.kotlin.js.backend.ast.JsLiteral
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.reference.CallArgumentTranslator
import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator
@@ -41,7 +41,7 @@ interface CallInfo {
val dispatchReceiver: JsExpression?
val extensionReceiver: JsExpression?
fun constructSafeCallIsNeeded(result: JsExpression): JsExpression
fun constructSafeCallIfNeeded(result: JsExpression): JsExpression
}
abstract class AbstractCallInfo : CallInfo {
@@ -162,7 +162,7 @@ private fun TranslationContext.createCallInfo(
val notNullConditionalForSafeCall: JsConditional? = notNullConditional
override fun constructSafeCallIsNeeded(result: JsExpression): JsExpression {
override fun constructSafeCallIfNeeded(result: JsExpression): JsExpression {
if (notNullConditionalForSafeCall == null) {
return result
} else {
@@ -21,7 +21,6 @@ import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.js.backend.ast.JsExpression
import org.jetbrains.kotlin.js.backend.ast.JsInvocation
import org.jetbrains.kotlin.js.backend.ast.JsNameRef
import org.jetbrains.kotlin.js.backend.ast.metadata.SideEffectKind
import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineResult
@@ -90,9 +89,10 @@ private fun ResolvedCall<out CallableDescriptor>.expectedReceivers(): Boolean {
return this.explicitReceiverKind != NO_EXPLICIT_RECEIVER
}
private fun translateCall(context: TranslationContext,
resolvedCall: ResolvedCall<out FunctionDescriptor>,
explicitReceivers: ExplicitReceivers
private fun translateCall(
context: TranslationContext,
resolvedCall: ResolvedCall<out FunctionDescriptor>,
explicitReceivers: ExplicitReceivers
): JsExpression {
if (resolvedCall is VariableAsFunctionResolvedCall) {
assert(explicitReceivers.extensionReceiver == null) { "VariableAsFunctionResolvedCall must have one receiver" }
@@ -138,7 +138,7 @@ private fun translateFunctionCall(
}
if (resolvedCall.resultingDescriptor.isSuspend && context.isInStateMachine) {
context.currentBlock.statements += JsAstUtils.asSyntheticStatement((callExpression as JsInvocation).apply { isSuspend = true })
context.currentBlock.statements += JsAstUtils.asSyntheticStatement(callExpression.apply { isSuspend = true })
val coroutineRef = TranslationUtils.translateContinuationArgument(context, resolvedCall)
return context.defineTemporary(JsNameRef("\$\$coroutineResult\$\$", coroutineRef).apply {
sideEffects = SideEffectKind.DEPENDS_ON_STATE
@@ -213,7 +213,7 @@ abstract class CallCase<in I : CallInfo> {
callInfo.bothReceivers()
}
return callInfo.constructSafeCallIsNeeded(result)
return callInfo.constructSafeCallIfNeeded(result)
}
}
@@ -235,7 +235,7 @@ interface DelegateIntrinsic<in I : CallInfo> {
}
if (result != null) {
return callInfo.constructSafeCallIsNeeded(result)
return callInfo.constructSafeCallIfNeeded(result)
} else {
return null
}