[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:
committed by
Mikhail Glukhikh
parent
311a91af79
commit
b7d8e879a6
+3
-1
@@ -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
|
||||
)
|
||||
|
||||
+199
@@ -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)) }
|
||||
}
|
||||
|
||||
}
|
||||
+13
-1
@@ -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)
|
||||
}
|
||||
|
||||
+10
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -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>()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user