[FIR JS] Report rtti-related diagnostics

This commit is contained in:
Nikolay Lunyak
2023-01-26 10:52:33 +02:00
parent 351588f0a7
commit 2b5d2dc539
18 changed files with 159 additions and 25 deletions
@@ -4960,6 +4960,27 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirJsErrors.CANNOT_CHECK_FOR_EXTERNAL_INTERFACE) { firDiagnostic ->
CannotCheckForExternalInterfaceImpl(
firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.a),
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirJsErrors.UNCHECKED_CAST_TO_EXTERNAL_INTERFACE) { firDiagnostic ->
UncheckedCastToExternalInterfaceImpl(
firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.a),
firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.b),
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirJsErrors.EXTERNAL_INTERFACE_AS_CLASS_LITERAL) { firDiagnostic ->
ExternalInterfaceAsClassLiteralImpl(
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirJsErrors.DELEGATION_BY_DYNAMIC) { firDiagnostic ->
DelegationByDynamicImpl(
firDiagnostic as KtPsiDiagnostic,
@@ -3447,6 +3447,21 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
abstract val type: KtType
}
abstract class CannotCheckForExternalInterface : KtFirDiagnostic<KtElement>() {
override val diagnosticClass get() = CannotCheckForExternalInterface::class
abstract val targetType: KtType
}
abstract class UncheckedCastToExternalInterface : KtFirDiagnostic<KtElement>() {
override val diagnosticClass get() = UncheckedCastToExternalInterface::class
abstract val sourceType: KtType
abstract val targetType: KtType
}
abstract class ExternalInterfaceAsClassLiteral : KtFirDiagnostic<KtElement>() {
override val diagnosticClass get() = ExternalInterfaceAsClassLiteral::class
}
abstract class DelegationByDynamic : KtFirDiagnostic<KtElement>() {
override val diagnosticClass get() = DelegationByDynamic::class
}
@@ -4172,6 +4172,24 @@ internal class NonExternalDeclarationInInappropriateFileImpl(
override val token: KtLifetimeToken,
) : KtFirDiagnostic.NonExternalDeclarationInInappropriateFile(), KtAbstractFirDiagnostic<KtElement>
internal class CannotCheckForExternalInterfaceImpl(
override val targetType: KtType,
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.CannotCheckForExternalInterface(), KtAbstractFirDiagnostic<KtElement>
internal class UncheckedCastToExternalInterfaceImpl(
override val sourceType: KtType,
override val targetType: KtType,
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.UncheckedCastToExternalInterface(), KtAbstractFirDiagnostic<KtElement>
internal class ExternalInterfaceAsClassLiteralImpl(
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.ExternalInterfaceAsClassLiteral(), KtAbstractFirDiagnostic<KtElement>
internal class DelegationByDynamicImpl(
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
@@ -86,6 +86,14 @@ object JS_DIAGNOSTICS_LIST : DiagnosticList("FirJsErrors") {
val NON_EXTERNAL_DECLARATION_IN_INAPPROPRIATE_FILE by error<KtElement>(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT) {
parameter<ConeKotlinType>("type")
}
val CANNOT_CHECK_FOR_EXTERNAL_INTERFACE by error<KtElement> {
parameter<ConeKotlinType>("targetType")
}
val UNCHECKED_CAST_TO_EXTERNAL_INTERFACE by warning<KtElement> {
parameter<ConeKotlinType>("sourceType")
parameter<ConeKotlinType>("targetType")
}
val EXTERNAL_INTERFACE_AS_CLASS_LITERAL by error<KtElement>()
}
val DYNAMICS by object : DiagnosticGroup("Dynamics") {
@@ -75,6 +75,9 @@ object FirJsErrors {
val EXTENSION_FUNCTION_IN_EXTERNAL_DECLARATION by error0<KtElement>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
val NON_ABSTRACT_MEMBER_OF_EXTERNAL_INTERFACE by error0<KtExpression>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
val NON_EXTERNAL_DECLARATION_IN_INAPPROPRIATE_FILE by error1<KtElement, ConeKotlinType>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
val CANNOT_CHECK_FOR_EXTERNAL_INTERFACE by error1<KtElement, ConeKotlinType>()
val UNCHECKED_CAST_TO_EXTERNAL_INTERFACE by warning2<KtElement, ConeKotlinType, ConeKotlinType>()
val EXTERNAL_INTERFACE_AS_CLASS_LITERAL by error0<KtElement>()
// Dynamics
val DELEGATION_BY_DYNAMIC by error0<KtElement>()
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.diagnostics.rendering.CommonRenderers
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers
import org.jetbrains.kotlin.fir.analysis.diagnostics.checkMissingMessages
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.CALL_TO_DEFINED_EXTERNALLY_FROM_NON_EXTERNAL_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.CANNOT_CHECK_FOR_EXTERNAL_INTERFACE
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.EXTENSION_FUNCTION_IN_EXTERNAL_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.EXTERNAL_ANONYMOUS_INITIALIZER
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.EXTERNAL_CLASS_CONSTRUCTOR_PROPERTY_PARAMETER
@@ -20,6 +21,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.EXTERNAL_DEL
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.EXTERNAL_ENUM_ENTRY_WITH_BODY
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.EXTERNAL_TYPE_EXTENDS_NON_EXTERNAL_TYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.DELEGATION_BY_DYNAMIC
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.EXTERNAL_INTERFACE_AS_CLASS_LITERAL
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.IMPLEMENTING_FUNCTION_INTERFACE
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.INLINE_CLASS_IN_EXTERNAL_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.INLINE_CLASS_IN_EXTERNAL_DECLARATION_WARNING
@@ -48,6 +50,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.OVERRIDING_E
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.UNCHECKED_CAST_TO_EXTERNAL_INTERFACE
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_BODY_OF_EXTERNAL_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_DEFAULT_VALUE_FOR_EXTERNAL_FUN_PARAMETER
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_EXTERNAL_DECLARATION
@@ -152,6 +155,18 @@ object FirJsErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
map.put(JS_NAME_PROHIBITED_FOR_OVERRIDE, "@JsName is prohibited for overridden members")
map.put(JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED, "@JsName annotation is prohibited for primary constructors")
map.put(JS_NAME_ON_ACCESSOR_AND_PROPERTY, "@JsName can be either on a property or its accessors, not both of them")
map.put(
CANNOT_CHECK_FOR_EXTERNAL_INTERFACE,
"Cannot check for external interface: {0}",
FirDiagnosticRenderers.RENDER_TYPE,
)
map.put(
UNCHECKED_CAST_TO_EXTERNAL_INTERFACE,
"Unchecked cast to external interface: {0} to {1}",
FirDiagnosticRenderers.RENDER_TYPE,
FirDiagnosticRenderers.RENDER_TYPE,
)
map.put(EXTERNAL_INTERFACE_AS_CLASS_LITERAL, "Can't refer to external interface from class literal")
map.checkMissingMessages(FirJsErrors)
}
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.fir.analysis.checkers.hasAnnotationOrInsideAnnotated
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.declarations.utils.isExternal
import org.jetbrains.kotlin.fir.declarations.utils.isInterface
import org.jetbrains.kotlin.fir.declarations.utils.modality
import org.jetbrains.kotlin.fir.isSubstitutionOrIntersectionOverride
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
@@ -84,6 +85,10 @@ fun FirBasedSymbol<*>.isNativeObject(session: FirSession): Boolean {
return false
}
fun FirBasedSymbol<*>.isNativeInterface(session: FirSession): Boolean {
return isNativeObject(session) && (fir as? FirClass)?.isInterface == true
}
private val FirBasedSymbol<*>.isExpect
get() = when (this) {
is FirCallableSymbol<*> -> isExpect
@@ -106,4 +111,6 @@ fun FirBasedSymbol<*>.isPredefinedObject(session: FirSession): Boolean {
fun FirBasedSymbol<*>.isNativeObject(context: CheckerContext) = isNativeObject(context.session)
fun FirBasedSymbol<*>.isNativeInterface(context: CheckerContext) = isNativeInterface(context.session)
fun FirBasedSymbol<*>.isPredefinedObject(context: CheckerContext) = isPredefinedObject(context.session)
@@ -16,6 +16,7 @@ object JsExpressionCheckers : ExpressionCheckers() {
override val basicExpressionCheckers: Set<FirBasicExpressionChecker>
get() = setOf(
FirJsDefinedExternallyCallChecker
FirJsDefinedExternallyCallChecker,
FirJsNativeRttiChecker,
)
}
@@ -0,0 +1,60 @@
/*
* 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.expression
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.*
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirBasicExpressionChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors
import org.jetbrains.kotlin.fir.analysis.js.checkers.isNativeInterface
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.types.coneType
object FirJsNativeRttiChecker : FirBasicExpressionChecker() {
override fun check(expression: FirStatement, context: CheckerContext, reporter: DiagnosticReporter) {
when (expression) {
is FirGetClassCall -> checkGetClassCall(expression, context, reporter)
is FirTypeOperatorCall -> checkTypeOperatorCall(expression, context, reporter)
else -> {}
}
}
private fun checkGetClassCall(expression: FirGetClassCall, context: CheckerContext, reporter: DiagnosticReporter) {
val declarationToCheck = expression.argument.typeRef.toRegularClassSymbol(context.session) ?: return
if (declarationToCheck.isNativeInterface(context)) {
reporter.reportOn(expression.source, FirJsErrors.EXTERNAL_INTERFACE_AS_CLASS_LITERAL, context)
}
}
private fun checkTypeOperatorCall(expression: FirTypeOperatorCall, context: CheckerContext, reporter: DiagnosticReporter) {
val targetTypeRef = expression.conversionTypeRef
val declarationToCheck = targetTypeRef.toRegularClassSymbol(context.session) ?: return
if (!declarationToCheck.isNativeInterface(context)) {
return
}
when (expression.operation) {
FirOperation.AS, FirOperation.SAFE_AS -> reporter.reportOn(
expression.source,
FirJsErrors.UNCHECKED_CAST_TO_EXTERNAL_INTERFACE,
expression.argument.typeRef.coneType,
targetTypeRef.coneType,
context,
)
FirOperation.IS, FirOperation.NOT_IS -> reporter.reportOn(
expression.source,
FirJsErrors.CANNOT_CHECK_FOR_EXTERNAL_INTERFACE,
targetTypeRef.coneType,
context,
)
else -> {}
}
}
}
@@ -1,5 +0,0 @@
external interface I
fun box(a: Any, b: Any): Pair<I, I?> {
return Pair(a as I, b as? I)
}
@@ -1,3 +1,6 @@
// FIR_IDENTICAL
// DIAGNOSTICS: +UNCHECKED_CAST_TO_EXTERNAL_INTERFACE
external interface I
fun box(a: Any, b: Any): Pair<I, I?> {
@@ -1,5 +0,0 @@
external interface I
fun box(a: Any, b: Any): Boolean {
return a is I && b !is I
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
external interface I
fun box(a: Any, b: Any): Boolean {
@@ -1,5 +0,0 @@
external interface I
fun box() {
println(I::class)
}
@@ -1,5 +1,8 @@
// FIR_IDENTICAL
external interface I
typealias TA = I
fun box() {
println(<!EXTERNAL_INTERFACE_AS_CLASS_LITERAL!>I::class<!>)
println(<!EXTERNAL_INTERFACE_AS_CLASS_LITERAL!>TA::class<!>)
}
@@ -7,3 +7,5 @@ public external interface I {
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public typealias TA = I
@@ -1,9 +0,0 @@
external interface I
external interface J
fun box(a: Any) = when (a) {
is I -> 0
!is J -> 1
else -> 2
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
external interface I
external interface J