[FIR JS] Implement FirJsRuntimeAnnotationChecker

This commit is contained in:
Nikolay Lunyak
2023-01-04 16:29:19 +02:00
committed by Space Team
parent 56285bb2d6
commit 7b8f5f9980
12 changed files with 97 additions and 69 deletions
@@ -1007,6 +1007,18 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirJsErrors.RUNTIME_ANNOTATION_NOT_SUPPORTED) { firDiagnostic ->
RuntimeAnnotationNotSupportedImpl(
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirJsErrors.RUNTIME_ANNOTATION_ON_EXTERNAL_DECLARATION) { firDiagnostic ->
RuntimeAnnotationOnExternalDeclarationImpl(
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirErrors.OPT_IN_USAGE) { firDiagnostic ->
OptInUsageImpl(
firDiagnostic.a,
@@ -724,6 +724,14 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = NestedJsModuleProhibited::class
}
abstract class RuntimeAnnotationNotSupported : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = RuntimeAnnotationNotSupported::class
}
abstract class RuntimeAnnotationOnExternalDeclaration : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = RuntimeAnnotationOnExternalDeclaration::class
}
abstract class OptInUsage : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = OptInUsage::class
abstract val optInMarkerFqName: FqName
@@ -870,6 +870,16 @@ internal class NestedJsModuleProhibitedImpl(
override val token: KtLifetimeToken,
) : KtFirDiagnostic.NestedJsModuleProhibited(), KtAbstractFirDiagnostic<KtElement>
internal class RuntimeAnnotationNotSupportedImpl(
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.RuntimeAnnotationNotSupported(), KtAbstractFirDiagnostic<PsiElement>
internal class RuntimeAnnotationOnExternalDeclarationImpl(
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.RuntimeAnnotationOnExternalDeclaration(), KtAbstractFirDiagnostic<PsiElement>
internal class OptInUsageImpl(
override val optInMarkerFqName: FqName,
override val message: String,
@@ -22,6 +22,8 @@ object JS_DIAGNOSTICS_LIST : DiagnosticList("FirJsErrors") {
val JS_MODULE_PROHIBITED_ON_VAR by error<KtElement>(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT)
val JS_MODULE_PROHIBITED_ON_NON_NATIVE by error<KtElement>(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT)
val NESTED_JS_MODULE_PROHIBITED by error<KtElement>(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT)
val RUNTIME_ANNOTATION_NOT_SUPPORTED by warning<PsiElement>(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT)
val RUNTIME_ANNOTATION_ON_EXTERNAL_DECLARATION by error<PsiElement>(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT)
}
val SUPERTYPES by object : DiagnosticGroup("Supertypes") {
@@ -26,6 +26,8 @@ object FirJsErrors {
val JS_MODULE_PROHIBITED_ON_VAR by error0<KtElement>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
val JS_MODULE_PROHIBITED_ON_NON_NATIVE by error0<KtElement>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
val NESTED_JS_MODULE_PROHIBITED by error0<KtElement>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
val RUNTIME_ANNOTATION_NOT_SUPPORTED by warning0<PsiElement>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
val RUNTIME_ANNOTATION_ON_EXTERNAL_DECLARATION by error0<PsiElement>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
// Supertypes
val WRONG_MULTIPLE_INHERITANCE by error1<KtElement, FirCallableSymbol<*>>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
@@ -17,6 +17,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_MODULE_PR
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.NESTED_JS_MODULE_PROHIBITED
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.OVERRIDING_EXTERNAL_FUN_WITH_OPTIONAL_PARAMS
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.OVERRIDING_EXTERNAL_FUN_WITH_OPTIONAL_PARAMS_WITH_FAKE
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.RUNTIME_ANNOTATION_NOT_SUPPORTED
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.RUNTIME_ANNOTATION_ON_EXTERNAL_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_JS_QUALIFIER
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_MULTIPLE_INHERITANCE
@@ -44,6 +46,11 @@ object FirJsErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
FirDiagnosticRenderers.SYMBOL
)
map.put(CALL_TO_DEFINED_EXTERNALLY_FROM_NON_EXTERNAL_DECLARATION, "This property can only be used from external declarations")
map.put(RUNTIME_ANNOTATION_ON_EXTERNAL_DECLARATION, "Runtime annotation can't be put on external declaration")
map.put(
RUNTIME_ANNOTATION_NOT_SUPPORTED,
"Reflection is not supported in JavaScript target, therefore you won't be able to read this annotation in run-time"
)
map.checkMissingMessages(FirJsErrors)
}
@@ -23,6 +23,7 @@ object JsDeclarationCheckers : DeclarationCheckers() {
override val basicDeclarationCheckers: Set<FirBasicDeclarationChecker>
get() = setOf(
FirJsModuleChecker,
FirJsRuntimeAnnotationChecker,
)
override val classCheckers: Set<FirClassChecker>
@@ -0,0 +1,33 @@
/*
* Copyright 2010-2023 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.js.checkers.declaration
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirBasicDeclarationChecker
import org.jetbrains.kotlin.fir.analysis.checkers.getExplicitAnnotationRetention
import org.jetbrains.kotlin.fir.analysis.js.checkers.isEffectivelyExternal
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
import org.jetbrains.kotlin.fir.expressions.coneClassLikeType
import org.jetbrains.kotlin.fir.resolve.toSymbol
object FirJsRuntimeAnnotationChecker : FirBasicDeclarationChecker() {
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
for (annotation in declaration.annotations) {
val annotationClass = annotation.coneClassLikeType?.lookupTag?.toSymbol(context.session) ?: continue
if (annotationClass.getExplicitAnnotationRetention(context.session) != AnnotationRetention.RUNTIME) continue
if (declaration is FirMemberDeclaration && declaration.symbol.isEffectivelyExternal(context)) {
reporter.reportOn(annotation.source, FirJsErrors.RUNTIME_ANNOTATION_ON_EXTERNAL_DECLARATION, context)
} else {
reporter.reportOn(annotation.source, FirJsErrors.RUNTIME_ANNOTATION_NOT_SUPPORTED, context)
}
}
}
}
@@ -10,19 +10,18 @@ import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirAnnotationContainer
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.context.findClosest
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.impl.FirPrimaryConstructor
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.languageVersionSettings
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.references.resolved
import org.jetbrains.kotlin.fir.references.toResolvedEnumEntrySymbol
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
@@ -36,12 +35,15 @@ fun FirRegularClass.getRetention(session: FirSession): AnnotationRetention {
return getRetentionAnnotation(session)?.getRetention() ?: AnnotationRetention.RUNTIME
}
fun FirAnnotation.getRetention(): AnnotationRetention {
val retentionArgument = findArgumentByName(ParameterNames.retentionValue) as? FirQualifiedAccessExpression
?: return AnnotationRetention.RUNTIME
val retentionName = retentionArgument.calleeReference.resolved?.name?.asString()
?: return AnnotationRetention.RUNTIME
return AnnotationRetention.values().firstOrNull { it.name == retentionName } ?: AnnotationRetention.RUNTIME
private fun FirAnnotation.getRetention(): AnnotationRetention? {
val propertyAccess = findArgumentByName(ParameterNames.retentionValue) as? FirQualifiedAccessExpression
val callableId = propertyAccess?.calleeReference?.toResolvedEnumEntrySymbol()?.callableId ?: return null
if (callableId.classId != StandardClassIds.AnnotationRetention) {
return null
}
return AnnotationRetention.values().firstOrNull { it.name == callableId.callableName.asString() }
}
private val defaultAnnotationTargets = KotlinTarget.DEFAULT_TARGET_SET
@@ -141,8 +143,12 @@ fun FirClassLikeSymbol<*>.containsRepeatableAnnotation(session: FirSession): Boo
return false
}
fun FirClassLikeSymbol<*>.getExplicitAnnotationRetention(session: FirSession): AnnotationRetention? {
return getAnnotationByClassId(StandardClassIds.Annotations.Retention, session)?.getRetention()
}
fun FirClassLikeSymbol<*>.getAnnotationRetention(session: FirSession): AnnotationRetention {
return getAnnotationByClassId(StandardClassIds.Annotations.Retention, session)?.getRetention() ?: AnnotationRetention.RUNTIME
return getExplicitAnnotationRetention(session) ?: AnnotationRetention.RUNTIME
}
fun FirAnnotationContainer.getDefaultUseSiteTarget(
@@ -56,6 +56,10 @@ fun FirReference.toResolvedConstructorSymbol(discardErrorReference: Boolean = fa
return this.toResolvedSymbol<FirConstructorSymbol>(discardErrorReference)
}
fun FirReference.toResolvedEnumEntrySymbol(discardErrorReference: Boolean = false): FirEnumEntrySymbol? {
return this.toResolvedSymbol<FirEnumEntrySymbol>(discardErrorReference)
}
@OptIn(ExperimentalContracts::class)
fun FirReference.isError(): Boolean {
contract {
@@ -1,59 +0,0 @@
@Retention(AnnotationRetention.BINARY)
annotation class X
@Retention(AnnotationRetention.RUNTIME)
annotation class Y
@X
external class A {
@X
fun f()
@X
val p: Int
@get:X
val r: Int
}
@Y
external class B {
@Y
fun f()
@Y
val p: Int
@get:Y
val r: Int
}
@X
class C {
@X
fun f() {}
@X
val p: Int = 0
val q: Int
@X get() = 0
@get:X
val r: Int = 0
}
@Y
class D {
@Y
fun f() {}
@Y
val p: Int = 0
val q: Int
@Y get() = 0
@get:Y
val r: Int = 0
}
@@ -1,3 +1,5 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: +RUNTIME_ANNOTATION_NOT_SUPPORTED
@Retention(AnnotationRetention.BINARY)
annotation class X