JS: fix translation of safe calls if suspend functions. See KT-15892
This commit is contained in:
@@ -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) {
|
||||
|
||||
+3
-2
@@ -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
|
||||
|
||||
+2
-14
@@ -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 {
|
||||
|
||||
+7
-7
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user