[ObjCExport] Add unavailable classifiers support

^KT-70596 Fixed
diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getAllContainingDeclarations.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getAllContainingDeclarations.kt
new file mode 100644
index 0000000..ec261db
--- /dev/null
+++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getAllContainingDeclarations.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2010-2024 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.objcexport.analysisApiUtils
+
+import org.jetbrains.kotlin.analysis.api.KaSession
+import org.jetbrains.kotlin.analysis.api.symbols.KaDeclarationSymbol
+import org.jetbrains.kotlin.analysis.api.symbols.KaSymbol
+import org.jetbrains.kotlin.tooling.core.withClosure
+
+internal fun KaSession.getAllContainingDeclarations(symbol: KaSymbol): List<KaDeclarationSymbol> {
+    return symbol.containingDeclaration?.withClosure { declaration ->
+        listOf(declaration) + listOfNotNull(declaration.containingDeclaration)
+    }?.toList() ?: emptyList()
+}
+
diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getAllVisibleInObjClassifiers.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getAllVisibleInObjClassifiers.kt
index 36a71be..d772f1a 100644
--- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getAllVisibleInObjClassifiers.kt
+++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getAllVisibleInObjClassifiers.kt
@@ -8,6 +8,7 @@
 import org.jetbrains.kotlin.analysis.api.KaSession
 import org.jetbrains.kotlin.analysis.api.symbols.KaClassSymbol
 import org.jetbrains.kotlin.analysis.api.symbols.KaFileSymbol
+import org.jetbrains.kotlin.objcexport.isUnavailableObjCClassifier
 import org.jetbrains.kotlin.tooling.core.withClosure
 
 
