From 0af43757e322d471b2822ae2ef583b46042cc232 Mon Sep 17 00:00:00 2001 From: Ivan Kochurkin Date: Sun, 3 Jul 2022 22:33:02 +0300 Subject: [PATCH] [FIR] Implement FirNativeIdentifierChecker * INVALID_CHARACTERS_NATIVE --- .../diagnostics/FirNativeDiagnosticsList.kt | 9 +++ .../diagnostics/native/FirNativeErrors.kt | 4 ++ .../native/FirNativeErrorsDefaultMessages.kt | 2 + .../checkers/FirNativeIdentifierChecker.kt | 55 +++++++++++++++++++ .../checkers/NativeDeclarationCheckers.kt | 3 +- .../LightTreePositioningStrategies.kt | 32 ++++++++++- .../diagnostics/PositioningStrategies.kt | 5 ++ .../nativeTests/identifiers.fir.kt | 48 ++++++++-------- 8 files changed, 131 insertions(+), 27 deletions(-) create mode 100644 compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeIdentifierChecker.kt diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirNativeDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirNativeDiagnosticsList.kt index 6a9c36b03c9..9c26a1c5779 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirNativeDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirNativeDiagnosticsList.kt @@ -5,8 +5,11 @@ package org.jetbrains.kotlin.fir.checkers.generator.diagnostics +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.fir.PrivateForInline import org.jetbrains.kotlin.fir.checkers.generator.diagnostics.model.DiagnosticList +import org.jetbrains.kotlin.fir.checkers.generator.diagnostics.model.PositioningStrategy import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtDeclaration @@ -30,5 +33,11 @@ object NATIVE_DIAGNOSTICS_LIST : DiagnosticList("FirNativeErrors") { val INAPPLICABLE_SHARED_IMMUTABLE_TOP_LEVEL by error() val INAPPLICABLE_THREAD_LOCAL by error() val INAPPLICABLE_THREAD_LOCAL_TOP_LEVEL by error() + val INVALID_CHARACTERS_NATIVE by deprecationError( + LanguageFeature.ProhibitInvalidCharsInNativeIdentifiers, + PositioningStrategy.NAME_IDENTIFIER + ) { + parameter("message") + } } } \ No newline at end of file diff --git a/compiler/fir/checkers/checkers.native/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrors.kt b/compiler/fir/checkers/checkers.native/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrors.kt index 999edcfc298..d14dbe5f7a1 100644 --- a/compiler/fir/checkers/checkers.native/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrors.kt +++ b/compiler/fir/checkers/checkers.native/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrors.kt @@ -5,7 +5,10 @@ package org.jetbrains.kotlin.fir.analysis.diagnostics.native +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.config.LanguageFeature.ProhibitInvalidCharsInNativeIdentifiers import org.jetbrains.kotlin.diagnostics.* +import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory import org.jetbrains.kotlin.fir.analysis.diagnostics.* import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol @@ -28,6 +31,7 @@ object FirNativeErrors { val INAPPLICABLE_SHARED_IMMUTABLE_TOP_LEVEL by error0() val INAPPLICABLE_THREAD_LOCAL by error0() val INAPPLICABLE_THREAD_LOCAL_TOP_LEVEL by error0() + val INVALID_CHARACTERS_NATIVE by deprecationError1(ProhibitInvalidCharsInNativeIdentifiers, SourceElementPositioningStrategies.NAME_IDENTIFIER) init { RootDiagnosticRendererFactory.registerFactory(FirNativeErrorsDefaultMessages) diff --git a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrorsDefaultMessages.kt b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrorsDefaultMessages.kt index bce1b85a91d..3d3d33c92b6 100644 --- a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrorsDefaultMessages.kt +++ b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrorsDefaultMessages.kt @@ -17,6 +17,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAP import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_THREAD_LOCAL_TOP_LEVEL import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INCOMPATIBLE_THROWS_INHERITED import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INCOMPATIBLE_THROWS_OVERRIDE +import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVALID_CHARACTERS_NATIVE import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.MISSING_EXCEPTION_IN_THROWS_ON_SUSPEND import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.THROWS_LIST_EMPTY @@ -39,6 +40,7 @@ object FirNativeErrorsDefaultMessages : BaseDiagnosticRendererFactory() { "@ThreadLocal is applicable only to property with backing field, to property with delegation or to objects" ) map.put(INAPPLICABLE_THREAD_LOCAL_TOP_LEVEL, "@ThreadLocal is applicable only to top level declarations") + map.put(INVALID_CHARACTERS_NATIVE, "Name {0}", TO_STRING) map.checkMissingMessages(FirNativeErrors) } diff --git a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeIdentifierChecker.kt b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeIdentifierChecker.kt new file mode 100644 index 00000000000..0dcc854fc9c --- /dev/null +++ b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeIdentifierChecker.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2010-2022 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.native.checkers + +import org.jetbrains.kotlin.KtFakeSourceElementKind +import org.jetbrains.kotlin.KtSourceElement +import org.jetbrains.kotlin.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.diagnostics.reportOn +import org.jetbrains.kotlin.fir.analysis.checkers.SourceNavigator +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirBasicDeclarationChecker +import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors +import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.name.Name + +object FirNativeIdentifierChecker : FirBasicDeclarationChecker() { + // Also includes characters used by IR mangler (see MangleConstant). + private val invalidChars = setOf( + '.', ';', ',', '(', ')', '[', ']', '{', '}', '/', '<', '>', + ':', '\\', '$', '&', '~', '*', '?', '#', '|', '§', '%', '@', + ) + + override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) { + val source = declaration.source + when (declaration) { + is FirRegularClass -> checkNameAndReport(declaration.name, source, context, reporter) + is FirSimpleFunction -> checkNameAndReport(declaration.name, source, context, reporter) + is FirTypeParameter -> checkNameAndReport(declaration.name, source, context, reporter) + is FirProperty -> checkNameAndReport(declaration.name, source, context, reporter) + is FirTypeAlias -> checkNameAndReport(declaration.name, source, context, reporter) + is FirValueParameter -> checkNameAndReport(declaration.name, source, context, reporter) + is FirEnumEntry -> checkNameAndReport(declaration.name, source, context, reporter) + else -> return + } + } + + private fun checkNameAndReport(name: Name, source: KtSourceElement?, context: CheckerContext, reporter: DiagnosticReporter) { + if (source != null && source.kind !is KtFakeSourceElementKind && !name.isSpecial) { + val text = name.asString() + val message = when { + text.isEmpty() -> "should not be empty" + text.any { it in invalidChars } -> "contains illegal characters: " + + invalidChars.intersect(text.toSet()).joinToString("", prefix = "\"", postfix = "\"") + else -> null + } + + if (message != null) { + reporter.reportOn(source, FirNativeErrors.INVALID_CHARACTERS_NATIVE, message, context) + } + } + } +} \ No newline at end of file diff --git a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/NativeDeclarationCheckers.kt b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/NativeDeclarationCheckers.kt index 1d1bcbd9f7d..c7a58dd1744 100644 --- a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/NativeDeclarationCheckers.kt +++ b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/NativeDeclarationCheckers.kt @@ -13,6 +13,7 @@ object NativeDeclarationCheckers : DeclarationCheckers() { get() = setOf( FirNativeThrowsChecker, FirNativeSharedImmutableChecker, - FirNativeThreadLocalChecker + FirNativeThreadLocalChecker, + FirNativeIdentifierChecker ) } \ No newline at end of file diff --git a/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/LightTreePositioningStrategies.kt b/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/LightTreePositioningStrategies.kt index 747f02c4381..777c1c0335e 100644 --- a/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/LightTreePositioningStrategies.kt +++ b/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/LightTreePositioningStrategies.kt @@ -855,6 +855,14 @@ object LightTreePositioningStrategies { if (node.tokenType == KtNodeTypes.LABEL_QUALIFIER) { return super.mark(node, startOffset, endOffset - 1, tree) } + if (node.tokenType == KtNodeTypes.PACKAGE_DIRECTIVE) { + val referenceExpression = tree.findLastDescendant(node) { + it.tokenType == KtNodeTypes.REFERENCE_EXPRESSION + } + if (referenceExpression != null) { + return markElement(referenceExpression, startOffset, endOffset, tree, node) + } + } return DEFAULT.mark(node, startOffset, endOffset, tree) } } @@ -1449,8 +1457,28 @@ fun FlyweightCapableTreeStructure.findFirstDescendant( ): LighterASTNode? { val childrenRef = Ref>() getChildren(node, childrenRef) - return childrenRef.get()?.firstOrNull { it != null && predicate(it) } - ?: childrenRef.get()?.firstNotNullOfOrNull { child -> child?.let { findFirstDescendant(it, predicate) } } + val nodes = childrenRef.get() + return nodes?.firstOrNull { it != null && predicate(it) } + ?: nodes?.firstNotNullOfOrNull { child -> child?.let { findFirstDescendant(it, predicate) } } +} + +fun FlyweightCapableTreeStructure.findLastDescendant( + node: LighterASTNode, + predicate: (LighterASTNode) -> Boolean +): LighterASTNode? { + val childrenRef = Ref>() + getChildren(node, childrenRef) + val nodes = childrenRef.get() + return nodes?.lastOrNull { it != null && predicate(it) } + ?: run { + for (child in nodes.reversed()) { + val result = child?.let { findLastDescendant(it, predicate) } + if (result != null) { + return result + } + } + return null + } } fun FlyweightCapableTreeStructure.collectDescendantsOfType( diff --git a/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/PositioningStrategies.kt b/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/PositioningStrategies.kt index c66bd4202f4..9e4f16ca553 100644 --- a/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/PositioningStrategies.kt +++ b/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/PositioningStrategies.kt @@ -847,6 +847,11 @@ object PositioningStrategies { } } else if (element is KtLabelReferenceExpression) { return super.mark(element.getReferencedNameElement()) + } else if (element is KtPackageDirective) { + val nameIdentifier = element.nameIdentifier + if (nameIdentifier != null) { + return super.mark(nameIdentifier) + } } return DEFAULT.mark(element) diff --git a/compiler/testData/diagnostics/nativeTests/identifiers.fir.kt b/compiler/testData/diagnostics/nativeTests/identifiers.fir.kt index a9c275ac3de..afb8202a9f8 100644 --- a/compiler/testData/diagnostics/nativeTests/identifiers.fir.kt +++ b/compiler/testData/diagnostics/nativeTests/identifiers.fir.kt @@ -8,61 +8,61 @@ package `check.pkg` // FILE: 2.kt package totally.normal.pkg -class `Check.Class` +class `Check.Class` class NormalClass { - fun `check$member`() {} + fun `check$member`() {} } -object `Check;Object` +object `Check;Object` object NormalObject -data class Pair(val first: Int, val `next,one`: Int) +data class Pair(val first: Int, val `next,one`: Int) object Delegate { operator fun getValue(thisRef: Any?, property: kotlin.reflect.KProperty<*>): Any? = null } -fun `check(function`() { - val `check)variable` = 1 - val `check[delegated[variable` by Delegate +fun `check(function`() { + val `check)variable` = 1 + val `check[delegated[variable` by Delegate val normalVariable = 2 val normalDelegatedVariable by Delegate - val (check, `destructuring]declaration`) = Pair(1, 2) + val (check, `destructuring]declaration`) = Pair(1, 2) } fun normalFunction() {} -val `check{property` = 1 -val `check}delegated}property` by Delegate +val `check{property` = 1 +val `check}delegated}property` by Delegate val normalProperty = 2 val normalDelegatedProperty by Delegate -fun checkValueParameter(`check/parameter`: Int) {} +fun checkValueParameter(`check/parameter`: Int) {} -fun <`check checkTypeParameter() {} +fun <`check, normalTypeParameter> checkTypeParameter() {} -enum class `Check>Enum>Entry` { - `CHECK:ENUM:ENTRY`; +enum class `Check>Enum>Entry` { + `CHECK:ENUM:ENTRY`; } -typealias `check\typealias` = Any +typealias `check\typealias` = Any -fun `check&`() {} +fun `check&`() {} -fun `check~`() {} +fun `check~`() {} -fun `check*`() {} +fun `check*`() {} -fun `check?`() {} +fun `check?`() {} -fun `check#`() {} +fun `check#`() {} -fun `check|`() {} +fun `check|`() {} -fun `check§`() {} +fun `check§`() {} -fun `check%`() {} +fun `check%`() {} -fun `check@`() {} +fun `check@`() {}