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 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 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 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 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 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 package org.jetbrains.kotlin.fir.analysis.checkers.expression
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn 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.*
import org.jetbrains.kotlin.fir.declarations.utils.isSuspend import org.jetbrains.kotlin.fir.declarations.utils.isSuspend
import org.jetbrains.kotlin.fir.expressions.FirExpression import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
import org.jetbrains.kotlin.fir.expressions.FirThisReceiverExpression
import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression
import org.jetbrains.kotlin.fir.references.FirResolvedCallableReference import org.jetbrains.kotlin.fir.references.FirResolvedCallableReference
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference 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_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_2_30_FQ_NAME
import org.jetbrains.kotlin.resolve.calls.checkers.COROUTINE_CONTEXT_1_3_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 import org.jetbrains.kotlin.utils.addToStdlib.lastIsInstanceOrNull
object FirSuspendCallChecker : FirQualifiedAccessExpressionChecker() { object FirSuspendCallChecker : FirQualifiedAccessExpressionChecker() {
@@ -44,13 +45,20 @@ object FirSuspendCallChecker : FirQualifiedAccessExpressionChecker() {
private val RESTRICTS_SUSPENSION_CLASS_ID = private val RESTRICTS_SUSPENSION_CLASS_ID =
ClassId(StandardNames.COROUTINES_PACKAGE_FQ_NAME_RELEASE, Name.identifier("RestrictsSuspension")) 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) @OptIn(SymbolInternals::class)
override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) { override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
val reference = expression.calleeReference as? FirResolvedNamedReference ?: return val reference = expression.calleeReference as? FirResolvedNamedReference ?: return
if (reference is FirResolvedCallableReference) return
val symbol = reference.resolvedSymbol as? FirCallableSymbol ?: return val symbol = reference.resolvedSymbol as? FirCallableSymbol ?: return
symbol.ensureResolved(FirResolvePhase.STATUS) symbol.ensureResolved(FirResolvePhase.STATUS)
val fir = symbol.fir as? FirCallableDeclaration ?: return 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) { when (fir) {
is FirSimpleFunction -> if (!fir.isSuspend) return is FirSimpleFunction -> if (!fir.isSuspend) return
is FirProperty -> if (symbol.callableId.asSingleFqName() !in SUSPEND_PROPERTIES_FQ_NAMES) 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? { private fun findEnclosingSuspendFunction(context: CheckerContext): FirFunction? {
return context.containingDeclarations.lastOrNull { return context.containingDeclarations.lastOrNull {
when (it) { 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.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.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.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.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
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MUST_BE_INITIALIZED_OR_BE_ABSTRACT 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_RETURN_NOT_ALLOWED
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_LOCAL_SUSPENSION_POINT 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_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_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_PRIVATE_OR_PROTECTED_CONSTRUCTOR_IN_SEALED
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_PUBLIC_CALL_FROM_PUBLIC_INLINE 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, ILLEGAL_RESTRICTED_SUSPENDING_FUNCTION_CALL,
"Restricted suspending functions can only invoke member or extension suspending functions on their restricted coroutine scope" "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 // Extended checkers group
map.put(REDUNDANT_VISIBILITY_MODIFIER, "Redundant visibility modifier") 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 // !LANGUAGE: +ReleaseCoroutines
// SKIP_TXT // 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 // !LANGUAGE: +ReleaseCoroutines
// SKIP_TXT // 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 // !LANGUAGE: +ReleaseCoroutines
// SKIP_TXT // SKIP_TXT
@@ -8,23 +8,23 @@ fun bar() {
println() println()
} }
kotlin.suspend { kotlin.<!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspend<!> {
} }
suspendLambda { <!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspendLambda<!> {
println() println()
} }
suspendLambda() { <!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspendLambda<!>() {
println() println()
} }
suspendLambda({ println() }) <!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspendLambda<!>({ println() })
suspendLambda<Unit> { <!NON_MODIFIER_FORM_FOR_BUILT_IN_SUSPEND!>suspendLambda<!><Unit> {
println() 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, 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 -> add(FirJvmErrors.CONFLICTING_JVM_DECLARATIONS) { firDiagnostic ->
ConflictingJvmDeclarationsImpl( ConflictingJvmDeclarationsImpl(
firDiagnostic as FirPsiDiagnostic, firDiagnostic as FirPsiDiagnostic,
@@ -2215,6 +2215,14 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = IllegalRestrictedSuspendingFunctionCall::class 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>() { abstract class ConflictingJvmDeclarations : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = ConflictingJvmDeclarations::class override val diagnosticClass get() = ConflictingJvmDeclarations::class
} }
@@ -3575,6 +3575,20 @@ internal class IllegalRestrictedSuspendingFunctionCallImpl(
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic) 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( internal class ConflictingJvmDeclarationsImpl(
firDiagnostic: FirPsiDiagnostic, firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken, override val token: ValidityToken,