FIR: introduce builtin suspend related diagnostics

This commit is contained in:
Mikhail Glukhikh
2021-07-26 14:44:28 +03:00
committed by teamcityserver
parent 391c4db87c
commit 229dfd3f5f
14 changed files with 113 additions and 215 deletions
@@ -1192,6 +1192,8 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
}
val NON_LOCAL_SUSPENSION_POINT by error<PsiElement>(PositioningStrategy.REFERENCED_NAME_BY_QUALIFIED)
val ILLEGAL_RESTRICTED_SUSPENDING_FUNCTION_CALL by error<PsiElement>(PositioningStrategy.REFERENCED_NAME_BY_QUALIFIED)
val NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND by error<PsiElement>(PositioningStrategy.REFERENCED_NAME_BY_QUALIFIED)
val MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND by error<PsiElement>(PositioningStrategy.REFERENCED_NAME_BY_QUALIFIED)
}
}
@@ -612,5 +612,7 @@ object FirErrors {
val ILLEGAL_SUSPEND_PROPERTY_ACCESS by error1<PsiElement, FirBasedSymbol<*>>(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED)
val NON_LOCAL_SUSPENSION_POINT by error0<PsiElement>(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED)
val ILLEGAL_RESTRICTED_SUSPENDING_FUNCTION_CALL by error0<PsiElement>(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED)
val NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND by error0<PsiElement>(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED)
val MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND by error0<PsiElement>(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED)
}
@@ -5,17 +5,17 @@
package org.jetbrains.kotlin.fir.analysis.checkers.expression
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.getChild
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.isSuspend
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
import org.jetbrains.kotlin.fir.expressions.FirThisReceiverExpression
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression
import org.jetbrains.kotlin.fir.references.FirResolvedCallableReference
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
@@ -34,6 +34,7 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.calls.checkers.COROUTINE_CONTEXT_1_2_20_FQ_NAME
import org.jetbrains.kotlin.resolve.calls.checkers.COROUTINE_CONTEXT_1_2_30_FQ_NAME
import org.jetbrains.kotlin.resolve.calls.checkers.COROUTINE_CONTEXT_1_3_FQ_NAME
import org.jetbrains.kotlin.serialization.deserialization.KOTLIN_SUSPEND_BUILT_IN_FUNCTION_FQ_NAME
import org.jetbrains.kotlin.utils.addToStdlib.lastIsInstanceOrNull
object FirSuspendCallChecker : FirQualifiedAccessExpressionChecker() {
@@ -44,13 +45,20 @@ object FirSuspendCallChecker : FirQualifiedAccessExpressionChecker() {
private val RESTRICTS_SUSPENSION_CLASS_ID =
ClassId(StandardNames.COROUTINES_PACKAGE_FQ_NAME_RELEASE, Name.identifier("RestrictsSuspension"))
private val BUILTIN_SUSPEND_NAME = KOTLIN_SUSPEND_BUILT_IN_FUNCTION_FQ_NAME.shortName()
@OptIn(SymbolInternals::class)
override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
val reference = expression.calleeReference as? FirResolvedNamedReference ?: return
if (reference is FirResolvedCallableReference) return
val symbol = reference.resolvedSymbol as? FirCallableSymbol ?: return
symbol.ensureResolved(FirResolvePhase.STATUS)
val fir = symbol.fir as? FirCallableDeclaration ?: return
if (reference.name == BUILTIN_SUSPEND_NAME ||
fir is FirSimpleFunction && fir.name == BUILTIN_SUSPEND_NAME
) {
checkSuspendModifierForm(expression, reference, symbol, context, reporter)
}
if (reference is FirResolvedCallableReference) return
when (fir) {
is FirSimpleFunction -> if (!fir.isSuspend) return
is FirProperty -> if (symbol.callableId.asSingleFqName() !in SUSPEND_PROPERTIES_FQ_NAMES) return
@@ -74,6 +82,50 @@ object FirSuspendCallChecker : FirQualifiedAccessExpressionChecker() {
}
}
private fun checkSuspendModifierForm(
expression: FirQualifiedAccessExpression,
reference: FirResolvedNamedReference,
symbol: FirCallableSymbol<*>,
context: CheckerContext,
reporter: DiagnosticReporter
) {
if (symbol.callableId.asSingleFqName() == KOTLIN_SUSPEND_BUILT_IN_FUNCTION_FQ_NAME) {
if (reference.name != BUILTIN_SUSPEND_NAME ||
expression.explicitReceiver != null ||
!expression.hasFormOfSuspendModifierForLambda()
) {
reporter.reportOn(expression.source, FirErrors.NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND, context)
}
} else {
if (reference.name == BUILTIN_SUSPEND_NAME && expression.hasFormOfSuspendModifierForLambda()) {
reporter.reportOn(expression.source, FirErrors.MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND, context)
}
}
}
private fun FirQualifiedAccessExpression.hasFormOfSuspendModifierForLambda(): Boolean {
if (this !is FirFunctionCall) return false
val reference = this.calleeReference
if (reference is FirResolvedCallableReference) return false
if (typeArguments.any { it.source != null }) return false
if (arguments.singleOrNull() is FirLambdaArgumentExpression) {
// No brackets should be in a selector call
val callExpressionSource =
if (explicitReceiver == null) source
else source?.getChild(KtNodeTypes.CALL_EXPRESSION, index = 1, depth = 1)
if (callExpressionSource?.getChild(KtNodeTypes.VALUE_ARGUMENT_LIST, depth = 1) == null) {
return true
}
}
if (source?.elementType == KtNodeTypes.BINARY_EXPRESSION) {
val lastArgument = arguments.lastOrNull()
if (lastArgument is FirAnonymousFunctionExpression && source?.getChild(KtNodeTypes.PARENTHESIZED, depth = 1) == null) {
return true
}
}
return false
}
private fun findEnclosingSuspendFunction(context: CheckerContext): FirFunction? {
return context.containingDeclarations.lastOrNull {
when (it) {
@@ -247,6 +247,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MANY_LAMBDA_EXPRE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.METHOD_OF_ANY_IMPLEMENTED_IN_INTERFACE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MISPLACED_TYPE_PARAMETER_CONSTRAINTS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MISSING_VAL_ON_ANNOTATION_PARAMETER
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MULTIPLE_VARARG_PARAMETERS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MUST_BE_INITIALIZED
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MUST_BE_INITIALIZED_OR_BE_ABSTRACT
@@ -264,6 +265,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_FINAL_MEMBER_
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_LOCAL_RETURN_NOT_ALLOWED
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_LOCAL_SUSPENSION_POINT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_MEMBER_FUNCTION_NO_BODY
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_PRIVATE_CONSTRUCTOR_IN_ENUM
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_PRIVATE_OR_PROTECTED_CONSTRUCTOR_IN_SEALED
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_PUBLIC_CALL_FROM_PUBLIC_INLINE
@@ -1497,6 +1499,14 @@ class FirDefaultErrorMessages {
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() { ... }''"
)
// Extended checkers group
map.put(REDUNDANT_VISIBILITY_MODIFIER, "Redundant visibility modifier")
@@ -1,76 +0,0 @@
// !LANGUAGE: +ReleaseCoroutines
// 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)
@Retention(AnnotationRetention.SOURCE)
annotation class Ann
fun bar() {
suspend {
println()
}
@Ann suspend {
println()
}
suspend @Ann {
println()
}
suspend() {
println()
}
suspend({ println() })
suspend<Unit> {
println()
}
val w: (suspend () -> Int) -> Any? = ::suspend
A().suspend {
println()
}
A().suspend() {
println()
}
A().suspend({ println() })
A().suspend<Unit> {
println()
}
with(A()) {
suspend {
println()
}
suspend() {
println()
}
suspend({ println() })
suspend<Unit> {
println()
}
}
A() suspend {
println()
}
A() suspend ({
println()
})
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !LANGUAGE: +ReleaseCoroutines
// SKIP_TXT
@@ -1,84 +0,0 @@
// !LANGUAGE: +ReleaseCoroutines
// SKIP_TXT
fun <R> suspend(block: R) = block
class A {
infix fun <R> suspend(block: R) = block
}
@Target(AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
annotation class Ann
fun bar() {
suspend {
println()
}
@Ann suspend {
println()
}
suspend @Ann {
println()
}
suspend() {
println()
}
suspend({ println() })
suspend<suspend () -> Unit> {
println()
}
suspend<Nothing?>(null)
val w: (Any?) -> Any? = ::suspend
A().suspend {
println()
}
A().suspend() {
println()
}
A().suspend({ println() })
A().suspend<suspend () -> Unit> {
println()
}
A().suspend<Nothing?>(null)
with(A()) {
suspend {
println()
}
suspend() {
println()
}
suspend({ println() })
suspend<suspend () -> Unit> {
println()
}
suspend<Nothing?>(null)
}
A() suspend {
println()
}
A() suspend ({
println()
})
A() suspend ""
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !LANGUAGE: +ReleaseCoroutines
// SKIP_TXT
@@ -1,45 +0,0 @@
// !LANGUAGE: +ReleaseCoroutines
// SKIP_TXT
fun bar() {
suspend {
println()
}
@Ann suspend {
println()
}
suspend @Ann {
println()
}
kotlin.suspend {
}
suspend() {
println()
}
suspend({ println() })
suspend<Unit> {
println()
}
val w: (suspend () -> Int) -> Any? = ::suspend
}
@Target(AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
annotation class Ann
fun main(suspend: WLambdaInvoke) {
suspend {}
}
class WLambdaInvoke {
operator fun invoke(l: () -> Unit) {}
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !LANGUAGE: +ReleaseCoroutines
// SKIP_TXT
@@ -8,23 +8,23 @@ fun bar() {
println()
}
kotlin.suspend {
kotlin.<!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspend<!> {
}
suspendLambda {
<!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspendLambda<!> {
println()
}
suspendLambda() {
<!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspendLambda<!>() {
println()
}
suspendLambda({ println() })
<!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspendLambda<!>({ println() })
suspendLambda<Unit> {
<!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspendLambda<!><Unit> {
println()
}
val w: (suspend () -> Int) -> Any? = ::suspendLambda
val w: (suspend () -> Int) -> Any? = ::<!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspendLambda<!>
}
@@ -3168,6 +3168,18 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND) { firDiagnostic ->
NonModifierFormForBuiltInSuspendImpl(
firDiagnostic as FirPsiDiagnostic,
token,
)
}
add(FirErrors.MODIFIER_FORM_FOR_NON_BUILT_IN_SUSPEND) { firDiagnostic ->
ModifierFormForNonBuiltInSuspendImpl(
firDiagnostic as FirPsiDiagnostic,
token,
)
}
add(FirJvmErrors.CONFLICTING_JVM_DECLARATIONS) { firDiagnostic ->
ConflictingJvmDeclarationsImpl(
firDiagnostic as FirPsiDiagnostic,
@@ -2215,6 +2215,14 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = IllegalRestrictedSuspendingFunctionCall::class
}
abstract class NonModifierFormForBuiltInSuspend : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = NonModifierFormForBuiltInSuspend::class
}
abstract class ModifierFormForNonBuiltInSuspend : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = ModifierFormForNonBuiltInSuspend::class
}
abstract class ConflictingJvmDeclarations : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = ConflictingJvmDeclarations::class
}
@@ -3575,6 +3575,20 @@ internal class IllegalRestrictedSuspendingFunctionCallImpl(
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class NonModifierFormForBuiltInSuspendImpl(
firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,
) : KtFirDiagnostic.NonModifierFormForBuiltInSuspend(), KtAbstractFirDiagnostic<PsiElement> {
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class ModifierFormForNonBuiltInSuspendImpl(
firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,
) : KtFirDiagnostic.ModifierFormForNonBuiltInSuspend(), KtAbstractFirDiagnostic<PsiElement> {
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class ConflictingJvmDeclarationsImpl(
firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,