From bf55744e648e6871e9913fbd2c27da5fb1c2b7d7 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Mon, 23 Jan 2017 13:13:32 +0300 Subject: [PATCH] JS: fix translation of safe calls if suspend functions. See KT-15892 --- .../coroutines/suspensionInsideSafeCall.kt | 1 - .../suspensionInsideSafeCallWithElvis.kt | 1 - .../ast/metadata/metadataProperties.kt | 2 +- .../js/coroutine/CoroutineBodyTransformer.kt | 27 +++++++------------ .../kotlin/js/coroutine/CoroutinePasses.kt | 15 ++++++++--- .../clean/RedundantStatementElimination.kt | 5 ++-- .../semantics/JsCodegenBoxTestGenerated.java | 16 ++--------- .../js/translate/callTranslator/CallInfo.kt | 10 +++---- .../callTranslator/CallTranslator.kt | 14 +++++----- 9 files changed, 39 insertions(+), 52 deletions(-) diff --git a/compiler/testData/codegen/box/coroutines/suspensionInsideSafeCall.kt b/compiler/testData/codegen/box/coroutines/suspensionInsideSafeCall.kt index 254bf439b0b..3e78928f4a6 100644 --- a/compiler/testData/codegen/box/coroutines/suspensionInsideSafeCall.kt +++ b/compiler/testData/codegen/box/coroutines/suspensionInsideSafeCall.kt @@ -1,6 +1,5 @@ // WITH_RUNTIME // WITH_COROUTINES -// IGNORE_BACKEND: JS import kotlin.coroutines.* import kotlin.coroutines.intrinsics.* diff --git a/compiler/testData/codegen/box/coroutines/suspensionInsideSafeCallWithElvis.kt b/compiler/testData/codegen/box/coroutines/suspensionInsideSafeCallWithElvis.kt index f36b1705fae..5039eddc558 100644 --- a/compiler/testData/codegen/box/coroutines/suspensionInsideSafeCallWithElvis.kt +++ b/compiler/testData/codegen/box/coroutines/suspensionInsideSafeCallWithElvis.kt @@ -1,6 +1,5 @@ // WITH_RUNTIME // WITH_COROUTINES -// IGNORE_BACKEND: JS import kotlin.coroutines.* import kotlin.coroutines.intrinsics.* 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 7cd5514548f..8dfba00d662 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 @@ -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 diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineBodyTransformer.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineBodyTransformer.kt index ffba91a468d..f22de1134f0 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineBodyTransformer.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineBodyTransformer.kt @@ -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) diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutinePasses.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutinePasses.kt index 5ec8a0b20a1..df74249cb3f 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutinePasses.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutinePasses.kt @@ -29,12 +29,19 @@ fun JsNode.collectNodesToSplit(breakContinueTargets: Map): 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): 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 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 1a0ad261d92..ac6f93d193f 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 @@ -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") diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/CallInfo.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/CallInfo.kt index b73c823e089..f02fbdb313b 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/CallInfo.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/CallInfo.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 { diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/CallTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/CallTranslator.kt index b904a067a74..6dd45fcba4a 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/CallTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/CallTranslator.kt @@ -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.expectedReceivers(): Boolean { return this.explicitReceiverKind != NO_EXPLICIT_RECEIVER } -private fun translateCall(context: TranslationContext, - resolvedCall: ResolvedCall, - explicitReceivers: ExplicitReceivers +private fun translateCall( + context: TranslationContext, + resolvedCall: ResolvedCall, + 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 { callInfo.bothReceivers() } - return callInfo.constructSafeCallIsNeeded(result) + return callInfo.constructSafeCallIfNeeded(result) } } @@ -235,7 +235,7 @@ interface DelegateIntrinsic { } if (result != null) { - return callInfo.constructSafeCallIsNeeded(result) + return callInfo.constructSafeCallIfNeeded(result) } else { return null }