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:
@@ -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);
|
||||
|
||||
+2
@@ -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(
|
||||
|
||||
+60
@@ -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
|
||||
}
|
||||
Vendored
+74
@@ -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()
|
||||
})
|
||||
}
|
||||
+82
@@ -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 ""
|
||||
}
|
||||
+33
@@ -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
|
||||
Vendored
+28
@@ -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<!>
|
||||
}
|
||||
+24
@@ -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");
|
||||
|
||||
compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java
Generated
+24
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user