diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java index fc73f85ad61..7f2a24472aa 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java @@ -2912,9 +2912,9 @@ public class ExpressionCodegen extends KtVisitor impleme boolean isSuspensionPoint, boolean isConstructor ) { - boolean isSafeCall = receiver instanceof StackValue.SafeCall; + boolean isSafeCallOrOnStack = receiver instanceof StackValue.SafeCall || receiver instanceof StackValue.OnStack; - if (isSuspensionPoint && !isSafeCall) { + if (isSuspensionPoint && !isSafeCallOrOnStack) { // Inline markers are used to spill the stack before coroutine suspension addInlineMarker(v, true); } @@ -2941,7 +2941,7 @@ public class ExpressionCodegen extends KtVisitor impleme // The problem is that the stack before the call is not restored in case of null receiver. // The solution is to spill stack just after receiver is loaded (after IFNULL) in case of safe call. // But the problem is that we should leave the receiver itself on the stack, so we store it in a temporary variable. - if (isSuspensionPoint && isSafeCall) { + if (isSuspensionPoint && isSafeCallOrOnStack) { int tmpVar = myFrameMap.enterTemp(receiver.type); v.store(tmpVar, receiver.type); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.kt index 0351ef49c32..ec0292b905f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.kt @@ -829,6 +829,19 @@ class ControlFlowInformationProvider private constructor( } } + pseudocode.traverse(TraversalOrder.FORWARD) { instruction -> + if (instruction !is VariableDeclarationInstruction || instruction.element !is KtProperty || !instruction.element.hasDelegate()) return@traverse + + val variableDescriptor = + trace[BindingContext.DECLARATION_TO_DESCRIPTOR, instruction.element] as? VariableDescriptorWithAccessors + ?: return@traverse + + containsNonTailCalls = + containsNonTailCalls || variableDescriptor.accessors.any { + trace[BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, it]?.candidateDescriptor?.isSuspend == true + } + } + if (containsNonTailCalls) { trace.record(BindingContext.CONTAINS_NON_TAIL_SUSPEND_CALLS, currentFunction.original) } diff --git a/compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/operators.kt b/compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/operators.kt new file mode 100644 index 00000000000..8978de73a63 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/operators.kt @@ -0,0 +1,104 @@ +// WITH_RUNTIME +// WITH_COROUTINES +// IGNORE_BACKEND: JS +import kotlin.coroutines.* +import kotlin.coroutines.intrinsics.* +import kotlin.reflect.KProperty + +suspend fun suspendThere(v: String): String = suspendCoroutineOrReturn { x -> + x.resume(v) + SUSPENDED_MARKER +} + +class A(val x: String) { + var isSetValueCalled = false + var isProvideDelegateCalled = false + var isMinusAssignCalled = false + var isIncCalled = false + operator suspend fun component1() = suspendThere(x + "K") + operator suspend fun getValue(thisRef: Any?, property: KProperty<*>) = suspendThere(x + "K") + operator suspend fun setValue(thisRef: Any?, property: KProperty<*>, value: String): Unit = suspendCoroutineOrReturn { x -> + if (value != "56") return@suspendCoroutineOrReturn Unit + isSetValueCalled = true + x.resume(Unit) + SUSPENDED_MARKER + } + + operator suspend fun provideDelegate(host: Any?, p: Any): A = suspendCoroutineOrReturn { x -> + isProvideDelegateCalled = true + x.resume(this) + SUSPENDED_MARKER + } + + operator suspend fun plus(y: String) = suspendThere(x + y) + operator suspend fun unaryPlus() = suspendThere(x + "K") + + operator suspend fun inc(): A = suspendCoroutineOrReturn { x -> + isProvideDelegateCalled = true + x.resume(this) + SUSPENDED_MARKER + } + + operator suspend fun minusAssign(y: String): Unit = suspendCoroutineOrReturn { x -> + if (y != "56") return@suspendCoroutineOrReturn Unit + isMinusAssignCalled = true + x.resume(Unit) + SUSPENDED_MARKER + } +} + +fun builder(c: suspend () -> Unit) { + c.startCoroutine(EmptyContinuation) +} + +var a = A("O") + +suspend fun foo1() { + var x by a + + if (x != "OK") throw RuntimeException("fail 1") + + x = "56" + + if (!a.isSetValueCalled || !a.isProvideDelegateCalled) throw RuntimeException("fail 2") +} + +suspend fun foo2() { + val (y) = a + if (y != "OK") throw RuntimeException("fail 3") +} + +suspend fun foo3() { + val y = a + "K" + if (y != "OK") throw RuntimeException("fail 4") +} + +suspend fun foo4() { + val y = + a + if (y != "OK") throw RuntimeException("fail 5") +} + +// TODO: KT-15930 +//suspend fun foo5() { +// a -= "56" +// if (!a.isMinusAssignCalled) throw RuntimeException("fail 6") +//} + +suspend fun foo6() { + var y = a++ + if (y.isIncCalled) throw RuntimeException("fail 7") +} + +fun box(): String { + + builder { + foo1() + foo2() + foo3() + foo4() + //foo5() + foo6() + } + + return "OK" +} diff --git a/compiler/testData/codegen/light-analysis/coroutines/suspendFunctionAsCoroutine/operators.txt b/compiler/testData/codegen/light-analysis/coroutines/suspendFunctionAsCoroutine/operators.txt new file mode 100644 index 00000000000..3698f50f2e3 --- /dev/null +++ b/compiler/testData/codegen/light-analysis/coroutines/suspendFunctionAsCoroutine/operators.txt @@ -0,0 +1,66 @@ +@kotlin.Metadata +public final class A { + private field isIncCalled: boolean + private field isMinusAssignCalled: boolean + private field isProvideDelegateCalled: boolean + private field isSetValueCalled: boolean + private final @org.jetbrains.annotations.NotNull field x: java.lang.String + public method (@org.jetbrains.annotations.NotNull p0: java.lang.String): void + public final @org.jetbrains.annotations.Nullable method component1(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.Continuation): java.lang.Object + public final @org.jetbrains.annotations.Nullable method getValue(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.NotNull p1: kotlin.reflect.KProperty, @org.jetbrains.annotations.NotNull p2: kotlin.coroutines.Continuation): java.lang.Object + public final @org.jetbrains.annotations.NotNull method getX(): java.lang.String + public final @org.jetbrains.annotations.Nullable method inc(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.Continuation): java.lang.Object + public final method isIncCalled(): boolean + public final method isMinusAssignCalled(): boolean + public final method isProvideDelegateCalled(): boolean + public final method isSetValueCalled(): boolean + public final @org.jetbrains.annotations.Nullable method minusAssign(@org.jetbrains.annotations.NotNull p0: java.lang.String, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.Continuation): java.lang.Object + public final @org.jetbrains.annotations.Nullable method plus(@org.jetbrains.annotations.NotNull p0: java.lang.String, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.Continuation): java.lang.Object + public final @org.jetbrains.annotations.Nullable method provideDelegate(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.NotNull p1: java.lang.Object, @org.jetbrains.annotations.NotNull p2: kotlin.coroutines.Continuation): java.lang.Object + public final method setIncCalled(p0: boolean): void + public final method setMinusAssignCalled(p0: boolean): void + public final method setProvideDelegateCalled(p0: boolean): void + public final method setSetValueCalled(p0: boolean): void + public final @org.jetbrains.annotations.Nullable method setValue(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.NotNull p1: kotlin.reflect.KProperty, @org.jetbrains.annotations.NotNull p2: java.lang.String, @org.jetbrains.annotations.NotNull p3: kotlin.coroutines.Continuation): java.lang.Object + public final @org.jetbrains.annotations.Nullable method unaryPlus(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.Continuation): java.lang.Object +} + +@kotlin.Metadata +public final class CoroutineUtilKt { + public final static @org.jetbrains.annotations.NotNull method handleExceptionContinuation(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1): kotlin.coroutines.Continuation + public final static @org.jetbrains.annotations.NotNull method handleResultContinuation(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1): kotlin.coroutines.Continuation +} + +@kotlin.Metadata +public class EmptyContinuation { + public final static field Companion: EmptyContinuation.Companion + private final @org.jetbrains.annotations.NotNull field context: kotlin.coroutines.CoroutineContext + inner class EmptyContinuation/Companion + public @synthetic.kotlin.jvm.GeneratedByJvmOverloads method (): void + public method (@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.CoroutineContext): void + public synthetic method (p0: kotlin.coroutines.CoroutineContext, p1: int, p2: kotlin.jvm.internal.DefaultConstructorMarker): void + public @org.jetbrains.annotations.NotNull method getContext(): kotlin.coroutines.CoroutineContext + public method resume(@org.jetbrains.annotations.Nullable p0: java.lang.Object): void + public method resumeWithException(@org.jetbrains.annotations.NotNull p0: java.lang.Throwable): void +} + +@kotlin.Metadata +public final static class EmptyContinuation/Companion { + inner class EmptyContinuation/Companion + private method (): void +} + +@kotlin.Metadata +public final class OperatorsKt { + private static @org.jetbrains.annotations.NotNull field a: A + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String + public final static method builder(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1): void + public final static @org.jetbrains.annotations.Nullable method foo1(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.Continuation): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method foo2(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.Continuation): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method foo3(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.Continuation): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method foo4(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.Continuation): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method foo6(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.Continuation): java.lang.Object + public final static @org.jetbrains.annotations.NotNull method getA(): A + public final static method setA(@org.jetbrains.annotations.NotNull p0: A): void + public final static @org.jetbrains.annotations.Nullable method suspendThere(@org.jetbrains.annotations.NotNull p0: java.lang.String, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.Continuation): java.lang.Object +} diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index d0c1e749bf0..dbc368c4f37 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -5179,6 +5179,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } + @TestMetadata("operators.kt") + public void testOperators() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/operators.kt"); + doTest(fileName); + } + @TestMetadata("privateFunctions.kt") public void testPrivateFunctions() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/privateFunctions.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 261b2bc5d4a..750ec46299a 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -5179,6 +5179,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("operators.kt") + public void testOperators() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/operators.kt"); + doTest(fileName); + } + @TestMetadata("privateFunctions.kt") public void testPrivateFunctions() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/privateFunctions.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeCodegenTestGenerated.java index cc581ad6786..6105c8419c0 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeCodegenTestGenerated.java @@ -5179,6 +5179,12 @@ public class LightAnalysisModeCodegenTestGenerated extends AbstractLightAnalysis doTest(fileName); } + @TestMetadata("operators.kt") + public void testOperators() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/operators.kt"); + doTest(fileName); + } + @TestMetadata("privateFunctions.kt") public void testPrivateFunctions() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/privateFunctions.kt"); diff --git a/core/descriptors/src/org/jetbrains/kotlin/descriptors/VariableDescriptorWithAccessors.kt b/core/descriptors/src/org/jetbrains/kotlin/descriptors/VariableDescriptorWithAccessors.kt index 2323a1244cb..e946cdc64e7 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/descriptors/VariableDescriptorWithAccessors.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/descriptors/VariableDescriptorWithAccessors.kt @@ -16,6 +16,8 @@ package org.jetbrains.kotlin.descriptors +import org.jetbrains.kotlin.utils.singletonOrEmptyList + interface VariableDescriptorWithAccessors : VariableDescriptor { val getter: VariableAccessorDescriptor? @@ -31,3 +33,6 @@ interface VariableDescriptorWithAccessors : VariableDescriptor { @Deprecated("Do not call this method in the compiler front-end.") val isDelegated: Boolean } + +val VariableDescriptorWithAccessors.accessors: List + get() = getter.singletonOrEmptyList() + setter.singletonOrEmptyList() 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 df0b1aae609..e9803cd8a91 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 @@ -5852,6 +5852,18 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { doTest(fileName); } + @TestMetadata("operators.kt") + public void testOperators() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/operators.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("privateFunctions.kt") public void testPrivateFunctions() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/privateFunctions.kt");