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")