[FIR] Support 4 diagnostics for pairs of modifiers

In particular, this commit includes:
* Attempt to abstract access to FirSourceElement via FirModifier
* Add more visit functions to DeclarationCheckersDiagnosticComponent
* Add messages+factories for 4 modifier-related errors and warnings
* Introduce FirModifierChecker
This commit is contained in:
FenstonSingel
2020-03-27 02:10:26 +03:00
committed by Mikhail Glukhikh
parent 311a91af79
commit b7d8e879a6
42 changed files with 656 additions and 122 deletions
@@ -9,7 +9,9 @@ import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
object DeclarationCheckers {
val DECLARATIONS: List<FirDeclarationChecker<FirDeclaration>> = listOf()
val DECLARATIONS: List<FirDeclarationChecker<FirDeclaration>> = listOf(
FirModifierChecker
)
val MEMBER_DECLARATIONS: List<FirDeclarationChecker<FirMemberDeclaration>> = DECLARATIONS + listOf(
FirInfixFunctionDeclarationChecker
)
@@ -0,0 +1,199 @@
/*
* Copyright 2010-2020 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.KtNodeTypes
import org.jetbrains.kotlin.fir.*
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.declarations.*
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens.*
object FirModifierChecker : FirDeclarationChecker<FirDeclaration>() {
private enum class CompatibilityType {
COMPATIBLE,
COMPATIBLE_FOR_CLASSES, // for functions and properties: error
REDUNDANT_1_TO_2, // first is redundant to second: warning
REDUNDANT_2_TO_1, // second is redundant to first: warning
DEPRECATED, // pair is deprecated and will soon become incompatible: warning
REPEATED, // first and second are the same: error
INCOMPATIBLE, // pair is incompatible: error
}
// first modifier in pair should also be first in spelling order and declaration's modifier list
private val compatibilityTypeMap = hashMapOf<Pair<KtModifierKeywordToken, KtModifierKeywordToken>, CompatibilityType>()
private fun recordCompatibilityType(compatibilityType: CompatibilityType, vararg list: KtModifierKeywordToken) {
for (firstKeyword in list) {
for (secondKeyword in list) {
if (firstKeyword != secondKeyword) {
compatibilityTypeMap[Pair(firstKeyword, secondKeyword)] = compatibilityType
}
}
}
}
private fun recordPairsCompatibleForClasses(vararg list: KtModifierKeywordToken) {
recordCompatibilityType(CompatibilityType.COMPATIBLE_FOR_CLASSES, *list)
}
private fun recordDeprecatedPairs(vararg list: KtModifierKeywordToken) {
recordCompatibilityType(CompatibilityType.DEPRECATED, *list)
}
private fun recordIncompatiblePairs(vararg list: KtModifierKeywordToken) {
recordCompatibilityType(CompatibilityType.INCOMPATIBLE, *list)
}
// note that order matters: the first argument is redundant to the second, not the other way around
private fun recordRedundantPairs(redundantKeyword: KtModifierKeywordToken, sufficientKeyword: KtModifierKeywordToken) {
compatibilityTypeMap[Pair(redundantKeyword, sufficientKeyword)] = CompatibilityType.REDUNDANT_1_TO_2
compatibilityTypeMap[Pair(sufficientKeyword, redundantKeyword)] = CompatibilityType.REDUNDANT_2_TO_1
}
// building the compatibility type mapping
init {
recordIncompatiblePairs(IN_KEYWORD, OUT_KEYWORD) // Variance
recordIncompatiblePairs(PRIVATE_KEYWORD, PROTECTED_KEYWORD, PUBLIC_KEYWORD, INTERNAL_KEYWORD) // Visibilities
recordIncompatiblePairs(HEADER_KEYWORD, EXPECT_KEYWORD, IMPL_KEYWORD, ACTUAL_KEYWORD)
recordIncompatiblePairs(FINAL_KEYWORD, ABSTRACT_KEYWORD)
recordIncompatiblePairs(FINAL_KEYWORD, OPEN_KEYWORD, SEALED_KEYWORD)
recordIncompatiblePairs(CROSSINLINE_KEYWORD, NOINLINE_KEYWORD)
recordIncompatiblePairs(DATA_KEYWORD, OPEN_KEYWORD)
recordIncompatiblePairs(DATA_KEYWORD, INNER_KEYWORD)
recordIncompatiblePairs(DATA_KEYWORD, ABSTRACT_KEYWORD)
recordIncompatiblePairs(DATA_KEYWORD, SEALED_KEYWORD)
recordIncompatiblePairs(DATA_KEYWORD, INLINE_KEYWORD)
recordIncompatiblePairs(CONST_KEYWORD, ABSTRACT_KEYWORD)
recordIncompatiblePairs(CONST_KEYWORD, OPEN_KEYWORD)
recordIncompatiblePairs(CONST_KEYWORD, OVERRIDE_KEYWORD)
recordIncompatiblePairs(PRIVATE_KEYWORD, OVERRIDE_KEYWORD)
recordPairsCompatibleForClasses(PRIVATE_KEYWORD, OPEN_KEYWORD)
recordPairsCompatibleForClasses(PRIVATE_KEYWORD, ABSTRACT_KEYWORD)
// 1. subclasses contained inside a sealed class can not be instantiated, because their constructors needs
// an instance of an outer sealed (effectively abstract) class
// 2. subclasses of a non-top-level sealed class must be declared inside the class
// (see the KEEP https://github.com/Kotlin/KEEP/blob/master/proposals/sealed-class-inheritance.md)
recordIncompatiblePairs(SEALED_KEYWORD, INNER_KEYWORD)
recordRedundantPairs(OPEN_KEYWORD, ABSTRACT_KEYWORD)
recordRedundantPairs(ABSTRACT_KEYWORD, SEALED_KEYWORD)
}
private fun deduceCompatibilityType(firstKeyword: KtModifierKeywordToken, secondKeyword: KtModifierKeywordToken): CompatibilityType =
if (firstKeyword == secondKeyword) {
CompatibilityType.REPEATED
} else {
compatibilityTypeMap[Pair(firstKeyword, secondKeyword)] ?: CompatibilityType.COMPATIBLE
}
private fun checkCompatibilityType(
firstModifier: FirModifier<*>,
secondModifier: FirModifier<*>,
reporter: DiagnosticReporter,
reportedNodes: MutableSet<FirModifier<*>>,
owner: FirDeclaration?
) {
val firstToken = firstModifier.token
val secondToken = secondModifier.token
when (val compatibilityType = deduceCompatibilityType(firstToken, secondToken)) {
CompatibilityType.COMPATIBLE -> {}
CompatibilityType.REPEATED ->
if (reportedNodes.add(secondModifier)) reporter.reportRepeatedModifier(secondModifier, secondToken)
CompatibilityType.REDUNDANT_2_TO_1 ->
reporter.reportRedundantModifier(secondModifier, secondToken, firstToken)
CompatibilityType.REDUNDANT_1_TO_2 ->
reporter.reportRedundantModifier(firstModifier, firstToken, secondToken)
CompatibilityType.DEPRECATED -> {
reporter.reportDeprecatedModifierPair(firstModifier, firstToken, secondToken)
reporter.reportDeprecatedModifierPair(secondModifier, secondToken, firstToken)
}
CompatibilityType.INCOMPATIBLE, CompatibilityType.COMPATIBLE_FOR_CLASSES -> {
if (compatibilityType == CompatibilityType.COMPATIBLE_FOR_CLASSES && owner is FirClass<*>) {
return
}
if (reportedNodes.add(firstModifier)) reporter.reportIncompatibleModifiers(firstModifier, firstToken, secondToken)
if (reportedNodes.add(secondModifier)) reporter.reportIncompatibleModifiers(secondModifier, secondToken, firstToken)
}
}
}
private fun checkModifiers(
list: FirModifierList,
owner: FirDeclaration,
reporter: DiagnosticReporter
) {
// general strategy: report no more than one error and any number of warnings
// therefore, a track of nodes with already reported errors should be kept
val reportedNodes = hashSetOf<FirModifier<*>>()
val modifiers = list.modifiers
for (secondModifier in modifiers) {
for (firstModifier in modifiers) {
if (firstModifier == secondModifier) {
break
}
checkCompatibilityType(firstModifier, secondModifier, reporter, reportedNodes, owner)
}
}
}
private fun isDeclarationMappedToSourceCorrectly(declaration: FirDeclaration, source: FirSourceElement): Boolean =
when (source.elementType) {
KtNodeTypes.CLASS -> declaration is FirClass<*>
KtNodeTypes.OBJECT_DECLARATION -> declaration is FirClass<*>
KtNodeTypes.PROPERTY -> declaration is FirProperty
KtNodeTypes.VALUE_PARAMETER -> declaration is FirValueParameter
// TODO more FIR-PSI relations possibly have to be added
else -> true
}
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
val source = declaration.source ?: return
if (!isDeclarationMappedToSourceCorrectly(declaration, source)) return
if (context.containingDeclarations.last() is FirDefaultPropertyAccessor) return
val modifierList = source.getModifierList()
modifierList?.let { checkModifiers(it, declaration, reporter) }
}
private fun DiagnosticReporter.reportRepeatedModifier(
modifier: FirModifier<*>, keyword: KtModifierKeywordToken
) {
val source = modifier.source
source?.let { report(FirErrors.REPEATED_MODIFIER.on(it, keyword)) }
}
private fun DiagnosticReporter.reportRedundantModifier(
modifier: FirModifier<*>, firstKeyword: KtModifierKeywordToken, secondKeyword: KtModifierKeywordToken
) {
val source = modifier.source
source?.let { report(FirErrors.REDUNDANT_MODIFIER.on(it, firstKeyword, secondKeyword)) }
}
private fun DiagnosticReporter.reportDeprecatedModifierPair(
modifier: FirModifier<*>, firstKeyword: KtModifierKeywordToken, secondKeyword: KtModifierKeywordToken
) {
val source = modifier.source
source?.let { report(FirErrors.DEPRECATED_MODIFIER_PAIR.on(it, firstKeyword, secondKeyword)) }
}
private fun DiagnosticReporter.reportIncompatibleModifiers(
modifier: FirModifier<*>, firstKeyword: KtModifierKeywordToken, secondKeyword: KtModifierKeywordToken
) {
val source = modifier.source
source?.let { report(FirErrors.INCOMPATIBLE_MODIFIERS.on(it, firstKeyword, secondKeyword)) }
}
}
@@ -21,6 +21,10 @@ class DeclarationCheckersDiagnosticComponent(collector: AbstractDiagnosticCollec
runCheck { DeclarationCheckers.MEMBER_DECLARATIONS.check(regularClass, data, it) }
}
override fun visitSealedClass(sealedClass: FirSealedClass, data: CheckerContext) {
runCheck { DeclarationCheckers.MEMBER_DECLARATIONS.check(sealedClass, data, it) }
}
override fun visitSimpleFunction(simpleFunction: FirSimpleFunction, data: CheckerContext) {
runCheck { DeclarationCheckers.MEMBER_DECLARATIONS.check(simpleFunction, data, it) }
}
@@ -45,11 +49,19 @@ class DeclarationCheckersDiagnosticComponent(collector: AbstractDiagnosticCollec
runCheck { DeclarationCheckers.DECLARATIONS.check(valueParameter, data, it) }
}
override fun visitTypeParameter(typeParameter: FirTypeParameter, data: CheckerContext) {
runCheck { DeclarationCheckers.DECLARATIONS.check(typeParameter, data, it) }
}
override fun visitEnumEntry(enumEntry: FirEnumEntry, data: CheckerContext) {
runCheck { DeclarationCheckers.DECLARATIONS.check(enumEntry, data, it) }
}
private fun <D : FirDeclaration> List<FirDeclarationChecker<D>>.check(declaration: D, context: CheckerContext, reporter: DiagnosticReporter) {
private fun <D : FirDeclaration> List<FirDeclarationChecker<D>>.check(
declaration: D,
context: CheckerContext,
reporter: DiagnosticReporter
) {
for (checker in this) {
checker.check(declaration, context, reporter)
}
@@ -9,18 +9,23 @@ import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.NULLABLE_STRING
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.SYMBOLS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.TO_STRING
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.AMBIGUITY
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ASSIGN_OPERATOR_AMBIGUITY
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DEPRECATED_MODIFIER_PAIR
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DESERIALIZATION_ERROR
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ENUM_AS_SUPERTYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ERROR_FROM_JAVA_RESOLUTION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ILLEGAL_CONST_EXPRESSION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INAPPLICABLE_CANDIDATE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INCOMPATIBLE_MODIFIERS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INFERENCE_ERROR
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_SUPERTYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.OTHER_ERROR
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.RECURSION_IN_IMPLICIT_TYPES
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.RECURSION_IN_SUPERTYPES
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.REDUNDANT_MODIFIER
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.REPEATED_MODIFIER
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SUPER_IS_NOT_AN_EXPRESSION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.SYNTAX_ERROR
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.TYPE_PARAMETER_AS_SUPERTYPE
@@ -52,6 +57,11 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
map.put(ERROR_FROM_JAVA_RESOLUTION, "Java resolution error")
map.put(OTHER_ERROR, "Unknown (other) error")
map.put(SUPER_IS_NOT_AN_EXPRESSION, "Super cannot be a callee")
map.put(REPEATED_MODIFIER, "Repeated ''{0}''", TO_STRING)
map.put(REDUNDANT_MODIFIER, "Modifier ''{0}'' is redundant because ''{1}'' is present", TO_STRING, TO_STRING)
map.put(DEPRECATED_MODIFIER_PAIR, "Modifier ''{0}'' is deprecated in presence of ''{1}''", TO_STRING, TO_STRING)
map.put(INCOMPATIBLE_MODIFIERS, "Modifier ''{0}'' is incompatible with ''{1}''", TO_STRING, TO_STRING)
}
}
}
@@ -22,4 +22,6 @@ object FirDiagnosticRenderers {
}
}
}
val TO_STRING = Renderer<Any> { it.toString() }
}
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
object FirErrors {
val UNRESOLVED_REFERENCE by error1<FirSourceElement, String?>()
@@ -34,4 +35,9 @@ object FirErrors {
val SUPER_IS_NOT_AN_EXPRESSION by error0<FirSourceElement>()
val INAPPLICABLE_INFIX_MODIFIER by existing<FirSourceElement, String>(Errors.INAPPLICABLE_INFIX_MODIFIER)
val REPEATED_MODIFIER by error1<FirSourceElement, KtModifierKeywordToken>()
val REDUNDANT_MODIFIER by error2<FirSourceElement, KtModifierKeywordToken, KtModifierKeywordToken>()
val DEPRECATED_MODIFIER_PAIR by error2<FirSourceElement, KtModifierKeywordToken, KtModifierKeywordToken>()
val INCOMPATIBLE_MODIFIERS by error2<FirSourceElement, KtModifierKeywordToken, KtModifierKeywordToken>()
}