[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
@@ -0,0 +1,31 @@
class A<<!INCOMPATIBLE_MODIFIERS!>in<!> <!INCOMPATIBLE_MODIFIERS!>out<!> T> { }
<!INCOMPATIBLE_MODIFIERS!>private<!> <!INCOMPATIBLE_MODIFIERS!>public<!> class B
<!INCOMPATIBLE_MODIFIERS!>protected<!> <!INCOMPATIBLE_MODIFIERS!>internal<!> class C
<!INCOMPATIBLE_MODIFIERS!>final<!> <!INCOMPATIBLE_MODIFIERS!>abstract<!> class D
<!INCOMPATIBLE_MODIFIERS!>final<!> <!INCOMPATIBLE_MODIFIERS!>open<!> class E
<!INCOMPATIBLE_MODIFIERS!>final<!> <!INCOMPATIBLE_MODIFIERS!>sealed<!> class F
<!INCOMPATIBLE_MODIFIERS!>open<!> <!INCOMPATIBLE_MODIFIERS!>sealed<!> class G
inline fun foo(
<!INCOMPATIBLE_MODIFIERS!>crossinline<!> <!INCOMPATIBLE_MODIFIERS!>noinline<!> first: () -> Unit,
second: () -> Unit
) { }
<!INCOMPATIBLE_MODIFIERS, REDUNDANT_MODIFIER!>open<!> <!INCOMPATIBLE_MODIFIERS!>abstract<!> <!INCOMPATIBLE_MODIFIERS!>data<!> class H(val i: Int)
<!INCOMPATIBLE_MODIFIERS!>sealed<!> <!INCOMPATIBLE_MODIFIERS!>data<!> class I(val i: Int)
<!INCOMPATIBLE_MODIFIERS!>inline<!> <!INCOMPATIBLE_MODIFIERS!>data<!> class J(val i: Int)
abstract class K {
<!INCOMPATIBLE_MODIFIERS!>private<!> <!INCOMPATIBLE_MODIFIERS!>open<!> val i1: Int = 0
<!INCOMPATIBLE_MODIFIERS!>private<!> <!INCOMPATIBLE_MODIFIERS!>abstract<!> val i2: Int
}
private open class L : K()
private abstract class M : K()
class X {
<!INCOMPATIBLE_MODIFIERS!>inner<!> <!INCOMPATIBLE_MODIFIERS!>data<!> class Y(val i: Int)
<!INCOMPATIBLE_MODIFIERS!>sealed<!> <!INCOMPATIBLE_MODIFIERS!>inner<!> class Z
}
@@ -0,0 +1,147 @@
FILE: incompatibleModifiers.kt
public final class A<out T> : R|kotlin/Any| {
public constructor<out T>(): R|A<T>| {
super<R|kotlin/Any|>()
}
}
private final class B : R|kotlin/Any| {
public[private] constructor(): R|B| {
super<R|kotlin/Any|>()
}
}
protected final class C : R|kotlin/Any| {
public[protected] constructor(): R|C| {
super<R|kotlin/Any|>()
}
}
public final class D : R|kotlin/Any| {
public constructor(): R|D| {
super<R|kotlin/Any|>()
}
}
public final class E : R|kotlin/Any| {
public constructor(): R|E| {
super<R|kotlin/Any|>()
}
}
public final class F : R|kotlin/Any| {
private constructor(): R|F| {
super<R|kotlin/Any|>()
}
}
public sealed class G : R|kotlin/Any| {
private constructor(): R|G| {
super<R|kotlin/Any|>()
}
}
public final inline fun foo(crossinline noinline first: R|() -> kotlin/Unit|, second: R|() -> kotlin/Unit|): R|kotlin/Unit| {
}
public abstract data class H : R|kotlin/Any| {
public constructor(i: R|kotlin/Int|): R|H| {
super<R|kotlin/Any|>()
}
public final val i: R|kotlin/Int| = R|<local>/i|
public get(): R|kotlin/Int|
public final fun component1(): R|kotlin/Int| {
^component1 this@R|/H|.R|/H.i|
}
public final fun copy(i: R|kotlin/Int| = this@R|/H|.R|/H.i|): R|H| {
}
}
public sealed data class I : R|kotlin/Any| {
private constructor(i: R|kotlin/Int|): R|I| {
super<R|kotlin/Any|>()
}
public final val i: R|kotlin/Int| = R|<local>/i|
public get(): R|kotlin/Int|
public final fun component1(): R|kotlin/Int| {
^component1 this@R|/I|.R|/I.i|
}
public final fun copy(i: R|kotlin/Int| = this@R|/I|.R|/I.i|): R|I| {
}
}
public final data inline class J : R|kotlin/Any| {
public constructor(i: R|kotlin/Int|): R|J| {
super<R|kotlin/Any|>()
}
public final val i: R|kotlin/Int| = R|<local>/i|
public get(): R|kotlin/Int|
public final fun component1(): R|kotlin/Int| {
^component1 this@R|/J|.R|/J.i|
}
public final fun copy(i: R|kotlin/Int| = this@R|/J|.R|/J.i|): R|J| {
}
}
public abstract class K : R|kotlin/Any| {
public constructor(): R|K| {
super<R|kotlin/Any|>()
}
private open val i1: R|kotlin/Int| = Int(0)
private get(): R|kotlin/Int|
private abstract val i2: R|kotlin/Int|
private get(): R|kotlin/Int|
}
private open class L : R|K| {
public[private] constructor(): R|L| {
super<R|K|>()
}
}
private abstract class M : R|K| {
public[private] constructor(): R|M| {
super<R|K|>()
}
}
public final class X : R|kotlin/Any| {
public constructor(): R|X| {
super<R|kotlin/Any|>()
}
public final inner data class Y : R|kotlin/Any| {
public constructor(i: R|kotlin/Int|): R|X.Y| {
super<R|kotlin/Any|>()
}
public final val i: R|kotlin/Int| = R|<local>/i|
public get(): R|kotlin/Int|
public final fun component1(): R|kotlin/Int| {
^component1 this@R|/X.Y|.R|/X.Y.i|
}
public final fun copy(i: R|kotlin/Int| = this@R|/X.Y|.R|/X.Y.i|): R|X.Y| {
}
}
public sealed inner class Z : R|kotlin/Any| {
private constructor(): R|X.Z| {
super<R|kotlin/Any|>()
}
}
}
@@ -0,0 +1,2 @@
<!REDUNDANT_MODIFIER!>open<!> abstract class A
<!REDUNDANT_MODIFIER!>abstract<!> sealed class B
@@ -0,0 +1,13 @@
FILE: redundantModifier.kt
public abstract class A : R|kotlin/Any| {
public constructor(): R|A| {
super<R|kotlin/Any|>()
}
}
public sealed class B : R|kotlin/Any| {
private constructor(): R|B| {
super<R|kotlin/Any|>()
}
}
@@ -0,0 +1,17 @@
open <!REPEATED_MODIFIER!>open<!> class A
internal <!REPEATED_MODIFIER!>internal<!> object B
enum <!REPEATED_MODIFIER!>enum<!> class C {
VALUE1, VALUE2;
protected <!REPEATED_MODIFIER!>protected<!> companion object {
private <!REPEATED_MODIFIER!>private<!> val D = 5
}
inline <!REPEATED_MODIFIER!>inline<!> fun foo(f: (Int) -> Int) = f(8)
}
open class E(private <!REPEATED_MODIFIER!>private<!> val int: Int = 5) {
protected <!REPEATED_MODIFIER!>protected<!> var double = int + 8.0
}
@@ -0,0 +1,54 @@
FILE: repeatedModifier.kt
public open class A : R|kotlin/Any| {
public constructor(): R|A| {
super<R|kotlin/Any|>()
}
}
internal final object B : R|kotlin/Any| {
private constructor(): R|B| {
super<R|kotlin/Any|>()
}
}
public final enum class C : R|kotlin/Enum<C>| {
private constructor(): R|C| {
super<R|kotlin/Enum<C>|>()
}
public final static enum entry VALUE1: R|C|
public final static enum entry VALUE2: R|C|
protected final companion object Companion : R|kotlin/Any| {
private constructor(): R|C.Companion| {
super<R|kotlin/Any|>()
}
private final val D: R|kotlin/Int| = Int(5)
private get(): R|kotlin/Int|
}
public final inline fun foo(f: R|(kotlin/Int) -> kotlin/Int|): R|kotlin/Int| {
^foo R|<local>/f|.R|FakeOverride<kotlin/Function1.invoke: R|kotlin/Int|>|(Int(8))
}
public final static fun values(): R|kotlin/Array<C>| {
}
public final static fun valueOf(value: R|kotlin/String|): R|C| {
}
}
public open class E : R|kotlin/Any| {
public constructor(int: R|kotlin/Int| = Int(5)): R|E| {
super<R|kotlin/Any|>()
}
private final val int: R|kotlin/Int| = R|<local>/int|
private get(): R|kotlin/Int|
protected final var double: R|kotlin/Double| = R|<local>/int|.R|kotlin/Int.plus|(Double(8.0))
protected get(): R|kotlin/Double|
protected set(value: R|kotlin/Double|): R|kotlin/Unit|
}
@@ -740,11 +740,26 @@ public class FirDiagnosticsTestGenerated extends AbstractFirDiagnosticsTest {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/diagnostics"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@TestMetadata("incompatibleModifiers.kt")
public void testIncompatibleModifiers() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/incompatibleModifiers.kt");
}
@TestMetadata("infixFunctions.kt")
public void testInfixFunctions() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/infixFunctions.kt");
}
@TestMetadata("redundantModifier.kt")
public void testRedundantModifier() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/redundantModifier.kt");
}
@TestMetadata("repeatedModifier.kt")
public void testRepeatedModifier() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/repeatedModifier.kt");
}
@TestMetadata("superIsNotAnExpression.kt")
public void testSuperIsNotAnExpression() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/superIsNotAnExpression.kt");
@@ -740,11 +740,26 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/diagnostics"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@TestMetadata("incompatibleModifiers.kt")
public void testIncompatibleModifiers() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/incompatibleModifiers.kt");
}
@TestMetadata("infixFunctions.kt")
public void testInfixFunctions() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/infixFunctions.kt");
}
@TestMetadata("redundantModifier.kt")
public void testRedundantModifier() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/redundantModifier.kt");
}
@TestMetadata("repeatedModifier.kt")
public void testRepeatedModifier() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/repeatedModifier.kt");
}
@TestMetadata("superIsNotAnExpression.kt")
public void testSuperIsNotAnExpression() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/superIsNotAnExpression.kt");
@@ -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>()
}
@@ -37,14 +37,34 @@ class FirLightModifierList(val modifierList: LighterASTNode, val tree: Flyweight
val modifierNodes = kidsRef.get()
return modifierNodes.filterNotNull()
.filter { it.tokenType is KtModifierKeywordToken }
.map { FirLightModifier(it, it.tokenType as KtModifierKeywordToken) }
.map { FirLightModifier(it, it.tokenType as KtModifierKeywordToken, tree) }
}
}
sealed class FirModifier<Node : Any>(val node: Node, val token: KtModifierKeywordToken)
class FirPsiModifier(node: ASTNode, token: KtModifierKeywordToken) : FirModifier<ASTNode>(node, token)
class FirPsiModifier(
node: ASTNode,
token: KtModifierKeywordToken
) : FirModifier<ASTNode>(node, token)
class FirLightModifier(node: LighterASTNode, token: KtModifierKeywordToken) : FirModifier<LighterASTNode>(node, token)
class FirLightModifier(
node: LighterASTNode,
token: KtModifierKeywordToken,
val tree: FlyweightCapableTreeStructure<LighterASTNode>
) : FirModifier<LighterASTNode>(node, token)
val FirModifier<*>.psi: PsiElement? get() = (this as? FirPsiModifier)?.node?.psi
val FirModifier<*>.psi: PsiElement? get() = (this as? FirPsiModifier)?.node?.psi
val FirModifier<*>.lightNode: LighterASTNode? get() = (this as? FirLightModifier)?.node
val FirModifier<*>.source: FirSourceElement?
get() = when (this) {
is FirPsiModifier -> this.psi?.toFirSourceElement()
is FirLightModifier -> {
// TODO pretty sure I got offsets wrong here
val startOffset = tree.getStartOffset(node)
val endOffset = tree.getEndOffset(node)
FirLightSourceElement(node, startOffset, endOffset, tree)
}
}
@@ -7,7 +7,7 @@ annotation class A
@A
open class B1 {
@A
private open fun foo() {}
<!INCOMPATIBLE_MODIFIERS!>private<!> <!INCOMPATIBLE_MODIFIERS!>open<!> fun foo() {}
}
class D1 : B1() {
@@ -2,7 +2,7 @@ interface SuperInterface
open class SuperClass
abstract data class Base(val x: Int)
<!INCOMPATIBLE_MODIFIERS!>abstract<!> <!INCOMPATIBLE_MODIFIERS!>data<!> class Base(val x: Int)
class Derived: Base(42)
@@ -1,3 +0,0 @@
class Outer {
inner data class Inner(val x: Int)
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
class Outer {
<!INCOMPATIBLE_MODIFIERS!>inner<!> <!INCOMPATIBLE_MODIFIERS!>data<!> class Inner(val x: Int)
}
@@ -1 +1 @@
inner data class Outer(val x: Int)
<!INCOMPATIBLE_MODIFIERS!>inner<!> <!INCOMPATIBLE_MODIFIERS!>data<!> class Outer(val x: Int)
@@ -1,4 +0,0 @@
sealed data class My(val x: Int) {
object Your: My(1)
class His(y: Int): My(y)
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
<!INCOMPATIBLE_MODIFIERS!>sealed<!> <!INCOMPATIBLE_MODIFIERS!>data<!> class My(val x: Int) {
object Your: My(1)
class His(y: Int): My(y)
@@ -30,4 +30,4 @@ open inline class D1(val x: Int)
abstract inline class D2(val x: Int)
sealed inline class D3(val x: Int)
inline data class D4(val x: String)
<!INCOMPATIBLE_MODIFIERS!>inline<!> <!INCOMPATIBLE_MODIFIERS!>data<!> class D4(val x: String)
@@ -2,9 +2,9 @@
package illegal_modifiers
abstract class A() {
abstract final fun f()
abstract open fun g()
final open fun h() {}
<!INCOMPATIBLE_MODIFIERS!>abstract<!> <!INCOMPATIBLE_MODIFIERS!>final<!> fun f()
abstract <!REDUNDANT_MODIFIER!>open<!> fun g()
<!INCOMPATIBLE_MODIFIERS!>final<!> <!INCOMPATIBLE_MODIFIERS!>open<!> fun h() {}
open var r: String
get
@@ -21,8 +21,8 @@ class FinalClass() {
open set(v: Int) {}
}
private public class C
private public object D
<!INCOMPATIBLE_MODIFIERS!>private<!> <!INCOMPATIBLE_MODIFIERS!>public<!> class C
<!INCOMPATIBLE_MODIFIERS!>private<!> <!INCOMPATIBLE_MODIFIERS!>public<!> object D
//A sample annotation to check annotation usage in parameters.
annotation class annotated(val text: String = "not given")
@@ -32,8 +32,8 @@ class LegalModifier(val a: Int, @annotated private var b: String, @annotated var
//Check illegal modifier in constructor parameters
class IllegalModifiers1(
in
out
<!INCOMPATIBLE_MODIFIERS!>in<!>
<!INCOMPATIBLE_MODIFIERS!>out<!>
reified
enum
private
@@ -41,7 +41,7 @@ class IllegalModifiers1(
a: Int)
//Check multiple illegal modifiers in constructor
class IllegalModifiers2(private abstract a: Int)
class IllegalModifiers2(<!INCOMPATIBLE_MODIFIERS!>private<!> <!INCOMPATIBLE_MODIFIERS!>abstract<!> a: Int)
//Check annotations with illegal modifiers in constructor
@@ -65,10 +65,10 @@ abstract class IllegalModifiers5() {
private fun qux() {
//Check illegal modifier in catch block
try {} catch (in out reified enum public e: Exception) {}
try {} catch (<!INCOMPATIBLE_MODIFIERS!>in<!> <!INCOMPATIBLE_MODIFIERS!>out<!> reified enum public e: Exception) {}
//Check multiple illegal modifiers in catch block
try {} catch (in out reified enum abstract public e: Exception) {}
try {} catch (<!INCOMPATIBLE_MODIFIERS!>in<!> <!INCOMPATIBLE_MODIFIERS!>out<!> reified enum abstract public e: Exception) {}
//Check annotations with illegal modifiers
try {} catch (@annotated("a text") abstract public e: Exception) {}
@@ -92,24 +92,24 @@ abstract class IllegalModifiers6() {
// strange inappropriate modifiers usages
override
out
in
<!INCOMPATIBLE_MODIFIERS!>out<!>
<!INCOMPATIBLE_MODIFIERS!>in<!>
vararg
reified
class IllegalModifiers7() {
enum
inner
annotation
out
in
<!INCOMPATIBLE_MODIFIERS!>out<!>
<!INCOMPATIBLE_MODIFIERS!>in<!>
vararg
reified
val x = 1
enum
inner
annotation
out
in
<!INCOMPATIBLE_MODIFIERS!>out<!>
<!INCOMPATIBLE_MODIFIERS!>in<!>
vararg
reified
const
@@ -118,46 +118,46 @@ class IllegalModifiers7() {
// Secondary constructors
class IllegalModifiers8 {
abstract
<!INCOMPATIBLE_MODIFIERS!>abstract<!>
enum
open
<!INCOMPATIBLE_MODIFIERS, REDUNDANT_MODIFIER!>open<!>
inner
annotation
override
out
in
final
<!INCOMPATIBLE_MODIFIERS!>override<!>
<!INCOMPATIBLE_MODIFIERS!>out<!>
<!INCOMPATIBLE_MODIFIERS!>in<!>
<!INCOMPATIBLE_MODIFIERS!>final<!>
vararg
reified
const<!SYNTAX!><!>
<!INCOMPATIBLE_MODIFIERS!>const<!><!SYNTAX!><!>
constructor() {}
constructor(private enum abstract x: Int) {}
constructor(<!INCOMPATIBLE_MODIFIERS!>private<!> enum <!INCOMPATIBLE_MODIFIERS!>abstract<!> x: Int) {}
}
class IllegalModifiers9 {
private protected constructor() {}
private internal constructor(x: Int) {}
<!INCOMPATIBLE_MODIFIERS!>private<!> <!INCOMPATIBLE_MODIFIERS!>protected<!> constructor() {}
<!INCOMPATIBLE_MODIFIERS!>private<!> <!INCOMPATIBLE_MODIFIERS!>internal<!> constructor(x: Int) {}
}
// Illegal modifiers on primary constructor
class IllegalModifiers10
abstract
<!INCOMPATIBLE_MODIFIERS!>abstract<!>
enum
open
<!INCOMPATIBLE_MODIFIERS, REDUNDANT_MODIFIER!>open<!>
inner
annotation
override
out
in
final
<!INCOMPATIBLE_MODIFIERS!>override<!>
<!INCOMPATIBLE_MODIFIERS!>out<!>
<!INCOMPATIBLE_MODIFIERS!>in<!>
<!INCOMPATIBLE_MODIFIERS!>final<!>
vararg
reified
const constructor()
<!INCOMPATIBLE_MODIFIERS!>const<!> constructor()
class IllegalModifiers11 private protected constructor()
class IllegalModifiers11 <!INCOMPATIBLE_MODIFIERS!>private<!> <!INCOMPATIBLE_MODIFIERS!>protected<!> constructor()
class Outer {
inner sealed class Inner
<!INCOMPATIBLE_MODIFIERS!>inner<!> <!INCOMPATIBLE_MODIFIERS!>sealed<!> class Inner
}
@@ -15,9 +15,9 @@ object A {
class B(const val constructor: Int = 5)
abstract class C {
open const val x: Int = 6
<!INCOMPATIBLE_MODIFIERS!>open<!> <!INCOMPATIBLE_MODIFIERS!>const<!> val x: Int = 6
abstract const val y: Int = 7
<!INCOMPATIBLE_MODIFIERS!>abstract<!> <!INCOMPATIBLE_MODIFIERS!>const<!> val y: Int = 7
companion object {
const val inCompaionObject = 8
@@ -25,7 +25,7 @@ abstract class C {
}
object D : C() {
override const val x: Int = 9
<!INCOMPATIBLE_MODIFIERS!>override<!> <!INCOMPATIBLE_MODIFIERS!>const<!> val x: Int = 9
const val inObject = 10
@@ -1,8 +1,8 @@
// !DIAGNOSTICS: -UNUSED_VARIABLE
interface Foo<T>
interface Foo1<in out T>
interface Foo2<in in T>
interface Foo1<<!INCOMPATIBLE_MODIFIERS!>in<!> <!INCOMPATIBLE_MODIFIERS!>out<!> T>
interface Foo2<in <!REPEATED_MODIFIER!>in<!> T>
fun test1(foo: Foo<in out Int>) = foo
fun test2(): Foo<in in Int> = throw Exception()
@@ -10,11 +10,11 @@ fun test2(): Foo<in in Int> = throw Exception()
fun test3() {
val f: Foo<out out out out Int>
class Bzz<in in T>
class Bzz<in <!REPEATED_MODIFIER!>in<!> T>
}
class A {
fun <out out T> bar() {
fun <out <!REPEATED_MODIFIER!>out<!> T> bar() {
}
}
@@ -8,13 +8,13 @@ fun gav(noinline x: (Int) -> Unit, crossinline y: (String) -> Int) {}
inline fun correct(noinline x: (Int) -> Unit, crossinline y: (String) -> Int) {}
inline fun incompatible(noinline crossinline x: () -> String) {}
inline fun incompatible(<!INCOMPATIBLE_MODIFIERS!>noinline<!> <!INCOMPATIBLE_MODIFIERS!>crossinline<!> x: () -> String) {}
class FunctionSubtype : () -> Unit {
override fun invoke() {}
}
inline fun functionSubtype(
noinline f: FunctionSubtype,
crossinline g: FunctionSubtype
noinline f: FunctionSubtype,
crossinline g: FunctionSubtype
) { }
@@ -19,30 +19,30 @@ class A : (vararg Int)->Unit {
get(): (vararg x: Int)->Unit = {}
}
val allProhibited: (abstract
val allProhibited: (<!INCOMPATIBLE_MODIFIERS, REDUNDANT_MODIFIER!>abstract<!>
annotation
companion
const
crossinline
data
<!INCOMPATIBLE_MODIFIERS!>const<!>
<!INCOMPATIBLE_MODIFIERS!>crossinline<!>
<!INCOMPATIBLE_MODIFIERS!>data<!>
enum
external
final
in
inline
inner
internal
<!INCOMPATIBLE_MODIFIERS!>final<!>
<!INCOMPATIBLE_MODIFIERS!>in<!>
<!INCOMPATIBLE_MODIFIERS!>inline<!>
<!INCOMPATIBLE_MODIFIERS!>inner<!>
<!INCOMPATIBLE_MODIFIERS!>internal<!>
lateinit
noinline
open
<!INCOMPATIBLE_MODIFIERS!>noinline<!>
<!INCOMPATIBLE_MODIFIERS, REDUNDANT_MODIFIER!>open<!>
operator
out
override
private
protected
public
<!INCOMPATIBLE_MODIFIERS!>out<!>
<!INCOMPATIBLE_MODIFIERS!>override<!>
<!INCOMPATIBLE_MODIFIERS!>private<!>
<!INCOMPATIBLE_MODIFIERS!>protected<!>
<!INCOMPATIBLE_MODIFIERS!>public<!>
reified
sealed
<!INCOMPATIBLE_MODIFIERS!>sealed<!>
tailrec
vararg
@@ -1,10 +1,10 @@
interface My {
open fun foo()
open fun bar() {}
open abstract fun baz(): Int
<!REDUNDANT_MODIFIER!>open<!> abstract fun baz(): Int
open val x: Int
open val y: String
get() = ""
open abstract val z: Double
<!REDUNDANT_MODIFIER!>open<!> abstract val z: Double
}
@@ -1,6 +1,6 @@
interface My {
private val x: Int
private abstract val xx: Int
<!INCOMPATIBLE_MODIFIERS!>private<!> <!INCOMPATIBLE_MODIFIERS!>abstract<!> val xx: Int
private val xxx: Int
get() = 0
final val y: Int
@@ -1,41 +1,41 @@
abstract abstract class Foo
public public class Bar
open open final class Baz {
private private fun foo() {}
abstract <!REPEATED_MODIFIER!>abstract<!> class Foo
public <!REPEATED_MODIFIER!>public<!> class Bar
<!INCOMPATIBLE_MODIFIERS!>open<!> <!REPEATED_MODIFIER!>open<!> <!INCOMPATIBLE_MODIFIERS!>final<!> class Baz {
private <!REPEATED_MODIFIER!>private<!> fun foo() {}
}
class Bzz(public public val q: Int = 1) {
public public val x: Int = 2
class Bzz(public <!REPEATED_MODIFIER!>public<!> val q: Int = 1) {
public <!REPEATED_MODIFIER!>public<!> val x: Int = 2
public val y: Int
public public get() = 3
public <!REPEATED_MODIFIER!>public<!> get() = 3
val z: Int
open final get() = 4
<!INCOMPATIBLE_MODIFIERS!>open<!> <!INCOMPATIBLE_MODIFIERS!>final<!> get() = 4
public public class B(public public val z: Int = 1) {
public public val y: Int = 2
public <!REPEATED_MODIFIER!>public<!> class B(public <!REPEATED_MODIFIER!>public<!> val z: Int = 1) {
public <!REPEATED_MODIFIER!>public<!> val y: Int = 2
public val x: Int
public public get() = 3
public <!REPEATED_MODIFIER!>public<!> get() = 3
}
public public object C {
public public val y: Int = 1
public public fun z(): Int = 1
public <!REPEATED_MODIFIER!>public<!> object C {
public <!REPEATED_MODIFIER!>public<!> val y: Int = 1
public <!REPEATED_MODIFIER!>public<!> fun z(): Int = 1
}
}
public public val bar: Int = 1
public <!REPEATED_MODIFIER!>public<!> val bar: Int = 1
public public fun foo(): Int = 1
public <!REPEATED_MODIFIER!>public<!> fun foo(): Int = 1
fun test() {
public public class B(public public val z: Int = 1) {
public public val y: Int = 2
public <!REPEATED_MODIFIER!>public<!> class B(public <!REPEATED_MODIFIER!>public<!> val z: Int = 1) {
public <!REPEATED_MODIFIER!>public<!> val y: Int = 2
public val x: Int
public public get() = 3
public <!REPEATED_MODIFIER!>public<!> get() = 3
}
}
@@ -1,13 +1,13 @@
// !LANGUAGE: +MultiPlatformProjects
header impl class First
<!INCOMPATIBLE_MODIFIERS!>header<!> <!INCOMPATIBLE_MODIFIERS!>impl<!> class First
header expect class Second
<!INCOMPATIBLE_MODIFIERS!>header<!> <!INCOMPATIBLE_MODIFIERS!>expect<!> class Second
header actual class Third
<!INCOMPATIBLE_MODIFIERS!>header<!> <!INCOMPATIBLE_MODIFIERS!>actual<!> class Third
impl expect class Fourth
<!INCOMPATIBLE_MODIFIERS!>impl<!> <!INCOMPATIBLE_MODIFIERS!>expect<!> class Fourth
impl actual class Fifth
<!INCOMPATIBLE_MODIFIERS!>impl<!> <!INCOMPATIBLE_MODIFIERS!>actual<!> class Fifth
expect actual class Sixth
<!INCOMPATIBLE_MODIFIERS!>expect<!> <!INCOMPATIBLE_MODIFIERS!>actual<!> class Sixth
@@ -11,7 +11,7 @@ public abstract class TestBug1() {
position(m1().second)
}
private abstract fun m1(): Pair<Int, Int>
<!INCOMPATIBLE_MODIFIERS!>private<!> <!INCOMPATIBLE_MODIFIERS!>abstract<!> fun m1(): Pair<Int, Int>
private fun position(p: Int) {}
@@ -19,6 +19,6 @@ public abstract class TestBug1() {
//from library
public class Pair<out A, out B> (
public val first: A,
public val second: B
public val first: A,
public val second: B
)
@@ -14,7 +14,7 @@ class C : A() {
//------------
open class D {
private open fun self() : D = this
<!INCOMPATIBLE_MODIFIERS!>private<!> <!INCOMPATIBLE_MODIFIERS!>open<!> fun self() : D = this
}
class E : D() {
+1 -1
View File
@@ -32,7 +32,7 @@ class E : C(), T {
}
class F : C(), T {
private override fun foo() {}
<!INCOMPATIBLE_MODIFIERS!>private<!> <!INCOMPATIBLE_MODIFIERS!>override<!> fun foo() {}
}
class G : C(), T {
@@ -1,3 +0,0 @@
final sealed class Base {
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
<!INCOMPATIBLE_MODIFIERS!>final<!> <!INCOMPATIBLE_MODIFIERS!>sealed<!> class Base {
}
@@ -1,3 +0,0 @@
open sealed class Base {
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
<!INCOMPATIBLE_MODIFIERS!>open<!> <!INCOMPATIBLE_MODIFIERS!>sealed<!> class Base {
}
@@ -1,3 +0,0 @@
abstract sealed class Base {
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
<!REDUNDANT_MODIFIER!>abstract<!> sealed class Base {
}
@@ -4,7 +4,7 @@ interface Allowed
open class NotAllowed
abstract data class Base(val x: Int)
<!INCOMPATIBLE_MODIFIERS!>abstract<!> <!INCOMPATIBLE_MODIFIERS!>data<!> class Base(val x: Int)
class Derived: Base(42)