From 6da3ecceab31fb309d0dca21f175f24625e2a2dc Mon Sep 17 00:00:00 2001 From: Pavel Kunyavskiy Date: Tue, 4 Jul 2023 10:42:02 +0200 Subject: [PATCH] [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 --- .../diagnostics/FirNativeDiagnosticsList.kt | 16 +++ .../checkers/checkers.native/build.gradle.kts | 1 + .../diagnostics/native/FirNativeErrors.kt | 5 + .../native/FirNativeErrorsDefaultMessages.kt | 29 +++++ ...veForwardDeclarationGetClassCallChecker.kt | 34 ++++++ ...rNativeForwardDeclarationReifiedChecker.kt | 43 ++++++++ ...veForwardDeclarationTypeOperatorChecker.kt | 59 ++++++++++ .../native/checkers/FirNativeHelpers.kt | 7 +- .../checkers/NativeExpressionCheckers.kt | 12 ++ .../nativeTests/forwardDeclarations.kt | 104 ++++++++++++++++++ native/frontend/build.gradle.kts | 1 + .../kotlin/descriptors/konan/utils.kt | 5 + .../diagnostics/DefaultErrorMessagesNative.kt | 21 ++++ .../resolve/konan/diagnostics/ErrorsNative.kt | 9 ++ .../NativeForwardDeclarationRttiChecker.kt | 56 ++++++++++ .../NativeReifiedForwardDeclarationChecker.kt | 33 ++++++ .../platform/NativePlatformConfigurator.kt | 6 +- .../DiagnosticsNativeTestGenerated.java | 6 + ...rontendNativeDiagnosticsTestGenerated.java | 6 + ...rontendNativeDiagnosticsTestGenerated.java | 6 + 20 files changed, 456 insertions(+), 3 deletions(-) create mode 100644 compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeForwardDeclarationGetClassCallChecker.kt create mode 100644 compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeForwardDeclarationReifiedChecker.kt create mode 100644 compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeForwardDeclarationTypeOperatorChecker.kt create mode 100644 compiler/testData/diagnostics/nativeTests/forwardDeclarations.kt create mode 100644 native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeForwardDeclarationRttiChecker.kt create mode 100644 native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeReifiedForwardDeclarationChecker.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 3e9913698b2..2160f44d56d 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 @@ -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() val INVALID_REFINES_IN_SWIFT_TARGETS by error() val SUBTYPE_OF_HIDDEN_FROM_OBJC by error() + + val CANNOT_CHECK_FOR_FORWARD_DECLARATION by error() { + parameter("type") + } + val UNCHECKED_CAST_TO_FORWARD_DECLARATION by warning { + parameter("sourceType") + parameter("destinationType") + } + val FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT by error { + parameter("type") + } + val FORWARD_DECLARATION_AS_CLASS_LITERAL by error { + parameter("type") + } } } diff --git a/compiler/fir/checkers/checkers.native/build.gradle.kts b/compiler/fir/checkers/checkers.native/build.gradle.kts index 126a184b469..7009cfd96f8 100644 --- a/compiler/fir/checkers/checkers.native/build.gradle.kts +++ b/compiler/fir/checkers/checkers.native/build.gradle.kts @@ -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()) } 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 81ac5b8ae58..0feac939619 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 @@ -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() val INVALID_REFINES_IN_SWIFT_TARGETS by error0() val SUBTYPE_OF_HIDDEN_FROM_OBJC by error0() + val CANNOT_CHECK_FOR_FORWARD_DECLARATION by error1() + val UNCHECKED_CAST_TO_FORWARD_DECLARATION by warning2() + val FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT by error1() + val FORWARD_DECLARATION_AS_CLASS_LITERAL by error1() 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 d16487640e4..7ba0837e141 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 @@ -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 + ) } } diff --git a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeForwardDeclarationGetClassCallChecker.kt b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeForwardDeclarationGetClassCallChecker.kt new file mode 100644 index 00000000000..2d644331c79 --- /dev/null +++ b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeForwardDeclarationGetClassCallChecker.kt @@ -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, + ) + } + } +} \ No newline at end of file diff --git a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeForwardDeclarationReifiedChecker.kt b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeForwardDeclarationReifiedChecker.kt new file mode 100644 index 00000000000..4a26c59f777 --- /dev/null +++ b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeForwardDeclarationReifiedChecker.kt @@ -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, + ) + } + } + } +} \ No newline at end of file diff --git a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeForwardDeclarationTypeOperatorChecker.kt b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeForwardDeclarationTypeOperatorChecker.kt new file mode 100644 index 00000000000..f7df9ff5105 --- /dev/null +++ b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeForwardDeclarationTypeOperatorChecker.kt @@ -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 -> {} + } + } +} \ No newline at end of file diff --git a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeHelpers.kt b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeHelpers.kt index e9f1a16ab7b..120da0366ea 100644 --- a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeHelpers.kt +++ b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeHelpers.kt @@ -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) } -} \ No newline at end of file +} + +fun FirRegularClassSymbol.forwardDeclarationKindOrNull(): NativeForwardDeclarationKind? = + NativeForwardDeclarationKind.packageFqNameToKind[classId.packageFqName] \ No newline at end of file diff --git a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/NativeExpressionCheckers.kt b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/NativeExpressionCheckers.kt index 8d49edf033f..b0c803d477d 100644 --- a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/NativeExpressionCheckers.kt +++ b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/NativeExpressionCheckers.kt @@ -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 + ) } \ No newline at end of file diff --git a/compiler/testData/diagnostics/nativeTests/forwardDeclarations.kt b/compiler/testData/diagnostics/nativeTests/forwardDeclarations.kt new file mode 100644 index 00000000000..821b8c2dedf --- /dev/null +++ b/compiler/testData/diagnostics/nativeTests/forwardDeclarations.kt @@ -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) { + x as? cnames.structs.FwdStruct + x as? objcnames.classes.FwdObjcClass + x as? objcnames.protocols.FwdProtocol + if (1 > 0) { x as cnames.structs.FwdStruct } + if (1 > 0) { x as objcnames.classes.FwdObjcClass } + if (1 > 0) { x as objcnames.protocols.FwdProtocol } +} + +fun testIs(x: Any) : Int { + return when { + x is cnames.structs.FwdStruct -> 1 + x is objcnames.classes.FwdObjcClass -> 2 + x is objcnames.protocols.FwdProtocol -> 3 + else -> 4 + } +} + +fun testIs2(x: lib.FwdStruct) = x is cnames.structs.FwdStruct +fun testIs3(x: lib.FwdObjcClass) = x is objcnames.classes.FwdObjcClass +fun testIs4(x: lib.FwdProtocol) = 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() { + cnames.structs.FwdStruct::class + objcnames.classes.FwdObjcClass::class + objcnames.protocols.FwdProtocol::class +} +inline fun inlineF(x: T) {} + +fun testInline1(x : cnames.structs.FwdStruct) = inlineF(x) +fun testInline2(x : objcnames.classes.FwdObjcClass) = inlineF(x) +fun testInline3(x : objcnames.protocols.FwdProtocol) = inlineF(x) +fun testInline4() { + val a : (cnames.structs.FwdStruct) -> Unit = ::inlineF + val b : (objcnames.classes.FwdObjcClass) -> Unit = ::inlineF + val c : (objcnames.protocols.FwdProtocol) -> Unit = ::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) = x as cnames.structs.FwdStruct +fun testUnCheckedAs2(x : lib2.FwdObjcClass) = x as objcnames.classes.FwdObjcClass +fun testUnCheckedAs3(x : lib2.FwdProtocol) = x as objcnames.protocols.FwdProtocol + +fun testUnCheckedAs4(x : lib.FwdStruct) = x as objcnames.classes.FwdObjcClass diff --git a/native/frontend/build.gradle.kts b/native/frontend/build.gradle.kts index 6b9b86e6b17..f4a2e8a2769 100644 --- a/native/frontend/build.gradle.kts +++ b/native/frontend/build.gradle.kts @@ -6,6 +6,7 @@ plugins { dependencies { compileOnly(project(":compiler:frontend")) compileOnly(project(":compiler:frontend.java")) + compileOnly(project(":core:compiler.common.native")) compileOnly(intellijCore()) api(project(":native:kotlin-native-utils")) } diff --git a/native/frontend/src/org/jetbrains/kotlin/descriptors/konan/utils.kt b/native/frontend/src/org/jetbrains/kotlin/descriptors/konan/utils.kt index 705a0c52995..5aa6daafc9e 100644 --- a/native/frontend/src/org/jetbrains/kotlin/descriptors/konan/utils.kt +++ b/native/frontend/src/org/jetbrains/kotlin/descriptors/konan/utils.kt @@ -5,10 +5,15 @@ package org.jetbrains.kotlin.descriptors.konan +import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME +import org.jetbrains.kotlin.name.NativeForwardDeclarationKind import org.jetbrains.kotlin.name.Name val NATIVE_STDLIB_MODULE_NAME = Name.special("<$KONAN_STDLIB_NAME>") fun ModuleDescriptor.isNativeStdlib() = name == NATIVE_STDLIB_MODULE_NAME +fun ClassDescriptor.getForwardDeclarationKindOrNull() = + NativeForwardDeclarationKind.packageFqNameToKind[(containingDeclaration as? PackageFragmentDescriptor)?.fqName] \ No newline at end of file diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/DefaultErrorMessagesNative.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/DefaultErrorMessagesNative.kt index a1eabba2ded..b951b62bbda 100644 --- a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/DefaultErrorMessagesNative.kt +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/DefaultErrorMessagesNative.kt @@ -69,6 +69,27 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy { ErrorsNative.SUBTYPE_OF_HIDDEN_FROM_OBJC, "Only @HiddenFromObjC declaration can be a subtype of @HiddenFromObjC declaration" ) + + put( + ErrorsNative.CANNOT_CHECK_FOR_FORWARD_DECLARATION, + "Cannot check for forward declaration: ''{0}''", + Renderers.RENDER_TYPE + ) + put(ErrorsNative.UNCHECKED_CAST_TO_FORWARD_DECLARATION, + "Unchecked cast to forward declaration: ''{0}'' to ''{1}''", + Renderers.RENDER_TYPE, + Renderers.RENDER_TYPE + ) + put( + ErrorsNative.FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT, + "Cannot pass forward declaration ''{0}'' for reified type parameter", + Renderers.RENDER_TYPE + ) + put( + ErrorsNative.FORWARD_DECLARATION_AS_CLASS_LITERAL, + "Can't refer to forward declaration ''{0}'' from class literal", + Renderers.RENDER_TYPE + ) } } diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/ErrorsNative.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/ErrorsNative.kt index fdf39d39e0f..28c1beacf17 100644 --- a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/ErrorsNative.kt +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/ErrorsNative.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.diagnostics.* import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.types.KotlinType object ErrorsNative { @JvmField @@ -60,6 +61,14 @@ object ErrorsNative { val INVALID_REFINES_IN_SWIFT_TARGETS = DiagnosticFactory0.create(Severity.ERROR) @JvmField val SUBTYPE_OF_HIDDEN_FROM_OBJC = DiagnosticFactory0.create(Severity.ERROR) + @JvmField + val CANNOT_CHECK_FOR_FORWARD_DECLARATION = DiagnosticFactory1.create(Severity.ERROR) + @JvmField + val UNCHECKED_CAST_TO_FORWARD_DECLARATION = DiagnosticFactory2.create(Severity.WARNING) + @JvmField + val FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT = DiagnosticFactory1.create(Severity.ERROR) + @JvmField + val FORWARD_DECLARATION_AS_CLASS_LITERAL = DiagnosticFactory1.create(Severity.ERROR) init { Errors.Initializer.initializeFactoryNames(ErrorsNative::class.java) diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeForwardDeclarationRttiChecker.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeForwardDeclarationRttiChecker.kt new file mode 100644 index 00000000000..4f0a2f1ff8a --- /dev/null +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeForwardDeclarationRttiChecker.kt @@ -0,0 +1,56 @@ +/* + * 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.resolve.konan.diagnostics + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.konan.getForwardDeclarationKindOrNull +import org.jetbrains.kotlin.psi.KtClassLiteralExpression +import org.jetbrains.kotlin.resolve.BindingTrace +import org.jetbrains.kotlin.resolve.calls.checkers.RttiExpressionChecker +import org.jetbrains.kotlin.resolve.calls.checkers.RttiExpressionInformation +import org.jetbrains.kotlin.resolve.calls.checkers.RttiOperation +import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperClassifiers +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.expressions.ClassLiteralChecker + +class NativeForwardDeclarationRttiChecker : RttiExpressionChecker, ClassLiteralChecker { + override fun check(rttiInformation: RttiExpressionInformation, reportOn: PsiElement, trace: BindingTrace) { + val sourceType = rttiInformation.sourceType + val targetType = rttiInformation.targetType + val targetDescriptor = targetType?.constructor?.declarationDescriptor + if (sourceType != null && targetDescriptor is ClassDescriptor) { + val kind = targetDescriptor.getForwardDeclarationKindOrNull() ?: return + when (rttiInformation.operation) { + RttiOperation.IS, + RttiOperation.NOT_IS -> trace.report(ErrorsNative.CANNOT_CHECK_FOR_FORWARD_DECLARATION.on(reportOn, targetType)) + + RttiOperation.AS, + RttiOperation.SAFE_AS -> { + // 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. + val sourceDescriptor = sourceType.constructor.declarationDescriptor as? ClassDescriptor + val isAllowedCast = sourceDescriptor != null && + sourceDescriptor.name == targetDescriptor.name && + sourceDescriptor.kind == kind.classKind && + sourceDescriptor.getAllSuperClassifiers().any { it.fqNameSafe == kind.matchSuperClassFqName } + if (!isAllowedCast) { + trace.report(ErrorsNative.UNCHECKED_CAST_TO_FORWARD_DECLARATION.on(reportOn, sourceType, targetType)) + } + } + } + } + } + + override fun check(expression: KtClassLiteralExpression, type: KotlinType, context: ResolutionContext<*>) { + val descriptor = type.constructor.declarationDescriptor as? ClassDescriptor + if (descriptor?.getForwardDeclarationKindOrNull() != null) { + context.trace.report(ErrorsNative.FORWARD_DECLARATION_AS_CLASS_LITERAL.on(expression, type)) + } + } +} diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeReifiedForwardDeclarationChecker.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeReifiedForwardDeclarationChecker.kt new file mode 100644 index 00000000000..146c55636c9 --- /dev/null +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeReifiedForwardDeclarationChecker.kt @@ -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.resolve.konan.diagnostics + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker +import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.descriptors.konan.* + +class NativeReifiedForwardDeclarationChecker : CallChecker { + override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) { + val typeArgumentList = resolvedCall.call.typeArgumentList?.arguments + for ((typeParam, typeArg) in resolvedCall.typeArguments) { + if (!typeParam.isReified) continue + + val typeArgDescriptor = typeArg.constructor.declarationDescriptor + if (typeArgDescriptor is ClassDescriptor && typeArgDescriptor.getForwardDeclarationKindOrNull() != null) { + val typeArgumentPsi = if (typeArgumentList != null) { + typeArgumentList[typeParam.index].typeReference + } else { + resolvedCall.call.calleeExpression + } + + context.trace.report(ErrorsNative.FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT.on(typeArgumentPsi!!, typeArg)) + } + } + } +} \ No newline at end of file diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/platform/NativePlatformConfigurator.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/platform/NativePlatformConfigurator.kt index 432fa427e6b..2774ccc7ea5 100644 --- a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/platform/NativePlatformConfigurator.kt +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/platform/NativePlatformConfigurator.kt @@ -21,19 +21,21 @@ import org.jetbrains.kotlin.resolve.konan.diagnostics.* object NativePlatformConfigurator : PlatformConfiguratorBase( additionalCallCheckers = listOf( SuperCallWithDefaultArgumentsChecker(), - LateinitIntrinsicApplicabilityChecker(isWarningInPre19 = true) + LateinitIntrinsicApplicabilityChecker(isWarningInPre19 = true), + NativeReifiedForwardDeclarationChecker(), ), additionalDeclarationCheckers = listOf( NativeThrowsChecker, NativeSharedImmutableChecker, NativeThreadLocalChecker, NativeObjCNameChecker, NativeObjCNameOverridesChecker, NativeObjCRefinementChecker, NativeObjCRefinementAnnotationChecker, - NativeObjCRefinementOverridesChecker, NativeHiddenFromObjCInheritanceChecker + NativeObjCRefinementOverridesChecker, NativeHiddenFromObjCInheritanceChecker, ) ) { override fun configureModuleComponents(container: StorageComponentContainer) { container.useInstance(NativeInliningRule) container.useImpl() + container.useImpl() } override fun configureModuleDependentCheckers(container: StorageComponentContainer) { diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/diagnostics/DiagnosticsNativeTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/diagnostics/DiagnosticsNativeTestGenerated.java index 525993b691c..db4f0140e69 100644 --- a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/diagnostics/DiagnosticsNativeTestGenerated.java +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/diagnostics/DiagnosticsNativeTestGenerated.java @@ -30,6 +30,12 @@ public class DiagnosticsNativeTestGenerated extends AbstractDiagnosticsNativeTes runTest("compiler/testData/diagnostics/nativeTests/cloneableInNative.kt"); } + @Test + @TestMetadata("forwardDeclarations.kt") + public void testForwardDeclarations() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/forwardDeclarations.kt"); + } + @Test @TestMetadata("identifiers.kt") public void testIdentifiers() throws Exception { diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/diagnostics/FirLightTreeOldFrontendNativeDiagnosticsTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/diagnostics/FirLightTreeOldFrontendNativeDiagnosticsTestGenerated.java index fc38ac9b5d3..de374c14454 100644 --- a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/diagnostics/FirLightTreeOldFrontendNativeDiagnosticsTestGenerated.java +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/diagnostics/FirLightTreeOldFrontendNativeDiagnosticsTestGenerated.java @@ -34,6 +34,12 @@ public class FirLightTreeOldFrontendNativeDiagnosticsTestGenerated extends Abstr runTest("compiler/testData/diagnostics/nativeTests/cloneableInNative.kt"); } + @Test + @TestMetadata("forwardDeclarations.kt") + public void testForwardDeclarations() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/forwardDeclarations.kt"); + } + @Test @TestMetadata("identifiers.kt") public void testIdentifiers() throws Exception { diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/diagnostics/FirPsiOldFrontendNativeDiagnosticsTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/diagnostics/FirPsiOldFrontendNativeDiagnosticsTestGenerated.java index d0286ddbeed..2057f860ef1 100644 --- a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/diagnostics/FirPsiOldFrontendNativeDiagnosticsTestGenerated.java +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/diagnostics/FirPsiOldFrontendNativeDiagnosticsTestGenerated.java @@ -34,6 +34,12 @@ public class FirPsiOldFrontendNativeDiagnosticsTestGenerated extends AbstractFir runTest("compiler/testData/diagnostics/nativeTests/cloneableInNative.kt"); } + @Test + @TestMetadata("forwardDeclarations.kt") + public void testForwardDeclarations() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/forwardDeclarations.kt"); + } + @Test @TestMetadata("identifiers.kt") public void testIdentifiers() throws Exception {