Prohibit labeled returns inside kotlin.suspend argument lambdas

Probably, it would be more correct to skip such lambdas when resolving
the returns' references, but it'd be more complicated and still useless
since non-local returns are impossible in such lambdas
(relevant parameter is noinline)

 #KT-22900 Fixed
This commit is contained in:
Denis Zharkov
2018-02-19 14:14:09 +03:00
parent bc2e81a1e4
commit 5edfc0f5cd
8 changed files with 66 additions and 0 deletions
@@ -996,6 +996,7 @@ public interface Errors {
DiagnosticFactory1<PsiElement, CallableDescriptor> ILLEGAL_SUSPEND_PROPERTY_ACCESS = DiagnosticFactory1.create(ERROR);
DiagnosticFactory0<PsiElement> ILLEGAL_RESTRICTED_SUSPENDING_FUNCTION_CALL = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<KtReturnExpression> RETURN_FOR_BUILT_IN_SUSPEND = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND = DiagnosticFactory0.create(WARNING);
DiagnosticFactory1<PsiElement, RenderedDiagnostic<?>> PLUGIN_ERROR = DiagnosticFactory1.create(ERROR);
@@ -897,6 +897,7 @@ public class DefaultErrorMessages {
MAP.put(ILLEGAL_SUSPEND_PROPERTY_ACCESS, "Suspend property ''{0}'' should be accessed only from a coroutine or suspend function", NAME);
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(NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND, "''suspend'' function can only be called in a form of modifier of a lambda: suspend { ... }");
MAP.put(RETURN_FOR_BUILT_IN_SUSPEND, "Using implicit label for this lambda is prohibited");
MAP.put(MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND, "Calls having a form of ''suspend {}'' are deprecated because ''suspend'' in the context will have a meaning of a modifier. Add empty argument list to the call: ''suspend() { ... }''");
MAP.put(PLUGIN_ERROR, "{0}", (d, c) -> d.getText());
@@ -920,6 +920,11 @@ public class KtPsiUtil {
return null;
}
public static boolean isLabeledFunctionLiteral(@NotNull KtFunctionLiteral functionLiteral) {
// KtFunctionLiteral -> KtLambdaExpression -> KtLabeledExpression
return functionLiteral.getParent().getParent() instanceof KtLabeledExpression;
}
@Nullable
public static KtExpression getLastElementDeparenthesized(
@Nullable KtExpression expression,
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
object LambdaWithSuspendModifierCallChecker : CallChecker {
@JvmField
val KOTLIN_SUSPEND_BUILT_IN_FUNCTION_FQ_NAME = FqName("kotlin.suspend")
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
@@ -31,6 +31,8 @@ import org.jetbrains.kotlin.resolve.BindingContextUtils;
import org.jetbrains.kotlin.resolve.ModifierCheckerCore;
import org.jetbrains.kotlin.resolve.ModifiersChecker;
import org.jetbrains.kotlin.resolve.calls.ArgumentTypeResolver;
import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt;
import org.jetbrains.kotlin.resolve.calls.checkers.LambdaWithSuspendModifierCallChecker;
import org.jetbrains.kotlin.resolve.calls.model.MutableDataFlowInfoForArguments;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo;
@@ -55,6 +57,7 @@ import org.jetbrains.kotlin.types.expressions.typeInfoFactory.TypeInfoFactoryKt;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import static org.jetbrains.kotlin.diagnostics.Errors.*;
import static org.jetbrains.kotlin.resolve.BindingContext.*;
@@ -663,6 +666,22 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
context.trace.report(RETURN_NOT_ALLOWED.on(expression));
resultType = ErrorUtils.createErrorType(RETURN_NOT_ALLOWED_MESSAGE);
}
else if (labelTargetElement instanceof KtFunctionLiteral
&& Objects.equals(expression.getLabelName(), "suspend")) {
KtExpression callExpression = KtPsiUtil.getParentCallIfPresent((KtFunction) labelTargetElement);
ResolvedCall<? extends CallableDescriptor> resolvedCall =
CallUtilKt.getResolvedCall(callExpression, context.trace.getBindingContext());
if (resolvedCall != null &&
!KtPsiUtil.isLabeledFunctionLiteral((KtFunctionLiteral) labelTargetElement) &&
Objects.equals(
DescriptorUtilsKt.fqNameOrNull(resolvedCall.getResultingDescriptor()),
LambdaWithSuspendModifierCallChecker.KOTLIN_SUSPEND_BUILT_IN_FUNCTION_FQ_NAME
)
) {
context.trace.report(RETURN_FOR_BUILT_IN_SUSPEND.on(expression));
}
}
}
}
@@ -0,0 +1,27 @@
// SKIP_TXT
fun bar() {
suspend {
<!RETURN_FOR_BUILT_IN_SUSPEND!>return@suspend<!>
}
suspend {
run {
<!RETURN_FOR_BUILT_IN_SUSPEND!>return@suspend<!>
}
}
suspend l@{
return@l
}
suspend suspend@{
return@suspend
}
val <!UNUSED_VARIABLE!>x<!> = suspend@{
suspend {
// Might be resolved to outer lambda, but doesn't make sense because suspend-lambdas here is noinline
<!RETURN_FOR_BUILT_IN_SUSPEND!>return<!LABEL_NAME_CLASH!>@suspend<!><!>
}
}
}
@@ -1374,6 +1374,12 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW
doTest(fileName);
}
@TestMetadata("returnLabelForBuiltInSuspend.kt")
public void testReturnLabelForBuiltInSuspend() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/returnLabelForBuiltInSuspend.kt");
doTest(fileName);
}
@TestMetadata("suspendApplicability.kt")
public void testSuspendApplicability() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/suspendApplicability.kt");
@@ -1374,6 +1374,12 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno
doTest(fileName);
}
@TestMetadata("returnLabelForBuiltInSuspend.kt")
public void testReturnLabelForBuiltInSuspend() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/returnLabelForBuiltInSuspend.kt");
doTest(fileName);
}
@TestMetadata("suspendApplicability.kt")
public void testSuspendApplicability() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/suspendApplicability.kt");