[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