@@ -36,7 +37,7 @@
 
 internal fun KaSession.getAllVisibleInObjClassifiers(symbols: Iterable<KaClassSymbol>): List<KaClassSymbol> {
     return symbols.withClosure<KaClassSymbol> { symbol ->
-        if (isVisibleInObjC(symbol)) {
+        if (isVisibleInObjC(symbol) && !isUnavailableObjCClassifier(symbol)) {
             symbol.memberScope.classifiers.filterIsInstance<KaClassSymbol>().asIterable()
         } else {
             emptyList()
diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getDeclaredSuperInterfaceSymbols.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getDeclaredSuperInterfaceSymbols.kt
index 8070314..2ce8c5c 100644
--- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getDeclaredSuperInterfaceSymbols.kt
+++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getDeclaredSuperInterfaceSymbols.kt
@@ -5,7 +5,6 @@
 
 package org.jetbrains.kotlin.objcexport.analysisApiUtils
 
-import org.jetbrains.kotlin.analysis.api.KaSession
 import org.jetbrains.kotlin.analysis.api.symbols.KaClassKind
 import org.jetbrains.kotlin.analysis.api.symbols.KaClassSymbol
 import org.jetbrains.kotlin.analysis.api.types.symbol
@@ -13,11 +12,11 @@
 /**
  * @return The **declared** super interfaces (**not the transitive closure**)
  */
-internal fun KaSession.getDeclaredSuperInterfaceSymbols(symbol: KaClassSymbol): List<KaClassSymbol> {
+internal fun getDeclaredSuperInterfaceSymbols(symbol: KaClassSymbol): List<KaClassSymbol> {
     return symbol.superTypes
         .asSequence()
         .mapNotNull { type -> type.symbol as? KaClassSymbol }
         .filter { !it.isCloneable } // TODO: Write unit test for this
         .filter { superInterface -> superInterface.classKind == KaClassKind.INTERFACE }
         .toList()
-}
+}
\ No newline at end of file
diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/isUnavailableObjCClassifier.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/isUnavailableObjCClassifier.kt
new file mode 100644
index 0000000..97edb1a
--- /dev/null
+++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/isUnavailableObjCClassifier.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2010-2024 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.objcexport
+
+import org.jetbrains.kotlin.analysis.api.KaSession
+import org.jetbrains.kotlin.analysis.api.annotations.KaAnnotated
+import org.jetbrains.kotlin.analysis.api.symbols.KaClassSymbol
+import org.jetbrains.kotlin.analysis.api.types.symbol
+import org.jetbrains.kotlin.backend.konan.objcexport.*
+import org.jetbrains.kotlin.name.NativeStandardInteropNames
+import org.jetbrains.kotlin.objcexport.analysisApiUtils.getAllContainingDeclarations
+
+/**
+ * Certain set of classifiers is visible, but should be replaced with interface/protocol with attribute `unavailable`
+ * ```objective-c
+ * __attribute__((unavailable("can't be imported")))
+ * @interface Foo
+ * @end
+ * ```
+ *
+ * Classifier is unavailable when:
+ * - Super type is mapped type, for example [List], any [NSNumberKind]
+ * - Super type is [NativeStandardInteropNames.objCObjectClassId] and annotated with [NativeStandardInteropNames.externalObjCClassClassId]
+ */
+internal fun KaSession.isUnavailableObjCClassifier(symbol: KaClassSymbol): Boolean {
+    return isSuperTypeMapped(symbol) || isObjCClass(symbol)
+}
+
+internal fun KaSession.unavailableObjCProtocol(
+    name: String,
+    symbol: KaClassSymbol,
+): ObjCProtocol {
+    return ObjCProtocolImpl(
+        name = name,
+        origin = null,
+        attributes = buildUnavailableAttribute(symbol),
+        comment = null,
+        superProtocols = emptyList(),
+        members = emptyList(),
+    )
+}
+
+internal fun KaSession.unavailableObjCInterface(
+    name: String,
+    symbol: KaClassSymbol,
+): ObjCInterface {
+    return ObjCInterfaceImpl(
+        name = name,
+        comment = null,
+        origin = null,
+        attributes = buildUnavailableAttribute(symbol),
+        superProtocols = emptyList(),
+        members = emptyList(),
+        categoryName = null,
+        generics = emptyList(),
+        superClass = "NSObject",
+        superClassGenerics = emptyList()
+    )
+}
+
+internal fun KaSession.isSuperTypeMapped(symbol: KaClassSymbol?): Boolean {
+    symbol?.superTypes?.forEach { type ->
+        if (isMappedObjCType(type) || isSuperTypeMapped(type.symbol as? KaClassSymbol)) return true
+    }
+    return false
+}
+
+/**
+ * K1 [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.attributesForUnexposed]
+ */
+private fun KaSession.buildUnavailableAttribute(descriptor: KaClassSymbol): List<String> {
+    val message = when {
+        isKotlinObjCClass(descriptor) -> "Kotlin subclass of Objective-C class "
+        else -> ""
+    } + "can't be imported"
+    return listOf("unavailable(\"$message\")")
+}
+
+/**
+ * [org.jetbrains.kotlin.ir.objcinterop.ObjCInteropKt.isKotlinObjCClass]
+ */
+internal fun KaSession.isKotlinObjCClass(symbol: KaClassSymbol): Boolean {
+    return isObjCClass(symbol) && !isAnnotatedAsExternalObjCClass(symbol)
+}
+
+internal fun isObjCClass(symbol: KaClassSymbol): Boolean {
+    return symbol.classId?.packageFqName != interopPackageName &&
+        symbol.superTypes.any { it.symbol?.classId?.asSingleFqName() == objCObjectFqName }
+}
+
+internal fun KaSession.isAnnotatedAsExternalObjCClass(symbol: KaClassSymbol): Boolean {
+    return symbol.isAnnotatedAsExternalObjCClass || getAllContainingDeclarations(symbol).filterIsInstance<KaClassSymbol>().any {
+        it.isAnnotatedAsExternalObjCClass
+    }
+}
+
+internal val KaAnnotated.isAnnotatedAsExternalObjCClass: Boolean
+    get() {
+        return annotations.any { annotation -> annotation.classId?.asSingleFqName() == externalObjCClassFqName }
+    }
+
+private val interopPackageName = NativeStandardInteropNames.cInteropPackage
+private val objCObjectFqName = NativeStandardInteropNames.objCObjectClassId.asSingleFqName()
+private val externalObjCClassFqName = NativeStandardInteropNames.externalObjCClassClassId.asSingleFqName()
\ No newline at end of file
diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCClass.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCClass.kt
index 3d78101..7637a0a 100644
--- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCClass.kt
+++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCClass.kt
@@ -13,75 +13,83 @@
 import org.jetbrains.kotlin.objcexport.analysisApiUtils.isCompanion
 import org.jetbrains.kotlin.objcexport.analysisApiUtils.isThrowable
 import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
-import org.jetbrains.kotlin.objcexport.extras.objCExportStubExtras
 
 
 fun ObjCExportContext.translateToObjCClass(symbol: KaClassSymbol): ObjCClass? = withClassifierContext(symbol) {
     require(
         symbol.classKind == KaClassKind.CLASS ||
-                symbol.classKind == KaClassKind.ENUM_CLASS ||
-                symbol.classKind == KaClassKind.COMPANION_OBJECT ||
-                symbol.classKind == KaClassKind.OBJECT
+            symbol.classKind == KaClassKind.ENUM_CLASS ||
+            symbol.classKind == KaClassKind.COMPANION_OBJECT ||
+            symbol.classKind == KaClassKind.OBJECT
     ) {
         "Unsupported symbol.classKind: ${symbol.classKind}"
     }
     if (!analysisSession.isVisibleInObjC(symbol)) return@withClassifierContext null
 
-    val enumKind = symbol.classKind == KaClassKind.ENUM_CLASS
-    val final = symbol.modality == KaSymbolModality.FINAL
-
     val name = getObjCClassOrProtocolName(symbol)
-    val attributes = (if (enumKind || final) listOf(OBJC_SUBCLASSING_RESTRICTED) else emptyList()) + name.toNameAttributes()
 
-    val comment: ObjCComment? = analysisSession.translateToObjCComment(symbol.annotations)
-    val origin = analysisSession.getObjCExportStubOrigin(symbol)
+    if (analysisSession.isUnavailableObjCClassifier(symbol)) {
+        analysisSession.unavailableObjCInterface(
+            name.objCName,
+            symbol
+        )
+    } else {
+        val enumKind = symbol.classKind == KaClassKind.ENUM_CLASS
+        val final = symbol.modality == KaSymbolModality.FINAL
 
-    val superClass = translateSuperClass(symbol)
-    val superProtocols: List<String> = superProtocols(symbol)
 
-    val members = buildList<ObjCExportStub> {
-        /* The order of members tries to replicate the K1 implementation explicitly */
-        this += translateToObjCConstructors(symbol)
+        val attributes = (if (enumKind || final) listOf(OBJC_SUBCLASSING_RESTRICTED) else emptyList()) + name.toNameAttributes()
 
-        if (symbol.isCompanion || analysisSession.hasCompanionObject(symbol)) {
-            this += buildCompanionProperty(symbol)
+        val comment: ObjCComment? = analysisSession.translateToObjCComment(symbol.annotations)
+        val origin = analysisSession.getObjCExportStubOrigin(symbol)
+
+        val superClass = translateSuperClass(symbol)
+        val superProtocols: List<String> = superProtocols(symbol)
+
+        val members = buildList<ObjCExportStub> {
+            /* The order of members tries to replicate the K1 implementation explicitly */
+            this += translateToObjCConstructors(symbol)
+
+            if (symbol.isCompanion || analysisSession.hasCompanionObject(symbol)) {
+                this += buildCompanionProperty(symbol)
+            }
+
+            this += analysisSession.getCallableSymbolsForObjCMemberTranslation(symbol)
+                .sortedWith(analysisSession.getStableCallableOrder())
+                .flatMap { translateToObjCExportStub(it) }
+
+            if (symbol.classKind == KaClassKind.ENUM_CLASS) {
+                this += translateEnumMembers(symbol)
+            }
+
+            if (analysisSession.isThrowable(symbol)) {
+                this += buildThrowableAsErrorMethod()
+            }
         }
 
-        this += analysisSession.getCallableSymbolsForObjCMemberTranslation(symbol)
-            .sortedWith(analysisSession.getStableCallableOrder())
-            .flatMap { translateToObjCExportStub(it) }
+        val categoryName: String? = null
 
-        if (symbol.classKind == KaClassKind.ENUM_CLASS) {
-            this += translateEnumMembers(symbol)
+        @OptIn(KaExperimentalApi::class)
+        val generics: List<ObjCGenericTypeDeclaration> = symbol.typeParameters.map { typeParameter ->
+            ObjCGenericTypeParameterDeclaration(
+                typeParameter.nameOrAnonymous.asString().toValidObjCSwiftIdentifier(),
+                ObjCVariance.fromKotlinVariance(typeParameter.variance)
+            )
         }
 
-        if (analysisSession.isThrowable(symbol)) {
-            this += buildThrowableAsErrorMethod()
-        }
-    }
-
-    val categoryName: String? = null
-
-    @OptIn(KaExperimentalApi::class)
-    val generics: List<ObjCGenericTypeDeclaration> = symbol.typeParameters.map { typeParameter ->
-        ObjCGenericTypeParameterDeclaration(
-            typeParameter.nameOrAnonymous.asString().toValidObjCSwiftIdentifier(),
-            ObjCVariance.fromKotlinVariance(typeParameter.variance)
+        ObjCInterfaceImpl(
+            name = name.objCName,
+            comment = comment,
+            origin = origin,
+            attributes = attributes,
+            superProtocols = superProtocols,
+            members = members,
+            categoryName = categoryName,
+            generics = generics,
+            superClass = superClass.superClassName.objCName,
+            superClassGenerics = superClass.superClassGenerics
         )
     }
-
-    ObjCInterfaceImpl(
-        name = name.objCName,
-        comment = comment,
-        origin = origin,
-        attributes = attributes,
-        superProtocols = superProtocols,
-        members = members,
-        categoryName = categoryName,
-        generics = generics,
-        superClass = superClass.superClassName.objCName,
-        superClassGenerics = superClass.superClassGenerics
-    )
 }
 
 /**
diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt
index 1a1006e..497dfc5 100644
--- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt
+++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt
@@ -149,26 +149,27 @@
         objCStubsByClassId[classId] = objCClass
         objCClass ?: return null
 
-        /*
-        To replicate the translation (and result stub order) of the K1 implementation:
-        1) Super interface / superclass symbols have to be translated right away
-        2) Super interface / superclass symbol export stubs (result of translation) have to be present in the stubs list before the
-        original stub
-         */
-        analysisSession.getDeclaredSuperInterfaceSymbols(symbol).filter { analysisSession.isVisibleInObjC(it) }
-            .forEach { superInterfaceSymbol ->
-                translateClassOrObjectSymbol(superInterfaceSymbol)?.let {
-                    objCProtocolForwardDeclarations += it.name
+        if (!analysisSession.isUnavailableObjCClassifier(symbol)) {
+            /*
+            To replicate the translation (and result stub order) of the K1 implementation:
+            1) Super interface / superclass symbols have to be translated right away
+            2) Super interface / superclass symbol export stubs (result of translation) have to be present in the stubs list before the
+            original stub
+             */
+            getDeclaredSuperInterfaceSymbols(symbol).filter { analysisSession.isVisibleInObjC(it) }
+                .forEach { superInterfaceSymbol ->
+                    translateClassOrObjectSymbol(superInterfaceSymbol)?.let {
+                        objCProtocolForwardDeclarations += it.name
+                    }
+                }
+
+            analysisSession.getSuperClassSymbolNotAny(symbol)?.takeIf { analysisSession.isVisibleInObjC(it) }?.let { superClassSymbol ->
+                translateClassOrObjectSymbol(superClassSymbol)?.let {
+                    objCClassForwardDeclarations += ObjCClassKey(it.name, superClassSymbol.classId?.packageFqName?.asString())
                 }
             }
-
-        analysisSession.getSuperClassSymbolNotAny(symbol)?.takeIf { analysisSession.isVisibleInObjC(it) }?.let { superClassSymbol ->
-            translateClassOrObjectSymbol(superClassSymbol)?.let {
-                objCClassForwardDeclarations += ObjCClassKey(it.name, superClassSymbol.classId?.packageFqName?.asString())
-            }
         }
 
-
         /* Note: It is important to add *this* stub to the result list only after translating/processing the superclass symbols */
         addObjCStubIfNotTranslated(objCClass, symbol.classId?.packageFqName?.asString())
         enqueueDependencyClasses(objCClass)
diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObject.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObject.kt
index 4a3c322..703c574 100644
--- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObject.kt
+++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObject.kt
@@ -19,38 +19,45 @@
     require(symbol.classKind == KaClassKind.OBJECT || symbol.classKind == KaClassKind.COMPANION_OBJECT)
     if (!analysisSession.isVisibleInObjC(symbol)) return@withClassifierContext null
 
-    val enumKind = symbol.classKind == KaClassKind.ENUM_CLASS
-    val final = symbol.modality == KaSymbolModality.FINAL
     val name = getObjCClassOrProtocolName(symbol)
-    val attributes = (if (enumKind || final) listOf(OBJC_SUBCLASSING_RESTRICTED) else emptyList()) + name.toNameAttributes()
-    val comment: ObjCComment? = analysisSession.translateToObjCComment(symbol.annotations)
-    val origin = analysisSession.getObjCExportStubOrigin(symbol)
-    val superProtocols: List<String> = superProtocols(symbol)
-    val categoryName: String? = null
-    val generics: List<ObjCGenericTypeDeclaration> = emptyList()
-    val superClass = translateSuperClass(symbol)
 
-    val objectMembers = mutableListOf<ObjCExportStub>()
-    objectMembers += translateToObjCConstructors(symbol)
-    objectMembers += getDefaultMembers(symbol, objectMembers)
-    objectMembers += with(analysisSession) {
-        symbol.declaredMemberScope.callables
-            .sortedWith(getStableCallableOrder())
-            .flatMap { translateToObjCExportStub(it) }
+    if (analysisSession.isUnavailableObjCClassifier(symbol)) {
+        analysisSession.unavailableObjCInterface(
+            name.objCName,
+            symbol
+        )
+    } else {
+        val enumKind = symbol.classKind == KaClassKind.ENUM_CLASS
+        val final = symbol.modality == KaSymbolModality.FINAL
+        val attributes = (if (enumKind || final) listOf(OBJC_SUBCLASSING_RESTRICTED) else emptyList()) + name.toNameAttributes()
+        val comment = analysisSession.translateToObjCComment(symbol.annotations)
+        val origin = analysisSession.getObjCExportStubOrigin(symbol)
+        val superProtocols = superProtocols(symbol)
+        val generics = emptyList<ObjCGenericTypeDeclaration>()
+        val superClass = translateSuperClass(symbol)
+
+        val objectMembers = mutableListOf<ObjCExportStub>()
+        objectMembers += translateToObjCConstructors(symbol)
+        objectMembers += getDefaultMembers(symbol, objectMembers)
+        objectMembers += with(analysisSession) {
+            symbol.declaredMemberScope.callables
+                .sortedWith(getStableCallableOrder())
+                .flatMap { translateToObjCExportStub(it) }
+        }
+
+        ObjCInterfaceImpl(
+            name = name.objCName,
+            comment = comment,
+            origin = origin,
+            attributes = attributes,
+            superProtocols = superProtocols,
+            members = objectMembers,
+            categoryName = null,
+            generics = generics,
+            superClass = superClass.superClassName.objCName,
+            superClassGenerics = superClass.superClassGenerics
+        )
     }
-
-    ObjCInterfaceImpl(
-        name = name.objCName,
-        comment = comment,
-        origin = origin,
-        attributes = attributes,
-        superProtocols = superProtocols,
-        members = objectMembers,
-        categoryName = categoryName,
-        generics = generics,
-        superClass = superClass.superClassName.objCName,
-        superClassGenerics = superClass.superClassGenerics
-    )
 }
 
 private fun ObjCExportContext.getDefaultMembers(symbol: KaClassSymbol, members: List<ObjCExportStub>): List<ObjCExportStub> {
diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObjectType.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObjectType.kt
index 7ece7df..6e664d0 100644
--- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObjectType.kt
+++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObjectType.kt
@@ -51,7 +51,7 @@
         requiresForwardDeclaration = true
     })
 
-    if (analysisSession.isExternalObjCClass(symbol) || analysisSession.isObjCForwardDeclaration(symbol)) {
+    if (isAnnotatedAsExternalObjCClass(symbol) || isObjCForwardDeclaration(symbol)) {
         return if (symbol.classKind == KaClassKind.INTERFACE) {
             ObjCProtocolType(symbol.nameOrAnonymous.asString().removeSuffix("Protocol"), extras = objCTypeExtras {
                 requiresForwardDeclaration = true
@@ -77,16 +77,16 @@
     return getDeclaredSuperInterfaceSymbols(symbol).any { superInterfaceSymbol -> isObjCMetaClass(superInterfaceSymbol) }
 }
 
-private fun KaSession.isObjCProtocolClass(symbol: KaClassSymbol): Boolean {
+internal fun KaSession.isObjCProtocolClass(symbol: KaClassSymbol): Boolean {
     if (symbol.classId == objCProtocolClassId) return true
     return getDeclaredSuperInterfaceSymbols(symbol).any { superInterfaceSymbol -> isObjCProtocolClass(superInterfaceSymbol) }
 }
 
-private fun KaSession.isExternalObjCClass(symbol: KaClassSymbol): Boolean {
+private fun isAnnotatedAsExternalObjCClass(symbol: KaClassSymbol): Boolean {
     return NativeStandardInteropNames.externalObjCClassClassId in symbol.annotations
 }
 
-private fun KaSession.isObjCForwardDeclaration(symbol: KaClassSymbol): Boolean {
+private fun isObjCForwardDeclaration(symbol: KaClassSymbol): Boolean {
     val classId = symbol.classId ?: return false
     return when (NativeForwardDeclarationKind.packageFqNameToKind[classId.packageFqName]) {
         null, NativeForwardDeclarationKind.Struct -> false
diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt
index ddb154f..14d4797 100644
--- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt
+++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt
@@ -23,25 +23,32 @@
     // TODO: Check error type!
     val name = getObjCClassOrProtocolName(symbol)
 
-    val members = analysisSession.getCallableSymbolsForObjCMemberTranslation(symbol)
-        .filter { analysisSession.isObjCBaseCallable(it) }
-        .sortedWith(analysisSession.getStableCallableOrder())
-        .flatMap { translateToObjCExportStub(it) }
+    if (analysisSession.isUnavailableObjCClassifier(symbol)) {
+        analysisSession.unavailableObjCProtocol(
+            name.objCName,
+            symbol
+        )
+    } else {
+        val members = analysisSession.getCallableSymbolsForObjCMemberTranslation(symbol)
+            .filter { analysisSession.isObjCBaseCallable(it) }
+            .sortedWith(analysisSession.getStableCallableOrder())
+            .flatMap { translateToObjCExportStub(it) }
 
-    val comment: ObjCComment? = analysisSession.translateToObjCComment(symbol.annotations)
+        val comment: ObjCComment? = analysisSession.translateToObjCComment(symbol.annotations)
 
-    ObjCProtocolImpl(
-        name = name.objCName,
-        comment = comment,
-        origin = analysisSession.getObjCExportStubOrigin(symbol),
-        attributes = name.toNameAttributes(),
-        superProtocols = superProtocols(symbol),
-        members = members
-    )
+        ObjCProtocolImpl(
+            name = name.objCName,
+            comment = comment,
+            origin = analysisSession.getObjCExportStubOrigin(symbol),
+            attributes = name.toNameAttributes(),
+            superProtocols = superProtocols(symbol),
+            members = members
+        )
+    }
 }
 
 internal fun ObjCExportContext.superProtocols(symbol: KaClassSymbol): List<String> {
-    return analysisSession.getDeclaredSuperInterfaceSymbols(symbol)
+    return getDeclaredSuperInterfaceSymbols(symbol)
         .filter { analysisSession.isVisibleInObjC(it) }
         .map { superInterface -> getObjCClassOrProtocolName(superInterface).objCName }
         .toList()
diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/GetAllContainingDeclarationsTest.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/GetAllContainingDeclarationsTest.kt
new file mode 100644
index 0000000..1f0f63e
--- /dev/null
+++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/GetAllContainingDeclarationsTest.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010-2024 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.objcexport.tests
+
+import org.jetbrains.kotlin.analysis.api.analyze
+import org.jetbrains.kotlin.analysis.api.symbols.name
+import org.jetbrains.kotlin.objcexport.analysisApiUtils.getAllContainingDeclarations
+import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis
+import org.jetbrains.kotlin.objcexport.testUtils.getClassOrFail
+import org.junit.jupiter.api.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+class GetAllContainingDeclarationsTest(
+    private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis,
+) {
+    @Test
+    fun `test - containing declarations`() {
+        val file = inlineSourceCodeAnalysis.createKtFile(
+            """
+            interface A {
+                interface B {
+                    interface C
+                }
+            }
+        """.trimIndent()
+        )
+
+        analyze(file) {
+            val a = getClassOrFail(file, "A")
+            val b = getClassOrFail(a.staticMemberScope, "B")
+            val c = getClassOrFail(b.staticMemberScope, "C")
+            assertTrue(getAllContainingDeclarations(a).isEmpty())
+            assertEquals(listOf("A"), getAllContainingDeclarations(b).map { it.name?.asString() })
+            assertEquals(listOf("B", "A"), getAllContainingDeclarations(c).map { it.name?.asString() })
+        }
+    }
+}
\ No newline at end of file
diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/UnavailableObjCClassifierTest.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/UnavailableObjCClassifierTest.kt
new file mode 100644
index 0000000..97e002f
--- /dev/null
+++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/UnavailableObjCClassifierTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2010-2024 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.objcexport.tests
+
+import org.jetbrains.kotlin.analysis.api.analyze
+import org.jetbrains.kotlin.objcexport.isAnnotatedAsExternalObjCClass
+import org.jetbrains.kotlin.objcexport.isObjCClass
+import org.jetbrains.kotlin.objcexport.isSuperTypeMapped
+import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis
+import org.jetbrains.kotlin.objcexport.testUtils.getClassOrFail
+import org.junit.jupiter.api.Test
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+class UnavailableObjCClassifierTest(
+    private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis,
+) {
+    @Test
+    fun `test - isSuperTypeMapped`() {
+        val file = inlineSourceCodeAnalysis.createKtFile(
+            """
+            interface Foo
+            interface ListType<T>: List<T> 
+            interface ListTypeExt<T>: ListType<T> 
+        """.trimIndent()
+        )
+
+        analyze(file) {
+            assertFalse(isSuperTypeMapped(getClassOrFail(file, "Foo")))
+            assertTrue(isSuperTypeMapped(getClassOrFail(file, "ListType")))
+            assertTrue(isSuperTypeMapped(getClassOrFail(file, "ListTypeExt")))
+        }
+    }
+
+    @Test
+    fun `test - isObjCClass`() {
+        val file = inlineSourceCodeAnalysis.createKtFile(
+            """
+            import kotlinx.cinterop.ObjCObject
+            interface Foo : ObjCObject
+            interface Bar : List<String>
+        """.trimIndent()
+        )
+
+        analyze(file) {
+            assertTrue(isObjCClass(getClassOrFail(file, "Foo")))
+            assertFalse(isObjCClass(getClassOrFail(file, "Bar")))
+        }
+    }
+
+    @Test
+    fun `test - isAnnotatedAsExternalObjCClass`() {
+        val file = inlineSourceCodeAnalysis.createKtFile(
+            """
+            import kotlinx.cinterop.ExternalObjCClass
+            @ExternalObjCClass
+            interface FooAnnotated
+            interface Foo
+        """.trimIndent()
+        )
+
+        analyze(file) {
+            assertTrue(isAnnotatedAsExternalObjCClass(getClassOrFail(file, "FooAnnotated")))
+            assertFalse(isAnnotatedAsExternalObjCClass(getClassOrFail(file, "Foo")))
+        }
+    }
+}
\ No newline at end of file
diff --git a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt
index 39b6def..a1de27a 100644
--- a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt
+++ b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt
@@ -599,6 +599,11 @@
         doTest(headersTestDataDir.resolve("varWithPrivateSetterTranslatedAsImmutableProperty"))
     }
 
+    @Test
+    fun `test - unavailable classifiers`() {
+        doTest(headersTestDataDir.resolve("unavailableClassifiers"))
+    }
+
     private fun doTest(root: File, configuration: Configuration = Configuration()) {
         if (!root.isDirectory) fail("Expected ${root.absolutePath} to be directory")
         val generatedHeaders = generator.generateHeaders(root, configuration).toString()
diff --git "a/native/objcexport-header-generator/testData/headers/unavailableClassifiers/\041unavailableClassifiers.h" "b/native/objcexport-header-generator/testData/headers/unavailableClassifiers/\041unavailableClassifiers.h"
new file mode 100644
index 0000000..d92399b
--- /dev/null
+++ "b/native/objcexport-header-generator/testData/headers/unavailableClassifiers/\041unavailableClassifiers.h"
@@ -0,0 +1,135 @@
+#import <Foundation/NSArray.h>
+#import <Foundation/NSDictionary.h>
+#import <Foundation/NSError.h>
+#import <Foundation/NSObject.h>
+#import <Foundation/NSSet.h>
+#import <Foundation/NSString.h>
+#import <Foundation/NSValue.h>
+
+@class AnnotatedEnum, AnnotatedObject, KotlinArray<T>, KotlinEnum<E>, KotlinEnumCompanion;
+
+@protocol KotlinComparable, KotlinIterator;
+
+NS_ASSUME_NONNULL_BEGIN
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunknown-warning-option"
+#pragma clang diagnostic ignored "-Wincompatible-property-type"
+#pragma clang diagnostic ignored "-Wnullability"
+
+#pragma push_macro("_Nullable_result")
+#if !__has_feature(nullability_nullable_result)
+#undef _Nullable_result
+#define _Nullable_result _Nullable
+#endif
+
+__attribute__((objc_subclassing_restricted))
+@interface AnnotatedClass : Base
+- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
++ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
+@end
+
+@protocol KotlinComparable
+@required
+- (int32_t)compareToOther:(id _Nullable)other __attribute__((swift_name("compareTo(other:)")));
+@end
+
+@interface KotlinEnum<E> : Base <KotlinComparable>
+- (instancetype)initWithName:(NSString *)name ordinal:(int32_t)ordinal __attribute__((swift_name("init(name:ordinal:)"))) __attribute__((objc_designated_initializer));
+@property (class, readonly, getter=companion) KotlinEnumCompanion *companion __attribute__((swift_name("companion")));
+- (int32_t)compareToOther:(E)other __attribute__((swift_name("compareTo(other:)")));
+- (BOOL)isEqual:(id _Nullable)other __attribute__((swift_name("isEqual(_:)")));
+- (NSUInteger)hash __attribute__((swift_name("hash()")));
+- (NSString *)description __attribute__((swift_name("description()")));
+@property (readonly) NSString *name __attribute__((swift_name("name")));
+@property (readonly) int32_t ordinal __attribute__((swift_name("ordinal")));
+@end
+
+__attribute__((objc_subclassing_restricted))
+@interface AnnotatedEnum : KotlinEnum<AnnotatedEnum *>
++ (instancetype)alloc __attribute__((unavailable));
++ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable));
+- (instancetype)initWithName:(NSString *)name ordinal:(int32_t)ordinal __attribute__((swift_name("init(name:ordinal:)"))) __attribute__((objc_designated_initializer)) __attribute__((unavailable));
++ (KotlinArray<AnnotatedEnum *> *)values __attribute__((swift_name("values()")));
+@property (class, readonly) NSArray<AnnotatedEnum *> *entries __attribute__((swift_name("entries")));
+@end
+
+@protocol AnnotatedInterface
+@required
+@end
+
+__attribute__((unavailable("can't be imported")))
+@interface AnnotatedObjCObjectClass : NSObject
+@end
+
+__attribute__((unavailable("can't be imported")))
+@interface AnnotatedObjCObjectEnum : NSObject
+@end
+
+__attribute__((unavailable("can't be imported")))
+@protocol AnnotatedObjCObjectInterface
+@required
+@end
+
+__attribute__((unavailable("can't be imported")))
+@interface AnnotatedObjCObjectObject : NSObject
+@end
+
+__attribute__((objc_subclassing_restricted))
+@interface AnnotatedObject : Base
++ (instancetype)alloc __attribute__((unavailable));
++ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable));
++ (instancetype)annotatedObject __attribute__((swift_name("init()")));
+@property (class, readonly, getter=shared) AnnotatedObject *shared __attribute__((swift_name("shared")));
+@end
+
+__attribute__((unavailable("can't be imported")))
+@interface ListClass : NSObject
+@end
+
+__attribute__((unavailable("can't be imported")))
+@protocol ListInterface
+@required
+@end
+
+__attribute__((unavailable("can't be imported")))
+@protocol ListInterfaceExtension
+@required
+@end
+
+__attribute__((unavailable("Kotlin subclass of Objective-C class can't be imported")))
+@interface ObjCObjectClass : NSObject
+@end
+
+__attribute__((unavailable("Kotlin subclass of Objective-C class can't be imported")))
+@protocol ObjCObjectInterface
+@required
+@end
+
+__attribute__((objc_subclassing_restricted))
+@interface KotlinEnumCompanion : Base
++ (instancetype)alloc __attribute__((unavailable));
++ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable));
++ (instancetype)companion __attribute__((swift_name("init()")));
+@property (class, readonly, getter=shared) KotlinEnumCompanion *shared __attribute__((swift_name("shared")));
+@end
+
+__attribute__((objc_subclassing_restricted))
+@interface KotlinArray<T> : Base
++ (instancetype)arrayWithSize:(int32_t)size init:(T _Nullable (^)(Int *))init __attribute__((swift_name("init(size:init:)")));
++ (instancetype)alloc __attribute__((unavailable));
++ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable));
+- (T _Nullable)getIndex:(int32_t)index __attribute__((swift_name("get(index:)")));
+- (id<KotlinIterator>)iterator __attribute__((swift_name("iterator()")));
+- (void)setIndex:(int32_t)index value:(T _Nullable)value __attribute__((swift_name("set(index:value:)")));
+@property (readonly) int32_t size __attribute__((swift_name("size")));
+@end
+
+@protocol KotlinIterator
+@required
+- (BOOL)hasNext __attribute__((swift_name("hasNext()")));
+- (id _Nullable)next __attribute__((swift_name("next()")));
+@end
+
+#pragma pop_macro("_Nullable_result")
+#pragma clang diagnostic pop
+NS_ASSUME_NONNULL_END
diff --git a/native/objcexport-header-generator/testData/headers/unavailableClassifiers/Foo.kt b/native/objcexport-header-generator/testData/headers/unavailableClassifiers/Foo.kt
new file mode 100644
index 0000000..780432d
--- /dev/null
+++ b/native/objcexport-header-generator/testData/headers/unavailableClassifiers/Foo.kt
@@ -0,0 +1,33 @@
+import kotlinx.cinterop.ObjCObject
+import kotlinx.cinterop.ExternalObjCClass
+
+abstract class ListClass<T> : List<T>
+interface ListInterface<T> : List<T>
+interface ListInterfaceExtension<T> : ListInterface<T>
+
+class ObjCObjectClass : ObjCObject
+interface ObjCObjectInterface : ObjCObject
+
+@ExternalObjCClass
+class AnnotatedClass
+
+@ExternalObjCClass
+interface AnnotatedInterface
+
+@ExternalObjCClass
+object AnnotatedObject
+
+@ExternalObjCClass
+enum class AnnotatedEnum
+
+@ExternalObjCClass
+class AnnotatedObjCObjectClass : ObjCObject
+
+@ExternalObjCClass
+interface AnnotatedObjCObjectInterface : ObjCObject
+
+@ExternalObjCClass
+object AnnotatedObjCObjectObject : ObjCObject
+
+@ExternalObjCClass
+enum class AnnotatedObjCObjectEnum : ObjCObject
\ No newline at end of file