Unbind modifiers checks from psi
Use common code in FE and FIR in modifier checker, refactor
This commit is contained in:
committed by
TeamCityServer
parent
ed0e3b0fed
commit
e85940a1ac
+8
-8
@@ -301,23 +301,23 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
|
||||
val MODIFIERS by object : DiagnosticGroup("Modifiers") {
|
||||
val INAPPLICABLE_INFIX_MODIFIER by error<PsiElement>()
|
||||
val REPEATED_MODIFIER by error<PsiElement> {
|
||||
parameter<KtModifierKeywordToken>("modifier")
|
||||
parameter<String>("modifier")
|
||||
}
|
||||
val REDUNDANT_MODIFIER by error<PsiElement> {
|
||||
parameter<KtModifierKeywordToken>("redundantModifier")
|
||||
parameter<KtModifierKeywordToken>("conflictingModifier")
|
||||
parameter<String>("redundantModifier")
|
||||
parameter<String>("conflictingModifier")
|
||||
}
|
||||
val DEPRECATED_MODIFIER_PAIR by error<PsiElement> {
|
||||
parameter<KtModifierKeywordToken>("deprecatedModifier")
|
||||
parameter<KtModifierKeywordToken>("conflictingModifier")
|
||||
parameter<String>("deprecatedModifier")
|
||||
parameter<String>("conflictingModifier")
|
||||
}
|
||||
val INCOMPATIBLE_MODIFIERS by error<PsiElement> {
|
||||
parameter<KtModifierKeywordToken>("modifier1")
|
||||
parameter<KtModifierKeywordToken>("modifier2")
|
||||
parameter<String>("modifier1")
|
||||
parameter<String>("modifier2")
|
||||
}
|
||||
val REDUNDANT_OPEN_IN_INTERFACE by warning<KtModifierListOwner>(PositioningStrategy.OPEN_MODIFIER)
|
||||
val WRONG_MODIFIER_TARGET by error<PsiElement> {
|
||||
parameter<KtModifierKeywordToken>("modifier")
|
||||
parameter<String>("modifier")
|
||||
parameter<String>("target")
|
||||
}
|
||||
val OPERATOR_MODIFIER_REQUIRED by error<PsiElement> {
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ object FirJvmExternalDeclarationChecker : FirBasicDeclarationChecker() {
|
||||
}
|
||||
val externalModifier = declaration.getModifier(KtTokens.EXTERNAL_KEYWORD)
|
||||
externalModifier?.let {
|
||||
reporter.reportOn(it.source, FirErrors.WRONG_MODIFIER_TARGET, it.token, target, context)
|
||||
reporter.reportOn(it.source, FirErrors.WRONG_MODIFIER_TARGET, it.token.toString(), target, context)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -254,12 +254,12 @@ object FirErrors {
|
||||
|
||||
// Modifiers
|
||||
val INAPPLICABLE_INFIX_MODIFIER by error0<PsiElement>()
|
||||
val REPEATED_MODIFIER by error1<PsiElement, KtModifierKeywordToken>()
|
||||
val REDUNDANT_MODIFIER by error2<PsiElement, KtModifierKeywordToken, KtModifierKeywordToken>()
|
||||
val DEPRECATED_MODIFIER_PAIR by error2<PsiElement, KtModifierKeywordToken, KtModifierKeywordToken>()
|
||||
val INCOMPATIBLE_MODIFIERS by error2<PsiElement, KtModifierKeywordToken, KtModifierKeywordToken>()
|
||||
val REPEATED_MODIFIER by error1<PsiElement, String>()
|
||||
val REDUNDANT_MODIFIER by error2<PsiElement, String, String>()
|
||||
val DEPRECATED_MODIFIER_PAIR by error2<PsiElement, String, String>()
|
||||
val INCOMPATIBLE_MODIFIERS by error2<PsiElement, String, String>()
|
||||
val REDUNDANT_OPEN_IN_INTERFACE by warning0<KtModifierListOwner>(SourceElementPositioningStrategies.OPEN_MODIFIER)
|
||||
val WRONG_MODIFIER_TARGET by error2<PsiElement, KtModifierKeywordToken, String>()
|
||||
val WRONG_MODIFIER_TARGET by error2<PsiElement, String, String>()
|
||||
val OPERATOR_MODIFIER_REQUIRED by error2<PsiElement, FirNamedFunctionSymbol, String>()
|
||||
val INFIX_MODIFIER_REQUIRED by error1<PsiElement, FirNamedFunctionSymbol>()
|
||||
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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 licensedot/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.fir.analysis.checkers
|
||||
|
||||
import org.jetbrains.kotlin.lexer.KtKeywordToken
|
||||
import org.jetbrains.kotlin.lexer.KtTokens.*
|
||||
import org.jetbrains.kotlin.resolve.KeywordType
|
||||
|
||||
fun getKeywordType(modifier: FirModifier<*>): KeywordType {
|
||||
return ktKeywordToKeywordTypeMap[modifier.token]!!
|
||||
}
|
||||
|
||||
private val ktKeywordToKeywordTypeMap: Map<KtKeywordToken, KeywordType> = mapOf(
|
||||
INNER_KEYWORD to KeywordType.Inner,
|
||||
OVERRIDE_KEYWORD to KeywordType.Override,
|
||||
PUBLIC_KEYWORD to KeywordType.Public,
|
||||
PROTECTED_KEYWORD to KeywordType.Protected,
|
||||
INTERNAL_KEYWORD to KeywordType.Internal,
|
||||
PRIVATE_KEYWORD to KeywordType.Private,
|
||||
COMPANION_KEYWORD to KeywordType.Companion,
|
||||
FINAL_KEYWORD to KeywordType.Final,
|
||||
VARARG_KEYWORD to KeywordType.Vararg,
|
||||
ENUM_KEYWORD to KeywordType.Enum,
|
||||
ABSTRACT_KEYWORD to KeywordType.Abstract,
|
||||
OPEN_KEYWORD to KeywordType.Open,
|
||||
SEALED_KEYWORD to KeywordType.Sealed,
|
||||
IN_KEYWORD to KeywordType.In,
|
||||
OUT_KEYWORD to KeywordType.Out,
|
||||
REIFIED_KEYWORD to KeywordType.Reified,
|
||||
LATEINIT_KEYWORD to KeywordType.Lateinit,
|
||||
DATA_KEYWORD to KeywordType.Data,
|
||||
INLINE_KEYWORD to KeywordType.Inline,
|
||||
NOINLINE_KEYWORD to KeywordType.Noinline,
|
||||
TAILREC_KEYWORD to KeywordType.Tailrec,
|
||||
SUSPEND_KEYWORD to KeywordType.Suspend,
|
||||
EXTERNAL_KEYWORD to KeywordType.External,
|
||||
ANNOTATION_KEYWORD to KeywordType.Annotation,
|
||||
CROSSINLINE_KEYWORD to KeywordType.Crossinline,
|
||||
CONST_KEYWORD to KeywordType.Const,
|
||||
OPERATOR_KEYWORD to KeywordType.Operator,
|
||||
INFIX_KEYWORD to KeywordType.Infix,
|
||||
HEADER_KEYWORD to KeywordType.Header,
|
||||
IMPL_KEYWORD to KeywordType.Impl,
|
||||
EXPECT_KEYWORD to KeywordType.Expect,
|
||||
ACTUAL_KEYWORD to KeywordType.Actual,
|
||||
FUN_KEYWORD to KeywordType.Fun,
|
||||
VALUE_KEYWORD to KeywordType.Value
|
||||
)
|
||||
+1
-1
@@ -29,7 +29,7 @@ object FirConstPropertyChecker : FirPropertyChecker() {
|
||||
if (declaration.isVar) {
|
||||
val constModifier = declaration.getModifier(KtTokens.CONST_KEYWORD)
|
||||
constModifier?.let {
|
||||
reporter.reportOn(it.source, FirErrors.WRONG_MODIFIER_TARGET, it.token, "vars", context)
|
||||
reporter.reportOn(it.source, FirErrors.WRONG_MODIFIER_TARGET, it.token.toString(), "vars", context)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+96
-152
@@ -11,129 +11,37 @@ import org.jetbrains.kotlin.fir.analysis.checkers.FirModifier
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.FirModifierList
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.getModifierList
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.getKeywordType
|
||||
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.*
|
||||
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||
import org.jetbrains.kotlin.lexer.KtTokens.*
|
||||
import org.jetbrains.kotlin.resolve.Compatibility
|
||||
import org.jetbrains.kotlin.resolve.KeywordType
|
||||
import org.jetbrains.kotlin.resolve.compatibility
|
||||
|
||||
object FirModifierChecker : FirBasicDeclarationChecker() {
|
||||
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
if (declaration is FirFile) return
|
||||
|
||||
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
|
||||
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, context) }
|
||||
}
|
||||
|
||||
// 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 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
|
||||
}
|
||||
}
|
||||
|
||||
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?,
|
||||
context: CheckerContext
|
||||
) {
|
||||
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, context)
|
||||
CompatibilityType.REDUNDANT_2_TO_1 ->
|
||||
reporter.reportRedundantModifier(secondModifier, secondToken, firstToken, context)
|
||||
CompatibilityType.REDUNDANT_1_TO_2 ->
|
||||
reporter.reportRedundantModifier(firstModifier, firstToken, secondToken, context)
|
||||
CompatibilityType.DEPRECATED -> {
|
||||
reporter.reportDeprecatedModifierPair(firstModifier, firstToken, secondToken, context)
|
||||
reporter.reportDeprecatedModifierPair(secondModifier, secondToken, firstToken, context)
|
||||
}
|
||||
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, context)
|
||||
if (reportedNodes.add(secondModifier)) reporter.reportIncompatibleModifiers(secondModifier, secondToken, firstToken, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkModifiers(
|
||||
list: FirModifierList,
|
||||
@@ -156,48 +64,84 @@ object FirModifierChecker : FirBasicDeclarationChecker() {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
private fun checkCompatibilityType(
|
||||
firstModifier: FirModifier<*>,
|
||||
secondModifier: FirModifier<*>,
|
||||
reporter: DiagnosticReporter,
|
||||
reportedNodes: MutableSet<FirModifier<*>>,
|
||||
owner: FirDeclaration?,
|
||||
context: CheckerContext
|
||||
) {
|
||||
val firstModifierType = getKeywordType(firstModifier)
|
||||
val secondModifierType = getKeywordType(secondModifier)
|
||||
when (val compatibilityType = compatibility(firstModifierType, secondModifierType)) {
|
||||
Compatibility.COMPATIBLE -> {
|
||||
}
|
||||
Compatibility.REPEATED ->
|
||||
if (reportedNodes.add(secondModifier)) {
|
||||
reporter.reportOn(secondModifier.source, FirErrors.REPEATED_MODIFIER, secondModifierType.render(), context)
|
||||
}
|
||||
Compatibility.REDUNDANT -> {
|
||||
reporter.reportOn(
|
||||
secondModifier.source,
|
||||
FirErrors.REDUNDANT_MODIFIER,
|
||||
secondModifierType.render(),
|
||||
firstModifierType.render(),
|
||||
context
|
||||
)
|
||||
}
|
||||
Compatibility.REVERSE_REDUNDANT -> {
|
||||
reporter.reportOn(
|
||||
firstModifier.source,
|
||||
FirErrors.REDUNDANT_MODIFIER,
|
||||
firstModifierType.render(),
|
||||
secondModifierType.render(),
|
||||
context
|
||||
)
|
||||
}
|
||||
Compatibility.DEPRECATED -> {
|
||||
reporter.reportOn(
|
||||
firstModifier.source,
|
||||
FirErrors.DEPRECATED_MODIFIER_PAIR,
|
||||
firstModifierType.render(),
|
||||
secondModifierType.render(),
|
||||
context
|
||||
)
|
||||
reporter.reportOn(
|
||||
secondModifier.source,
|
||||
FirErrors.DEPRECATED_MODIFIER_PAIR,
|
||||
secondModifierType.render(),
|
||||
firstModifierType.render(),
|
||||
context
|
||||
)
|
||||
}
|
||||
Compatibility.INCOMPATIBLE, Compatibility.COMPATIBLE_FOR_CLASSES_ONLY -> {
|
||||
if (compatibilityType == Compatibility.COMPATIBLE_FOR_CLASSES_ONLY && owner is FirClass) {
|
||||
return
|
||||
}
|
||||
if (reportedNodes.add(firstModifier)) {
|
||||
reporter.reportOn(
|
||||
firstModifier.source,
|
||||
FirErrors.INCOMPATIBLE_MODIFIERS,
|
||||
firstModifierType.render(),
|
||||
secondModifierType.render(),
|
||||
context
|
||||
)
|
||||
}
|
||||
if (reportedNodes.add(secondModifier)) {
|
||||
reporter.reportOn(
|
||||
secondModifier.source,
|
||||
FirErrors.INCOMPATIBLE_MODIFIERS,
|
||||
secondModifierType.render(),
|
||||
firstModifierType.render(),
|
||||
context
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
if (declaration is FirFile) return
|
||||
|
||||
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, context) }
|
||||
}
|
||||
|
||||
private fun DiagnosticReporter.reportRepeatedModifier(
|
||||
modifier: FirModifier<*>, keyword: KtModifierKeywordToken, context: CheckerContext
|
||||
) {
|
||||
reportOn(modifier.source, FirErrors.REPEATED_MODIFIER, keyword, context)
|
||||
}
|
||||
|
||||
private fun DiagnosticReporter.reportRedundantModifier(
|
||||
modifier: FirModifier<*>, firstKeyword: KtModifierKeywordToken, secondKeyword: KtModifierKeywordToken, context: CheckerContext
|
||||
) {
|
||||
reportOn(modifier.source, FirErrors.REDUNDANT_MODIFIER, firstKeyword, secondKeyword, context)
|
||||
}
|
||||
|
||||
private fun DiagnosticReporter.reportDeprecatedModifierPair(
|
||||
modifier: FirModifier<*>, firstKeyword: KtModifierKeywordToken, secondKeyword: KtModifierKeywordToken, context: CheckerContext
|
||||
) {
|
||||
reportOn(modifier.source, FirErrors.DEPRECATED_MODIFIER_PAIR, firstKeyword, secondKeyword, context)
|
||||
}
|
||||
|
||||
private fun DiagnosticReporter.reportIncompatibleModifiers(
|
||||
modifier: FirModifier<*>, firstKeyword: KtModifierKeywordToken, secondKeyword: KtModifierKeywordToken, context: CheckerContext
|
||||
) {
|
||||
reportOn(modifier.source, FirErrors.INCOMPATIBLE_MODIFIERS, firstKeyword, secondKeyword, context)
|
||||
private fun KeywordType.render(): String {
|
||||
return this.toString().lowercase()
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ object FirSuspendModifierChecker : FirTypeRefChecker() {
|
||||
reporter.reportOn(
|
||||
suspendModifier.source,
|
||||
FirErrors.WRONG_MODIFIER_TARGET,
|
||||
suspendModifier.token,
|
||||
suspendModifier.token.toString(),
|
||||
"non-functional type",
|
||||
context
|
||||
)
|
||||
|
||||
+5
-5
@@ -725,12 +725,12 @@ class FirDefaultErrorMessages {
|
||||
|
||||
// Modifiers
|
||||
map.put(INAPPLICABLE_INFIX_MODIFIER, "''infix'' modifier is inapplicable on this function")
|
||||
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)
|
||||
map.put(REPEATED_MODIFIER, "Repeated ''{0}''", STRING)
|
||||
map.put(REDUNDANT_MODIFIER, "Modifier ''{0}'' is redundant because ''{1}'' is present", STRING, STRING)
|
||||
map.put(DEPRECATED_MODIFIER_PAIR, "Modifier ''{0}'' is deprecated in presence of ''{1}''", STRING, STRING)
|
||||
map.put(INCOMPATIBLE_MODIFIERS, "Modifier ''{0}'' is incompatible with ''{1}''", STRING, STRING)
|
||||
map.put(REDUNDANT_OPEN_IN_INTERFACE, "Modifier 'open' is redundant for abstract interface members")
|
||||
map.put(WRONG_MODIFIER_TARGET, "Modifier ''{0}'' is not applicable to ''{1}''", TO_STRING, TO_STRING)
|
||||
map.put(WRONG_MODIFIER_TARGET, "Modifier ''{0}'' is not applicable to ''{1}''", STRING, STRING)
|
||||
map.put(INFIX_MODIFIER_REQUIRED, "''infix'' modifier is required on ''{0}''", TO_STRING)
|
||||
|
||||
// Classes and interfaces
|
||||
|
||||
@@ -224,7 +224,7 @@ public interface Errors {
|
||||
DiagnosticFactory2<PsiElement, KtModifierKeywordToken, KtModifierKeywordToken> REDUNDANT_MODIFIER = DiagnosticFactory2.create(WARNING);
|
||||
DiagnosticFactory2<PsiElement, KtModifierKeywordToken, String> WRONG_MODIFIER_TARGET = DiagnosticFactory2.create(ERROR);
|
||||
DiagnosticFactory2<PsiElement, KtModifierKeywordToken, String> DEPRECATED_MODIFIER_FOR_TARGET = DiagnosticFactory2.create(WARNING);
|
||||
DiagnosticFactory2<PsiElement, KtModifierKeywordToken, KtModifierKeywordToken> DEPRECATED_MODIFIER = DiagnosticFactory2.create(WARNING);
|
||||
DiagnosticFactory2<PsiElement, KtModifierKeywordToken, String> DEPRECATED_MODIFIER = DiagnosticFactory2.create(WARNING);
|
||||
DiagnosticFactory2<PsiElement, KtModifierKeywordToken, String> REDUNDANT_MODIFIER_FOR_TARGET = DiagnosticFactory2.create(WARNING);
|
||||
DiagnosticFactory0<KtDeclaration> NO_EXPLICIT_VISIBILITY_IN_API_MODE = DiagnosticFactory0.create(ERROR, DECLARATION_START_TO_NAME);
|
||||
DiagnosticFactory0<KtNamedDeclaration> NO_EXPLICIT_RETURN_TYPE_IN_API_MODE = DiagnosticFactory0.create(ERROR, DECLARATION_NAME);
|
||||
|
||||
@@ -14,252 +14,106 @@ import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
|
||||
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget.*
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.lexer.KtKeywordToken
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||
import org.jetbrains.kotlin.lexer.KtTokens.*
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtDeclarationWithBody
|
||||
import org.jetbrains.kotlin.psi.KtModifierList
|
||||
import org.jetbrains.kotlin.psi.KtModifierListOwner
|
||||
import org.jetbrains.kotlin.resolve.KeywordType.*
|
||||
import org.jetbrains.kotlin.resolve.KeywordType.Annotation
|
||||
import org.jetbrains.kotlin.resolve.calls.checkers.checkCoroutinesFeature
|
||||
import java.util.*
|
||||
|
||||
object ModifierCheckerCore {
|
||||
private enum class Compatibility {
|
||||
// modifier pair is compatible: ok (default)
|
||||
COMPATIBLE,
|
||||
// second is redundant to first: warning
|
||||
REDUNDANT,
|
||||
// first is redundant to second: warning
|
||||
REVERSE_REDUNDANT,
|
||||
// error
|
||||
REPEATED,
|
||||
// pair is deprecated, will become incompatible: warning
|
||||
DEPRECATED,
|
||||
// pair is incompatible: error
|
||||
INCOMPATIBLE,
|
||||
// same but only for functions / properties: error
|
||||
COMPATIBLE_FOR_CLASSES_ONLY
|
||||
}
|
||||
|
||||
private val defaultVisibilityTargets = EnumSet.of(
|
||||
CLASS_ONLY, OBJECT, INTERFACE, ENUM_CLASS, ANNOTATION_CLASS,
|
||||
MEMBER_FUNCTION, TOP_LEVEL_FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER,
|
||||
MEMBER_PROPERTY, TOP_LEVEL_PROPERTY, CONSTRUCTOR, TYPEALIAS
|
||||
private val ktKeywordToKeywordTypeMap: Map<KtKeywordToken, KeywordType> = mapOf(
|
||||
INNER_KEYWORD to Inner,
|
||||
OVERRIDE_KEYWORD to Override,
|
||||
PUBLIC_KEYWORD to Public,
|
||||
PROTECTED_KEYWORD to Protected,
|
||||
INTERNAL_KEYWORD to Internal,
|
||||
PRIVATE_KEYWORD to Private,
|
||||
COMPANION_KEYWORD to KeywordType.Companion,
|
||||
FINAL_KEYWORD to Final,
|
||||
VARARG_KEYWORD to Vararg,
|
||||
ENUM_KEYWORD to KeywordType.Enum,
|
||||
ABSTRACT_KEYWORD to Abstract,
|
||||
OPEN_KEYWORD to Open,
|
||||
SEALED_KEYWORD to Sealed,
|
||||
IN_KEYWORD to In,
|
||||
OUT_KEYWORD to Out,
|
||||
REIFIED_KEYWORD to Reified,
|
||||
LATEINIT_KEYWORD to Lateinit,
|
||||
DATA_KEYWORD to Data,
|
||||
INLINE_KEYWORD to Inline,
|
||||
NOINLINE_KEYWORD to Noinline,
|
||||
TAILREC_KEYWORD to Tailrec,
|
||||
SUSPEND_KEYWORD to Suspend,
|
||||
EXTERNAL_KEYWORD to External,
|
||||
ANNOTATION_KEYWORD to Annotation,
|
||||
CROSSINLINE_KEYWORD to Crossinline,
|
||||
CONST_KEYWORD to Const,
|
||||
OPERATOR_KEYWORD to Operator,
|
||||
INFIX_KEYWORD to Infix,
|
||||
HEADER_KEYWORD to Header,
|
||||
IMPL_KEYWORD to Impl,
|
||||
EXPECT_KEYWORD to Expect,
|
||||
ACTUAL_KEYWORD to Actual,
|
||||
FUN_KEYWORD to Fun,
|
||||
VALUE_KEYWORD to Value
|
||||
)
|
||||
|
||||
val possibleTargetMap = mapOf<KtModifierKeywordToken, Set<KotlinTarget>>(
|
||||
ENUM_KEYWORD to EnumSet.of(ENUM_CLASS),
|
||||
ABSTRACT_KEYWORD to EnumSet.of(CLASS_ONLY, LOCAL_CLASS, INTERFACE, MEMBER_PROPERTY, MEMBER_FUNCTION),
|
||||
OPEN_KEYWORD to EnumSet.of(CLASS_ONLY, LOCAL_CLASS, INTERFACE, MEMBER_PROPERTY, MEMBER_FUNCTION),
|
||||
FINAL_KEYWORD to EnumSet.of(CLASS_ONLY, LOCAL_CLASS, ENUM_CLASS, OBJECT, MEMBER_PROPERTY, MEMBER_FUNCTION),
|
||||
SEALED_KEYWORD to EnumSet.of(CLASS_ONLY, INTERFACE),
|
||||
INNER_KEYWORD to EnumSet.of(CLASS_ONLY),
|
||||
OVERRIDE_KEYWORD to EnumSet.of(MEMBER_PROPERTY, MEMBER_FUNCTION),
|
||||
PRIVATE_KEYWORD to defaultVisibilityTargets,
|
||||
PUBLIC_KEYWORD to defaultVisibilityTargets,
|
||||
INTERNAL_KEYWORD to defaultVisibilityTargets,
|
||||
PROTECTED_KEYWORD to EnumSet.of(
|
||||
CLASS_ONLY, OBJECT, INTERFACE, ENUM_CLASS, ANNOTATION_CLASS,
|
||||
MEMBER_FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, MEMBER_PROPERTY, CONSTRUCTOR, TYPEALIAS
|
||||
),
|
||||
IN_KEYWORD to EnumSet.of(TYPE_PARAMETER, TYPE_PROJECTION),
|
||||
OUT_KEYWORD to EnumSet.of(TYPE_PARAMETER, TYPE_PROJECTION),
|
||||
REIFIED_KEYWORD to EnumSet.of(TYPE_PARAMETER),
|
||||
VARARG_KEYWORD to EnumSet.of(VALUE_PARAMETER, PROPERTY_PARAMETER),
|
||||
COMPANION_KEYWORD to EnumSet.of(OBJECT),
|
||||
LATEINIT_KEYWORD to EnumSet.of(MEMBER_PROPERTY, TOP_LEVEL_PROPERTY, LOCAL_VARIABLE),
|
||||
DATA_KEYWORD to EnumSet.of(CLASS_ONLY, LOCAL_CLASS),
|
||||
INLINE_KEYWORD to EnumSet.of(FUNCTION, PROPERTY, PROPERTY_GETTER, PROPERTY_SETTER, CLASS_ONLY),
|
||||
NOINLINE_KEYWORD to EnumSet.of(VALUE_PARAMETER),
|
||||
TAILREC_KEYWORD to EnumSet.of(FUNCTION),
|
||||
SUSPEND_KEYWORD to EnumSet.of(MEMBER_FUNCTION, TOP_LEVEL_FUNCTION, LOCAL_FUNCTION),
|
||||
EXTERNAL_KEYWORD to EnumSet.of(FUNCTION, PROPERTY, PROPERTY_GETTER, PROPERTY_SETTER, CLASS),
|
||||
ANNOTATION_KEYWORD to EnumSet.of(ANNOTATION_CLASS),
|
||||
CROSSINLINE_KEYWORD to EnumSet.of(VALUE_PARAMETER),
|
||||
CONST_KEYWORD to EnumSet.of(MEMBER_PROPERTY, TOP_LEVEL_PROPERTY),
|
||||
OPERATOR_KEYWORD to EnumSet.of(FUNCTION),
|
||||
INFIX_KEYWORD to EnumSet.of(FUNCTION),
|
||||
HEADER_KEYWORD to EnumSet.of(TOP_LEVEL_FUNCTION, TOP_LEVEL_PROPERTY, CLASS_ONLY, OBJECT, INTERFACE, ENUM_CLASS, ANNOTATION_CLASS),
|
||||
IMPL_KEYWORD to EnumSet.of(
|
||||
TOP_LEVEL_FUNCTION,
|
||||
MEMBER_FUNCTION,
|
||||
TOP_LEVEL_PROPERTY,
|
||||
MEMBER_PROPERTY,
|
||||
CONSTRUCTOR,
|
||||
CLASS_ONLY,
|
||||
OBJECT,
|
||||
INTERFACE,
|
||||
ENUM_CLASS,
|
||||
ANNOTATION_CLASS,
|
||||
TYPEALIAS
|
||||
),
|
||||
EXPECT_KEYWORD to EnumSet.of(TOP_LEVEL_FUNCTION, TOP_LEVEL_PROPERTY, CLASS_ONLY, OBJECT, INTERFACE, ENUM_CLASS, ANNOTATION_CLASS),
|
||||
ACTUAL_KEYWORD to EnumSet.of(
|
||||
TOP_LEVEL_FUNCTION,
|
||||
MEMBER_FUNCTION,
|
||||
TOP_LEVEL_PROPERTY,
|
||||
MEMBER_PROPERTY,
|
||||
CONSTRUCTOR,
|
||||
CLASS_ONLY,
|
||||
OBJECT,
|
||||
INTERFACE,
|
||||
ENUM_CLASS,
|
||||
ANNOTATION_CLASS,
|
||||
TYPEALIAS
|
||||
),
|
||||
FUN_KEYWORD to EnumSet.of(INTERFACE),
|
||||
VALUE_KEYWORD to EnumSet.of(CLASS_ONLY)
|
||||
)
|
||||
|
||||
private val featureDependencies = mapOf(
|
||||
SUSPEND_KEYWORD to listOf(LanguageFeature.Coroutines),
|
||||
INLINE_KEYWORD to listOf(LanguageFeature.InlineProperties, LanguageFeature.InlineClasses),
|
||||
HEADER_KEYWORD to listOf(LanguageFeature.MultiPlatformProjects),
|
||||
IMPL_KEYWORD to listOf(LanguageFeature.MultiPlatformProjects),
|
||||
EXPECT_KEYWORD to listOf(LanguageFeature.MultiPlatformProjects),
|
||||
ACTUAL_KEYWORD to listOf(LanguageFeature.MultiPlatformProjects),
|
||||
LATEINIT_KEYWORD to listOf(LanguageFeature.LateinitTopLevelProperties, LanguageFeature.LateinitLocalVariables),
|
||||
FUN_KEYWORD to listOf(LanguageFeature.FunctionalInterfaceConversion)
|
||||
)
|
||||
|
||||
private val featureDependenciesTargets = mapOf(
|
||||
LanguageFeature.InlineProperties to setOf(PROPERTY, PROPERTY_GETTER, PROPERTY_SETTER),
|
||||
LanguageFeature.LateinitLocalVariables to setOf(LOCAL_VARIABLE),
|
||||
LanguageFeature.LateinitTopLevelProperties to setOf(TOP_LEVEL_PROPERTY),
|
||||
LanguageFeature.InlineClasses to setOf(CLASS_ONLY),
|
||||
LanguageFeature.JvmInlineValueClasses to setOf(CLASS_ONLY),
|
||||
LanguageFeature.FunctionalInterfaceConversion to setOf(INTERFACE)
|
||||
)
|
||||
|
||||
// NOTE: deprecated targets must be possible!
|
||||
private val deprecatedTargetMap = mapOf<KtModifierKeywordToken, Set<KotlinTarget>>()
|
||||
|
||||
private val deprecatedModifierMap = mapOf(
|
||||
HEADER_KEYWORD to EXPECT_KEYWORD,
|
||||
IMPL_KEYWORD to ACTUAL_KEYWORD
|
||||
)
|
||||
|
||||
// NOTE: redundant targets must be possible!
|
||||
private val redundantTargetMap = mapOf<KtModifierKeywordToken, Set<KotlinTarget>>(
|
||||
OPEN_KEYWORD to EnumSet.of(INTERFACE)
|
||||
)
|
||||
|
||||
private val possibleParentTargetPredicateMap = mapOf<KtModifierKeywordToken, TargetAllowedPredicate>(
|
||||
INNER_KEYWORD to or(
|
||||
always(CLASS_ONLY, LOCAL_CLASS, ENUM_CLASS),
|
||||
ifSupported(LanguageFeature.InnerClassInEnumEntryClass, ENUM_ENTRY)
|
||||
),
|
||||
OVERRIDE_KEYWORD to always(CLASS_ONLY, LOCAL_CLASS, OBJECT, OBJECT_LITERAL, INTERFACE, ENUM_CLASS, ENUM_ENTRY),
|
||||
PROTECTED_KEYWORD to always(CLASS_ONLY, LOCAL_CLASS, ENUM_CLASS, COMPANION_OBJECT),
|
||||
INTERNAL_KEYWORD to always(CLASS_ONLY, LOCAL_CLASS, OBJECT, OBJECT_LITERAL, ENUM_CLASS, ENUM_ENTRY, FILE),
|
||||
PRIVATE_KEYWORD to always(CLASS_ONLY, LOCAL_CLASS, OBJECT, OBJECT_LITERAL, INTERFACE, ENUM_CLASS, ENUM_ENTRY, FILE),
|
||||
COMPANION_KEYWORD to always(CLASS_ONLY, INTERFACE, ENUM_CLASS, ANNOTATION_CLASS),
|
||||
FINAL_KEYWORD to always(CLASS_ONLY, LOCAL_CLASS, OBJECT, OBJECT_LITERAL, ENUM_CLASS, ENUM_ENTRY, ANNOTATION_CLASS, FILE),
|
||||
VARARG_KEYWORD to always(CONSTRUCTOR, FUNCTION, CLASS)
|
||||
)
|
||||
|
||||
private val deprecatedParentTargetMap = mapOf<KtModifierKeywordToken, Set<KotlinTarget>>()
|
||||
|
||||
fun isPossibleParentTarget(
|
||||
modifier: KtModifierKeywordToken,
|
||||
parentTarget: KotlinTarget,
|
||||
fun check(
|
||||
listOwner: KtModifierListOwner,
|
||||
trace: BindingTrace,
|
||||
descriptor: DeclarationDescriptor?,
|
||||
languageVersionSettings: LanguageVersionSettings
|
||||
): Boolean {
|
||||
deprecatedParentTargetMap[modifier]?.let {
|
||||
if (parentTarget in it) return false
|
||||
}
|
||||
|
||||
possibleParentTargetPredicateMap[modifier]?.let {
|
||||
return it.isAllowed(parentTarget, languageVersionSettings)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// First modifier in pair should be also first in declaration
|
||||
private val mutualCompatibility = buildCompatibilityMap()
|
||||
|
||||
private fun buildCompatibilityMap(): Map<Pair<KtModifierKeywordToken, KtModifierKeywordToken>, Compatibility> {
|
||||
val result = hashMapOf<Pair<KtModifierKeywordToken, KtModifierKeywordToken>, Compatibility>()
|
||||
// Variance: in + out are incompatible
|
||||
result += incompatibilityRegister(IN_KEYWORD, OUT_KEYWORD)
|
||||
// Visibilities: incompatible
|
||||
result += incompatibilityRegister(PRIVATE_KEYWORD, PROTECTED_KEYWORD, PUBLIC_KEYWORD, INTERNAL_KEYWORD)
|
||||
// Abstract + open + final + sealed: incompatible
|
||||
result += incompatibilityRegister(ABSTRACT_KEYWORD, OPEN_KEYWORD, FINAL_KEYWORD, SEALED_KEYWORD)
|
||||
// data + open, data + inner, data + abstract, data + sealed, data + inline, data + value
|
||||
result += incompatibilityRegister(DATA_KEYWORD, OPEN_KEYWORD)
|
||||
result += incompatibilityRegister(DATA_KEYWORD, INNER_KEYWORD)
|
||||
result += incompatibilityRegister(DATA_KEYWORD, ABSTRACT_KEYWORD)
|
||||
result += incompatibilityRegister(DATA_KEYWORD, SEALED_KEYWORD)
|
||||
result += incompatibilityRegister(DATA_KEYWORD, INLINE_KEYWORD)
|
||||
result += incompatibilityRegister(DATA_KEYWORD, VALUE_KEYWORD)
|
||||
// open is redundant to abstract & override
|
||||
result += redundantRegister(ABSTRACT_KEYWORD, OPEN_KEYWORD)
|
||||
// abstract is redundant to sealed
|
||||
result += redundantRegister(SEALED_KEYWORD, ABSTRACT_KEYWORD)
|
||||
|
||||
// const is incompatible with abstract, open, override
|
||||
result += incompatibilityRegister(CONST_KEYWORD, ABSTRACT_KEYWORD)
|
||||
result += incompatibilityRegister(CONST_KEYWORD, OPEN_KEYWORD)
|
||||
result += incompatibilityRegister(CONST_KEYWORD, OVERRIDE_KEYWORD)
|
||||
|
||||
// private is incompatible with override
|
||||
result += incompatibilityRegister(PRIVATE_KEYWORD, OVERRIDE_KEYWORD)
|
||||
// private is compatible with open / abstract only for classes
|
||||
result += compatibilityForClassesRegister(PRIVATE_KEYWORD, OPEN_KEYWORD)
|
||||
result += compatibilityForClassesRegister(PRIVATE_KEYWORD, ABSTRACT_KEYWORD)
|
||||
|
||||
result += incompatibilityRegister(CROSSINLINE_KEYWORD, NOINLINE_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)
|
||||
result += incompatibilityRegister(SEALED_KEYWORD, INNER_KEYWORD)
|
||||
|
||||
// header / expect / impl / actual are all incompatible
|
||||
result += incompatibilityRegister(HEADER_KEYWORD, EXPECT_KEYWORD, IMPL_KEYWORD, ACTUAL_KEYWORD)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun redundantRegister(
|
||||
sufficient: KtModifierKeywordToken,
|
||||
redundant: KtModifierKeywordToken
|
||||
): Map<Pair<KtModifierKeywordToken, KtModifierKeywordToken>, Compatibility> {
|
||||
return mapOf(
|
||||
Pair(sufficient, redundant) to Compatibility.REDUNDANT,
|
||||
Pair(redundant, sufficient) to Compatibility.REVERSE_REDUNDANT
|
||||
)
|
||||
}
|
||||
|
||||
private fun compatibilityRegister(
|
||||
compatibility: Compatibility, vararg list: KtModifierKeywordToken
|
||||
): Map<Pair<KtModifierKeywordToken, KtModifierKeywordToken>, Compatibility> {
|
||||
val result = hashMapOf<Pair<KtModifierKeywordToken, KtModifierKeywordToken>, Compatibility>()
|
||||
for (first in list) {
|
||||
for (second in list) {
|
||||
if (first != second) {
|
||||
result[Pair(first, second)] = compatibility
|
||||
) {
|
||||
if (listOwner is KtDeclarationWithBody) {
|
||||
// JetFunction or JetPropertyAccessor
|
||||
for (parameter in listOwner.valueParameters) {
|
||||
if (!parameter.hasValOrVar()) {
|
||||
check(parameter, trace, trace[BindingContext.VALUE_PARAMETER, parameter], languageVersionSettings)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
val actualTargets = AnnotationChecker.getDeclarationSiteActualTargetList(
|
||||
listOwner, descriptor as? ClassDescriptor, trace.bindingContext
|
||||
)
|
||||
val list = listOwner.modifierList ?: return
|
||||
checkModifierList(list, trace, descriptor?.containingDeclaration, actualTargets, languageVersionSettings)
|
||||
}
|
||||
|
||||
private fun compatibilityForClassesRegister(vararg list: KtModifierKeywordToken) =
|
||||
compatibilityRegister(Compatibility.COMPATIBLE_FOR_CLASSES_ONLY, *list)
|
||||
private val MODIFIER_KEYWORD_SET = TokenSet.orSet(SOFT_KEYWORDS, TokenSet.create(IN_KEYWORD, FUN_KEYWORD))
|
||||
|
||||
private fun incompatibilityRegister(vararg list: KtModifierKeywordToken) = compatibilityRegister(Compatibility.INCOMPATIBLE, *list)
|
||||
private fun checkModifierList(
|
||||
list: KtModifierList,
|
||||
trace: BindingTrace,
|
||||
parentDescriptor: DeclarationDescriptor?,
|
||||
actualTargets: List<KotlinTarget>,
|
||||
languageVersionSettings: LanguageVersionSettings
|
||||
) {
|
||||
if (list.stub != null) return
|
||||
|
||||
private fun deprecationRegister(vararg list: KtModifierKeywordToken) = compatibilityRegister(Compatibility.DEPRECATED, *list)
|
||||
// It's a list of all nodes with error already reported
|
||||
// General strategy: report no more than one error but any number of warnings
|
||||
val incorrectNodes = hashSetOf<ASTNode>()
|
||||
|
||||
private fun compatibility(first: KtModifierKeywordToken, second: KtModifierKeywordToken): Compatibility {
|
||||
return if (first == second) {
|
||||
Compatibility.REPEATED
|
||||
} else {
|
||||
mutualCompatibility[Pair(first, second)] ?: Compatibility.COMPATIBLE
|
||||
val children = list.node.getChildren(MODIFIER_KEYWORD_SET)
|
||||
for (second in children) {
|
||||
for (first in children) {
|
||||
if (first == second) {
|
||||
break
|
||||
}
|
||||
checkCompatibility(trace, first, second, list.owner, incorrectNodes)
|
||||
}
|
||||
if (second !in incorrectNodes) {
|
||||
when {
|
||||
!checkTarget(trace, second, actualTargets) -> incorrectNodes += second
|
||||
!checkParent(trace, second, parentDescriptor, languageVersionSettings) -> incorrectNodes += second
|
||||
!checkLanguageLevelSupport(trace, second, languageVersionSettings, actualTargets) -> incorrectNodes += second
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,32 +124,32 @@ object ModifierCheckerCore {
|
||||
owner: PsiElement,
|
||||
incorrectNodes: MutableSet<ASTNode>
|
||||
) {
|
||||
val first = firstNode.elementType as KtModifierKeywordToken
|
||||
val second = secondNode.elementType as KtModifierKeywordToken
|
||||
val compatibility = compatibility(first, second)
|
||||
when (compatibility) {
|
||||
val (firstModifier, firstModifierType) = getModifierAndModifierType(firstNode)
|
||||
val (secondModifier, secondModifierType) = getModifierAndModifierType(secondNode)
|
||||
|
||||
when (val compatibility = compatibility(firstModifierType, secondModifierType)) {
|
||||
Compatibility.COMPATIBLE -> {
|
||||
}
|
||||
Compatibility.REPEATED -> if (incorrectNodes.add(secondNode)) {
|
||||
trace.report(Errors.REPEATED_MODIFIER.on(secondNode.psi, first))
|
||||
trace.report(Errors.REPEATED_MODIFIER.on(secondNode.psi, firstModifier))
|
||||
}
|
||||
Compatibility.REDUNDANT ->
|
||||
trace.report(Errors.REDUNDANT_MODIFIER.on(secondNode.psi, second, first))
|
||||
trace.report(Errors.REDUNDANT_MODIFIER.on(secondNode.psi, secondModifier, firstModifier))
|
||||
Compatibility.REVERSE_REDUNDANT ->
|
||||
trace.report(Errors.REDUNDANT_MODIFIER.on(firstNode.psi, first, second))
|
||||
trace.report(Errors.REDUNDANT_MODIFIER.on(firstNode.psi, firstModifier, secondModifier))
|
||||
Compatibility.DEPRECATED -> {
|
||||
trace.report(Errors.DEPRECATED_MODIFIER_PAIR.on(firstNode.psi, first, second))
|
||||
trace.report(Errors.DEPRECATED_MODIFIER_PAIR.on(secondNode.psi, second, first))
|
||||
trace.report(Errors.DEPRECATED_MODIFIER_PAIR.on(firstNode.psi, firstModifier, secondModifier))
|
||||
trace.report(Errors.DEPRECATED_MODIFIER_PAIR.on(secondNode.psi, secondModifier, firstModifier))
|
||||
}
|
||||
Compatibility.COMPATIBLE_FOR_CLASSES_ONLY, Compatibility.INCOMPATIBLE -> {
|
||||
if (compatibility == Compatibility.COMPATIBLE_FOR_CLASSES_ONLY) {
|
||||
if (owner is KtClassOrObject) return
|
||||
}
|
||||
if (incorrectNodes.add(firstNode)) {
|
||||
trace.report(Errors.INCOMPATIBLE_MODIFIERS.on(firstNode.psi, first, second))
|
||||
trace.report(Errors.INCOMPATIBLE_MODIFIERS.on(firstNode.psi, firstModifier, secondModifier))
|
||||
}
|
||||
if (incorrectNodes.add(secondNode)) {
|
||||
trace.report(Errors.INCOMPATIBLE_MODIFIERS.on(secondNode.psi, second, first))
|
||||
trace.report(Errors.INCOMPATIBLE_MODIFIERS.on(secondNode.psi, secondModifier, firstModifier))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -303,18 +157,19 @@ object ModifierCheckerCore {
|
||||
|
||||
// Should return false if error is reported, true otherwise
|
||||
private fun checkTarget(trace: BindingTrace, node: ASTNode, actualTargets: List<KotlinTarget>): Boolean {
|
||||
val modifier = node.elementType as KtModifierKeywordToken
|
||||
val possibleTargets = possibleTargetMap[modifier] ?: emptySet()
|
||||
val (modifier, modifierType) = getModifierAndModifierType(node)
|
||||
|
||||
val possibleTargets = possibleTargetMap[modifierType] ?: emptySet()
|
||||
if (!actualTargets.any { it in possibleTargets }) {
|
||||
trace.report(Errors.WRONG_MODIFIER_TARGET.on(node.psi, modifier, actualTargets.firstOrNull()?.description ?: "this"))
|
||||
return false
|
||||
}
|
||||
val deprecatedModifierReplacement = deprecatedModifierMap[modifier]
|
||||
val deprecatedTargets = deprecatedTargetMap[modifier] ?: emptySet()
|
||||
val redundantTargets = redundantTargetMap[modifier] ?: emptySet()
|
||||
val deprecatedModifierReplacement = deprecatedModifierMap[modifierType]
|
||||
val deprecatedTargets = deprecatedTargetMap[modifierType] ?: emptySet()
|
||||
val redundantTargets = redundantTargetMap[modifierType] ?: emptySet()
|
||||
when {
|
||||
deprecatedModifierReplacement != null ->
|
||||
trace.report(Errors.DEPRECATED_MODIFIER.on(node.psi, modifier, deprecatedModifierReplacement))
|
||||
trace.report(Errors.DEPRECATED_MODIFIER.on(node.psi, modifier, deprecatedModifierReplacement.render()))
|
||||
actualTargets.any { it in deprecatedTargets } ->
|
||||
trace.report(
|
||||
Errors.DEPRECATED_MODIFIER_FOR_TARGET.on(
|
||||
@@ -335,15 +190,59 @@ object ModifierCheckerCore {
|
||||
return true
|
||||
}
|
||||
|
||||
// Should return false if error is reported, true otherwise
|
||||
private fun checkParent(
|
||||
trace: BindingTrace,
|
||||
node: ASTNode,
|
||||
parentDescriptor: DeclarationDescriptor?,
|
||||
languageVersionSettings: LanguageVersionSettings
|
||||
): Boolean {
|
||||
val (modifier, modifierType) = getModifierAndModifierType(node)
|
||||
|
||||
val actualParents: List<KotlinTarget> = when (parentDescriptor) {
|
||||
is ClassDescriptor -> KotlinTarget.classActualTargets(
|
||||
parentDescriptor.kind,
|
||||
isInnerClass = parentDescriptor.isInner,
|
||||
isCompanionObject = parentDescriptor.isCompanionObject,
|
||||
isLocalClass = DescriptorUtils.isLocal(parentDescriptor)
|
||||
)
|
||||
is PropertySetterDescriptor -> listOf(PROPERTY_SETTER)
|
||||
is PropertyGetterDescriptor -> listOf(PROPERTY_GETTER)
|
||||
is FunctionDescriptor -> listOf(FUNCTION)
|
||||
else -> listOf(FILE)
|
||||
}
|
||||
val deprecatedParents = deprecatedParentTargetMap[modifierType]
|
||||
if (deprecatedParents != null && actualParents.any { it in deprecatedParents }) {
|
||||
trace.report(
|
||||
Errors.DEPRECATED_MODIFIER_CONTAINING_DECLARATION.on(
|
||||
node.psi,
|
||||
modifier,
|
||||
actualParents.firstOrNull()?.description ?: "this scope"
|
||||
)
|
||||
)
|
||||
return true
|
||||
}
|
||||
val possibleParentPredicate = possibleParentTargetPredicateMap[modifierType] ?: return true
|
||||
if (actualParents.any { possibleParentPredicate.isAllowed(it, languageVersionSettings) }) return true
|
||||
trace.report(
|
||||
Errors.WRONG_MODIFIER_CONTAINING_DECLARATION.on(
|
||||
node.psi,
|
||||
modifier,
|
||||
actualParents.firstOrNull()?.description ?: "this scope"
|
||||
)
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
private fun checkLanguageLevelSupport(
|
||||
trace: BindingTrace,
|
||||
node: ASTNode,
|
||||
languageVersionSettings: LanguageVersionSettings,
|
||||
actualTargets: List<KotlinTarget>
|
||||
): Boolean {
|
||||
val modifier = node.elementType as KtModifierKeywordToken
|
||||
val (_, modifierType) = getModifierAndModifierType(node)
|
||||
|
||||
val dependencies = featureDependencies[modifier] ?: return true
|
||||
val dependencies = featureDependencies[modifierType] ?: return true
|
||||
for (dependency in dependencies) {
|
||||
val restrictedTargets = featureDependenciesTargets[dependency]
|
||||
if (restrictedTargets != null && actualTargets.intersect(restrictedTargets).isEmpty()) {
|
||||
@@ -385,128 +284,8 @@ object ModifierCheckerCore {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// Should return false if error is reported, true otherwise
|
||||
private fun checkParent(
|
||||
trace: BindingTrace,
|
||||
node: ASTNode,
|
||||
parentDescriptor: DeclarationDescriptor?,
|
||||
languageVersionSettings: LanguageVersionSettings
|
||||
): Boolean {
|
||||
private fun getModifierAndModifierType(node: ASTNode): Pair<KtModifierKeywordToken, KeywordType> {
|
||||
val modifier = node.elementType as KtModifierKeywordToken
|
||||
val actualParents: List<KotlinTarget> = when (parentDescriptor) {
|
||||
is ClassDescriptor -> KotlinTarget.classActualTargets(
|
||||
parentDescriptor.kind,
|
||||
isInnerClass = parentDescriptor.isInner,
|
||||
isCompanionObject = parentDescriptor.isCompanionObject,
|
||||
isLocalClass = DescriptorUtils.isLocal(parentDescriptor)
|
||||
)
|
||||
is PropertySetterDescriptor -> listOf(PROPERTY_SETTER)
|
||||
is PropertyGetterDescriptor -> listOf(PROPERTY_GETTER)
|
||||
is FunctionDescriptor -> listOf(FUNCTION)
|
||||
else -> listOf(FILE)
|
||||
}
|
||||
val deprecatedParents = deprecatedParentTargetMap[modifier]
|
||||
if (deprecatedParents != null && actualParents.any { it in deprecatedParents }) {
|
||||
trace.report(
|
||||
Errors.DEPRECATED_MODIFIER_CONTAINING_DECLARATION.on(
|
||||
node.psi,
|
||||
modifier,
|
||||
actualParents.firstOrNull()?.description ?: "this scope"
|
||||
)
|
||||
)
|
||||
return true
|
||||
}
|
||||
val possibleParentPredicate = possibleParentTargetPredicateMap[modifier] ?: return true
|
||||
if (actualParents.any { possibleParentPredicate.isAllowed(it, languageVersionSettings) }) return true
|
||||
trace.report(
|
||||
Errors.WRONG_MODIFIER_CONTAINING_DECLARATION.on(
|
||||
node.psi,
|
||||
modifier,
|
||||
actualParents.firstOrNull()?.description ?: "this scope"
|
||||
)
|
||||
)
|
||||
return false
|
||||
return Pair(modifier, ktKeywordToKeywordTypeMap[modifier]!!)
|
||||
}
|
||||
|
||||
private val MODIFIER_KEYWORD_SET = TokenSet.orSet(SOFT_KEYWORDS, TokenSet.create(IN_KEYWORD, FUN_KEYWORD))
|
||||
|
||||
private fun checkModifierList(
|
||||
list: KtModifierList,
|
||||
trace: BindingTrace,
|
||||
parentDescriptor: DeclarationDescriptor?,
|
||||
actualTargets: List<KotlinTarget>,
|
||||
languageVersionSettings: LanguageVersionSettings
|
||||
) {
|
||||
if (list.stub != null) return
|
||||
|
||||
// It's a list of all nodes with error already reported
|
||||
// General strategy: report no more than one error but any number of warnings
|
||||
val incorrectNodes = hashSetOf<ASTNode>()
|
||||
|
||||
val children = list.node.getChildren(MODIFIER_KEYWORD_SET)
|
||||
for (second in children) {
|
||||
for (first in children) {
|
||||
if (first == second) {
|
||||
break
|
||||
}
|
||||
checkCompatibility(trace, first, second, list.owner, incorrectNodes)
|
||||
}
|
||||
if (second !in incorrectNodes) {
|
||||
when {
|
||||
!checkTarget(trace, second, actualTargets) -> incorrectNodes += second
|
||||
!checkParent(trace, second, parentDescriptor, languageVersionSettings) -> incorrectNodes += second
|
||||
!checkLanguageLevelSupport(trace, second, languageVersionSettings, actualTargets) -> incorrectNodes += second
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun check(
|
||||
listOwner: KtModifierListOwner,
|
||||
trace: BindingTrace,
|
||||
descriptor: DeclarationDescriptor?,
|
||||
languageVersionSettings: LanguageVersionSettings
|
||||
) {
|
||||
if (listOwner is KtDeclarationWithBody) {
|
||||
// JetFunction or JetPropertyAccessor
|
||||
for (parameter in listOwner.valueParameters) {
|
||||
if (!parameter.hasValOrVar()) {
|
||||
check(parameter, trace, trace[BindingContext.VALUE_PARAMETER, parameter], languageVersionSettings)
|
||||
}
|
||||
}
|
||||
}
|
||||
val actualTargets = AnnotationChecker.getDeclarationSiteActualTargetList(
|
||||
listOwner, descriptor as? ClassDescriptor, trace.bindingContext
|
||||
)
|
||||
val list = listOwner.modifierList ?: return
|
||||
checkModifierList(list, trace, descriptor?.containingDeclaration, actualTargets, languageVersionSettings)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private interface TargetAllowedPredicate {
|
||||
fun isAllowed(target: KotlinTarget, languageVersionSettings: LanguageVersionSettings): Boolean
|
||||
}
|
||||
|
||||
|
||||
private fun always(target: KotlinTarget, vararg targets: KotlinTarget) = object : TargetAllowedPredicate {
|
||||
private val targetSet = EnumSet.of(target, *targets)
|
||||
|
||||
override fun isAllowed(target: KotlinTarget, languageVersionSettings: LanguageVersionSettings) =
|
||||
target in targetSet
|
||||
}
|
||||
|
||||
private fun ifSupported(languageFeature: LanguageFeature, target: KotlinTarget, vararg targets: KotlinTarget) =
|
||||
object : TargetAllowedPredicate {
|
||||
private val targetSet = EnumSet.of(target, *targets)
|
||||
|
||||
override fun isAllowed(target: KotlinTarget, languageVersionSettings: LanguageVersionSettings) =
|
||||
languageVersionSettings.supportsFeature(languageFeature) && target in targetSet
|
||||
}
|
||||
|
||||
private fun or(p1: TargetAllowedPredicate, p2: TargetAllowedPredicate) = object : TargetAllowedPredicate {
|
||||
override fun isAllowed(target: KotlinTarget, languageVersionSettings: LanguageVersionSettings) =
|
||||
p1.isAllowed(target, languageVersionSettings) ||
|
||||
p2.isAllowed(target, languageVersionSettings)
|
||||
}
|
||||
}
|
||||
+400
@@ -0,0 +1,400 @@
|
||||
/*
|
||||
* 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.resolve;
|
||||
|
||||
import org.jetbrains.kotlin.config.LanguageFeature
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
|
||||
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget.*
|
||||
import java.util.*
|
||||
import org.jetbrains.kotlin.resolve.KeywordType.*
|
||||
|
||||
enum class KeywordType {
|
||||
Inner,
|
||||
Override,
|
||||
Public,
|
||||
Protected,
|
||||
Internal,
|
||||
Private,
|
||||
Companion,
|
||||
Final,
|
||||
Vararg,
|
||||
Enum,
|
||||
Abstract,
|
||||
Open,
|
||||
Sealed,
|
||||
In,
|
||||
Out,
|
||||
Reified,
|
||||
Lateinit,
|
||||
Data,
|
||||
Inline,
|
||||
Noinline,
|
||||
Tailrec,
|
||||
Suspend,
|
||||
External,
|
||||
Annotation,
|
||||
Crossinline,
|
||||
Const,
|
||||
Operator,
|
||||
Infix,
|
||||
Header,
|
||||
Impl,
|
||||
Expect,
|
||||
Actual,
|
||||
Fun,
|
||||
Value
|
||||
}
|
||||
|
||||
fun KeywordType.render(): String {
|
||||
return toString().lowercase()
|
||||
}
|
||||
|
||||
enum class Compatibility {
|
||||
// modifier pair is compatible: ok (default)
|
||||
COMPATIBLE,
|
||||
|
||||
// second is redundant to first: warning
|
||||
REDUNDANT,
|
||||
|
||||
// first is redundant to second: warning
|
||||
REVERSE_REDUNDANT,
|
||||
|
||||
// error
|
||||
REPEATED,
|
||||
|
||||
// pair is deprecated, will become incompatible: warning
|
||||
DEPRECATED,
|
||||
|
||||
// pair is incompatible: error
|
||||
INCOMPATIBLE,
|
||||
|
||||
// same but only for functions / properties: error
|
||||
COMPATIBLE_FOR_CLASSES_ONLY
|
||||
}
|
||||
|
||||
val compatibilityTypeMap = hashMapOf<Pair<KeywordType, KeywordType>, Compatibility>()
|
||||
|
||||
fun compatibility(first: KeywordType, second: KeywordType): Compatibility {
|
||||
return if (first == second) {
|
||||
Compatibility.REPEATED
|
||||
} else {
|
||||
mutualCompatibility[Pair(first, second)] ?: Compatibility.COMPATIBLE
|
||||
}
|
||||
}
|
||||
|
||||
// First modifier in pair should be also first in declaration
|
||||
private val mutualCompatibility = buildCompatibilityMap()
|
||||
|
||||
private fun buildCompatibilityMap(): Map<Pair<KeywordType, KeywordType>, Compatibility> {
|
||||
val result = hashMapOf<Pair<KeywordType, KeywordType>, Compatibility>()
|
||||
// Variance: in + out are incompatible
|
||||
result += incompatibilityRegister(In, Out)
|
||||
// Visibilities: incompatible
|
||||
result += incompatibilityRegister(Private, Protected, Public, Internal)
|
||||
// Abstract + open + final + sealed: incompatible
|
||||
result += incompatibilityRegister(Abstract, Open, Final, Sealed)
|
||||
// data + open, data + inner, data + abstract, data + sealed, data + inline, data + value
|
||||
result += incompatibilityRegister(Data, Open)
|
||||
result += incompatibilityRegister(Data, Inner)
|
||||
result += incompatibilityRegister(Data, Abstract)
|
||||
result += incompatibilityRegister(Data, Sealed)
|
||||
result += incompatibilityRegister(Data, Inline)
|
||||
result += incompatibilityRegister(Data, Value)
|
||||
// open is redundant to abstract & override
|
||||
result += redundantRegister(Abstract, Open)
|
||||
// abstract is redundant to sealed
|
||||
result += redundantRegister(Sealed, Abstract)
|
||||
|
||||
// const is incompatible with abstract, open, override
|
||||
result += incompatibilityRegister(Const, Abstract)
|
||||
result += incompatibilityRegister(Const, Open)
|
||||
result += incompatibilityRegister(Const, Override)
|
||||
|
||||
// private is incompatible with override
|
||||
result += incompatibilityRegister(Private, Override)
|
||||
// private is compatible with open / abstract only for classes
|
||||
result += compatibilityForClassesRegister(Private, Open)
|
||||
result += compatibilityForClassesRegister(Private, Abstract)
|
||||
|
||||
result += incompatibilityRegister(Crossinline, Noinline)
|
||||
|
||||
// 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)
|
||||
result += incompatibilityRegister(Sealed, Inner)
|
||||
|
||||
// header / expect / impl / actual are all incompatible
|
||||
result += incompatibilityRegister(Header, Expect, Impl, Actual)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun incompatibilityRegister(vararg list: KeywordType): Map<Pair<KeywordType, KeywordType>, Compatibility> {
|
||||
return compatibilityRegister(Compatibility.INCOMPATIBLE, *list)
|
||||
}
|
||||
|
||||
private fun redundantRegister(
|
||||
sufficient: KeywordType,
|
||||
redundant: KeywordType
|
||||
): Map<Pair<KeywordType, KeywordType>, Compatibility> {
|
||||
return mapOf(
|
||||
Pair(sufficient, redundant) to Compatibility.REDUNDANT,
|
||||
Pair(redundant, sufficient) to Compatibility.REVERSE_REDUNDANT
|
||||
)
|
||||
}
|
||||
|
||||
private fun compatibilityForClassesRegister(vararg list: KeywordType) =
|
||||
compatibilityRegister(Compatibility.COMPATIBLE_FOR_CLASSES_ONLY, *list)
|
||||
|
||||
private fun compatibilityRegister(
|
||||
compatibility: Compatibility, vararg list: KeywordType
|
||||
): Map<Pair<KeywordType, KeywordType>, Compatibility> {
|
||||
val result = hashMapOf<Pair<KeywordType, KeywordType>, Compatibility>()
|
||||
for (first in list) {
|
||||
for (second in list) {
|
||||
if (first != second) {
|
||||
result[Pair(first, second)] = compatibility
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
val featureDependencies = mapOf(
|
||||
Suspend to listOf(LanguageFeature.Coroutines),
|
||||
Inline to listOf(LanguageFeature.InlineProperties, LanguageFeature.InlineClasses),
|
||||
Header to listOf(LanguageFeature.MultiPlatformProjects),
|
||||
Impl to listOf(LanguageFeature.MultiPlatformProjects),
|
||||
Expect to listOf(LanguageFeature.MultiPlatformProjects),
|
||||
Actual to listOf(LanguageFeature.MultiPlatformProjects),
|
||||
Lateinit to listOf(LanguageFeature.LateinitTopLevelProperties, LanguageFeature.LateinitLocalVariables),
|
||||
Fun to listOf(LanguageFeature.FunctionalInterfaceConversion)
|
||||
)
|
||||
|
||||
val featureDependenciesTargets = mapOf(
|
||||
LanguageFeature.InlineProperties to setOf(PROPERTY, PROPERTY_GETTER, PROPERTY_SETTER),
|
||||
LanguageFeature.LateinitLocalVariables to setOf(LOCAL_VARIABLE),
|
||||
LanguageFeature.LateinitTopLevelProperties to setOf(TOP_LEVEL_PROPERTY),
|
||||
LanguageFeature.InlineClasses to setOf(CLASS_ONLY),
|
||||
LanguageFeature.JvmInlineValueClasses to setOf(CLASS_ONLY),
|
||||
LanguageFeature.FunctionalInterfaceConversion to setOf(INTERFACE)
|
||||
)
|
||||
|
||||
val defaultVisibilityTargets: EnumSet<KotlinTarget> = EnumSet.of(
|
||||
CLASS_ONLY, OBJECT, INTERFACE, ENUM_CLASS, ANNOTATION_CLASS,
|
||||
MEMBER_FUNCTION, TOP_LEVEL_FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER,
|
||||
MEMBER_PROPERTY, TOP_LEVEL_PROPERTY, CONSTRUCTOR, TYPEALIAS
|
||||
)
|
||||
|
||||
val possibleTargetMap = mapOf(
|
||||
KeywordType.Enum to EnumSet.of(ENUM_CLASS),
|
||||
Abstract to EnumSet.of(
|
||||
CLASS_ONLY,
|
||||
LOCAL_CLASS,
|
||||
INTERFACE,
|
||||
MEMBER_PROPERTY,
|
||||
MEMBER_FUNCTION
|
||||
),
|
||||
Open to EnumSet.of(
|
||||
CLASS_ONLY,
|
||||
LOCAL_CLASS,
|
||||
INTERFACE,
|
||||
MEMBER_PROPERTY,
|
||||
MEMBER_FUNCTION
|
||||
),
|
||||
Final to EnumSet.of(
|
||||
CLASS_ONLY,
|
||||
LOCAL_CLASS,
|
||||
ENUM_CLASS,
|
||||
OBJECT,
|
||||
MEMBER_PROPERTY,
|
||||
MEMBER_FUNCTION
|
||||
),
|
||||
Sealed to EnumSet.of(CLASS_ONLY, INTERFACE),
|
||||
Inner to EnumSet.of(CLASS_ONLY),
|
||||
Override to EnumSet.of(MEMBER_PROPERTY, MEMBER_FUNCTION),
|
||||
Private to defaultVisibilityTargets,
|
||||
Public to defaultVisibilityTargets,
|
||||
Internal to defaultVisibilityTargets,
|
||||
Protected to EnumSet.of(
|
||||
CLASS_ONLY,
|
||||
OBJECT,
|
||||
INTERFACE,
|
||||
ENUM_CLASS,
|
||||
ANNOTATION_CLASS,
|
||||
MEMBER_FUNCTION,
|
||||
PROPERTY_GETTER,
|
||||
PROPERTY_SETTER,
|
||||
MEMBER_PROPERTY,
|
||||
CONSTRUCTOR,
|
||||
TYPEALIAS
|
||||
),
|
||||
In to EnumSet.of(TYPE_PARAMETER, TYPE_PROJECTION),
|
||||
Out to EnumSet.of(TYPE_PARAMETER, TYPE_PROJECTION),
|
||||
Reified to EnumSet.of(TYPE_PARAMETER),
|
||||
Vararg to EnumSet.of(VALUE_PARAMETER, PROPERTY_PARAMETER),
|
||||
KeywordType.Companion to EnumSet.of(OBJECT),
|
||||
Lateinit to EnumSet.of(MEMBER_PROPERTY, TOP_LEVEL_PROPERTY, LOCAL_VARIABLE),
|
||||
Data to EnumSet.of(CLASS_ONLY, LOCAL_CLASS),
|
||||
Inline to EnumSet.of(
|
||||
FUNCTION,
|
||||
PROPERTY,
|
||||
PROPERTY_GETTER,
|
||||
PROPERTY_SETTER,
|
||||
CLASS_ONLY
|
||||
),
|
||||
Noinline to EnumSet.of(VALUE_PARAMETER),
|
||||
Tailrec to EnumSet.of(FUNCTION),
|
||||
Suspend to EnumSet.of(MEMBER_FUNCTION, TOP_LEVEL_FUNCTION, LOCAL_FUNCTION),
|
||||
External to EnumSet.of(
|
||||
FUNCTION,
|
||||
PROPERTY,
|
||||
PROPERTY_GETTER,
|
||||
PROPERTY_SETTER,
|
||||
CLASS
|
||||
),
|
||||
KeywordType.Annotation to EnumSet.of(ANNOTATION_CLASS), // TODO: Workaround for FIR, https://youtrack.jetbrains.com/issue/KT-48157
|
||||
Crossinline to EnumSet.of(VALUE_PARAMETER),
|
||||
Const to EnumSet.of(MEMBER_PROPERTY, TOP_LEVEL_PROPERTY),
|
||||
Operator to EnumSet.of(FUNCTION),
|
||||
Infix to EnumSet.of(FUNCTION),
|
||||
Header to EnumSet.of(
|
||||
TOP_LEVEL_FUNCTION,
|
||||
TOP_LEVEL_PROPERTY,
|
||||
CLASS_ONLY,
|
||||
OBJECT,
|
||||
INTERFACE,
|
||||
ENUM_CLASS,
|
||||
ANNOTATION_CLASS
|
||||
),
|
||||
Impl to EnumSet.of(
|
||||
TOP_LEVEL_FUNCTION,
|
||||
MEMBER_FUNCTION,
|
||||
TOP_LEVEL_PROPERTY,
|
||||
MEMBER_PROPERTY,
|
||||
CONSTRUCTOR,
|
||||
CLASS_ONLY,
|
||||
OBJECT,
|
||||
INTERFACE,
|
||||
ENUM_CLASS,
|
||||
ANNOTATION_CLASS,
|
||||
TYPEALIAS
|
||||
),
|
||||
Expect to EnumSet.of(
|
||||
TOP_LEVEL_FUNCTION,
|
||||
TOP_LEVEL_PROPERTY,
|
||||
CLASS_ONLY,
|
||||
OBJECT,
|
||||
INTERFACE,
|
||||
ENUM_CLASS,
|
||||
ANNOTATION_CLASS
|
||||
),
|
||||
Actual to EnumSet.of(
|
||||
TOP_LEVEL_FUNCTION,
|
||||
MEMBER_FUNCTION,
|
||||
TOP_LEVEL_PROPERTY,
|
||||
MEMBER_PROPERTY,
|
||||
CONSTRUCTOR,
|
||||
CLASS_ONLY,
|
||||
OBJECT,
|
||||
INTERFACE,
|
||||
ENUM_CLASS,
|
||||
ANNOTATION_CLASS,
|
||||
TYPEALIAS
|
||||
),
|
||||
Fun to EnumSet.of(INTERFACE),
|
||||
Value to EnumSet.of(CLASS_ONLY)
|
||||
)
|
||||
|
||||
// NOTE: deprecated targets must be possible!
|
||||
val deprecatedTargetMap = mapOf<KeywordType, Set<KotlinTarget>>()
|
||||
|
||||
val deprecatedParentTargetMap = mapOf<KeywordType, Set<KotlinTarget>>()
|
||||
|
||||
val deprecatedModifierMap = mapOf(
|
||||
Header to Expect,
|
||||
Impl to Actual
|
||||
)
|
||||
|
||||
// NOTE: redundant targets must be possible!
|
||||
val redundantTargetMap = mapOf<KeywordType, Set<KotlinTarget>>(
|
||||
Open to EnumSet.of(INTERFACE)
|
||||
)
|
||||
|
||||
interface TargetAllowedPredicate {
|
||||
fun isAllowed(target: KotlinTarget, languageVersionSettings: LanguageVersionSettings): Boolean
|
||||
}
|
||||
|
||||
fun always(target: KotlinTarget, vararg targets: KotlinTarget) = object : TargetAllowedPredicate {
|
||||
private val targetSet = EnumSet.of(target, *targets)
|
||||
|
||||
override fun isAllowed(target: KotlinTarget, languageVersionSettings: LanguageVersionSettings) =
|
||||
target in targetSet
|
||||
}
|
||||
|
||||
fun ifSupported(languageFeature: LanguageFeature, target: KotlinTarget, vararg targets: KotlinTarget) =
|
||||
object : TargetAllowedPredicate {
|
||||
private val targetSet = EnumSet.of(target, *targets)
|
||||
|
||||
override fun isAllowed(target: KotlinTarget, languageVersionSettings: LanguageVersionSettings) =
|
||||
languageVersionSettings.supportsFeature(languageFeature) && target in targetSet
|
||||
}
|
||||
|
||||
fun or(p1: TargetAllowedPredicate, p2: TargetAllowedPredicate) = object : TargetAllowedPredicate {
|
||||
override fun isAllowed(target: KotlinTarget, languageVersionSettings: LanguageVersionSettings) =
|
||||
p1.isAllowed(target, languageVersionSettings) ||
|
||||
p2.isAllowed(target, languageVersionSettings)
|
||||
}
|
||||
|
||||
val possibleParentTargetPredicateMap = mapOf(
|
||||
Inner to or(
|
||||
always(CLASS_ONLY, LOCAL_CLASS, ENUM_CLASS),
|
||||
ifSupported(LanguageFeature.InnerClassInEnumEntryClass, ENUM_ENTRY)
|
||||
),
|
||||
Override to always(
|
||||
CLASS_ONLY,
|
||||
LOCAL_CLASS,
|
||||
OBJECT,
|
||||
OBJECT_LITERAL,
|
||||
INTERFACE,
|
||||
ENUM_CLASS,
|
||||
ENUM_ENTRY
|
||||
),
|
||||
Protected to always(CLASS_ONLY, LOCAL_CLASS, ENUM_CLASS, COMPANION_OBJECT),
|
||||
Internal to always(
|
||||
CLASS_ONLY,
|
||||
LOCAL_CLASS,
|
||||
OBJECT,
|
||||
OBJECT_LITERAL,
|
||||
ENUM_CLASS,
|
||||
ENUM_ENTRY,
|
||||
FILE
|
||||
),
|
||||
Private to always(
|
||||
CLASS_ONLY,
|
||||
LOCAL_CLASS,
|
||||
OBJECT,
|
||||
OBJECT_LITERAL,
|
||||
INTERFACE,
|
||||
ENUM_CLASS,
|
||||
ENUM_ENTRY,
|
||||
FILE
|
||||
),
|
||||
KeywordType.Companion to always(CLASS_ONLY, INTERFACE, ENUM_CLASS, ANNOTATION_CLASS),
|
||||
Final to always(
|
||||
CLASS_ONLY,
|
||||
LOCAL_CLASS,
|
||||
OBJECT,
|
||||
OBJECT_LITERAL,
|
||||
ENUM_CLASS,
|
||||
ENUM_ENTRY,
|
||||
ANNOTATION_CLASS,
|
||||
FILE
|
||||
),
|
||||
Vararg to always(CONSTRUCTOR, FUNCTION, CLASS)
|
||||
)
|
||||
+1
-1
@@ -49,4 +49,4 @@ final value class D0(val x: Int)
|
||||
<!INLINE_CLASS_NOT_FINAL!>abstract<!> value class D2(val x: Int)
|
||||
<!INLINE_CLASS_NOT_FINAL!>sealed<!> value class D3(val x: Int)
|
||||
|
||||
value data class D4(val x: String)
|
||||
<!INCOMPATIBLE_MODIFIERS!>value<!> <!INCOMPATIBLE_MODIFIERS!>data<!> class D4(val x: String)
|
||||
|
||||
+5
-5
@@ -724,19 +724,19 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
|
||||
|
||||
abstract class RepeatedModifier : KtFirDiagnostic<PsiElement>() {
|
||||
override val diagnosticClass get() = RepeatedModifier::class
|
||||
abstract val modifier: KtModifierKeywordToken
|
||||
abstract val modifier: String
|
||||
}
|
||||
|
||||
abstract class RedundantModifier : KtFirDiagnostic<PsiElement>() {
|
||||
override val diagnosticClass get() = RedundantModifier::class
|
||||
abstract val redundantModifier: KtModifierKeywordToken
|
||||
abstract val conflictingModifier: KtModifierKeywordToken
|
||||
abstract val redundantModifier: String
|
||||
abstract val conflictingModifier: String
|
||||
}
|
||||
|
||||
abstract class DeprecatedModifierPair : KtFirDiagnostic<PsiElement>() {
|
||||
override val diagnosticClass get() = DeprecatedModifierPair::class
|
||||
abstract val deprecatedModifier: KtModifierKeywordToken
|
||||
abstract val conflictingModifier: KtModifierKeywordToken
|
||||
abstract val deprecatedModifier: String
|
||||
abstract val conflictingModifier: String
|
||||
}
|
||||
|
||||
abstract class IncompatibleModifiers : KtFirDiagnostic<PsiElement>() {
|
||||
|
||||
+7
-7
@@ -1150,7 +1150,7 @@ internal class InapplicableInfixModifierImpl(
|
||||
}
|
||||
|
||||
internal class RepeatedModifierImpl(
|
||||
override val modifier: KtModifierKeywordToken,
|
||||
override val modifier: String,
|
||||
firDiagnostic: FirPsiDiagnostic,
|
||||
override val token: ValidityToken,
|
||||
) : KtFirDiagnostic.RepeatedModifier(), KtAbstractFirDiagnostic<PsiElement> {
|
||||
@@ -1158,8 +1158,8 @@ internal class RepeatedModifierImpl(
|
||||
}
|
||||
|
||||
internal class RedundantModifierImpl(
|
||||
override val redundantModifier: KtModifierKeywordToken,
|
||||
override val conflictingModifier: KtModifierKeywordToken,
|
||||
override val redundantModifier: String,
|
||||
override val conflictingModifier: String,
|
||||
firDiagnostic: FirPsiDiagnostic,
|
||||
override val token: ValidityToken,
|
||||
) : KtFirDiagnostic.RedundantModifier(), KtAbstractFirDiagnostic<PsiElement> {
|
||||
@@ -1167,8 +1167,8 @@ internal class RedundantModifierImpl(
|
||||
}
|
||||
|
||||
internal class DeprecatedModifierPairImpl(
|
||||
override val deprecatedModifier: KtModifierKeywordToken,
|
||||
override val conflictingModifier: KtModifierKeywordToken,
|
||||
override val deprecatedModifier: String,
|
||||
override val conflictingModifier: String,
|
||||
firDiagnostic: FirPsiDiagnostic,
|
||||
override val token: ValidityToken,
|
||||
) : KtFirDiagnostic.DeprecatedModifierPair(), KtAbstractFirDiagnostic<PsiElement> {
|
||||
@@ -1176,8 +1176,8 @@ internal class DeprecatedModifierPairImpl(
|
||||
}
|
||||
|
||||
internal class IncompatibleModifiersImpl(
|
||||
override val modifier1: KtModifierKeywordToken,
|
||||
override val modifier2: KtModifierKeywordToken,
|
||||
override val modifier1: String,
|
||||
override val modifier2: String,
|
||||
firDiagnostic: FirPsiDiagnostic,
|
||||
override val token: ValidityToken,
|
||||
) : KtFirDiagnostic.IncompatibleModifiers(), KtAbstractFirDiagnostic<PsiElement> {
|
||||
|
||||
Reference in New Issue
Block a user