From aa50f1d8242ca9dc32194029518a7c4996169fac Mon Sep 17 00:00:00 2001 From: Denis Zharkov Date: Wed, 30 Nov 2016 14:59:58 +0300 Subject: [PATCH] Report error when suspend call is illegal in the scope Namely when it's not contained inside coroutine or another suspend function #KT-14924 In Progress --- .../org/jetbrains/kotlin/diagnostics/Errors.java | 1 + .../diagnostics/rendering/DefaultErrorMessages.java | 1 + .../resolve/calls/checkers/coroutineCallChecker.kt | 7 +++++-- .../tests/coroutines/illegalSuspendCalls.kt | 13 +++++++++++++ .../tests/coroutines/illegalSuspendCalls.txt | 12 ++++++++++++ .../kotlin/checkers/DiagnosticsTestGenerated.java | 6 ++++++ 6 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/coroutines/illegalSuspendCalls.kt create mode 100644 compiler/testData/diagnostics/tests/coroutines/illegalSuspendCalls.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 79bdc858dc0..a9eb7d888ae 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -862,6 +862,7 @@ public interface Errors { DiagnosticFactory0 NON_LOCAL_SUSPENSION_POINT = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 ILLEGAL_SUSPEND_FUNCTION_CALL = DiagnosticFactory0.create(ERROR); // Error sets diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index 81a79f735f9..a537e69ce50 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -820,6 +820,7 @@ public class DefaultErrorMessages { MAP.put(INLINE_CALL_CYCLE, "The ''{0}'' invocation is a part of inline cycle", NAME); MAP.put(NON_LOCAL_RETURN_IN_DISABLED_INLINE, "Non-local returns are not allowed with inlining disabled"); MAP.put(NON_LOCAL_SUSPENSION_POINT, "Suspension functions can be called only within coroutine body"); + MAP.put(ILLEGAL_SUSPEND_FUNCTION_CALL, "Suspend functions are only allowed to be called from a coroutine or another suspend function"); MAP.setImmutable(); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/coroutineCallChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/coroutineCallChecker.kt index 8995e512fc1..3afcc95f91f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/coroutineCallChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/coroutineCallChecker.kt @@ -47,11 +47,11 @@ object CoroutineSuspendCallChecker : CallChecker { when { enclosingSuspendFunction != null -> { - // TODO: check if tail call + // Tail calls checks happen during control flow analysis + // Here we only record enclosing function mapping (for backends purposes) context.trace.record(BindingContext.ENCLOSING_SUSPEND_FUNCTION_FOR_SUSPEND_FUNCTION_CALL, resolvedCall.call, enclosingSuspendFunction) } closestCoroutineReceiver != null -> { - val callElement = resolvedCall.call.callElement as KtExpression if (!InlineUtil.checkNonLocalReturnUsage(closestCoroutineReceiver.declarationDescriptor, callElement, context.resolutionContext)) { @@ -60,6 +60,9 @@ object CoroutineSuspendCallChecker : CallChecker { context.trace.record(BindingContext.COROUTINE_RECEIVER_FOR_SUSPENSION_POINT, resolvedCall.call, closestCoroutineReceiver) } + else -> { + context.trace.report(Errors.ILLEGAL_SUSPEND_FUNCTION_CALL.on(reportOn)) + } } } } diff --git a/compiler/testData/diagnostics/tests/coroutines/illegalSuspendCalls.kt b/compiler/testData/diagnostics/tests/coroutines/illegalSuspendCalls.kt new file mode 100644 index 00000000000..eddaa3c96a3 --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/illegalSuspendCalls.kt @@ -0,0 +1,13 @@ +suspend fun foo() {} + +fun noSuspend() { + foo() +} + +class A { + init { + foo() + } +} + +val x = foo() diff --git a/compiler/testData/diagnostics/tests/coroutines/illegalSuspendCalls.txt b/compiler/testData/diagnostics/tests/coroutines/illegalSuspendCalls.txt new file mode 100644 index 00000000000..5e94d2d7c4e --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/illegalSuspendCalls.txt @@ -0,0 +1,12 @@ +package + +public val x: kotlin.Unit +public suspend fun foo(): kotlin.Unit +public fun noSuspend(): kotlin.Unit + +public final class A { + public constructor A() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 871e443e9f8..6c8ffe6a6f4 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -4252,6 +4252,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("illegalSuspendCalls.kt") + public void testIllegalSuspendCalls() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/coroutines/illegalSuspendCalls.kt"); + doTest(fileName); + } + @TestMetadata("interceptResume.kt") public void testInterceptResume() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/coroutines/interceptResume.kt");