Add call checks related to suspend lambda modifier's introduction

- Prohibit non-modifier-like calls on kotlin.suspend
- Add warning on modifier-like calls to anything but kotlin.suspend

 #KT-22766 In Progress
 #KT-22562 In Progress
This commit is contained in:
Denis Zharkov
2018-01-25 13:46:56 +03:00
parent ef01f641f3
commit 99c7d8d4d5
10 changed files with 330 additions and 1 deletions
@@ -983,6 +983,8 @@ public interface Errors {
DiagnosticFactory1<PsiElement, CallableDescriptor> ILLEGAL_SUSPEND_FUNCTION_CALL = DiagnosticFactory1.create(ERROR);
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<PsiElement> MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND = DiagnosticFactory0.create(WARNING);
DiagnosticFactory1<PsiElement, RenderedDiagnostic<?>> PLUGIN_ERROR = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<PsiElement, RenderedDiagnostic<?>> PLUGIN_WARNING = DiagnosticFactory1.create(WARNING);
@@ -873,6 +873,8 @@ public class DefaultErrorMessages {
MAP.put(ILLEGAL_SUSPEND_FUNCTION_CALL, "Suspend function ''{0}'' should be called only from a coroutine or another suspend function", NAME);
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(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());
MAP.put(PLUGIN_WARNING, "{0}", (d, c) -> d.getText());
@@ -91,7 +91,7 @@ private val DEFAULT_CALL_CHECKERS = listOf(
CoroutineSuspendCallChecker, BuilderFunctionsCallChecker, DslScopeViolationCallChecker, MissingDependencyClassChecker,
CallableReferenceCompatibilityChecker(), LateinitIntrinsicApplicabilityChecker,
UnderscoreUsageChecker, AssigningNamedArgumentToVarargChecker(),
PrimitiveNumericComparisonCallChecker
PrimitiveNumericComparisonCallChecker, LambdaWithSuspendModifierCallChecker
)
private val DEFAULT_TYPE_CHECKERS = emptyList<AdditionalTypeChecker>()
private val DEFAULT_CLASSIFIER_USAGE_CHECKERS = listOf(
@@ -0,0 +1,60 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.resolve.calls.checkers
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.Call
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtLambdaExpression
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.resolve.calls.callResolverUtil.isInfixCall
import org.jetbrains.kotlin.resolve.calls.callUtil.isCallableReference
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
object LambdaWithSuspendModifierCallChecker : CallChecker {
private val KOTLIN_SUSPEND_BUILT_IN_FUNCTION_FQ_NAME = FqName("kotlin.suspend")
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
val descriptor = resolvedCall.candidateDescriptor
val call = resolvedCall.call
val callName = call.referencedName()
if (callName != "suspend" && descriptor.name.asString() != "suspend") return
when (descriptor.fqNameOrNull()) {
KOTLIN_SUSPEND_BUILT_IN_FUNCTION_FQ_NAME -> {
if (!call.hasFormOfSuspendModifierForLambda() || call.explicitReceiver != null) {
context.trace.report(Errors.NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND.on(reportOn))
}
}
else -> {
if (call.hasFormOfSuspendModifierForLambda()) {
context.trace.report(Errors.MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND.on(reportOn))
}
}
}
}
private fun Call.hasFormOfSuspendModifierForLambda() =
referencedName() == "suspend"
&& !isCallableReference()
&& typeArguments.isEmpty()
&& (hasNoArgumentListButDanglingLambdas() || isInfixWithRightLambda())
private fun Call.referencedName() =
calleeExpression?.safeAs<KtSimpleNameExpression>()?.getReferencedName()
private fun Call.hasNoArgumentListButDanglingLambdas() =
valueArgumentList?.leftParenthesis == null && functionLiteralArguments.isNotEmpty()
private fun Call.isInfixWithRightLambda() =
isInfixCall(this)
&& callElement.safeAs<KtBinaryExpression>()?.right is KtLambdaExpression
}
@@ -0,0 +1,74 @@
// SKIP_TXT
fun <R> suspend(block: suspend () -> R): suspend () -> R = block
class A {
infix fun <R> suspend(block: suspend () -> R): suspend () -> R = block
}
@Target(AnnotationTarget.EXPRESSION)
annotation class Ann
fun bar() {
<!MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND!>suspend<!> {
println()
}
@Ann <!MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND!>suspend<!> {
println()
}
<!MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND!>suspend<!> @Ann {
println()
}
suspend() {
println()
}
suspend({ println() })
suspend<Unit> {
println()
}
val <!UNUSED_VARIABLE!>w<!>: (suspend () -> Int) -> Any? = ::suspend
A().<!MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND!>suspend<!> {
println()
}
A().suspend() {
println()
}
A().suspend({ println() })
A().suspend<Unit> {
println()
}
with(A()) {
<!MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND!>suspend<!> {
println()
}
suspend() {
println()
}
suspend({ println() })
suspend<Unit> {
println()
}
}
A() <!MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND!>suspend<!> {
println()
}
A() suspend ({
println()
})
}
@@ -0,0 +1,82 @@
// SKIP_TXT
fun <R> suspend(block: R) = block
class A {
infix fun <R> suspend(block: R) = block
}
@Target(AnnotationTarget.EXPRESSION)
annotation class Ann
fun bar() {
<!MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND!>suspend<!> {
println()
}
@Ann <!MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND!>suspend<!> {
println()
}
<!MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND!>suspend<!> @Ann {
println()
}
suspend() {
println()
}
suspend({ println() })
suspend<suspend () -> Unit> {
println()
}
suspend<Nothing?>(null)
val <!UNUSED_VARIABLE!>w<!>: (Any?) -> Any? = ::suspend
A().<!MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND!>suspend<!> {
println()
}
A().suspend() {
println()
}
A().suspend({ println() })
A().suspend<suspend () -> Unit> {
println()
}
A().suspend<Nothing?>(null)
with(A()) {
<!MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND!>suspend<!> {
println()
}
suspend() {
println()
}
suspend({ println() })
suspend<suspend () -> Unit> {
println()
}
suspend<Nothing?>(null)
}
A() <!MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND!>suspend<!> {
println()
}
A() suspend ({
println()
})
A() suspend ""
}
@@ -0,0 +1,33 @@
// SKIP_TXT
fun bar() {
suspend {
println()
}
@Ann suspend {
println()
}
suspend @Ann {
println()
}
kotlin.<!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspend<!> {
}
<!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspend<!>() {
println()
}
<!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspend<!>({ println() })
<!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspend<!><Unit> {
println()
}
val <!UNUSED_VARIABLE!>w<!>: (suspend () -> Int) -> Any? = ::<!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspend<!>
}
@Target(AnnotationTarget.EXPRESSION)
annotation class Ann
@@ -0,0 +1,28 @@
// SKIP_TXT
import kotlin.suspend as suspendLambda
fun bar() {
<!UNRESOLVED_REFERENCE!>suspend<!> {
println()
}
kotlin.<!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspend<!> {
}
<!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspendLambda<!> {
println()
}
<!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspendLambda<!>() {
println()
}
<!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspendLambda<!>({ println() })
<!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspendLambda<!><Unit> {
println()
}
val <!UNUSED_VARIABLE!>w<!>: (suspend () -> Int) -> Any? = ::<!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspendLambda<!>
}
@@ -1332,6 +1332,18 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW
doTest(fileName);
}
@TestMetadata("modifierFormForNonBuiltInSuspend.kt")
public void testModifierFormForNonBuiltInSuspend() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/modifierFormForNonBuiltInSuspend.kt");
doTest(fileName);
}
@TestMetadata("modifierFormForNonBuiltInSuspendWithAnyParameter.kt")
public void testModifierFormForNonBuiltInSuspendWithAnyParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/modifierFormForNonBuiltInSuspendWithAnyParameter.kt");
doTest(fileName);
}
@TestMetadata("noDefaultCoroutineImports.kt")
public void testNoDefaultCoroutineImports() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/noDefaultCoroutineImports.kt");
@@ -1344,6 +1356,18 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW
doTest(fileName);
}
@TestMetadata("nonModifierFormForBuiltIn.kt")
public void testNonModifierFormForBuiltIn() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/nonModifierFormForBuiltIn.kt");
doTest(fileName);
}
@TestMetadata("nonModifierFormForBuiltInRenameOnImport.kt")
public void testNonModifierFormForBuiltInRenameOnImport() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/nonModifierFormForBuiltInRenameOnImport.kt");
doTest(fileName);
}
@TestMetadata("operators.kt")
public void testOperators() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/operators.kt");
@@ -1332,6 +1332,18 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno
doTest(fileName);
}
@TestMetadata("modifierFormForNonBuiltInSuspend.kt")
public void testModifierFormForNonBuiltInSuspend() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/modifierFormForNonBuiltInSuspend.kt");
doTest(fileName);
}
@TestMetadata("modifierFormForNonBuiltInSuspendWithAnyParameter.kt")
public void testModifierFormForNonBuiltInSuspendWithAnyParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/modifierFormForNonBuiltInSuspendWithAnyParameter.kt");
doTest(fileName);
}
@TestMetadata("noDefaultCoroutineImports.kt")
public void testNoDefaultCoroutineImports() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/noDefaultCoroutineImports.kt");
@@ -1344,6 +1356,18 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno
doTest(fileName);
}
@TestMetadata("nonModifierFormForBuiltIn.kt")
public void testNonModifierFormForBuiltIn() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/nonModifierFormForBuiltIn.kt");
doTest(fileName);
}
@TestMetadata("nonModifierFormForBuiltInRenameOnImport.kt")
public void testNonModifierFormForBuiltInRenameOnImport() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/nonModifierFormForBuiltInRenameOnImport.kt");
doTest(fileName);
}
@TestMetadata("operators.kt")
public void testOperators() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/operators.kt");