[K/N] Implement frontend checkers for usages forward declaration type

Forward declaration type doesn't exist in runtime. This restricts
its possible usages.

^KT-59764
This commit is contained in:
Pavel Kunyavskiy
2023-07-04 10:42:02 +02:00
committed by Space Team
parent 27043bd8a6
commit 6da3ecceab
20 changed files with 456 additions and 3 deletions
@@ -7,12 +7,14 @@ package org.jetbrains.kotlin.fir.checkers.generator.diagnostics
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.diagnostics.Severity
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.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtElement
@@ -65,5 +67,19 @@ object NATIVE_DIAGNOSTICS_LIST : DiagnosticList("FirNativeErrors") {
val INVALID_OBJC_HIDES_TARGETS by error<KtElement>()
val INVALID_REFINES_IN_SWIFT_TARGETS by error<KtElement>()
val SUBTYPE_OF_HIDDEN_FROM_OBJC by error<KtElement>()
val CANNOT_CHECK_FOR_FORWARD_DECLARATION by error<KtElement>() {
parameter<ConeKotlinType>("type")
}
val UNCHECKED_CAST_TO_FORWARD_DECLARATION by warning<KtElement> {
parameter<ConeKotlinType>("sourceType")
parameter<ConeKotlinType>("destinationType")
}
val FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT by error<KtElement> {
parameter<ConeKotlinType>("type")
}
val FORWARD_DECLARATION_AS_CLASS_LITERAL by error<KtElement> {
parameter<ConeKotlinType>("type")
}
}
}
@@ -15,6 +15,7 @@ dependencies {
implementation(project(":compiler:frontend"))
implementation(project(":compiler:psi"))
compileOnly(project(":core:compiler.common.native"))
compileOnly(commonDependency("org.jetbrains.kotlin:kotlin-reflect")) { isTransitive = false }
compileOnly(intellijCore())
}
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory
import org.jetbrains.kotlin.fir.analysis.diagnostics.*
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtElement
@@ -47,6 +48,10 @@ object FirNativeErrors {
val INVALID_OBJC_HIDES_TARGETS by error0<KtElement>()
val INVALID_REFINES_IN_SWIFT_TARGETS by error0<KtElement>()
val SUBTYPE_OF_HIDDEN_FROM_OBJC by error0<KtElement>()
val CANNOT_CHECK_FOR_FORWARD_DECLARATION by error1<KtElement, ConeKotlinType>()
val UNCHECKED_CAST_TO_FORWARD_DECLARATION by warning2<KtElement, ConeKotlinType, ConeKotlinType>()
val FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT by error1<KtElement, ConeKotlinType>()
val FORWARD_DECLARATION_AS_CLASS_LITERAL by error1<KtElement, ConeKotlinType>()
init {
RootDiagnosticRendererFactory.registerFactory(FirNativeErrorsDefaultMessages)
@@ -8,9 +8,14 @@ package org.jetbrains.kotlin.fir.analysis.diagnostics.native
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.diagnostics.KtDiagnosticRenderers.TO_STRING
import org.jetbrains.kotlin.diagnostics.rendering.BaseDiagnosticRendererFactory
import org.jetbrains.kotlin.diagnostics.rendering.Renderers
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.SYMBOL
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.SYMBOLS
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.CANNOT_CHECK_FOR_FORWARD_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.EMPTY_OBJC_NAME
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.FORWARD_DECLARATION_AS_CLASS_LITERAL
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_EXACT_OBJC_NAME
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_OBJC_NAME
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_SHARED_IMMUTABLE_PROPERTY
@@ -33,6 +38,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.REDU
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.NON_LITERAL_OBJC_NAME_ARG
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.SUBTYPE_OF_HIDDEN_FROM_OBJC
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.THROWS_LIST_EMPTY
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.UNCHECKED_CAST_TO_FORWARD_DECLARATION
object FirNativeErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
override val MAP = KtDiagnosticFactoryToRendererMap("FIR").also { map ->
@@ -79,5 +85,28 @@ object FirNativeErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
map.put(MISSING_EXACT_OBJC_NAME, "Exact @ObjCName is required to have an ObjC name")
map.put(NON_LITERAL_OBJC_NAME_ARG, "@ObjCName accepts only literal string and boolean values")
map.put(SUBTYPE_OF_HIDDEN_FROM_OBJC, "Only @HiddenFromObjC declaration can be a subtype of @HiddenFromObjC declaration")
map.put(
CANNOT_CHECK_FOR_FORWARD_DECLARATION,
"Cannot check for forward declaration: ''{0}''",
FirDiagnosticRenderers.RENDER_TYPE
)
map.put(
UNCHECKED_CAST_TO_FORWARD_DECLARATION,
"Unchecked cast to forward declaration: ''{0}'' to ''{1}''",
FirDiagnosticRenderers.RENDER_TYPE,
FirDiagnosticRenderers.RENDER_TYPE
)
map.put(
FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT,
"Cannot pass forward declaration ''{0}'' for reified type parameter",
FirDiagnosticRenderers.RENDER_TYPE
)
map.put(
FORWARD_DECLARATION_AS_CLASS_LITERAL,
"Can't refer to forward declaration ''{0}'' from class literal",
FirDiagnosticRenderers.RENDER_TYPE
)
}
}
@@ -0,0 +1,34 @@
/*
* 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.
*/
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.expression.FirGetClassCallChecker
import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClassSymbol
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors
import org.jetbrains.kotlin.fir.analysis.native.checkers.forwardDeclarationKindOrNull
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.types.coneType
object FirNativeForwardDeclarationGetClassCallChecker : FirGetClassCallChecker() {
override fun check(expression: FirGetClassCall, context: CheckerContext, reporter: DiagnosticReporter) {
val declarationToCheck = expression.argument.typeRef.toRegularClassSymbol(context.session) ?: return
if (expression.arguments.firstOrNull() !is FirResolvedQualifier) {
return
}
if (declarationToCheck.forwardDeclarationKindOrNull() != null) {
reporter.reportOn(
expression.source,
FirNativeErrors.FORWARD_DECLARATION_AS_CLASS_LITERAL,
expression.argument.typeRef.coneType,
context,
)
}
}
}
@@ -0,0 +1,43 @@
/*
* 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.native.checkers
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.expression.FirQualifiedAccessExpressionChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
import org.jetbrains.kotlin.fir.references.toResolvedCallableSymbol
import org.jetbrains.kotlin.fir.types.toConeTypeProjection
import org.jetbrains.kotlin.fir.types.toRegularClassSymbol
import org.jetbrains.kotlin.fir.types.type
object FirNativeForwardDeclarationReifiedChecker : FirQualifiedAccessExpressionChecker() {
override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
val calleeReference = expression.calleeReference
val typeArguments = expression.typeArguments
val typeParameters = calleeReference.toResolvedCallableSymbol()?.typeParameterSymbols ?: return
val count = minOf(typeArguments.size, typeParameters.size)
for (index in 0 until count) {
val typeArgumentProjection = typeArguments.elementAt(index)
val source = typeArgumentProjection.source ?: calleeReference.source ?: continue
val typeArgument = typeArgumentProjection.toConeTypeProjection().type ?: continue
val typeParameter = typeParameters[index]
if (typeParameter.isReified && typeArgument.toRegularClassSymbol(context.session)?.forwardDeclarationKindOrNull() != null) {
reporter.reportOn(
source,
FirNativeErrors.FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT,
typeArgument,
context,
)
}
}
}
}
@@ -0,0 +1,59 @@
/*
* 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.native.checkers
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.FirTypeOperatorCallChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.resolve.lookupSuperTypes
import org.jetbrains.kotlin.fir.types.classId
import org.jetbrains.kotlin.fir.types.coneType
object FirNativeForwardDeclarationTypeOperatorChecker : FirTypeOperatorCallChecker() {
override fun check(expression: FirTypeOperatorCall, context: CheckerContext, reporter: DiagnosticReporter) {
val targetTypeRef = expression.conversionTypeRef
val declarationToCheck = targetTypeRef.toRegularClassSymbol(context.session) ?: return
val fwdKind = declarationToCheck.forwardDeclarationKindOrNull() ?: return
when (expression.operation) {
FirOperation.AS, FirOperation.SAFE_AS -> {
val sourceTypeRef = expression.argument.typeRef
val sourceClass = sourceTypeRef.toRegularClassSymbol(context.session)
// It can make sense to avoid warning if sourceClass is subclass of class with such property,
// but for the sake of simplicity, we don't do it now.
if (sourceClass != null && sourceClass.classKind == fwdKind.classKind && sourceClass.name == declarationToCheck.name) {
val supers = lookupSuperTypes(
symbol = sourceClass,
lookupInterfaces = true,
deep = true,
useSiteSession = context.session,
)
if (supers.any { it.classId == fwdKind.matchSuperClassId }) {
return
}
}
reporter.reportOn(
expression.source,
FirNativeErrors.UNCHECKED_CAST_TO_FORWARD_DECLARATION,
expression.argument.typeRef.coneType,
sourceTypeRef.coneType,
context,
)
}
FirOperation.IS, FirOperation.NOT_IS -> reporter.reportOn(
expression.source,
FirNativeErrors.CANNOT_CHECK_FOR_FORWARD_DECLARATION,
targetTypeRef.coneType,
context,
)
else -> {}
}
}
}
@@ -11,7 +11,9 @@ import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.getAnnotationByClassId
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.NativeForwardDeclarationKind
fun DiagnosticReporter.reportIfHasAnnotation(
declaration: FirDeclaration,
@@ -23,4 +25,7 @@ fun DiagnosticReporter.reportIfHasAnnotation(
if (annotation != null) {
reportOn(annotation.source, error, context)
}
}
}
fun FirRegularClassSymbol.forwardDeclarationKindOrNull(): NativeForwardDeclarationKind? =
NativeForwardDeclarationKind.packageFqNameToKind[classId.packageFqName]
@@ -5,7 +5,19 @@
package org.jetbrains.kotlin.fir.analysis.native.checkers
import FirNativeForwardDeclarationGetClassCallChecker
import org.jetbrains.kotlin.fir.analysis.checkers.expression.ExpressionCheckers
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirGetClassCallChecker
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirQualifiedAccessExpressionChecker
object NativeExpressionCheckers : ExpressionCheckers() {
override val typeOperatorCallCheckers = setOf(
FirNativeForwardDeclarationTypeOperatorChecker,
)
override val getClassCallCheckers = setOf(
FirNativeForwardDeclarationGetClassCallChecker
)
override val qualifiedAccessExpressionCheckers = setOf(
FirNativeForwardDeclarationReifiedChecker
)
}
@@ -0,0 +1,104 @@
// FIR_IDENTICAL
// WITH_STDLIB
// FILE: stdlib.kt
package kotlinx.cinterop
abstract class CStructVar
interface ObjCObject
abstract class ObjCObjectBase : ObjCObject
// FILE: cnames.kt
package cnames.structs
class FwdStruct {}
// FILE: objcnamesClasses.kt
package objcnames.classes
class FwdObjcClass {}
// FILE: objcnamesProtocols.kt
package objcnames.protocols
interface FwdProtocol {}
// FILE: lib.kt
package lib
import kotlinx.cinterop.*
class FwdStruct : CStructVar()
class FwdObjcClass : ObjCObjectBase()
interface FwdProtocol : ObjCObject
// FILE: lib2.kt
package lib2
class FwdStruct
class FwdObjcClass
interface FwdProtocol
// FILE: main.kt
// this inspections differs in K1/K2, but unrelated to what is tested in this test
@file:Suppress("CAST_NEVER_SUCCEEDS", "UNUSED_PARAMETER", "UNUSED_EXPRESSION", "UNUSED_VARIABLE", "INCOMPATIBLE_TYPES")
fun testUnckeckedAsFromAny(x: Any) {
<!UNCHECKED_CAST_TO_FORWARD_DECLARATION!>x as? cnames.structs.FwdStruct<!>
<!UNCHECKED_CAST_TO_FORWARD_DECLARATION!>x as? objcnames.classes.FwdObjcClass<!>
<!UNCHECKED_CAST_TO_FORWARD_DECLARATION!>x as? objcnames.protocols.FwdProtocol<!>
if (1 > 0) { <!UNCHECKED_CAST_TO_FORWARD_DECLARATION!>x as cnames.structs.FwdStruct<!> }
if (1 > 0) { <!UNCHECKED_CAST_TO_FORWARD_DECLARATION!>x as objcnames.classes.FwdObjcClass<!> }
if (1 > 0) { <!UNCHECKED_CAST_TO_FORWARD_DECLARATION!>x as objcnames.protocols.FwdProtocol<!> }
}
fun testIs(x: Any) : Int {
return when {
<!CANNOT_CHECK_FOR_FORWARD_DECLARATION!>x is cnames.structs.FwdStruct<!> -> 1
<!CANNOT_CHECK_FOR_FORWARD_DECLARATION!>x is objcnames.classes.FwdObjcClass<!> -> 2
<!CANNOT_CHECK_FOR_FORWARD_DECLARATION!>x is objcnames.protocols.FwdProtocol<!> -> 3
else -> 4
}
}
fun testIs2(x: lib.FwdStruct) = <!CANNOT_CHECK_FOR_FORWARD_DECLARATION!>x is cnames.structs.FwdStruct<!>
fun testIs3(x: lib.FwdObjcClass) = <!CANNOT_CHECK_FOR_FORWARD_DECLARATION!>x is objcnames.classes.FwdObjcClass<!>
fun testIs4(x: lib.FwdProtocol) = <!CANNOT_CHECK_FOR_FORWARD_DECLARATION!>x is objcnames.protocols.FwdProtocol<!>
fun testClass1(x : cnames.structs.FwdStruct) = x::class
fun testClass2(x : objcnames.classes.FwdObjcClass) = x::class
fun testClass3(x : objcnames.protocols.FwdProtocol) = x::class
fun testClass4() {
<!FORWARD_DECLARATION_AS_CLASS_LITERAL!>cnames.structs.FwdStruct::class<!>
<!FORWARD_DECLARATION_AS_CLASS_LITERAL!>objcnames.classes.FwdObjcClass::class<!>
<!FORWARD_DECLARATION_AS_CLASS_LITERAL!>objcnames.protocols.FwdProtocol::class<!>
}
inline fun <reified T> inlineF(x: T) {}
fun testInline1(x : cnames.structs.FwdStruct) = <!FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT!>inlineF<!>(x)
fun testInline2(x : objcnames.classes.FwdObjcClass) = <!FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT!>inlineF<!>(x)
fun testInline3(x : objcnames.protocols.FwdProtocol) = <!FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT!>inlineF<!>(x)
fun testInline4() {
val a : (cnames.structs.FwdStruct) -> Unit = ::<!FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT!>inlineF<!>
val b : (objcnames.classes.FwdObjcClass) -> Unit = ::<!FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT!>inlineF<!>
val c : (objcnames.protocols.FwdProtocol) -> Unit = ::<!FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT!>inlineF<!>
}
fun testCheckedAs1(x : lib.FwdStruct) = x as cnames.structs.FwdStruct
fun testCheckedAs2(x : lib.FwdObjcClass) = x as objcnames.classes.FwdObjcClass
fun testCheckedAs3(x : lib.FwdProtocol) = x as objcnames.protocols.FwdProtocol
fun testUnCheckedAs1(x : lib2.FwdStruct) = <!UNCHECKED_CAST_TO_FORWARD_DECLARATION!>x as cnames.structs.FwdStruct<!>
fun testUnCheckedAs2(x : lib2.FwdObjcClass) = <!UNCHECKED_CAST_TO_FORWARD_DECLARATION!>x as objcnames.classes.FwdObjcClass<!>
fun testUnCheckedAs3(x : lib2.FwdProtocol) = <!UNCHECKED_CAST_TO_FORWARD_DECLARATION!>x as objcnames.protocols.FwdProtocol<!>
fun testUnCheckedAs4(x : lib.FwdStruct) = <!UNCHECKED_CAST_TO_FORWARD_DECLARATION!>x as objcnames.classes.FwdObjcClass<!>