[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)
}
}