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 8fe4ad64f71..91eba98924f 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 @@ -61,7 +61,7 @@ object FirNativeErrorsDefaultMessages : BaseDiagnosticRendererFactory() { ) map.put( INVALID_OBJC_REFINEMENT_TARGETS, - "Refines annotations are only applicable to annotations with targets FUNCTION and/or PROPERTY" + "Refines annotations are only applicable to annotations with targets CLASS, FUNCTION and/or PROPERTY" ) map.put(INAPPLICABLE_OBJC_NAME, "@ObjCName is not applicable on overrides") map.put(INVALID_OBJC_NAME, "@ObjCName should have a name and/or swiftName") diff --git a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCRefinementAnnotationChecker.kt b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCRefinementAnnotationChecker.kt index 4133c639d2a..c845a858930 100644 --- a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCRefinementAnnotationChecker.kt +++ b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCRefinementAnnotationChecker.kt @@ -21,7 +21,7 @@ import org.jetbrains.kotlin.fir.expressions.FirAnnotation object FirNativeObjCRefinementAnnotationChecker : FirRegularClassChecker() { - private val supportedTargets = arrayOf(KotlinTarget.FUNCTION, KotlinTarget.PROPERTY) + private val supportedTargets = arrayOf(KotlinTarget.FUNCTION, KotlinTarget.PROPERTY, KotlinTarget.CLASS) override fun check(declaration: FirRegularClass, context: CheckerContext, reporter: DiagnosticReporter) { if (declaration.classKind != ClassKind.ANNOTATION_CLASS) return diff --git a/compiler/testData/diagnostics/nativeTests/objCRefinement.kt b/compiler/testData/diagnostics/nativeTests/objCRefinement.kt index 79d28ac2608..25d854b530f 100644 --- a/compiler/testData/diagnostics/nativeTests/objCRefinement.kt +++ b/compiler/testData/diagnostics/nativeTests/objCRefinement.kt @@ -25,7 +25,7 @@ public annotation class ShouldRefineInSwift package plugin @HidesFromObjC -@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION) +@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) annotation class PluginHiddenFromObjC @@ -45,7 +45,7 @@ import plugin.PluginShouldRefineInSwift annotation class MyRefinedAnnotationA @HidesFromObjC -@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) +@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FILE) @Retention(AnnotationRetention.BINARY) annotation class MyRefinedAnnotationB diff --git a/compiler/testData/diagnostics/nativeTests/objCRefinement.txt b/compiler/testData/diagnostics/nativeTests/objCRefinement.txt index 7440eceb870..532511f2665 100644 --- a/compiler/testData/diagnostics/nativeTests/objCRefinement.txt +++ b/compiler/testData/diagnostics/nativeTests/objCRefinement.txt @@ -88,7 +88,7 @@ public interface InterfaceB { public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } -@kotlin.native.HidesFromObjC @kotlin.annotation.Target(allowedTargets = {AnnotationTarget.PROPERTY, AnnotationTarget.CLASS}) @kotlin.annotation.Retention(value = AnnotationRetention.BINARY) public final annotation class MyRefinedAnnotationB : kotlin.Annotation { +@kotlin.native.HidesFromObjC @kotlin.annotation.Target(allowedTargets = {AnnotationTarget.PROPERTY, AnnotationTarget.FILE}) @kotlin.annotation.Retention(value = AnnotationRetention.BINARY) public final annotation class MyRefinedAnnotationB : kotlin.Annotation { public constructor MyRefinedAnnotationB() public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int @@ -146,7 +146,7 @@ package kotlin { package plugin { - @kotlin.native.HidesFromObjC @kotlin.annotation.Target(allowedTargets = {AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION}) @kotlin.annotation.Retention(value = AnnotationRetention.BINARY) public final annotation class PluginHiddenFromObjC : kotlin.Annotation { + @kotlin.native.HidesFromObjC @kotlin.annotation.Target(allowedTargets = {AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.CLASS}) @kotlin.annotation.Retention(value = AnnotationRetention.BINARY) public final annotation class PluginHiddenFromObjC : kotlin.Annotation { public constructor PluginHiddenFromObjC() public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SpecialBackendChecksTraversal.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SpecialBackendChecksTraversal.kt index fca251bd024..18c398099f0 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SpecialBackendChecksTraversal.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SpecialBackendChecksTraversal.kt @@ -596,9 +596,36 @@ private class BackendChecker( if (declaration.isKotlinObjCClass()) { checkKotlinObjCClass(declaration) } + checkHiddenFromObjCRestrictions(declaration) super.visitClass(declaration) } + + private fun IrClass.isHiddenFromObjC(): Boolean { + return annotations.any { annotationCall -> + val annotationClass = annotationCall.symbol.owner.parentAsClass + annotationClass.annotations.hasAnnotation(KonanFqNames.hidesFromObjC) + } + } + + private fun checkHiddenFromObjCRestrictions(declaration: IrClass) { + // If declaration itself. is marked as hidden, we don't need to do anything + if (declaration.isHiddenFromObjC()) return + // Enum entries inherit from their classes, so if enum class is marked as hidden, there is no need to + // produce additional errors. + if (declaration.kind == ClassKind.ENUM_ENTRY) return + // Private and local classes does not leak to Objective-C API surface, so it is OK for them + // to inherit from hidden types. + if (DescriptorVisibilities.isPrivate(declaration.visibility) || declaration.isLocal) return + if (declaration.superClass?.isHiddenFromObjC() == true) { + reportError(declaration, "Only @HiddenFromObjC declaration can inherit from class annotated with @HiddenFromObjC") + } + declaration.getSuperInterfaces().forEach { + if (it.isHiddenFromObjC()) { + reportError(declaration, "Only @HiddenFromObjC declaration can implement interface annotated with @HiddenFromObjC") + } + } + } } private fun BackendChecker.checkCanGenerateCCall(expression: IrCall, isInvoke: Boolean) { diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt index 81f573aec3a..d4bf34d0e11 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt @@ -108,6 +108,16 @@ private fun CallableMemberDescriptor.isHiddenFromObjC(): Boolean = when { } } +/** + * Check if the given class or its enclosing declaration is marked as @HiddenFromObjC. + */ +internal fun ClassDescriptor.isHiddenFromObjC(): Boolean = when { + (this.containingDeclaration as? ClassDescriptor)?.isHiddenFromObjC() == true -> true + else -> annotations.any { annotation -> + annotation.annotationClass?.annotations?.any { it.fqName == KonanFqNames.hidesFromObjC } == true + } +} + internal fun ObjCExportMapper.shouldBeExposed(descriptor: ClassDescriptor): Boolean = shouldBeVisible(descriptor) && !isSpecialMapped(descriptor) && !descriptor.defaultType.isObjCObjectType() @@ -175,7 +185,7 @@ internal fun ObjCExportMapper.shouldBeVisible(descriptor: ClassDescriptor): Bool descriptor.isEffectivelyPublicApi && when (descriptor.kind) { ClassKind.CLASS, ClassKind.INTERFACE, ClassKind.ENUM_CLASS, ClassKind.OBJECT -> true ClassKind.ENUM_ENTRY, ClassKind.ANNOTATION_CLASS -> false - } && !descriptor.isExpect && !descriptor.isInlined() && !isHiddenByDeprecation(descriptor) + } && !descriptor.isExpect && !descriptor.isInlined() && !isHiddenByDeprecation(descriptor) && !descriptor.isHiddenFromObjC() private fun ObjCExportMapper.isBase(descriptor: CallableMemberDescriptor): Boolean = descriptor.overriddenDescriptors.all { !shouldBeExposed(it) } diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/Annotations.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/Annotations.kt index 16701403862..f1e501bfa49 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/Annotations.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/Annotations.kt @@ -107,12 +107,12 @@ public actual annotation class CName(actual val externName: String = "", actual public actual annotation class ObjCName(actual val name: String = "", actual val swiftName: String = "", actual val exact: Boolean = false) /** - * Meta-annotation that instructs the Kotlin compiler to remove the annotated function or property from the public Objective-C API. + * Meta-annotation that instructs the Kotlin compiler to remove the annotated class, function or property from the public Objective-C API. * * Annotation processors that refine the public Objective-C API can annotate their annotations with this meta-annotation * to have the original declarations automatically removed from the public API. * - * Note: only annotations with [AnnotationTarget.FUNCTION] and/or [AnnotationTarget.PROPERTY] are supported. + * Note: only annotations with [AnnotationTarget.CLASS], [AnnotationTarget.FUNCTION] and/or [AnnotationTarget.PROPERTY] are supported. */ @Target(AnnotationTarget.ANNOTATION_CLASS) @Retention(AnnotationRetention.BINARY) @@ -122,10 +122,10 @@ public actual annotation class ObjCName(actual val name: String = "", actual val public actual annotation class HidesFromObjC /** - * Instructs the Kotlin compiler to remove this function or property from the public Objective-C API. + * Instructs the Kotlin compiler to remove this class, function or property from the public Objective-C API. */ @HidesFromObjC -@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION) +@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) @MustBeDocumented @ExperimentalObjCRefinement diff --git a/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt b/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt index 45091e4def7..431d5f8651a 100644 --- a/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt +++ b/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt @@ -69,12 +69,12 @@ expect annotation class FreezingIsDeprecated public expect annotation class ObjCName(val name: String = "", val swiftName: String = "", val exact: Boolean = false) /** - * Meta-annotation that instructs the Kotlin compiler to remove the annotated function or property from the public Objective-C API. + * Meta-annotation that instructs the Kotlin compiler to remove the annotated class, function or property from the public Objective-C API. * * Annotation processors that refine the public Objective-C API can annotate their annotations with this meta-annotation * to have the original declarations automatically removed from the public API. * - * Note: only annotations with [AnnotationTarget.FUNCTION] and/or [AnnotationTarget.PROPERTY] are supported. + * Note: only annotations with [AnnotationTarget.CLASS], [AnnotationTarget.FUNCTION] and/or [AnnotationTarget.PROPERTY] are supported. */ @Target(AnnotationTarget.ANNOTATION_CLASS) @Retention(AnnotationRetention.BINARY) @@ -85,10 +85,10 @@ public expect annotation class ObjCName(val name: String = "", val swiftName: St public expect annotation class HidesFromObjC() /** - * Instructs the Kotlin compiler to remove this function or property from the public Objective-C API. + * Instructs the Kotlin compiler to remove this class, function or property from the public Objective-C API. */ @HidesFromObjC -@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION) +@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) @MustBeDocumented @OptionalExpectation 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 dbc2d5a7b8d..767820e0e7f 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 @@ -64,7 +64,7 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy { ) put( ErrorsNative.INVALID_OBJC_REFINEMENT_TARGETS, - "Refines annotations are only applicable to annotations with targets FUNCTION and/or PROPERTY" + "Refines annotations are only applicable to annotations with targets CLASS, FUNCTION and/or PROPERTY" ) } } diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeObjCRefinementAnnotationChecker.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeObjCRefinementAnnotationChecker.kt index da139e224b3..18ce765dda1 100644 --- a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeObjCRefinementAnnotationChecker.kt +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeObjCRefinementAnnotationChecker.kt @@ -21,7 +21,7 @@ import org.jetbrains.kotlin.resolve.konan.diagnostics.NativeObjCRefinementChecke object NativeObjCRefinementAnnotationChecker : DeclarationChecker { - private val supportedTargets = arrayOf(KotlinTarget.FUNCTION, KotlinTarget.PROPERTY) + private val supportedTargets = arrayOf(KotlinTarget.FUNCTION, KotlinTarget.PROPERTY, KotlinTarget.CLASS) override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { if (descriptor !is ClassDescriptor || descriptor.kind != ClassKind.ANNOTATION_CLASS) return