FIR checker: Create a new kind of checker FirTypeChecker and add

FirSuspendModifierChecker to report WRONG_MODIFIER_TARGET for `suspend`
on  non-functional types.
This commit is contained in:
Mark Punzalan
2021-04-21 16:23:43 +03:00
committed by TeamCityServer
parent 4282d17467
commit e69b729e21
19 changed files with 254 additions and 62 deletions
@@ -9,11 +9,17 @@ import org.jetbrains.kotlin.fir.checkers.generator.diagnostics.DIAGNOSTICS_LIST
import org.jetbrains.kotlin.fir.checkers.generator.diagnostics.generateDiagnostics
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.types.FirTypeRef
import java.io.File
fun main(args: Array<String>) {
val generationPath = args.firstOrNull()?.let { File(it) } ?: File("compiler/fir/checkers/gen").absoluteFile
val typePackage = "org.jetbrains.kotlin.fir.analysis.checkers.type"
generateCheckersComponents(generationPath, typePackage, "FirTypeChecker") {
alias<FirTypeRef>("TypeRefChecker")
}
val expressionPackage = "org.jetbrains.kotlin.fir.analysis.checkers.expression"
generateCheckersComponents(generationPath, expressionPackage, "FirExpressionChecker") {
alias<FirStatement>("BasicExpressionChecker")
@@ -0,0 +1,25 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.fir.analysis.checkers.type
import org.jetbrains.kotlin.fir.analysis.CheckersComponentInternal
/*
* This file was generated automatically
* DO NOT MODIFY IT MANUALLY
*/
internal class ComposedTypeCheckers : TypeCheckers() {
override val typeRefCheckers: Set<FirTypeRefChecker>
get() = _typeRefCheckers
private val _typeRefCheckers: MutableSet<FirTypeRefChecker> = mutableSetOf()
@CheckersComponentInternal
internal fun register(checkers: TypeCheckers) {
_typeRefCheckers += checkers.typeRefCheckers
}
}
@@ -0,0 +1,15 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.fir.analysis.checkers.type
/*
* This file was generated automatically
* DO NOT MODIFY IT MANUALLY
*/
import org.jetbrains.kotlin.fir.types.FirTypeRef
typealias FirTypeRefChecker = FirTypeChecker<FirTypeRef>
@@ -0,0 +1,23 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.fir.analysis.checkers.type
import org.jetbrains.kotlin.fir.analysis.CheckersComponentInternal
/*
* This file was generated automatically
* DO NOT MODIFY IT MANUALLY
*/
abstract class TypeCheckers {
companion object {
val EMPTY: TypeCheckers = object : TypeCheckers() {}
}
open val typeRefCheckers: Set<FirTypeRefChecker> = emptySet()
@CheckersComponentInternal internal val allTypeRefCheckers: Set<FirTypeRefChecker> get() = typeRefCheckers
}
@@ -13,6 +13,8 @@ import org.jetbrains.kotlin.fir.analysis.checkers.declaration.ComposedDeclaratio
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers
import org.jetbrains.kotlin.fir.analysis.checkers.expression.ComposedExpressionCheckers
import org.jetbrains.kotlin.fir.analysis.checkers.expression.ExpressionCheckers
import org.jetbrains.kotlin.fir.analysis.checkers.type.ComposedTypeCheckers
import org.jetbrains.kotlin.fir.analysis.checkers.type.TypeCheckers
import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension
@RequiresOptIn
@@ -26,6 +28,9 @@ class CheckersComponent : FirSessionComponent {
val expressionCheckers: ExpressionCheckers get() = _expressionCheckers
private val _expressionCheckers = ComposedExpressionCheckers()
val typeCheckers: TypeCheckers get() = _typeCheckers
private val _typeCheckers = ComposedTypeCheckers()
@SessionConfiguration
@OptIn(CheckersComponentInternal::class)
fun register(checkers: DeclarationCheckers) {
@@ -38,10 +43,17 @@ class CheckersComponent : FirSessionComponent {
_expressionCheckers.register(checkers)
}
@SessionConfiguration
@OptIn(CheckersComponentInternal::class)
fun register(checkers: TypeCheckers) {
_typeCheckers.register(checkers)
}
@SessionConfiguration
fun register(checkers: FirAdditionalCheckersExtension) {
register(checkers.declarationCheckers)
register(checkers.expressionCheckers)
register(checkers.typeCheckers)
}
}
@@ -1,59 +0,0 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
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.declarations.FirFile
import org.jetbrains.kotlin.fir.types.FirFunctionTypeRef
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
import org.jetbrains.kotlin.fir.types.FirTypeRefWithNullability
import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitorVoid
import org.jetbrains.kotlin.lexer.KtTokens
object FirSuspendOnInvalidTypeChecker : FirFileChecker() {
override fun check(declaration: FirFile, context: CheckerContext, reporter: DiagnosticReporter) {
// Traverse the entire file to look for source type refs (i.e., Fir(Dynamic|User|Function)TypeRef).
declaration.accept(object : FirDefaultVisitorVoid() {
override fun visitElement(element: FirElement) {
if (element.source?.kind is FirFakeSourceElementKind) return
element.acceptChildren(this)
}
override fun visitResolvedTypeRef(resolvedTypeRef: FirResolvedTypeRef) {
// We will only get to the source type refs by visiting delegatedTypeRef.
super.visitResolvedTypeRef(resolvedTypeRef)
resolvedTypeRef.delegatedTypeRef?.accept(this)
}
override fun visitTypeRefWithNullability(typeRefWithNullability: FirTypeRefWithNullability) {
// `suspend` is invalid for non-function types (i.e., FirDynamicTypeRef or FirUserTypeRef).
//
// It is also invalid for nullable function types, e.g., `suspend (() -> Int)?`.
// The correct way to denote a nullable suspend function type is `(suspend () -> Int)?`.
// In both cases, the source for the FirFunctionTypeRef is the TYPE_REFERENCE element.
// In the invalid case, the modifier list is in the source TYPE_REFERENCE element.
// But in the correct case, the modifier list is in the child NULLABLE_TYPE element.
// Therefore, if the FirFunctionTypeRef is marked nullable, it is invalid to HAVE the `suspend` modifier
// on the source element, even though it seems counter-intuitive.
val suspendModifier = typeRefWithNullability.getModifier(KtTokens.SUSPEND_KEYWORD) ?: return
if (typeRefWithNullability !is FirFunctionTypeRef || typeRefWithNullability.isMarkedNullable) {
reporter.reportOn(
suspendModifier.source,
FirErrors.WRONG_MODIFIER_TARGET,
suspendModifier.token,
"non-functional type",
context
)
}
}
})
}
}
@@ -0,0 +1,47 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.fir.analysis.checkers.type
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.getModifier
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.types.FirFunctionTypeRef
import org.jetbrains.kotlin.fir.types.FirTypeRef
import org.jetbrains.kotlin.fir.types.FirTypeRefWithNullability
import org.jetbrains.kotlin.lexer.KtTokens
object FirSuspendModifierChecker : FirTypeRefChecker() {
override fun check(typeRef: FirTypeRef, context: CheckerContext, reporter: DiagnosticReporter) {
// We are only interested in source type refs (i.e., Fir(Dynamic|User|Function)TypeRef).
if (typeRef !is FirTypeRefWithNullability) return
val suspendModifier = typeRef.getModifier(KtTokens.SUSPEND_KEYWORD) ?: return
// `suspend` is invalid for non-function types (i.e., FirDynamicTypeRef or FirUserTypeRef).
//
// It is also invalid for nullable function types, e.g., `suspend (() -> Int)?`.
// The correct way to denote a nullable suspend function type is `(suspend () -> Int)?`.
// (To clarify: You can "mark nullable" a suspend function type, but you cannot "mark suspend" a nullable function type.)
//
// In both invalid and correct cases, the source for the FirFunctionTypeRef is the TYPE_REFERENCE element.
// In the invalid case, the `suspend` modifier is in the source TYPE_REFERENCE element.
// But in the correct case, the `suspend` modifier is in the child NULLABLE_TYPE element, i.e., the source TYPE_REFERENCE element
// will not have the `suspend` modifier.
//
// In both cases, the FirFunctionTypeRef is marked nullable. But it is invalid to have the `suspend` modifier on the source element.
if (typeRef !is FirFunctionTypeRef || typeRef.isMarkedNullable) {
reporter.reportOn(
suspendModifier.source,
FirErrors.WRONG_MODIFIER_TARGET,
suspendModifier.token,
"non-functional type",
context
)
}
}
}
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.fir.analysis.checkers.type
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.types.FirTypeRef
abstract class FirTypeChecker<in T : FirTypeRef> {
/**
* [FirTypeChecker] should only be used when the check can be performed independent of the context of the type refs. That is,
* you should NOT be examining containing declarations, qualified accesses, etc. when writing a FirTypeChecker.
*
* If the check is dependent on context, or if it is specific to type refs in a certain kind of declaration or expression,
* please write a [org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirDeclarationChecker] or
* [org.jetbrains.kotlin.fir.analysis.checkers.expression.FirExpressionChecker] instead.
*/
abstract fun check(typeRef: T, context: CheckerContext, reporter: DiagnosticReporter)
}
@@ -72,6 +72,7 @@ fun AbstractDiagnosticCollector.registerAllComponents() {
initializeComponents(
DeclarationCheckersDiagnosticComponent(this),
ExpressionCheckersDiagnosticComponent(this),
TypeCheckersDiagnosticComponent(this),
ErrorNodeDiagnosticCollectorComponent(this),
ControlFlowAnalysisDiagnosticComponent(this),
)
@@ -0,0 +1,64 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.fir.analysis.collectors.components
import org.jetbrains.kotlin.fir.analysis.CheckersComponentInternal
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.type.FirTypeChecker
import org.jetbrains.kotlin.fir.analysis.checkers.type.TypeCheckers
import org.jetbrains.kotlin.fir.analysis.checkersComponent
import org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.types.*
@OptIn(CheckersComponentInternal::class)
class TypeCheckersDiagnosticComponent(
collector: AbstractDiagnosticCollector,
private val checkers: TypeCheckers = collector.session.checkersComponent.typeCheckers,
) : AbstractDiagnosticCollectorComponent(collector) {
override fun visitDynamicTypeRef(dynamicTypeRef: FirDynamicTypeRef, data: CheckerContext) {
checkers.allTypeRefCheckers.check(dynamicTypeRef, data, reporter)
}
override fun visitFunctionTypeRef(functionTypeRef: FirFunctionTypeRef, data: CheckerContext) {
checkers.allTypeRefCheckers.check(functionTypeRef, data, reporter)
}
override fun visitUserTypeRef(userTypeRef: FirUserTypeRef, data: CheckerContext) {
checkers.allTypeRefCheckers.check(userTypeRef, data, reporter)
}
override fun visitResolvedTypeRef(resolvedTypeRef: FirResolvedTypeRef, data: CheckerContext) {
checkers.allTypeRefCheckers.check(resolvedTypeRef, data, reporter)
}
override fun visitErrorTypeRef(errorTypeRef: FirErrorTypeRef, data: CheckerContext) {
checkers.allTypeRefCheckers.check(errorTypeRef, data, reporter)
}
override fun visitTypeRefWithNullability(typeRefWithNullability: FirTypeRefWithNullability, data: CheckerContext) {
checkers.allTypeRefCheckers.check(typeRefWithNullability, data, reporter)
}
override fun visitImplicitTypeRef(implicitTypeRef: FirImplicitTypeRef, data: CheckerContext) {
checkers.allTypeRefCheckers.check(implicitTypeRef, data, reporter)
}
override fun visitTypeRef(typeRef: FirTypeRef, data: CheckerContext) {
checkers.allTypeRefCheckers.check(typeRef, data, reporter)
}
private fun <T : FirTypeRef> Collection<FirTypeChecker<T>>.check(
typeRef: T,
context: CheckerContext,
reporter: DiagnosticReporter
) {
for (checker in this) {
checker.check(typeRef, context, reporter)
}
}
}
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.fir.analysis.extensions
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers
import org.jetbrains.kotlin.fir.analysis.checkers.expression.ExpressionCheckers
import org.jetbrains.kotlin.fir.analysis.checkers.type.TypeCheckers
import org.jetbrains.kotlin.fir.extensions.AbstractFirAdditionalCheckersExtension
import org.jetbrains.kotlin.fir.extensions.FirExtensionPointName
import org.jetbrains.kotlin.fir.extensions.FirExtensionService
@@ -19,6 +20,7 @@ abstract class FirAdditionalCheckersExtension(session: FirSession) : AbstractFir
open val declarationCheckers: DeclarationCheckers = DeclarationCheckers.EMPTY
open val expressionCheckers: ExpressionCheckers = ExpressionCheckers.EMPTY
open val typeCheckers: TypeCheckers = TypeCheckers.EMPTY
final override val name: FirExtensionPointName
get() = NAME
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.fir.session.FirSessionFactory
fun FirSessionFactory.FirSessionConfigurator.registerCommonCheckers() {
useCheckers(CommonDeclarationCheckers)
useCheckers(CommonExpressionCheckers)
useCheckers(CommonTypeCheckers)
}
fun FirSessionFactory.FirSessionConfigurator.registerExtendedCommonCheckers() {
@@ -95,7 +95,6 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
FirKClassWithIncorrectTypeArgumentChecker,
FirTopLevelFunctionsChecker,
FirTopLevelPropertiesChecker,
FirSuspendOnInvalidTypeChecker,
)
override val controlFlowAnalyserCheckers: Set<FirControlFlowChecker>
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.fir.checkers
import org.jetbrains.kotlin.fir.analysis.cfa.AbstractFirPropertyInitializationChecker
import org.jetbrains.kotlin.fir.analysis.cfa.FirCallsEffectAnalyzer
import org.jetbrains.kotlin.fir.analysis.cfa.FirPropertyInitializationAnalyzer
import org.jetbrains.kotlin.fir.analysis.cfa.FirReturnsImpliesAnalyzer
import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.*
import org.jetbrains.kotlin.fir.analysis.checkers.type.FirSuspendModifierChecker
import org.jetbrains.kotlin.fir.analysis.checkers.type.FirTypeRefChecker
import org.jetbrains.kotlin.fir.analysis.checkers.type.TypeCheckers
object CommonTypeCheckers : TypeCheckers() {
override val typeRefCheckers: Set<FirTypeRefChecker> = setOf(
FirSuspendModifierChecker
)
}
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.fir.PrivateSessionConstructor
import org.jetbrains.kotlin.fir.SessionConfiguration
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers
import org.jetbrains.kotlin.fir.analysis.checkers.expression.ExpressionCheckers
import org.jetbrains.kotlin.fir.analysis.checkers.type.TypeCheckers
import org.jetbrains.kotlin.fir.analysis.checkersComponent
import org.jetbrains.kotlin.fir.analysis.extensions.additionalCheckers
import org.jetbrains.kotlin.fir.checkers.registerCommonCheckers
@@ -53,6 +54,10 @@ object FirSessionFactory {
session.checkersComponent.register(checkers)
}
fun useCheckers(checkers: TypeCheckers) {
session.checkersComponent.register(checkers)
}
@SessionConfiguration
fun configure() {
session.extensionService.registerExtensions(registeredExtensions.reduce(BunchOfRegisteredExtensions::plus))
@@ -22,7 +22,8 @@ typealias Test15 = (@A() suspend () -> Unit)?
typealias Test16 = (@A suspend () -> Unit)?
typealias Test17 = @A suspend RS.() -> Unit
typealias Test18 = (suspend () -> Unit)?
typealias Test19 = (@A({ val x: <!WRONG_MODIFIER_TARGET!>suspend<!> String? = null; "" }()) suspend () -> Unit)?
typealias Test19 = (@A({ val x: <!WRONG_MODIFIER_TARGET, WRONG_MODIFIER_TARGET!>suspend<!> String? = null; "" }()) suspend () -> Unit)?
typealias Test20 = (@A("".let { val x: <!WRONG_MODIFIER_TARGET, WRONG_MODIFIER_TARGET!>suspend<!> String? = null; it }) suspend () -> Unit)?
interface Supertype1 : suspend () -> Unit {
@@ -23,6 +23,7 @@ typealias Test16 = (@A suspend () -> Unit)?
typealias Test17 = @A suspend RS.() -> Unit
typealias Test18 = (suspend () -> Unit)?
typealias Test19 = (@A(<!ANNOTATION_ARGUMENT_MUST_BE_CONST!>{ val x: <!WRONG_MODIFIER_TARGET, WRONG_MODIFIER_TARGET!>suspend<!> String? = null; "" }()<!>) suspend () -> Unit)?
typealias Test20 = (@A(<!ANNOTATION_ARGUMENT_MUST_BE_CONST!>"".let { val x: <!WRONG_MODIFIER_TARGET, WRONG_MODIFIER_TARGET!>suspend<!> String? = null; it }<!>) suspend () -> Unit)?
interface Supertype1 : <!SUPERTYPE_IS_SUSPEND_FUNCTION_TYPE!>suspend () -> Unit<!> {
@@ -47,6 +47,7 @@ public typealias Test17 = (@A suspend RS.() -> kotlin.Unit)
public typealias Test18 = (suspend () -> kotlin.Unit)?
public typealias Test19 = (@A suspend () -> kotlin.Unit)?
public typealias Test2 = suspend kotlin.Int.() -> kotlin.Unit
public typealias Test20 = (@A suspend () -> kotlin.Unit)?
public typealias Test3 = () -> kotlin.Unit
public typealias Test4 = Action
public typealias Test5 = kotlin.collections.List<suspend () -> kotlin.Unit>
@@ -6,13 +6,13 @@
package org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.fir.FirPsiSourceElement
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.SessionConfiguration
import org.jetbrains.kotlin.fir.analysis.CheckersComponent
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers
import org.jetbrains.kotlin.fir.analysis.checkers.expression.ExpressionCheckers
import org.jetbrains.kotlin.fir.analysis.checkers.type.TypeCheckers
import org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector
import org.jetbrains.kotlin.fir.analysis.collectors.components.*
import org.jetbrains.kotlin.fir.analysis.diagnostics.*
@@ -27,11 +27,13 @@ internal abstract class AbstractFirIdeDiagnosticsCollector(
init {
val declarationCheckers = CheckersFactory.createDeclarationCheckers(useExtendedCheckers)
val expressionCheckers = CheckersFactory.createExpressionCheckers(useExtendedCheckers)
val typeCheckers = CheckersFactory.createTypeCheckers(useExtendedCheckers)
@Suppress("LeakingThis")
initializeComponents(
DeclarationCheckersDiagnosticComponent(this, declarationCheckers),
ExpressionCheckersDiagnosticComponent(this, expressionCheckers),
TypeCheckersDiagnosticComponent(this, typeCheckers),
ErrorNodeDiagnosticCollectorComponent(this),
ControlFlowAnalysisDiagnosticComponent(this, declarationCheckers),
)
@@ -113,6 +115,8 @@ private object CheckersFactory {
fun createExpressionCheckers(useExtendedCheckers: Boolean): ExpressionCheckers =
if (useExtendedCheckers) ExtendedExpressionCheckers else CommonExpressionCheckers
fun createTypeCheckers(useExtendedCheckers: Boolean): TypeCheckers = CommonTypeCheckers
// TODO hack to have all checkers present in DeclarationCheckers.memberDeclarationCheckers and similar
// If use ExtendedDeclarationCheckers directly when DeclarationCheckers.memberDeclarationCheckers will not contain basicDeclarationCheckers
@OptIn(SessionConfiguration::class)