Support non-tail suspend calls in front-end

#KT-15597 In Progress
This commit is contained in:
Denis Zharkov
2017-01-09 18:17:40 +03:00
parent 2cb9d3a8ad
commit a048dde7a5
15 changed files with 34 additions and 53 deletions
@@ -2229,9 +2229,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
@NotNull @NotNull
private Type getReturnTypeForNonLocalReturn(DeclarationDescriptor elementDescriptor) { private Type getReturnTypeForNonLocalReturn(DeclarationDescriptor elementDescriptor) {
return (elementDescriptor instanceof AnonymousFunctionDescriptor return elementDescriptor instanceof FunctionDescriptor && ((FunctionDescriptor) elementDescriptor).isSuspend()
&& ((AnonymousFunctionDescriptor) elementDescriptor).isCoroutine())
|| (elementDescriptor instanceof FunctionDescriptor && ((FunctionDescriptor) elementDescriptor).isSuspend())
? getBoxedReturnTypeForSuspend((FunctionDescriptor) elementDescriptor) ? getBoxedReturnTypeForSuspend((FunctionDescriptor) elementDescriptor)
: typeMapper.mapReturnType((CallableDescriptor) elementDescriptor); : typeMapper.mapReturnType((CallableDescriptor) elementDescriptor);
} }
@@ -827,7 +827,7 @@ class ControlFlowInformationProvider private constructor(
val isUsedAsExpression = instruction.owner.getUsages(instruction.outputValue).isNotEmpty() val isUsedAsExpression = instruction.owner.getUsages(instruction.outputValue).isNotEmpty()
if (!isUsedAsExpression || !instruction.isTailCall(enclosingSuspendFunction) || isInsideTry(element)) { if (!isUsedAsExpression || !instruction.isTailCall(enclosingSuspendFunction) || isInsideTry(element)) {
trace.report(Errors.SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE.on(element)) trace.record(BindingContext.CONTAINS_NON_TAIL_SUSPEND_CALLS, currentFunction.original)
} }
} }
} }
@@ -21,6 +21,6 @@ import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
val CallableDescriptor.isSuspendLambda get() = this is AnonymousFunctionDescriptor && this.isCoroutine val CallableDescriptor.isSuspendLambda get() = this is AnonymousFunctionDescriptor && this.isSuspend
val ValueParameterDescriptor.hasSuspendFunctionType get() = returnType?.isSuspendFunctionType == true val ValueParameterDescriptor.hasSuspendFunctionType get() = returnType?.isSuspendFunctionType == true
@@ -23,20 +23,21 @@ import org.jetbrains.kotlin.descriptors.annotations.Annotations;
import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.name.Name;
public class AnonymousFunctionDescriptor extends SimpleFunctionDescriptorImpl { public class AnonymousFunctionDescriptor extends SimpleFunctionDescriptorImpl {
private final boolean isCoroutine; private final boolean isSuspend;
public AnonymousFunctionDescriptor( public AnonymousFunctionDescriptor(
@NotNull DeclarationDescriptor containingDeclaration, @NotNull DeclarationDescriptor containingDeclaration,
@NotNull Annotations annotations, @NotNull Annotations annotations,
@NotNull Kind kind, @NotNull Kind kind,
@NotNull SourceElement source, @NotNull SourceElement source,
boolean isCoroutine boolean isSuspend
) { ) {
super(containingDeclaration, null, annotations, Name.special("<anonymous>"), kind, source); super(containingDeclaration, null, annotations, Name.special("<anonymous>"), kind, source);
this.isCoroutine = isCoroutine; this.isSuspend = isSuspend;
} }
public boolean isCoroutine() { @Override
return isCoroutine; public boolean isSuspend() {
return isSuspend;
} }
} }
@@ -891,7 +891,6 @@ public interface Errors {
DiagnosticFactory0<PsiElement> NON_LOCAL_SUSPENSION_POINT = DiagnosticFactory0.create(ERROR); DiagnosticFactory0<PsiElement> NON_LOCAL_SUSPENSION_POINT = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> ILLEGAL_SUSPEND_FUNCTION_CALL = DiagnosticFactory0.create(ERROR); DiagnosticFactory0<PsiElement> ILLEGAL_SUSPEND_FUNCTION_CALL = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> ILLEGAL_RESTRICTED_SUSPENDING_FUNCTION_CALL = DiagnosticFactory0.create(ERROR); DiagnosticFactory0<PsiElement> ILLEGAL_RESTRICTED_SUSPENDING_FUNCTION_CALL = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE = DiagnosticFactory0.create(ERROR);
// Error sets // Error sets
@@ -891,7 +891,6 @@ public class DefaultErrorMessages {
MAP.put(NON_LOCAL_SUSPENSION_POINT, "Suspension functions can be called only within coroutine body"); 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.put(ILLEGAL_SUSPEND_FUNCTION_CALL, "Suspend functions are only allowed to be called from a coroutine or another suspend function");
MAP.put(ILLEGAL_RESTRICTED_SUSPENDING_FUNCTION_CALL, "Restricted suspending functions can only invoke member or extension suspending functions on their restricted coroutine scope"); MAP.put(ILLEGAL_RESTRICTED_SUSPENDING_FUNCTION_CALL, "Restricted suspending functions can only invoke member or extension suspending functions on their restricted coroutine scope");
MAP.put(SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE, "Only tail calls are allowed to another suspension function, and these calls must be used as return values");
MAP.setImmutable(); MAP.setImmutable();
@@ -135,8 +135,8 @@ public interface BindingContext {
WritableSlice<KtExpression, ResolvedCall<FunctionDescriptor>> LOOP_RANGE_NEXT_RESOLVED_CALL = Slices.createSimpleSlice(); WritableSlice<KtExpression, ResolvedCall<FunctionDescriptor>> LOOP_RANGE_NEXT_RESOLVED_CALL = Slices.createSimpleSlice();
WritableSlice<KtExpression, ResolvedCall<FunctionDescriptor>> RETURN_HANDLE_RESULT_RESOLVED_CALL = Slices.createSimpleSlice(); WritableSlice<KtExpression, ResolvedCall<FunctionDescriptor>> RETURN_HANDLE_RESULT_RESOLVED_CALL = Slices.createSimpleSlice();
WritableSlice<Call, CallableDescriptor> ENCLOSING_SUSPEND_LAMBDA_FOR_SUSPENSION_POINT = Slices.createSimpleSlice(); WritableSlice<Call, FunctionDescriptor> ENCLOSING_SUSPEND_FUNCTION_FOR_SUSPEND_FUNCTION_CALL = Slices.createSimpleSlice();
WritableSlice<Call, SimpleFunctionDescriptor> ENCLOSING_SUSPEND_FUNCTION_FOR_SUSPEND_FUNCTION_CALL = Slices.createSimpleSlice(); WritableSlice<FunctionDescriptor, Boolean> CONTAINS_NON_TAIL_SUSPEND_CALLS = Slices.createSimpleSetSlice();
WritableSlice<VariableAccessorDescriptor, ResolvedCall<FunctionDescriptor>> DELEGATED_PROPERTY_RESOLVED_CALL = Slices.createSimpleSlice(); WritableSlice<VariableAccessorDescriptor, ResolvedCall<FunctionDescriptor>> DELEGATED_PROPERTY_RESOLVED_CALL = Slices.createSimpleSlice();
WritableSlice<VariableAccessorDescriptor, Call> DELEGATED_PROPERTY_CALL = Slices.createSimpleSlice(); WritableSlice<VariableAccessorDescriptor, Call> DELEGATED_PROPERTY_CALL = Slices.createSimpleSlice();
@@ -20,10 +20,8 @@ import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.coroutines.hasSuspendFunctionType import org.jetbrains.kotlin.coroutines.hasSuspendFunctionType
import org.jetbrains.kotlin.coroutines.isSuspendLambda
import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Errors
@@ -47,40 +45,26 @@ object CoroutineSuspendCallChecker : CallChecker {
val descriptor = resolvedCall.candidateDescriptor as? SimpleFunctionDescriptor ?: return val descriptor = resolvedCall.candidateDescriptor as? SimpleFunctionDescriptor ?: return
if (!descriptor.isSuspend) return if (!descriptor.isSuspend) return
val (closestSuspendLambdaScope, closestSuspensionLambdaDescriptor) = val enclosingSuspendFunction =
context.scope context.scope
.parentsWithSelf.firstOrNull { .parentsWithSelf.firstOrNull {
it is LexicalScope && it.kind == LexicalScopeKind.FUNCTION_INNER_SCOPE && it is LexicalScope && it.kind == LexicalScopeKind.FUNCTION_INNER_SCOPE &&
it.ownerDescriptor.safeAs<CallableDescriptor>()?.isSuspendLambda == true it.ownerDescriptor.safeAs<FunctionDescriptor>()?.isSuspend == true
}?.let { it to it.cast<LexicalScope>().ownerDescriptor.cast<CallableDescriptor>() } }?.cast<LexicalScope>()?.ownerDescriptor?.cast<FunctionDescriptor>()
?: null to null
val enclosingSuspendFunction =
context.scope.parentsWithSelf.filterIsInstance<LexicalScope>().takeWhile { it != closestSuspendLambdaScope }
.firstOrNull {
(it.ownerDescriptor as? FunctionDescriptor)?.isSuspend == true
}?.ownerDescriptor as? SimpleFunctionDescriptor
when { when {
enclosingSuspendFunction != null -> { enclosingSuspendFunction != null -> {
// 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)
checkRestrictsSuspension(enclosingSuspendFunction, resolvedCall, reportOn, context)
}
closestSuspensionLambdaDescriptor != null -> {
val callElement = resolvedCall.call.callElement as KtExpression val callElement = resolvedCall.call.callElement as KtExpression
if (!InlineUtil.checkNonLocalReturnUsage(closestSuspensionLambdaDescriptor, callElement, context.resolutionContext)) { if (!InlineUtil.checkNonLocalReturnUsage(enclosingSuspendFunction, callElement, context.resolutionContext)) {
context.trace.report(Errors.NON_LOCAL_SUSPENSION_POINT.on(reportOn)) context.trace.report(Errors.NON_LOCAL_SUSPENSION_POINT.on(reportOn))
} }
context.trace.record( context.trace.record(
BindingContext.ENCLOSING_SUSPEND_LAMBDA_FOR_SUSPENSION_POINT, resolvedCall.call, closestSuspensionLambdaDescriptor BindingContext.ENCLOSING_SUSPEND_FUNCTION_FOR_SUSPEND_FUNCTION_CALL, resolvedCall.call, enclosingSuspendFunction
) )
checkRestrictsSuspension(closestSuspensionLambdaDescriptor, resolvedCall, reportOn, context) checkRestrictsSuspension(enclosingSuspendFunction, resolvedCall, reportOn, context)
} }
else -> { else -> {
context.trace.report(Errors.ILLEGAL_SUSPEND_FUNCTION_CALL.on(reportOn)) context.trace.report(Errors.ILLEGAL_SUSPEND_FUNCTION_CALL.on(reportOn))
@@ -5,15 +5,15 @@ import kotlin.coroutines.intrinsics.*
fun nonSuspend() {} fun nonSuspend() {}
suspend fun foo() { suspend fun foo() {
<!SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE!>suspendCoroutineOrReturn { x: Continuation<Int> -> }<!> suspendCoroutineOrReturn { x: Continuation<Int> -> }
nonSuspend() nonSuspend()
} }
suspend fun unitSuspend() { suspend fun unitSuspend() {
<!SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE!>suspendCoroutineOrReturn { x: Continuation<Int> -> }<!> suspendCoroutineOrReturn { x: Continuation<Int> -> }
} }
suspend fun baz(): Int = run { suspend fun baz(): Int = run {
<!SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE!>suspendCoroutineOrReturn { x: Continuation<Int> -> }<!> suspendCoroutineOrReturn { x: Continuation<Int> -> }
} }
@@ -3,7 +3,7 @@ suspend fun unit() {}
suspend fun foo() { suspend fun foo() {
suspend fun bar() { suspend fun bar() {
<!SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE, SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE!>baz()<!> baz()
return unit() return unit()
} }
@@ -2,7 +2,7 @@
import kotlin.coroutines.* import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.* import kotlin.coroutines.intrinsics.*
suspend fun suspendLogAndThrow(exception: Throwable): Nothing = <!SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE!>suspendCoroutineOrReturn { c -> suspend fun suspendLogAndThrow(exception: Throwable): Nothing = suspendCoroutineOrReturn { c ->
c.resumeWithException(exception) c.resumeWithException(exception)
SUSPENDED_MARKER SUSPENDED_MARKER
}<!> }
@@ -1,5 +1,5 @@
suspend fun unit1() { suspend fun unit1() {
<!SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE!>unit1()<!> unit1()
} }
suspend fun unit2() { suspend fun unit2() {
@@ -13,6 +13,6 @@ suspend fun int1(): Int {
suspend fun int2(): Int = int2() suspend fun int2(): Int = int2()
suspend fun int3(): Int { suspend fun int3(): Int {
<!SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE!>int3()<!> int3()
return int3() return int3()
} }
@@ -8,15 +8,15 @@ suspend fun baz(): Int = 1
suspend fun tryCatch(): Int { suspend fun tryCatch(): Int {
return try { return try {
<!SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE!>suspendCoroutineOrReturn { x: Continuation<Int> -> }<!> suspendCoroutineOrReturn { x: Continuation<Int> -> }
} catch (e: Exception) { } catch (e: Exception) {
<!SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE!>baz()<!> // another suspend function baz() // another suspend function
} }
} }
suspend fun tryFinally(): Int { suspend fun tryFinally(): Int {
return try { return try {
<!SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE!>suspendCoroutineOrReturn { x: Continuation<Int> -> }<!> suspendCoroutineOrReturn { x: Continuation<Int> -> }
} finally { } finally {
nonSuspend() nonSuspend()
} }
@@ -26,16 +26,16 @@ suspend fun returnInFinally(): Int {
try { try {
} finally { } finally {
// Probably this is too restrictive, but it does not matter much // Probably this is too restrictive, but it does not matter much
return <!SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE, SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE!>baz()<!> return baz()
} }
} }
suspend fun tryCatchFinally(): Int { suspend fun tryCatchFinally(): Int {
return try { return try {
<!SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE!>suspendCoroutineOrReturn { x: Continuation<Int> -> }<!> suspendCoroutineOrReturn { x: Continuation<Int> -> }
} catch (e: Exception) { } catch (e: Exception) {
<!SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE!>baz()<!> // another suspend function baz() // another suspend function
} finally { } finally {
<!SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE, SUSPENSION_CALL_MUST_BE_USED_AS_RETURN_VALUE!>baz()<!> baz()
} }
} }
@@ -54,4 +54,4 @@ fun KotlinType.getJetTypeFqName(printTypeArguments: Boolean): String {
fun ClassDescriptor.hasPrimaryConstructor(): Boolean = unsubstitutedPrimaryConstructor != null fun ClassDescriptor.hasPrimaryConstructor(): Boolean = unsubstitutedPrimaryConstructor != null
val DeclarationDescriptor.isCoroutineLambda: Boolean val DeclarationDescriptor.isCoroutineLambda: Boolean
get() = this is AnonymousFunctionDescriptor && isCoroutine get() = this is AnonymousFunctionDescriptor && isSuspend
@@ -339,7 +339,7 @@ public final class TranslationUtils {
@NotNull @NotNull
public static JsExpression translateContinuationArgument(@NotNull TranslationContext context, @NotNull ResolvedCall<?> resolvedCall) { public static JsExpression translateContinuationArgument(@NotNull TranslationContext context, @NotNull ResolvedCall<?> resolvedCall) {
CallableDescriptor continuationDescriptor = CallableDescriptor continuationDescriptor =
context.bindingContext().get(BindingContext.ENCLOSING_SUSPEND_LAMBDA_FOR_SUSPENSION_POINT, resolvedCall.getCall()); context.bindingContext().get(BindingContext.ENCLOSING_SUSPEND_FUNCTION_FOR_SUSPEND_FUNCTION_CALL, resolvedCall.getCall());
if (continuationDescriptor == null) { if (continuationDescriptor == null) {
continuationDescriptor = getEnclosingContinuationParameter(context); continuationDescriptor = getEnclosingContinuationParameter(context);