From 2ab6493d82866a000323544ebd00defcaaea40bb Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Mon, 29 Sep 2025 13:48:09 +0200 Subject: [PATCH 01/20] make 'something' visible end-to-end --- .../konan/objcexport/ObjCExportHeaderGenerator.kt | 2 +- .../konan/objcexport/ObjCExportTranslator.kt | 14 +++++++++----- .../backend/konan/objcexport/StubRenderer.kt | 11 +++++++++++ .../kotlin/backend/konan/objcexport/stubs.kt | 12 ++++++++++++ 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt index 1b60e07c1b579..338df7a84f14b 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt @@ -198,7 +198,7 @@ abstract class ObjCExportHeaderGenerator @InternalKotlinNativeApi constructor( private fun generateClass(descriptor: ClassDescriptor) { if (!generatedClasses.add(descriptor)) return - stubs.add(translator.translateClass(descriptor)) + stubs.addAll(translator.translateClass(descriptor)) } private fun generateInterface(descriptor: ClassDescriptor) { diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt index 280f43af52df2..493b183e85f97 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt @@ -36,7 +36,7 @@ interface ObjCExportTranslator { fun generateBaseDeclarations(): List fun getClassIfExtension(receiverType: KotlinType): ClassDescriptor? fun translateFile(file: SourceFile, declarations: List): ObjCInterface - fun translateClass(descriptor: ClassDescriptor): ObjCInterface + fun translateClass(descriptor: ClassDescriptor): List fun translateInterface(descriptor: ClassDescriptor): ObjCProtocol fun translateExtensions(classDescriptor: ClassDescriptor, declarations: List): ObjCInterface } @@ -172,10 +172,10 @@ class ObjCExportTranslatorImpl( ) } - override fun translateClass(descriptor: ClassDescriptor): ObjCInterface { + override fun translateClass(descriptor: ClassDescriptor): List { require(!descriptor.isInterface) if (!mapper.shouldBeExposed(descriptor)) { - return translateUnexposedClassAsUnavailableStub(descriptor) + return listOf(translateUnexposedClassAsUnavailableStub(descriptor)) } val genericExportScope = createGenericExportScope(descriptor) @@ -201,6 +201,7 @@ class ObjCExportTranslatorImpl( } val superProtocols: List = descriptor.superProtocols + val additionalArtifacts = mutableListOf() val members: List = buildMembers { val presentConstructors = mutableSetOf() @@ -284,6 +285,9 @@ class ObjCExportTranslatorImpl( ClassKind.ENUM_CLASS -> { val type = mapType(descriptor.defaultType, ReferenceBridge, ObjCRootExportScope) + additionalArtifacts.add( + ObjCNativeEnum(descriptor.name.asString(), descriptor.enumEntries.map { it.name.asString() } )) + descriptor.enumEntries.forEach { val entryName = namer.getEnumEntrySelector(it) val swiftName = namer.getEnumEntrySwiftName(it) @@ -337,7 +341,7 @@ class ObjCExportTranslatorImpl( val generics = mapTypeConstructorParameters(descriptor) val superClassGenerics = superClassGenerics(genericExportScope) - return objCInterface( + return additionalArtifacts + listOf(objCInterface( name, generics = generics, descriptor = descriptor, @@ -347,7 +351,7 @@ class ObjCExportTranslatorImpl( members = members, attributes = attributes, comment = objCCommentOrNull(mustBeDocumentedAttributeList(descriptor.annotations)) - ) + )) } internal fun createGenericExportScope(descriptor: ClassDescriptor): ObjCExportScope = if (objcGenerics) { diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt index 7142753fa6186..88b12dc1488f7 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt @@ -84,6 +84,9 @@ object StubRenderer { is ObjCProperty -> { +renderProperty(this) } + is ObjCNativeEnum -> { + +renderNativeEnum(this) + } else -> throw IllegalArgumentException("unsupported stub: " + stub::class) } } @@ -119,6 +122,14 @@ object StubRenderer { append(';') } + private fun renderNativeEnum(nativeEnum: ObjCNativeEnum): String = buildString { + append(" typedef NS_ENUM(int32_t, ${nativeEnum.name}) {\n") + for ((index, literal) in nativeEnum.literals.withIndex()) { + append(" $literal = $index,\n") + } + append(" };\n") + } + private fun renderMethod(method: ObjCMethod): String = buildString { fun appendStaticness() { if (method.isInstanceMethod) { diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt index d6fdf0fbad1dc..5e002a3563a37 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt @@ -10,6 +10,7 @@ package org.jetbrains.kotlin.backend.konan.objcexport import org.jetbrains.kotlin.tooling.core.Extras import org.jetbrains.kotlin.tooling.core.HasExtras import org.jetbrains.kotlin.tooling.core.emptyExtras +import org.jetbrains.kotlin.tooling.core.mutableExtrasOf @Deprecated("Use 'ObjCExportStub' instead", replaceWith = ReplaceWith("ObjCExportStub")) @Suppress("unused") @@ -45,6 +46,17 @@ val ObjCExportStub.psiOrNull abstract class ObjCTopLevel : ObjCExportStub +class ObjCNativeEnum( + override val name: String, + val literals: List, +) : ObjCTopLevel() { + override val comment: ObjCComment? + get() = null + override val origin: ObjCExportStubOrigin? + get() = null + override val extras = mutableExtrasOf() +} + sealed class ObjCClass : ObjCTopLevel() { abstract val attributes: List abstract val superProtocols: List From b824f55e7f586c745212eb0ac9c8bb9576e53e5f Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Thu, 2 Oct 2025 14:32:56 +0200 Subject: [PATCH 02/20] End-to-end wireup with test; method body missing --- .../main/kotlin/kotlin/native/Annotations.kt | 15 ++- .../kotlin/annotations/NativeAnnotations.kt | 17 ++++ .../experimental/ExperimentalObjCEnum.kt | 16 ++++ .../objcexport/analysisApiUtils/errors.kt | 3 +- .../mangling/mangleExtensionFacadesMembers.kt | 3 +- .../mangling/mangleObjCInterface.kt | 3 +- .../kotlin/objcexport/translateToObjCClass.kt | 3 +- .../translateToObjCExtensionFacades.kt | 3 +- .../objcexport/translateToObjCHeader.kt | 1 + .../objcexport/translateToObjCObject.kt | 3 +- .../translateToObjCTopLevelFacade.kt | 3 +- .../objcexport/ObjCExportHeaderGenerator.kt | 2 +- .../konan/objcexport/ObjCExportLazy.kt | 3 + .../objcexport/ObjCExportStubFactories.kt | 4 +- .../konan/objcexport/ObjCExportTranslator.kt | 40 ++++++-- .../objcexport/ObjCExportBaseDeclarations.kt | 10 +- .../backend/konan/objcexport/StubRenderer.kt | 15 +-- .../kotlin/backend/konan/objcexport/stubs.kt | 4 +- .../CategorizedInterfacesComparatorTests.kt | 1 + .../tests/ObjCExportHeaderGeneratorTest.kt | 5 + .../!simpleEnumClassWithObjCEnum.h | 91 +++++++++++++++++++ .../simpleEnumClassWithObjCEnum/Foo.kt | 8 ++ 22 files changed, 222 insertions(+), 31 deletions(-) create mode 100644 libraries/stdlib/src/kotlin/experimental/ExperimentalObjCEnum.kt create mode 100644 native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/!simpleEnumClassWithObjCEnum.h create mode 100644 native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/Foo.kt 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 c47c7e300472d..618a28fee0a0b 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/Annotations.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/Annotations.kt @@ -113,9 +113,22 @@ public actual annotation class CName(actual val externName: String = "", actual @Retention(AnnotationRetention.BINARY) @MustBeDocumented @ExperimentalObjCName -@SinceKotlin("1.8") +@SinceKotlin("2.2.21") public actual annotation class ObjCName(actual val name: String = "", actual val swiftName: String = "", actual val exact: Boolean = false) +/** + * Instructs the Kotlin compiler to generate a NS_ENUM typedef for the annotated enum class. The name of the generated type will + * be the name of the enum type with "_Enum" appended. This name can be overridden with the "name" parameter, which is treated + * as an exact name. The enum literals will be prefixed with the type name and an underscore, as they live in a global namespace. + * Swift naming will automatically remove these prefixes. + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.BINARY) +@MustBeDocumented +@ExperimentalObjCName +@SinceKotlin("2.2.21") +public actual annotation class ObjCEnum(actual val name: String = "") + /** * Meta-annotation that instructs the Kotlin compiler to remove the annotated class, function or property from the public Objective-C API. * diff --git a/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt b/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt index 26328c074e7d5..e6a6516240867 100644 --- a/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt +++ b/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt @@ -6,6 +6,7 @@ package kotlin.native import kotlin.experimental.ExperimentalNativeApi +import kotlin.experimental.ExperimentalObjCEnum import kotlin.experimental.ExperimentalObjCName import kotlin.experimental.ExperimentalObjCRefinement @@ -71,6 +72,22 @@ public expect annotation class FreezingIsDeprecated @SinceKotlin("1.8") public expect annotation class ObjCName(val name: String = "", val swiftName: String = "", val exact: Boolean = false) +/** + * Instructs the Kotlin compiler to generate a NS_ENUM typedef for the annotated enum class. The name of the generated type will + * be the name of the enum type with "_Enum" appended. This name can be overridden with the "name" parameter, which is treated + * as an exact name. The enum literals will be prefixed with the type name and an underscore, as they live in a global namespace. + * Swift naming will automatically remove these disambiguation prefixes. + */ +@Target( + AnnotationTarget.CLASS, +) +@Retention(AnnotationRetention.BINARY) +@MustBeDocumented +@OptionalExpectation +@ExperimentalObjCEnum +@SinceKotlin("2.2.21") +public expect annotation class ObjCEnum(val name: String = "") + /** * Meta-annotation that instructs the Kotlin compiler to remove the annotated class, function or property from the public Objective-C API. * diff --git a/libraries/stdlib/src/kotlin/experimental/ExperimentalObjCEnum.kt b/libraries/stdlib/src/kotlin/experimental/ExperimentalObjCEnum.kt new file mode 100644 index 0000000000000..0dc6afc89a4ac --- /dev/null +++ b/libraries/stdlib/src/kotlin/experimental/ExperimentalObjCEnum.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2010-2022 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 kotlin.experimental + +/** + * This annotation marks the experimental [ObjCEnum][kotlin.native.ObjCEnum] annotation. + */ +@RequiresOptIn +@Target(AnnotationTarget.ANNOTATION_CLASS) +@Retention(AnnotationRetention.BINARY) +@MustBeDocumented +@SinceKotlin("2.2.21") +public annotation class ExperimentalObjCEnum diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/errors.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/errors.kt index 0430e79142442..b672479ef94c5 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/errors.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/errors.kt @@ -66,7 +66,8 @@ internal val KtObjCExportSession.errorInterface categoryName = null, generics = emptyList(), superClass = getDefaultSuperClassOrProtocolName().objCName, - superClassGenerics = emptyList() + superClassGenerics = emptyList(), + nativeEnum = null, ) internal val objCErrorType = ObjCClassType(errorClassName, extras = objCTypeExtras { diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/mangling/mangleExtensionFacadesMembers.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/mangling/mangleExtensionFacadesMembers.kt index d2e1f7d329182..e6a985916b354 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/mangling/mangleExtensionFacadesMembers.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/mangling/mangleExtensionFacadesMembers.kt @@ -35,7 +35,8 @@ internal fun mangleExtensionFacadesMembers(stubs: List): List categoryName = extensionsCategoryName, generics = emptyList(), superClass = null, - superClassGenerics = emptyList() + superClassGenerics = emptyList(), + nativeEnum = null, ) } 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 12a8c34ffed23..5ebb46f252822 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 @@ -202,6 +202,7 @@ private class KtObjCExportHeaderGenerator( is ObjCProperty -> listOf(childStub.type) is ObjCInterface -> childStub.superClassGenerics is ObjCTopLevel -> emptyList() + is ObjCNativeEnum -> emptyList() } }.map { type -> /** 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 f948064ff725d..4d5dd32a5488f 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 @@ -49,7 +49,8 @@ fun ObjCExportContext.translateToObjCObject(symbol: KaClassSymbol): ObjCClass? = categoryName = categoryName, generics = generics, superClass = superClass.superClassName.objCName, - superClassGenerics = superClass.superClassGenerics + superClassGenerics = superClass.superClassGenerics, + nativeEnum = null, ) } diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelFacade.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelFacade.kt index e7b3c1eae2cd7..305ec7efc5b6d 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelFacade.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelFacade.kt @@ -66,7 +66,8 @@ fun ObjCExportContext.translateToObjCTopLevelFacade(file: KtResolvedObjCExportFi categoryName = null, generics = emptyList(), superClass = exportSession.getDefaultSuperClassOrProtocolName().objCName, - superClassGenerics = emptyList() + superClassGenerics = emptyList(), + nativeEnum = null, ) return null } \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt index 338df7a84f14b..1b60e07c1b579 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt @@ -198,7 +198,7 @@ abstract class ObjCExportHeaderGenerator @InternalKotlinNativeApi constructor( private fun generateClass(descriptor: ClassDescriptor) { if (!generatedClasses.add(descriptor)) return - stubs.addAll(translator.translateClass(descriptor)) + stubs.add(translator.translateClass(descriptor)) } private fun generateInterface(descriptor: ClassDescriptor) { diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportLazy.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportLazy.kt index f3097839ba44d..4ca3ed3948c0f 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportLazy.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportLazy.kt @@ -433,6 +433,9 @@ private abstract class LazyObjCInterface( final override val comment: ObjCComment? get() = realStub.comment + + override val nativeEnum: ObjCNativeEnum? + get() = realStub.nativeEnum } private abstract class LazyObjCProtocol( diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportStubFactories.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportStubFactories.kt index afcf4ccd94121..115ba0f221e32 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportStubFactories.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportStubFactories.kt @@ -57,6 +57,7 @@ fun ObjCInterfaceImpl( members: List = emptyList(), attributes: List = emptyList(), comment: ObjCComment? = null, + nativeEnum: ObjCNativeEnum? = null, ) = ObjCInterfaceImpl( name = name, comment = comment, @@ -67,7 +68,8 @@ fun ObjCInterfaceImpl( categoryName = categoryName, generics = generics, superClass = superClass, - superClassGenerics = superClassGenerics + superClassGenerics = superClassGenerics, + nativeEnum = nativeEnum ) fun ObjCMethod( diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt index 493b183e85f97..b7e1dcaf808fa 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt @@ -17,6 +17,7 @@ import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.ir.objcinterop.* import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.constants.ArrayValue import org.jetbrains.kotlin.resolve.constants.KClassValue import org.jetbrains.kotlin.resolve.deprecation.DeprecationInfo @@ -36,7 +37,7 @@ interface ObjCExportTranslator { fun generateBaseDeclarations(): List fun getClassIfExtension(receiverType: KotlinType): ClassDescriptor? fun translateFile(file: SourceFile, declarations: List): ObjCInterface - fun translateClass(descriptor: ClassDescriptor): List + fun translateClass(descriptor: ClassDescriptor): ObjCInterface fun translateInterface(descriptor: ClassDescriptor): ObjCProtocol fun translateExtensions(classDescriptor: ClassDescriptor, declarations: List): ObjCInterface } @@ -172,10 +173,10 @@ class ObjCExportTranslatorImpl( ) } - override fun translateClass(descriptor: ClassDescriptor): List { + override fun translateClass(descriptor: ClassDescriptor): ObjCInterface { require(!descriptor.isInterface) if (!mapper.shouldBeExposed(descriptor)) { - return listOf(translateUnexposedClassAsUnavailableStub(descriptor)) + return translateUnexposedClassAsUnavailableStub(descriptor) } val genericExportScope = createGenericExportScope(descriptor) @@ -200,8 +201,8 @@ class ObjCExportTranslatorImpl( referenceClass(superClass) } + var nativeEnum: ObjCNativeEnum? = null val superProtocols: List = descriptor.superProtocols - val additionalArtifacts = mutableListOf() val members: List = buildMembers { val presentConstructors = mutableSetOf() @@ -285,8 +286,24 @@ class ObjCExportTranslatorImpl( ClassKind.ENUM_CLASS -> { val type = mapType(descriptor.defaultType, ReferenceBridge, ObjCRootExportScope) - additionalArtifacts.add( - ObjCNativeEnum(descriptor.name.asString(), descriptor.enumEntries.map { it.name.asString() } )) + descriptor.annotations.findAnnotation(FqName("kotlin.native.ObjCEnum"))?.let { + val name = it.allValueArguments.entries.find { it.key.asString() == "name" }?.value?.value?.toString() + val nsEnumName = name ?: "${namer.getClassOrProtocolName(descriptor).objCName}_Enum" + nativeEnum = + ObjCNativeEnum(nsEnumName, descriptor.enumEntries.map { it.name.asString() } ) + + add { + ObjCMethod( + null, + null, + false, + ObjCRawType(nsEnumName), + listOf("toNSEnum"), + emptyList(), + listOf(OBJC_METHOD_FAMILY_NONE), + ) + } + } descriptor.enumEntries.forEach { val entryName = namer.getEnumEntrySelector(it) @@ -341,7 +358,7 @@ class ObjCExportTranslatorImpl( val generics = mapTypeConstructorParameters(descriptor) val superClassGenerics = superClassGenerics(genericExportScope) - return additionalArtifacts + listOf(objCInterface( + return objCInterface( name, generics = generics, descriptor = descriptor, @@ -350,8 +367,9 @@ class ObjCExportTranslatorImpl( superProtocols = superProtocols, members = members, attributes = attributes, - comment = objCCommentOrNull(mustBeDocumentedAttributeList(descriptor.annotations)) - )) + comment = objCCommentOrNull(mustBeDocumentedAttributeList(descriptor.annotations)), + nativeEnum = nativeEnum, + ) } internal fun createGenericExportScope(descriptor: ClassDescriptor): ObjCExportScope = if (objcGenerics) { @@ -1093,6 +1111,7 @@ private fun objCInterface( members: List = emptyList(), attributes: List = emptyList(), comment: ObjCComment? = null, + nativeEnum: ObjCNativeEnum? = null, ): ObjCInterface = ObjCInterfaceImpl( name.objCName, generics, @@ -1103,7 +1122,8 @@ private fun objCInterface( null, members, attributes + name.toNameAttributes(), - comment + comment, + nativeEnum, ) private fun objCProtocol( diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportBaseDeclarations.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportBaseDeclarations.kt index 309d18842ab3d..31fdfcdb25eb9 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportBaseDeclarations.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportBaseDeclarations.kt @@ -66,7 +66,8 @@ fun objCBaseDeclarations( attributes = emptyList(), members = emptyList(), superClass = null, - superClassGenerics = emptyList() + superClassGenerics = emptyList(), + nativeEnum = null, ) } @@ -117,7 +118,8 @@ fun objCBaseDeclarations( superProtocols = emptyList(), generics = emptyList(), superClass = null, - superClassGenerics = emptyList() + superClassGenerics = emptyList(), + nativeEnum = null ) } @@ -223,6 +225,7 @@ private fun objCInterface( members: List = emptyList(), attributes: List = emptyList(), comment: ObjCComment? = null, + nativeEnum: ObjCNativeEnum? = null, ): ObjCInterface = ObjCInterfaceImpl( name = name.objCName, generics = generics, @@ -233,7 +236,8 @@ private fun objCInterface( members = members, attributes = attributes.plus(name.toNameAttributes()), comment = comment, - categoryName = null + categoryName = null, + nativeEnum = nativeEnum, ) private fun objCProtocol( diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt index 88b12dc1488f7..2bdb352b0339f 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt @@ -71,11 +71,13 @@ object StubRenderer { +"@end" } is ObjCInterface -> { + nativeEnum?.let { +renderNativeEnumType(it) } attributes.forEach { +renderAttribute(it) } +renderInterfaceHeader() renderMembers(this, shouldExportKDoc) + nativeEnum?.let { +renderNativeEnumAccessor(it) } +"@end" } is ObjCMethod -> { @@ -84,9 +86,6 @@ object StubRenderer { is ObjCProperty -> { +renderProperty(this) } - is ObjCNativeEnum -> { - +renderNativeEnum(this) - } else -> throw IllegalArgumentException("unsupported stub: " + stub::class) } } @@ -122,14 +121,16 @@ object StubRenderer { append(';') } - private fun renderNativeEnum(nativeEnum: ObjCNativeEnum): String = buildString { - append(" typedef NS_ENUM(int32_t, ${nativeEnum.name}) {\n") + private fun renderNativeEnumType(nativeEnum: ObjCNativeEnum): String = buildString { + append("typedef NS_ENUM(int32_t, ${nativeEnum.name}) {\n") for ((index, literal) in nativeEnum.literals.withIndex()) { - append(" $literal = $index,\n") + append(" ${nativeEnum.name}_$literal = $index,\n") } - append(" };\n") + append("};") } + private fun renderNativeEnumAccessor(nativeEnum: ObjCNativeEnum) = "+(${nativeEnum.name})toNSEnum;" + private fun renderMethod(method: ObjCMethod): String = buildString { fun appendStaticness() { if (method.isInstanceMethod) { diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt index 5e002a3563a37..e56822948f293 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt @@ -49,7 +49,7 @@ abstract class ObjCTopLevel : ObjCExportStub class ObjCNativeEnum( override val name: String, val literals: List, -) : ObjCTopLevel() { +) : ObjCExportStub { override val comment: ObjCComment? get() = null override val origin: ObjCExportStubOrigin? @@ -70,6 +70,7 @@ abstract class ObjCInterface : ObjCClass() { abstract val generics: List abstract val superClass: String? abstract val superClassGenerics: List + abstract val nativeEnum: ObjCNativeEnum? } class ObjCComment(val contentLines: List) { @@ -103,6 +104,7 @@ class ObjCInterfaceImpl( override val superClass: String?, override val superClassGenerics: List, override val extras: Extras = emptyExtras(), + override val nativeEnum: ObjCNativeEnum?, ) : ObjCInterface() class ObjCMethod( diff --git a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/CategorizedInterfacesComparatorTests.kt b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/CategorizedInterfacesComparatorTests.kt index 04cb458ea6112..22f235aac47a2 100644 --- a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/CategorizedInterfacesComparatorTests.kt +++ b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/CategorizedInterfacesComparatorTests.kt @@ -79,4 +79,5 @@ private fun ObjCInterfaceImpl( generics = emptyList(), superClass = null, superClassGenerics = emptyList(), + nativeEnum = null, ) \ 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 240e700608c1a..b2a653ea1146b 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 @@ -48,6 +48,11 @@ class ObjCExportHeaderGeneratorTest(private val generator: HeaderGenerator) { doTest(headersTestDataDir.resolve("simpleEnumClass")) } + @Test + fun `test - simpleEnumClassWithObjCEnum`() { + doTest(headersTestDataDir.resolve("simpleEnumClassWithObjCEnum")) + } + @Test fun `test - simpleObject`() { doTest(headersTestDataDir.resolve("simpleObject")) diff --git a/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/!simpleEnumClassWithObjCEnum.h b/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/!simpleEnumClassWithObjCEnum.h new file mode 100644 index 0000000000000..5d2b04f778ad6 --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/!simpleEnumClassWithObjCEnum.h @@ -0,0 +1,91 @@ +#import +#import +#import +#import +#import +#import +#import + +@class Foo, KotlinArray, KotlinEnum, 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 + +@protocol KotlinComparable +@required +- (int32_t)compareToOther:(id _Nullable)other __attribute__((swift_name("compareTo(other:)"))); +@end + +@interface KotlinEnum : Base +- (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 + + +/** + * @note annotations + * kotlin.experimental.ExperimentalObjCEnum + * kotlin.native.ObjCEnum(name="OBJCFoo") +*/ +typedef NS_ENUM(int32_t, OBJCFoo_Enum) { + OBJCFoo_Enum_A = 0, + OBJCFoo_Enum_B = 1, + OBJCFoo_Enum_C = 2, +}; +__attribute__((objc_subclassing_restricted)) +@interface Foo : KotlinEnum ++ (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)); +@property (class, readonly) Foo *a __attribute__((swift_name("a"))); +@property (class, readonly) Foo *b __attribute__((swift_name("b"))); +@property (class, readonly) Foo *c __attribute__((swift_name("c"))); ++ (KotlinArray *)values __attribute__((swift_name("values()"))); +@property (class, readonly) NSArray *entries __attribute__((swift_name("entries"))); +@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 : 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)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/simpleEnumClassWithObjCEnum/Foo.kt b/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/Foo.kt new file mode 100644 index 0000000000000..a649226ce2192 --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/Foo.kt @@ -0,0 +1,8 @@ +import kotlin.native.ObjCEnum +import kotlin.experimental.ExperimentalObjCEnum + +@ExperimentalObjCEnum +@ObjCEnum("OBJCFoo") +enum class Foo { + A, B, C +} \ No newline at end of file From e180ad4cf3f1a57df409ae647aec942f70290a2f Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Fri, 3 Oct 2025 18:41:44 +0200 Subject: [PATCH 03/20] current state (incomplete) --- .../konan/llvm/objcexport/ObjCExportCodeGenerator.kt | 10 ++++++++++ .../backend/konan/objcexport/ObjCExportCodeSpec.kt | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt index bde5124cd2339..4dd802fc3aa22 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt @@ -1430,6 +1430,9 @@ private fun ObjCExportCodeGenerator.createTypeAdapter( is ObjCInitMethodForKotlinConstructor -> { adapters += createConstructorAdapter(it.baseMethod) } + is ObjCGetterForNSEnumType -> { + adapters += createNSEnumAdapter(it.irClass) + } is ObjCFactoryMethodForKotlinArrayConstructor -> { classAdapters += createArrayConstructorAdapter(it.baseMethod) } @@ -1711,6 +1714,13 @@ private fun ObjCExportCodeGenerator.createObjectInstanceAdapter( return objCToKotlinMethodAdapter(selector, methodBridge, imp) } +private fun ObjCExportCodeGenerator.createNSEnumAdapter(irClass: IrClass): ObjCToKotlinMethodAdapter { + val bridgeName = irClass.computeTypeInfoSymbolName() // ??? + return generateObjCToKotlinSyntheticGetter("toNSEnum", bridgeName) { // bridgeName sounds weird as suffix + // get "ordinal" + } +} + private fun ObjCExportCodeGenerator.createEnumEntryAdapter( irEnumEntry: IrEnumEntry, selector: String diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt index 7e6f8f78705ee..c71f1a3ab32e9 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.backend.konan.descriptors.isInterface import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI +import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.util.IdSignature import org.jetbrains.kotlin.ir.util.SymbolTable @@ -89,6 +90,9 @@ internal fun ObjCExportedInterface.createCodeSpec(symbolTable: SymbolTable): Obj } if (descriptor.kind == ClassKind.ENUM_CLASS) { + // TODO: Gate by annotation + methods.add(ObjCGetterForNSEnumType(irClassSymbol.owner)) // This looks dodgy + descriptor.enumEntries.mapTo(methods) { ObjCGetterForKotlinEnumEntry(symbolTable.descriptorExtension.referenceEnumEntry(it), namer.getEnumEntrySelector(it)) } @@ -224,6 +228,13 @@ internal class ObjCGetterForKotlinEnumEntry( "ObjC spec of getter `$selector` for `$irEnumEntrySymbol`" } +internal class ObjCGetterForNSEnumType( + val irClass: IrClass +) : ObjCMethodSpec() { + override fun toString(): String = + "ObjC spec of toNSEnum()" +} + internal class ObjCClassMethodForKotlinEnumValuesOrEntries( val valuesFunctionSymbol: IrFunctionSymbol, val selector: String From 6ab07aede01eedf1cfac3dbfa2972bb5157d01ad Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Wed, 8 Oct 2025 13:04:33 +0200 Subject: [PATCH 04/20] cleanup --- .../objcexport/ObjCExportCodeGenerator.kt | 4 +-- .../konan/objcexport/ObjCExportCodeSpec.kt | 12 ++++++--- .../objcexport/ObjCExportHeaderGenerator.kt | 4 ++- .../konan/objcexport/ObjCExportLazy.kt | 5 +--- .../konan/objcexport/ObjCExportNamer.kt | 8 ++++++ .../objcexport/ObjCExportStubFactories.kt | 1 - .../konan/objcexport/ObjCExportTranslator.kt | 27 ++++++++++--------- .../objcexport/ObjCExportBaseDeclarations.kt | 4 --- .../backend/konan/objcexport/StubRenderer.kt | 12 +++------ .../kotlin/backend/konan/objcexport/stubs.kt | 2 -- .../CategorizedInterfacesComparatorTests.kt | 1 - 11 files changed, 41 insertions(+), 39 deletions(-) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt index 4dd802fc3aa22..7c471f1577012 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt @@ -1430,9 +1430,9 @@ private fun ObjCExportCodeGenerator.createTypeAdapter( is ObjCInitMethodForKotlinConstructor -> { adapters += createConstructorAdapter(it.baseMethod) } - is ObjCGetterForNSEnumType -> { +/* is ObjCGetterForNSEnumType -> { adapters += createNSEnumAdapter(it.irClass) - } + } */ is ObjCFactoryMethodForKotlinArrayConstructor -> { classAdapters += createArrayConstructorAdapter(it.baseMethod) } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt index c71f1a3ab32e9..238c988b4925b 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt @@ -90,8 +90,13 @@ internal fun ObjCExportedInterface.createCodeSpec(symbolTable: SymbolTable): Obj } if (descriptor.kind == ClassKind.ENUM_CLASS) { - // TODO: Gate by annotation - methods.add(ObjCGetterForNSEnumType(irClassSymbol.owner)) // This looks dodgy + namer.getNSEnumFunctionName(descriptor)?.let { selector -> + val ordinalDescriptor = descriptor.contributedMethods.find { it.name.asString() == "ordinal" }!! + val bridge = mapper.bridgeMethod(ordinalDescriptor) + val symbol = symbolTable.descriptorExtension.referenceSimpleFunction(ordinalDescriptor) + val method = ObjCMethodSpec.BaseMethod(symbol, bridge, selector) + methods.add(ObjCMethodForKotlinMethod(method)) + } descriptor.enumEntries.mapTo(methods) { ObjCGetterForKotlinEnumEntry(symbolTable.descriptorExtension.referenceEnumEntry(it), namer.getEnumEntrySelector(it)) @@ -228,12 +233,13 @@ internal class ObjCGetterForKotlinEnumEntry( "ObjC spec of getter `$selector` for `$irEnumEntrySymbol`" } +/* internal class ObjCGetterForNSEnumType( val irClass: IrClass ) : ObjCMethodSpec() { override fun toString(): String = "ObjC spec of toNSEnum()" -} +}*/ internal class ObjCClassMethodForKotlinEnumValuesOrEntries( val valuesFunctionSymbol: IrFunctionSymbol, diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt index 1b60e07c1b579..78ff1c0607be7 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt @@ -198,7 +198,9 @@ abstract class ObjCExportHeaderGenerator @InternalKotlinNativeApi constructor( private fun generateClass(descriptor: ClassDescriptor) { if (!generatedClasses.add(descriptor)) return - stubs.add(translator.translateClass(descriptor)) + val translatedClass = translator.translateClass(descriptor) + stubs.add(translatedClass.objCInterface) + stubs.addAll(translatedClass.auxiliaryDeclarations) } private fun generateInterface(descriptor: ClassDescriptor) { diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportLazy.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportLazy.kt index 4ca3ed3948c0f..8606dcb13c362 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportLazy.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportLazy.kt @@ -356,7 +356,7 @@ class ObjCExportLazyImpl( override val origin: ObjCExportStubOrigin? by lazy { ObjCExportStubOrigin(descriptor) } - override fun computeRealStub(): ObjCInterface = lazy.translator.translateClass(descriptor) + override fun computeRealStub(): ObjCInterface = lazy.translator.translateClass(descriptor).objCInterface } private class LazyObjCFileInterface( @@ -433,9 +433,6 @@ private abstract class LazyObjCInterface( final override val comment: ObjCComment? get() = realStub.comment - - override val nativeEnum: ObjCNativeEnum? - get() = realStub.nativeEnum } private abstract class LazyObjCProtocol( diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt index 863a7a610597c..0d901e5b96077 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.library.shortName import org.jetbrains.kotlin.library.uniqueName import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType @@ -98,6 +99,13 @@ interface ObjCExportNamer { fun getCompanionObjectPropertySelector(descriptor: ClassDescriptor): String fun needsExplicitMethodFamily(name: String): Boolean + // Null means no NSEnum function. + fun getNSEnumFunctionName(descriptor: ClassDescriptor): String? = + descriptor.annotations.findAnnotation(FqName("kotlin.native.ObjCEnum"))?.let { + val name = it.allValueArguments.entries.find { it.key.asString() == "name" }?.value?.value?.toString() + name ?: "${getClassOrProtocolName(descriptor).objCName}_Enum" + } + companion object { @InternalKotlinNativeApi const val kotlinThrowableAsErrorMethodName: String = "asError" diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportStubFactories.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportStubFactories.kt index 115ba0f221e32..311183df9ee7e 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportStubFactories.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportStubFactories.kt @@ -69,7 +69,6 @@ fun ObjCInterfaceImpl( generics = generics, superClass = superClass, superClassGenerics = superClassGenerics, - nativeEnum = nativeEnum ) fun ObjCMethod( diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt index b7e1dcaf808fa..e42863aa5ab4b 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt @@ -33,11 +33,17 @@ import org.jetbrains.kotlin.types.typeUtil.supertypes import org.jetbrains.kotlin.utils.addIfNotNull import kotlin.* + +data class TranslatedClass( + val objCInterface: ObjCInterface, + val auxiliaryDeclarations: List = emptyList(), +) + interface ObjCExportTranslator { fun generateBaseDeclarations(): List fun getClassIfExtension(receiverType: KotlinType): ClassDescriptor? fun translateFile(file: SourceFile, declarations: List): ObjCInterface - fun translateClass(descriptor: ClassDescriptor): ObjCInterface + fun translateClass(descriptor: ClassDescriptor): TranslatedClass fun translateInterface(descriptor: ClassDescriptor): ObjCProtocol fun translateExtensions(classDescriptor: ClassDescriptor, declarations: List): ObjCInterface } @@ -173,12 +179,13 @@ class ObjCExportTranslatorImpl( ) } - override fun translateClass(descriptor: ClassDescriptor): ObjCInterface { + override fun translateClass(descriptor: ClassDescriptor): TranslatedClass { require(!descriptor.isInterface) if (!mapper.shouldBeExposed(descriptor)) { - return translateUnexposedClassAsUnavailableStub(descriptor) + return TranslatedClass(translateUnexposedClassAsUnavailableStub(descriptor)) } + val auxiliaryDeclarations = mutableListOf() val genericExportScope = createGenericExportScope(descriptor) fun superClassGenerics(genericExportScope: ObjCExportScope): List { @@ -201,7 +208,6 @@ class ObjCExportTranslatorImpl( referenceClass(superClass) } - var nativeEnum: ObjCNativeEnum? = null val superProtocols: List = descriptor.superProtocols val members: List = buildMembers { val presentConstructors = mutableSetOf() @@ -286,11 +292,9 @@ class ObjCExportTranslatorImpl( ClassKind.ENUM_CLASS -> { val type = mapType(descriptor.defaultType, ReferenceBridge, ObjCRootExportScope) - descriptor.annotations.findAnnotation(FqName("kotlin.native.ObjCEnum"))?.let { - val name = it.allValueArguments.entries.find { it.key.asString() == "name" }?.value?.value?.toString() - val nsEnumName = name ?: "${namer.getClassOrProtocolName(descriptor).objCName}_Enum" - nativeEnum = - ObjCNativeEnum(nsEnumName, descriptor.enumEntries.map { it.name.asString() } ) + namer.getNSEnumFunctionName(descriptor)?.let { nsEnumName -> + auxiliaryDeclarations.add( + ObjCNativeEnum(nsEnumName, descriptor.enumEntries.map { it.name.asString() } )) add { ObjCMethod( @@ -358,7 +362,7 @@ class ObjCExportTranslatorImpl( val generics = mapTypeConstructorParameters(descriptor) val superClassGenerics = superClassGenerics(genericExportScope) - return objCInterface( + return TranslatedClass(objCInterface( name, generics = generics, descriptor = descriptor, @@ -368,8 +372,7 @@ class ObjCExportTranslatorImpl( members = members, attributes = attributes, comment = objCCommentOrNull(mustBeDocumentedAttributeList(descriptor.annotations)), - nativeEnum = nativeEnum, - ) + ), auxiliaryDeclarations.toList()) } internal fun createGenericExportScope(descriptor: ClassDescriptor): ObjCExportScope = if (objcGenerics) { diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportBaseDeclarations.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportBaseDeclarations.kt index 31fdfcdb25eb9..14eae44584766 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportBaseDeclarations.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportBaseDeclarations.kt @@ -67,7 +67,6 @@ fun objCBaseDeclarations( members = emptyList(), superClass = null, superClassGenerics = emptyList(), - nativeEnum = null, ) } @@ -119,7 +118,6 @@ fun objCBaseDeclarations( generics = emptyList(), superClass = null, superClassGenerics = emptyList(), - nativeEnum = null ) } @@ -225,7 +223,6 @@ private fun objCInterface( members: List = emptyList(), attributes: List = emptyList(), comment: ObjCComment? = null, - nativeEnum: ObjCNativeEnum? = null, ): ObjCInterface = ObjCInterfaceImpl( name = name.objCName, generics = generics, @@ -237,7 +234,6 @@ private fun objCInterface( attributes = attributes.plus(name.toNameAttributes()), comment = comment, categoryName = null, - nativeEnum = nativeEnum, ) private fun objCProtocol( diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt index 2bdb352b0339f..9a017200061ef 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt @@ -71,14 +71,7 @@ object StubRenderer { +"@end" } is ObjCInterface -> { - nativeEnum?.let { +renderNativeEnumType(it) } - attributes.forEach { - +renderAttribute(it) - } +renderInterfaceHeader() - renderMembers(this, shouldExportKDoc) - nativeEnum?.let { +renderNativeEnumAccessor(it) } - +"@end" } is ObjCMethod -> { +renderMethod(this) @@ -86,6 +79,9 @@ object StubRenderer { is ObjCProperty -> { +renderProperty(this) } + is ObjCNativeEnum -> { + +renderNativeEnumType(this) + } else -> throw IllegalArgumentException("unsupported stub: " + stub::class) } } @@ -129,8 +125,6 @@ object StubRenderer { append("};") } - private fun renderNativeEnumAccessor(nativeEnum: ObjCNativeEnum) = "+(${nativeEnum.name})toNSEnum;" - private fun renderMethod(method: ObjCMethod): String = buildString { fun appendStaticness() { if (method.isInstanceMethod) { diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt index e56822948f293..9174db4048f82 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt @@ -70,7 +70,6 @@ abstract class ObjCInterface : ObjCClass() { abstract val generics: List abstract val superClass: String? abstract val superClassGenerics: List - abstract val nativeEnum: ObjCNativeEnum? } class ObjCComment(val contentLines: List) { @@ -104,7 +103,6 @@ class ObjCInterfaceImpl( override val superClass: String?, override val superClassGenerics: List, override val extras: Extras = emptyExtras(), - override val nativeEnum: ObjCNativeEnum?, ) : ObjCInterface() class ObjCMethod( diff --git a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/CategorizedInterfacesComparatorTests.kt b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/CategorizedInterfacesComparatorTests.kt index 22f235aac47a2..04cb458ea6112 100644 --- a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/CategorizedInterfacesComparatorTests.kt +++ b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/CategorizedInterfacesComparatorTests.kt @@ -79,5 +79,4 @@ private fun ObjCInterfaceImpl( generics = emptyList(), superClass = null, superClassGenerics = emptyList(), - nativeEnum = null, ) \ No newline at end of file From b925ff223a7748f2ea83d93d5c321827df9047c3 Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Wed, 8 Oct 2025 13:29:02 +0200 Subject: [PATCH 05/20] cleanup --- .../runtime/src/main/kotlin/kotlin/native/Annotations.kt | 2 +- .../jetbrains/kotlin/objcexport/analysisApiUtils/errors.kt | 3 +-- .../objcexport/mangling/mangleExtensionFacadesMembers.kt | 3 +-- .../kotlin/objcexport/mangling/mangleObjCInterface.kt | 3 +-- .../org/jetbrains/kotlin/objcexport/translateToObjCClass.kt | 3 +-- .../kotlin/objcexport/translateToObjCExtensionFacades.kt | 3 +-- .../jetbrains/kotlin/objcexport/translateToObjCObject.kt | 3 +-- .../backend/konan/objcexport/ObjCExportStubFactories.kt | 3 +-- .../backend/konan/objcexport/ObjCExportBaseDeclarations.kt | 6 +++--- 9 files changed, 11 insertions(+), 18 deletions(-) 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 618a28fee0a0b..2027980986b3e 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/Annotations.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/Annotations.kt @@ -113,7 +113,7 @@ public actual annotation class CName(actual val externName: String = "", actual @Retention(AnnotationRetention.BINARY) @MustBeDocumented @ExperimentalObjCName -@SinceKotlin("2.2.21") +@SinceKotlin("1.8") public actual annotation class ObjCName(actual val name: String = "", actual val swiftName: String = "", actual val exact: Boolean = false) /** diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/errors.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/errors.kt index b672479ef94c5..0430e79142442 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/errors.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/errors.kt @@ -66,8 +66,7 @@ internal val KtObjCExportSession.errorInterface categoryName = null, generics = emptyList(), superClass = getDefaultSuperClassOrProtocolName().objCName, - superClassGenerics = emptyList(), - nativeEnum = null, + superClassGenerics = emptyList() ) internal val objCErrorType = ObjCClassType(errorClassName, extras = objCTypeExtras { diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/mangling/mangleExtensionFacadesMembers.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/mangling/mangleExtensionFacadesMembers.kt index e6a985916b354..d2e1f7d329182 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/mangling/mangleExtensionFacadesMembers.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/mangling/mangleExtensionFacadesMembers.kt @@ -35,8 +35,7 @@ internal fun mangleExtensionFacadesMembers(stubs: List): List categoryName = extensionsCategoryName, generics = emptyList(), superClass = null, - superClassGenerics = emptyList(), - nativeEnum = null, + superClassGenerics = emptyList() ) } 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 4d5dd32a5488f..f948064ff725d 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 @@ -49,8 +49,7 @@ fun ObjCExportContext.translateToObjCObject(symbol: KaClassSymbol): ObjCClass? = categoryName = categoryName, generics = generics, superClass = superClass.superClassName.objCName, - superClassGenerics = superClass.superClassGenerics, - nativeEnum = null, + superClassGenerics = superClass.superClassGenerics ) } diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportStubFactories.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportStubFactories.kt index 311183df9ee7e..afcf4ccd94121 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportStubFactories.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportStubFactories.kt @@ -57,7 +57,6 @@ fun ObjCInterfaceImpl( members: List = emptyList(), attributes: List = emptyList(), comment: ObjCComment? = null, - nativeEnum: ObjCNativeEnum? = null, ) = ObjCInterfaceImpl( name = name, comment = comment, @@ -68,7 +67,7 @@ fun ObjCInterfaceImpl( categoryName = categoryName, generics = generics, superClass = superClass, - superClassGenerics = superClassGenerics, + superClassGenerics = superClassGenerics ) fun ObjCMethod( diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportBaseDeclarations.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportBaseDeclarations.kt index 14eae44584766..309d18842ab3d 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportBaseDeclarations.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportBaseDeclarations.kt @@ -66,7 +66,7 @@ fun objCBaseDeclarations( attributes = emptyList(), members = emptyList(), superClass = null, - superClassGenerics = emptyList(), + superClassGenerics = emptyList() ) } @@ -117,7 +117,7 @@ fun objCBaseDeclarations( superProtocols = emptyList(), generics = emptyList(), superClass = null, - superClassGenerics = emptyList(), + superClassGenerics = emptyList() ) } @@ -233,7 +233,7 @@ private fun objCInterface( members = members, attributes = attributes.plus(name.toNameAttributes()), comment = comment, - categoryName = null, + categoryName = null ) private fun objCProtocol( From e7ec42e30b73a4d5af3ad3f4bd6e140e085be2fd Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Wed, 8 Oct 2025 13:41:08 +0200 Subject: [PATCH 06/20] cleanup --- .../kotlin/objcexport/translateToObjCTopLevelFacade.kt | 3 +-- .../kotlin/backend/konan/objcexport/ObjCExportTranslator.kt | 4 +--- .../kotlin/backend/konan/objcexport/StubRenderer.kt | 5 +++++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelFacade.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelFacade.kt index 305ec7efc5b6d..e7b3c1eae2cd7 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelFacade.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelFacade.kt @@ -66,8 +66,7 @@ fun ObjCExportContext.translateToObjCTopLevelFacade(file: KtResolvedObjCExportFi categoryName = null, generics = emptyList(), superClass = exportSession.getDefaultSuperClassOrProtocolName().objCName, - superClassGenerics = emptyList(), - nativeEnum = null, + superClassGenerics = emptyList() ) return null } \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt index e42863aa5ab4b..5cfe295079d52 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt @@ -1114,7 +1114,6 @@ private fun objCInterface( members: List = emptyList(), attributes: List = emptyList(), comment: ObjCComment? = null, - nativeEnum: ObjCNativeEnum? = null, ): ObjCInterface = ObjCInterfaceImpl( name.objCName, generics, @@ -1125,8 +1124,7 @@ private fun objCInterface( null, members, attributes + name.toNameAttributes(), - comment, - nativeEnum, + comment ) private fun objCProtocol( diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt index 9a017200061ef..532bc7f883e93 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt @@ -71,7 +71,12 @@ object StubRenderer { +"@end" } is ObjCInterface -> { + attributes.forEach { + +renderAttribute(it) + } +renderInterfaceHeader() + renderMembers(this, shouldExportKDoc) + +"@end" } is ObjCMethod -> { +renderMethod(this) From 7e4fb66e59f693e729d547899b6b691bb4b66985 Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Wed, 8 Oct 2025 14:33:14 +0200 Subject: [PATCH 07/20] ordinal should be in KotlinEnum --- .../kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt | 6 ++++-- .../kotlin/backend/konan/objcexport/ObjCExportTranslator.kt | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt index 238c988b4925b..b56bc21b9d8af 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt @@ -91,7 +91,8 @@ internal fun ObjCExportedInterface.createCodeSpec(symbolTable: SymbolTable): Obj if (descriptor.kind == ClassKind.ENUM_CLASS) { namer.getNSEnumFunctionName(descriptor)?.let { selector -> - val ordinalDescriptor = descriptor.contributedMethods.find { it.name.asString() == "ordinal" }!! + val superClass = descriptor.getSuperClassNotAny()!! // ordinal is declared in KotlinEnum + val ordinalDescriptor = superClass.contributedMethods.find { it.name.asString() == "ordinal" }!! val bridge = mapper.bridgeMethod(ordinalDescriptor) val symbol = symbolTable.descriptorExtension.referenceSimpleFunction(ordinalDescriptor) val method = ObjCMethodSpec.BaseMethod(symbol, bridge, selector) @@ -239,7 +240,8 @@ internal class ObjCGetterForNSEnumType( ) : ObjCMethodSpec() { override fun toString(): String = "ObjC spec of toNSEnum()" -}*/ +} + */ internal class ObjCClassMethodForKotlinEnumValuesOrEntries( val valuesFunctionSymbol: IrFunctionSymbol, diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt index 5cfe295079d52..8acd4daeddce1 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt @@ -300,11 +300,11 @@ class ObjCExportTranslatorImpl( ObjCMethod( null, null, - false, + true, ObjCRawType(nsEnumName), listOf("toNSEnum"), emptyList(), - listOf(OBJC_METHOD_FAMILY_NONE), + emptyList() ) } } From b713914f2aca3127b4c2bc52eb565f3639c09fa3 Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Mon, 13 Oct 2025 16:05:45 +0200 Subject: [PATCH 08/20] refactored to avoid ObjCMethodForKotlinMethod --- .../objcexport/ObjCExportCodeGenerator.kt | 14 ++++++++++--- .../konan/objcexport/ObjCExportCodeSpec.kt | 19 +++++++++++------ .../main/kotlin/kotlin/native/Annotations.kt | 3 ++- .../framework/objcexport/nativeEnum.kt | 12 +++++++++++ .../framework/objcexport/nativeEnum.swift | 21 +++++++++++++++++++ .../simpleEnumClassWithObjCEnum/Foo.kt | 3 ++- 6 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 native/native.tests/testData/framework/objcexport/nativeEnum.kt create mode 100644 native/native.tests/testData/framework/objcexport/nativeEnum.swift diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt index 7c471f1577012..83e3f500040a7 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.backend.konan.llvm.objcexport.KotlinToObjCMethodAdap import org.jetbrains.kotlin.backend.konan.lower.getLoweredConstructorFunction import org.jetbrains.kotlin.backend.konan.lower.getObjectClassInstanceFunction import org.jetbrains.kotlin.backend.konan.objcexport.* +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCMethodSpec.BaseMethod import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.incremental.components.NoLookupLocation @@ -1375,6 +1376,13 @@ private fun ObjCExportCodeGenerator.createArrayConstructorAdapter( return objCToKotlinMethodAdapter(selectorName, methodBridge, imp) } +private fun ObjCExportCodeGenerator.createNSEnumAdapter( + implementation: IrFunction, + baseMethod: BaseMethod +): ObjCToKotlinMethodAdapter { + return createMethodAdapter(implementation, baseMethod) +} + private fun ObjCExportCodeGenerator.vtableIndex(irFunction: IrSimpleFunction): Int? { assert(irFunction.isOverridable) val irClass = irFunction.parentAsClass @@ -1430,9 +1438,9 @@ private fun ObjCExportCodeGenerator.createTypeAdapter( is ObjCInitMethodForKotlinConstructor -> { adapters += createConstructorAdapter(it.baseMethod) } -/* is ObjCGetterForNSEnumType -> { - adapters += createNSEnumAdapter(it.irClass) - } */ + is ObjCGetterForNSEnumType -> { + adapters += createNSEnumAdapter(it.implementation, it.baseMethod) + } is ObjCFactoryMethodForKotlinArrayConstructor -> { classAdapters += createArrayConstructorAdapter(it.baseMethod) } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt index b56bc21b9d8af..628ef2bc10063 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt @@ -13,6 +13,7 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.util.IdSignature import org.jetbrains.kotlin.ir.util.SymbolTable @@ -92,11 +93,14 @@ internal fun ObjCExportedInterface.createCodeSpec(symbolTable: SymbolTable): Obj if (descriptor.kind == ClassKind.ENUM_CLASS) { namer.getNSEnumFunctionName(descriptor)?.let { selector -> val superClass = descriptor.getSuperClassNotAny()!! // ordinal is declared in KotlinEnum - val ordinalDescriptor = superClass.contributedMethods.find { it.name.asString() == "ordinal" }!! + val ordinalDescriptor = superClass.contributedMethods.find { it.name.asString() == "" }!! + // if (ordinalDescriptor == null) { + // throw RuntimeException("ordinal not found; contributedMethods: ${descriptor.contributedMethods} super: ${superClass.contributedMethods}") + // } val bridge = mapper.bridgeMethod(ordinalDescriptor) val symbol = symbolTable.descriptorExtension.referenceSimpleFunction(ordinalDescriptor) val method = ObjCMethodSpec.BaseMethod(symbol, bridge, selector) - methods.add(ObjCMethodForKotlinMethod(method)) + methods += ObjCGetterForNSEnumType(symbol.owner, method) } descriptor.enumEntries.mapTo(methods) { @@ -170,6 +174,7 @@ internal fun ObjCExportCodeSpec.dumpSelectorToSignatureMapping(path: String) { is ObjCClassMethodForKotlinEnumValuesOrEntries -> false is ObjCGetterForKotlinEnumEntry -> false is ObjCGetterForObjectInstance -> false + is ObjCGetterForNSEnumType -> true } fun ObjCMethodSpec.getMapping(objcClass: String): String? = when (this) { @@ -180,6 +185,7 @@ internal fun ObjCExportCodeSpec.dumpSelectorToSignatureMapping(path: String) { is ObjCInitMethodForKotlinConstructor -> "$objcClass.${baseMethod.selector},${baseMethod.symbol.signature}" is ObjCKotlinThrowableAsErrorMethod -> null is ObjCMethodForKotlinMethod -> "$objcClass.${baseMethod.selector},${baseMethod.symbol.signature}" + is ObjCGetterForNSEnumType -> "$objcClass.${baseMethod.selector},${baseMethod.symbol.signature}" } out.println("\n# Instance methods mapping") for (type in types) { @@ -234,14 +240,15 @@ internal class ObjCGetterForKotlinEnumEntry( "ObjC spec of getter `$selector` for `$irEnumEntrySymbol`" } -/* + internal class ObjCGetterForNSEnumType( - val irClass: IrClass + val implementation: IrFunction, + val baseMethod: BaseMethod ) : ObjCMethodSpec() { override fun toString(): String = - "ObjC spec of toNSEnum()" + "ObjC spec of ${baseMethod.selector} for ${baseMethod.symbol}" } - */ + internal class ObjCClassMethodForKotlinEnumValuesOrEntries( val valuesFunctionSymbol: IrFunctionSymbol, 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 2027980986b3e..593301b483225 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/Annotations.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/Annotations.kt @@ -6,6 +6,7 @@ package kotlin.native import kotlin.experimental.ExperimentalNativeApi +import kotlin.experimental.ExperimentalObjCEnum import kotlin.experimental.ExperimentalObjCName import kotlin.experimental.ExperimentalObjCRefinement @@ -125,7 +126,7 @@ public actual annotation class ObjCName(actual val name: String = "", actual val @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) @MustBeDocumented -@ExperimentalObjCName +@ExperimentalObjCEnum @SinceKotlin("2.2.21") public actual annotation class ObjCEnum(actual val name: String = "") diff --git a/native/native.tests/testData/framework/objcexport/nativeEnum.kt b/native/native.tests/testData/framework/objcexport/nativeEnum.kt new file mode 100644 index 0000000000000..774e9ddad8cf0 --- /dev/null +++ b/native/native.tests/testData/framework/objcexport/nativeEnum.kt @@ -0,0 +1,12 @@ +import kotlin.native.ObjCEnum +import kotlin.experimental.ExperimentalObjCEnum + +// @file:OptIn(ExperimentalObjCEnum::class) + +// package nativeEnum + +@OptIn(kotlin.experimental.ExperimentalObjCEnum::class) +@ObjCEnum("OBJCFoo") +enum class MyKotlinEnum { + A, B, C +} \ No newline at end of file diff --git a/native/native.tests/testData/framework/objcexport/nativeEnum.swift b/native/native.tests/testData/framework/objcexport/nativeEnum.swift new file mode 100644 index 0000000000000..bde0de35559cb --- /dev/null +++ b/native/native.tests/testData/framework/objcexport/nativeEnum.swift @@ -0,0 +1,21 @@ +import Kt + + +private func testNativeEnumValues() throws { + let ktEnum = MyKotlinEnum.A + let nsEnum = ktEnum.toNSEnum() + + switch(nsEnum) { + case .A: try assertEquals(nsEnum, ktEnum.toNSEnum()) + case .B: try fail() + case .C: try fail() + } +} + +class NativeEnumTests : SimpleTestProvider { + override init() { + super.init() + + test("TestNativeEnumValues", testNativeEnumValues) + } +} diff --git a/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/Foo.kt b/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/Foo.kt index a649226ce2192..b6425d70d7323 100644 --- a/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/Foo.kt +++ b/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/Foo.kt @@ -1,7 +1,8 @@ import kotlin.native.ObjCEnum import kotlin.experimental.ExperimentalObjCEnum -@ExperimentalObjCEnum +@file:OptIn(ExperimentalObjCEnum::class) + @ObjCEnum("OBJCFoo") enum class Foo { A, B, C From b126cd4ed37746f1536af997bf54f000bf070818 Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Fri, 17 Oct 2025 18:35:00 +0200 Subject: [PATCH 09/20] move symbol resolution from ObjCExportCodeSpec to ObjCExportCodeGenerator --- .../konan/llvm/objcexport/ObjCExportCodeGenerator.kt | 2 +- .../kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt | 4 ++-- .../testData/framework/objcexport/nativeEnum.kt | 4 ++-- .../testData/framework/objcexport/nativeEnum.swift | 8 ++++---- .../backend/konan/objcexport/ObjCExportHeaderGenerator.kt | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt index 83e3f500040a7..436242ab84f16 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt @@ -1439,7 +1439,7 @@ private fun ObjCExportCodeGenerator.createTypeAdapter( adapters += createConstructorAdapter(it.baseMethod) } is ObjCGetterForNSEnumType -> { - adapters += createNSEnumAdapter(it.implementation, it.baseMethod) + adapters += createNSEnumAdapter(it.symbol.owner, it.baseMethod) } is ObjCFactoryMethodForKotlinArrayConstructor -> { classAdapters += createArrayConstructorAdapter(it.baseMethod) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt index 628ef2bc10063..2da0e426567be 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt @@ -100,7 +100,7 @@ internal fun ObjCExportedInterface.createCodeSpec(symbolTable: SymbolTable): Obj val bridge = mapper.bridgeMethod(ordinalDescriptor) val symbol = symbolTable.descriptorExtension.referenceSimpleFunction(ordinalDescriptor) val method = ObjCMethodSpec.BaseMethod(symbol, bridge, selector) - methods += ObjCGetterForNSEnumType(symbol.owner, method) + methods += ObjCGetterForNSEnumType(symbol, method) } descriptor.enumEntries.mapTo(methods) { @@ -242,7 +242,7 @@ internal class ObjCGetterForKotlinEnumEntry( internal class ObjCGetterForNSEnumType( - val implementation: IrFunction, + val symbol: IrSimpleFunctionSymbol, val baseMethod: BaseMethod ) : ObjCMethodSpec() { override fun toString(): String = diff --git a/native/native.tests/testData/framework/objcexport/nativeEnum.kt b/native/native.tests/testData/framework/objcexport/nativeEnum.kt index 774e9ddad8cf0..3740be2b1b657 100644 --- a/native/native.tests/testData/framework/objcexport/nativeEnum.kt +++ b/native/native.tests/testData/framework/objcexport/nativeEnum.kt @@ -1,10 +1,10 @@ +package nativeEnum + import kotlin.native.ObjCEnum import kotlin.experimental.ExperimentalObjCEnum // @file:OptIn(ExperimentalObjCEnum::class) -// package nativeEnum - @OptIn(kotlin.experimental.ExperimentalObjCEnum::class) @ObjCEnum("OBJCFoo") enum class MyKotlinEnum { diff --git a/native/native.tests/testData/framework/objcexport/nativeEnum.swift b/native/native.tests/testData/framework/objcexport/nativeEnum.swift index bde0de35559cb..96bb09d4aa4b7 100644 --- a/native/native.tests/testData/framework/objcexport/nativeEnum.swift +++ b/native/native.tests/testData/framework/objcexport/nativeEnum.swift @@ -2,13 +2,13 @@ import Kt private func testNativeEnumValues() throws { - let ktEnum = MyKotlinEnum.A + let ktEnum = MyKotlinEnum.a let nsEnum = ktEnum.toNSEnum() switch(nsEnum) { - case .A: try assertEquals(nsEnum, ktEnum.toNSEnum()) - case .B: try fail() - case .C: try fail() + case .a: try assertEquals(actual: nsEnum, expected: ktEnum.toNSEnum()) + case .b: try fail() + case .c: try fail() } } diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt index 78ff1c0607be7..dbfc852141e80 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt @@ -199,8 +199,8 @@ abstract class ObjCExportHeaderGenerator @InternalKotlinNativeApi constructor( private fun generateClass(descriptor: ClassDescriptor) { if (!generatedClasses.add(descriptor)) return val translatedClass = translator.translateClass(descriptor) - stubs.add(translatedClass.objCInterface) stubs.addAll(translatedClass.auxiliaryDeclarations) + stubs.add(translatedClass.objCInterface) } private fun generateInterface(descriptor: ClassDescriptor) { From f51d934dd78081529a46e4c80cc056547eadffe7 Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Mon, 20 Oct 2025 16:32:09 +0200 Subject: [PATCH 10/20] swift test compiles --- .../llvm/objcexport/ObjCExportCodeGenerator.kt | 7 ------- .../backend/konan/objcexport/ObjCExportCodeSpec.kt | 5 +---- .../src/main/kotlin/kotlin/native/Annotations.kt | 4 ++-- .../src/kotlin/annotations/NativeAnnotations.kt | 4 ++-- .../testData/framework/objcexport/nativeEnum.swift | 6 +++--- .../backend/konan/objcexport/ObjCExportNamer.kt | 4 ++-- .../konan/objcexport/ObjCExportTranslator.kt | 3 +-- .../kotlin/backend/konan/objcexport/StubRenderer.kt | 2 +- .../!simpleEnumClassWithObjCEnum.h | 13 +++++++------ 9 files changed, 19 insertions(+), 29 deletions(-) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt index 436242ab84f16..a7bd496402f14 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt @@ -1722,13 +1722,6 @@ private fun ObjCExportCodeGenerator.createObjectInstanceAdapter( return objCToKotlinMethodAdapter(selector, methodBridge, imp) } -private fun ObjCExportCodeGenerator.createNSEnumAdapter(irClass: IrClass): ObjCToKotlinMethodAdapter { - val bridgeName = irClass.computeTypeInfoSymbolName() // ??? - return generateObjCToKotlinSyntheticGetter("toNSEnum", bridgeName) { // bridgeName sounds weird as suffix - // get "ordinal" - } -} - private fun ObjCExportCodeGenerator.createEnumEntryAdapter( irEnumEntry: IrEnumEntry, selector: String diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt index 2da0e426567be..c4fe5a41194b3 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt @@ -12,10 +12,7 @@ import org.jetbrains.kotlin.backend.konan.descriptors.isInterface import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI -import org.jetbrains.kotlin.ir.declarations.IrClass -import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.symbols.* -import org.jetbrains.kotlin.ir.util.IdSignature import org.jetbrains.kotlin.ir.util.SymbolTable import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny import java.io.PrintStream @@ -91,7 +88,7 @@ internal fun ObjCExportedInterface.createCodeSpec(symbolTable: SymbolTable): Obj } if (descriptor.kind == ClassKind.ENUM_CLASS) { - namer.getNSEnumFunctionName(descriptor)?.let { selector -> + namer.getNSEnumFunctionTypeName(descriptor)?.let { selector -> val superClass = descriptor.getSuperClassNotAny()!! // ordinal is declared in KotlinEnum val ordinalDescriptor = superClass.contributedMethods.find { it.name.asString() == "" }!! // if (ordinalDescriptor == null) { 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 593301b483225..a8d6a4b156d15 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/Annotations.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/Annotations.kt @@ -119,8 +119,8 @@ public actual annotation class ObjCName(actual val name: String = "", actual val /** * Instructs the Kotlin compiler to generate a NS_ENUM typedef for the annotated enum class. The name of the generated type will - * be the name of the enum type with "_Enum" appended. This name can be overridden with the "name" parameter, which is treated - * as an exact name. The enum literals will be prefixed with the type name and an underscore, as they live in a global namespace. + * be the name of the enum type with "Enum" appended. This name can be overridden with the "name" parameter, which is treated + * as an exact name. The enum literals will be prefixed with the type name, as they live in a global namespace. * Swift naming will automatically remove these prefixes. */ @Target(AnnotationTarget.CLASS) diff --git a/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt b/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt index e6a6516240867..a68ac78cfb7e5 100644 --- a/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt +++ b/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt @@ -74,8 +74,8 @@ public expect annotation class ObjCName(val name: String = "", val swiftName: St /** * Instructs the Kotlin compiler to generate a NS_ENUM typedef for the annotated enum class. The name of the generated type will - * be the name of the enum type with "_Enum" appended. This name can be overridden with the "name" parameter, which is treated - * as an exact name. The enum literals will be prefixed with the type name and an underscore, as they live in a global namespace. + * be the name of the enum type with "Enum" appended. This name can be overridden with the "name" parameter, which is treated + * as an exact name. The enum literals will be prefixed with the type name, as they live in a global namespace. * Swift naming will automatically remove these disambiguation prefixes. */ @Target( diff --git a/native/native.tests/testData/framework/objcexport/nativeEnum.swift b/native/native.tests/testData/framework/objcexport/nativeEnum.swift index 96bb09d4aa4b7..9eb28e7e058fd 100644 --- a/native/native.tests/testData/framework/objcexport/nativeEnum.swift +++ b/native/native.tests/testData/framework/objcexport/nativeEnum.swift @@ -6,9 +6,9 @@ private func testNativeEnumValues() throws { let nsEnum = ktEnum.toNSEnum() switch(nsEnum) { - case .a: try assertEquals(actual: nsEnum, expected: ktEnum.toNSEnum()) - case .b: try fail() - case .c: try fail() + case .A: try assertEquals(actual: nsEnum, expected: ktEnum.toNSEnum()) + case .B: try fail() + case .C: try fail() } } diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt index 0d901e5b96077..495efa4ea4fe3 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt @@ -100,10 +100,10 @@ interface ObjCExportNamer { fun needsExplicitMethodFamily(name: String): Boolean // Null means no NSEnum function. - fun getNSEnumFunctionName(descriptor: ClassDescriptor): String? = + fun getNSEnumFunctionTypeName(descriptor: ClassDescriptor): String? = descriptor.annotations.findAnnotation(FqName("kotlin.native.ObjCEnum"))?.let { val name = it.allValueArguments.entries.find { it.key.asString() == "name" }?.value?.value?.toString() - name ?: "${getClassOrProtocolName(descriptor).objCName}_Enum" + name ?: "${getClassOrProtocolName(descriptor).objCName}Enum" } companion object { diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt index 8acd4daeddce1..e6680cb4d85bf 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt @@ -17,7 +17,6 @@ import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.ir.objcinterop.* import org.jetbrains.kotlin.name.ClassId -import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.constants.ArrayValue import org.jetbrains.kotlin.resolve.constants.KClassValue import org.jetbrains.kotlin.resolve.deprecation.DeprecationInfo @@ -292,7 +291,7 @@ class ObjCExportTranslatorImpl( ClassKind.ENUM_CLASS -> { val type = mapType(descriptor.defaultType, ReferenceBridge, ObjCRootExportScope) - namer.getNSEnumFunctionName(descriptor)?.let { nsEnumName -> + namer.getNSEnumFunctionTypeName(descriptor)?.let { nsEnumName -> auxiliaryDeclarations.add( ObjCNativeEnum(nsEnumName, descriptor.enumEntries.map { it.name.asString() } )) diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt index 532bc7f883e93..b50c411eeba42 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt @@ -125,7 +125,7 @@ object StubRenderer { private fun renderNativeEnumType(nativeEnum: ObjCNativeEnum): String = buildString { append("typedef NS_ENUM(int32_t, ${nativeEnum.name}) {\n") for ((index, literal) in nativeEnum.literals.withIndex()) { - append(" ${nativeEnum.name}_$literal = $index,\n") + append(" ${nativeEnum.name}$literal = $index,\n") } append("};") } diff --git a/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/!simpleEnumClassWithObjCEnum.h b/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/!simpleEnumClassWithObjCEnum.h index 5d2b04f778ad6..8feacfa92592e 100644 --- a/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/!simpleEnumClassWithObjCEnum.h +++ b/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/!simpleEnumClassWithObjCEnum.h @@ -38,22 +38,23 @@ NS_ASSUME_NONNULL_BEGIN @property (readonly) int32_t ordinal __attribute__((swift_name("ordinal"))); @end +typedef NS_ENUM(int32_t, OBJCFoo) { + OBJCFooA = 0, + OBJCFooB = 1, + OBJCFooC = 2, +}; + /** * @note annotations - * kotlin.experimental.ExperimentalObjCEnum * kotlin.native.ObjCEnum(name="OBJCFoo") */ -typedef NS_ENUM(int32_t, OBJCFoo_Enum) { - OBJCFoo_Enum_A = 0, - OBJCFoo_Enum_B = 1, - OBJCFoo_Enum_C = 2, -}; __attribute__((objc_subclassing_restricted)) @interface Foo : KotlinEnum + (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)); +- (OBJCFoo)toNSEnum; @property (class, readonly) Foo *a __attribute__((swift_name("a"))); @property (class, readonly) Foo *b __attribute__((swift_name("b"))); @property (class, readonly) Foo *c __attribute__((swift_name("c"))); From 854371fab9147f7dd75d2a949a64e79041264e15 Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Mon, 20 Oct 2025 17:19:46 +0200 Subject: [PATCH 11/20] remove footgun --- .../backend/konan/objcexport/ObjCExportCodeSpec.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt index c4fe5a41194b3..81ccad2284e53 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt @@ -88,15 +88,12 @@ internal fun ObjCExportedInterface.createCodeSpec(symbolTable: SymbolTable): Obj } if (descriptor.kind == ClassKind.ENUM_CLASS) { - namer.getNSEnumFunctionTypeName(descriptor)?.let { selector -> + if (namer.getNSEnumFunctionTypeName(descriptor) != null) { val superClass = descriptor.getSuperClassNotAny()!! // ordinal is declared in KotlinEnum - val ordinalDescriptor = superClass.contributedMethods.find { it.name.asString() == "" }!! - // if (ordinalDescriptor == null) { - // throw RuntimeException("ordinal not found; contributedMethods: ${descriptor.contributedMethods} super: ${superClass.contributedMethods}") - // } + val ordinalDescriptor = superClass.contributedMethods.first { it.name.asString() == "" } val bridge = mapper.bridgeMethod(ordinalDescriptor) val symbol = symbolTable.descriptorExtension.referenceSimpleFunction(ordinalDescriptor) - val method = ObjCMethodSpec.BaseMethod(symbol, bridge, selector) + val method = ObjCMethodSpec.BaseMethod(symbol, bridge, "toNSEnum") methods += ObjCGetterForNSEnumType(symbol, method) } From 40133726d406745fbf9478adb6410a27089d3bec Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Fri, 24 Oct 2025 18:31:01 +0200 Subject: [PATCH 12/20] objc/switft name distinction / adjustment --- .../framework/objcexport/nativeEnum.swift | 6 +++--- .../backend/konan/objcexport/ObjCExportNamer.kt | 2 +- .../konan/objcexport/ObjCExportTranslator.kt | 10 +++++++--- .../backend/konan/objcexport/ObjCExportName.kt | 16 ++++++++++++++++ .../backend/konan/objcexport/StubRenderer.kt | 2 +- .../kotlin/backend/konan/objcexport/stubs.kt | 2 +- .../!simpleEnumClassWithObjCEnum.h | 6 +++--- 7 files changed, 32 insertions(+), 12 deletions(-) diff --git a/native/native.tests/testData/framework/objcexport/nativeEnum.swift b/native/native.tests/testData/framework/objcexport/nativeEnum.swift index 9eb28e7e058fd..96bb09d4aa4b7 100644 --- a/native/native.tests/testData/framework/objcexport/nativeEnum.swift +++ b/native/native.tests/testData/framework/objcexport/nativeEnum.swift @@ -6,9 +6,9 @@ private func testNativeEnumValues() throws { let nsEnum = ktEnum.toNSEnum() switch(nsEnum) { - case .A: try assertEquals(actual: nsEnum, expected: ktEnum.toNSEnum()) - case .B: try fail() - case .C: try fail() + case .a: try assertEquals(actual: nsEnum, expected: ktEnum.toNSEnum()) + case .b: try fail() + case .c: try fail() } } diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt index 495efa4ea4fe3..e996a7b756c8c 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt @@ -103,7 +103,7 @@ interface ObjCExportNamer { fun getNSEnumFunctionTypeName(descriptor: ClassDescriptor): String? = descriptor.annotations.findAnnotation(FqName("kotlin.native.ObjCEnum"))?.let { val name = it.allValueArguments.entries.find { it.key.asString() == "name" }?.value?.value?.toString() - name ?: "${getClassOrProtocolName(descriptor).objCName}Enum" + name ?: "${getClassOrProtocolName(descriptor).objCName}_Enum" } companion object { diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt index e6680cb4d85bf..94fa646cd6382 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt @@ -291,16 +291,20 @@ class ObjCExportTranslatorImpl( ClassKind.ENUM_CLASS -> { val type = mapType(descriptor.defaultType, ReferenceBridge, ObjCRootExportScope) - namer.getNSEnumFunctionTypeName(descriptor)?.let { nsEnumName -> + namer.getNSEnumFunctionTypeName(descriptor)?.let { nsEnumTypeName -> auxiliaryDeclarations.add( - ObjCNativeEnum(nsEnumName, descriptor.enumEntries.map { it.name.asString() } )) + ObjCNativeEnum(nsEnumTypeName, descriptor.enumEntries.map { + ObjcExportNativeEnumEntryName( + objCName = namer.getEnumEntrySelector(it).replaceFirstChar { it.uppercaseChar() }, + swiftName = namer.getEnumEntrySwiftName(it)) + } )) add { ObjCMethod( null, null, true, - ObjCRawType(nsEnumName), + ObjCRawType(nsEnumTypeName), listOf("toNSEnum"), emptyList(), emptyList() diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportName.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportName.kt index 8122553c2886f..d8d039b87a5f9 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportName.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportName.kt @@ -22,6 +22,8 @@ interface ObjCExportFunctionName : ObjCExportName interface ObjCExportFileName : ObjCExportName +interface ObjcExportNativeEnumEntryName : ObjCExportName + fun ObjCExportClassOrProtocolName( swiftName: String, objCName: String, @@ -62,6 +64,14 @@ fun ObjCExportFileName( objCName = objCName ) +fun ObjcExportNativeEnumEntryName( + swiftName: String, + objCName: String, +): ObjcExportNativeEnumEntryName = ObjcExportNativeEnumEntryNameImpl( + swiftName = swiftName, + objCName = objCName, +) + private data class ObjCExportPropertyNameImpl( override val swiftName: String, override val objCName: String, @@ -78,6 +88,12 @@ private data class ObjCExportFileNameImpl( ) : ObjCExportFileName +private data class ObjcExportNativeEnumEntryNameImpl( + override val swiftName: String, + override val objCName: String, +) : ObjcExportNativeEnumEntryName + + fun ObjCExportClassOrProtocolName.toNameAttributes(): List = listOfNotNull( binaryName.takeIf { it != objCName }?.let { objcRuntimeNameAttribute(it) }, swiftName.takeIf { it != objCName }?.let { swiftNameAttribute(it) } diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt index b50c411eeba42..81fedb7476573 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt @@ -125,7 +125,7 @@ object StubRenderer { private fun renderNativeEnumType(nativeEnum: ObjCNativeEnum): String = buildString { append("typedef NS_ENUM(int32_t, ${nativeEnum.name}) {\n") for ((index, literal) in nativeEnum.literals.withIndex()) { - append(" ${nativeEnum.name}$literal = $index,\n") + append(" ${nativeEnum.name}${literal.objCName} NS_SWIFT_NAME(${literal.swiftName}) = $index,\n") } append("};") } diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt index 9174db4048f82..b8fa5e0311502 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt @@ -48,7 +48,7 @@ abstract class ObjCTopLevel : ObjCExportStub class ObjCNativeEnum( override val name: String, - val literals: List, + val literals: List, ) : ObjCExportStub { override val comment: ObjCComment? get() = null diff --git a/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/!simpleEnumClassWithObjCEnum.h b/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/!simpleEnumClassWithObjCEnum.h index 8feacfa92592e..0a2078a175662 100644 --- a/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/!simpleEnumClassWithObjCEnum.h +++ b/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/!simpleEnumClassWithObjCEnum.h @@ -39,9 +39,9 @@ NS_ASSUME_NONNULL_BEGIN @end typedef NS_ENUM(int32_t, OBJCFoo) { - OBJCFooA = 0, - OBJCFooB = 1, - OBJCFooC = 2, + OBJCFooA NS_SWIFT_NAME(a) = 0, + OBJCFooB NS_SWIFT_NAME(b) = 1, + OBJCFooC NS_SWIFT_NAME(c) = 2, }; From 29e80311439e84eb01e53a9857db71ec327754f6 Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Fri, 31 Oct 2025 14:22:43 -0400 Subject: [PATCH 13/20] first round of review comments / snapshot before changing to property --- .../objcexport/ObjCExportCodeGenerator.kt | 10 +++--- .../konan/objcexport/ObjCExportCodeSpec.kt | 6 ++-- .../main/kotlin/kotlin/native/Annotations.kt | 2 +- .../kotlin/backend/konan/KonanFqNames.kt | 1 + .../framework/objcexport/nativeEnum.kt | 2 -- .../objcexport/translateToObjCHeader.kt | 2 +- .../konan/objcexport/ObjCExportTranslator.kt | 34 ++++++++++--------- .../backend/konan/objcexport/StubRenderer.kt | 4 +-- .../kotlin/backend/konan/objcexport/stubs.kt | 4 +-- 9 files changed, 35 insertions(+), 30 deletions(-) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt index a7bd496402f14..552811b100b97 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt @@ -1377,10 +1377,12 @@ private fun ObjCExportCodeGenerator.createArrayConstructorAdapter( } private fun ObjCExportCodeGenerator.createNSEnumAdapter( - implementation: IrFunction, - baseMethod: BaseMethod + symbol: IrSimpleFunctionSymbol, + methodBridge: MethodBridge, + selectorName: String ): ObjCToKotlinMethodAdapter { - return createMethodAdapter(implementation, baseMethod) + val imp = generateObjCImp(symbol.owner, symbol.owner.getLowered(), methodBridge) + return objCToKotlinMethodAdapter(selectorName, methodBridge, imp) } private fun ObjCExportCodeGenerator.vtableIndex(irFunction: IrSimpleFunction): Int? { @@ -1439,7 +1441,7 @@ private fun ObjCExportCodeGenerator.createTypeAdapter( adapters += createConstructorAdapter(it.baseMethod) } is ObjCGetterForNSEnumType -> { - adapters += createNSEnumAdapter(it.symbol.owner, it.baseMethod) + adapters += createNSEnumAdapter(it.symbol, it.bridge, it. selector) } is ObjCFactoryMethodForKotlinArrayConstructor -> { classAdapters += createArrayConstructorAdapter(it.baseMethod) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt index 81ccad2284e53..bcdec2f0a2311 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt @@ -91,10 +91,10 @@ internal fun ObjCExportedInterface.createCodeSpec(symbolTable: SymbolTable): Obj if (namer.getNSEnumFunctionTypeName(descriptor) != null) { val superClass = descriptor.getSuperClassNotAny()!! // ordinal is declared in KotlinEnum val ordinalDescriptor = superClass.contributedMethods.first { it.name.asString() == "" } - val bridge = mapper.bridgeMethod(ordinalDescriptor) val symbol = symbolTable.descriptorExtension.referenceSimpleFunction(ordinalDescriptor) + val bridge = mapper.bridgeMethod(ordinalDescriptor) val method = ObjCMethodSpec.BaseMethod(symbol, bridge, "toNSEnum") - methods += ObjCGetterForNSEnumType(symbol, method) + methods += ObjCGetterForNSEnumType(symbol, bridge, "toNSEnum", method) } descriptor.enumEntries.mapTo(methods) { @@ -237,6 +237,8 @@ internal class ObjCGetterForKotlinEnumEntry( internal class ObjCGetterForNSEnumType( val symbol: IrSimpleFunctionSymbol, + val bridge: MethodBridge, + val selector: String, val baseMethod: BaseMethod ) : ObjCMethodSpec() { override fun toString(): String = 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 a8d6a4b156d15..b0f59be358f93 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/Annotations.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/Annotations.kt @@ -127,7 +127,7 @@ public actual annotation class ObjCName(actual val name: String = "", actual val @Retention(AnnotationRetention.BINARY) @MustBeDocumented @ExperimentalObjCEnum -@SinceKotlin("2.2.21") +@SinceKotlin("2.3") public actual annotation class ObjCEnum(actual val name: String = "") /** diff --git a/native/base/src/main/kotlin/org/jetbrains/kotlin/backend/konan/KonanFqNames.kt b/native/base/src/main/kotlin/org/jetbrains/kotlin/backend/konan/KonanFqNames.kt index c06ccf588adbc..d25af6acedb2f 100644 --- a/native/base/src/main/kotlin/org/jetbrains/kotlin/backend/konan/KonanFqNames.kt +++ b/native/base/src/main/kotlin/org/jetbrains/kotlin/backend/konan/KonanFqNames.kt @@ -38,6 +38,7 @@ object KonanFqNames { val eagerInitialization = FqName("kotlin.native.EagerInitialization") val noReorderFields = FqName("kotlin.native.internal.NoReorderFields") val objCName = FqName("kotlin.native.ObjCName") + val objCEnum = FqName("kotlin.native.ObjCEnum") val hidesFromObjC = FqName("kotlin.native.HidesFromObjC") val refinesInSwift = FqName("kotlin.native.RefinesInSwift") val shouldRefineInSwift = FqName("kotlin.native.ShouldRefineInSwift") diff --git a/native/native.tests/testData/framework/objcexport/nativeEnum.kt b/native/native.tests/testData/framework/objcexport/nativeEnum.kt index 3740be2b1b657..fd9445ace3229 100644 --- a/native/native.tests/testData/framework/objcexport/nativeEnum.kt +++ b/native/native.tests/testData/framework/objcexport/nativeEnum.kt @@ -3,8 +3,6 @@ package nativeEnum import kotlin.native.ObjCEnum import kotlin.experimental.ExperimentalObjCEnum -// @file:OptIn(ExperimentalObjCEnum::class) - @OptIn(kotlin.experimental.ExperimentalObjCEnum::class) @ObjCEnum("OBJCFoo") enum class MyKotlinEnum { 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 5ebb46f252822..2f1cc5ca24b32 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 @@ -202,7 +202,7 @@ private class KtObjCExportHeaderGenerator( is ObjCProperty -> listOf(childStub.type) is ObjCInterface -> childStub.superClassGenerics is ObjCTopLevel -> emptyList() - is ObjCNativeEnum -> emptyList() + is ObjCNSEnum -> emptyList() } }.map { type -> /** diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt index 94fa646cd6382..e67dca5a364fd 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt @@ -33,9 +33,9 @@ import org.jetbrains.kotlin.utils.addIfNotNull import kotlin.* -data class TranslatedClass( - val objCInterface: ObjCInterface, +class TranslatedClass( val auxiliaryDeclarations: List = emptyList(), + val objCInterface: ObjCInterface, ) interface ObjCExportTranslator { @@ -181,7 +181,7 @@ class ObjCExportTranslatorImpl( override fun translateClass(descriptor: ClassDescriptor): TranslatedClass { require(!descriptor.isInterface) if (!mapper.shouldBeExposed(descriptor)) { - return TranslatedClass(translateUnexposedClassAsUnavailableStub(descriptor)) + return TranslatedClass(objCInterface = translateUnexposedClassAsUnavailableStub(descriptor)) } val auxiliaryDeclarations = mutableListOf() @@ -293,7 +293,7 @@ class ObjCExportTranslatorImpl( namer.getNSEnumFunctionTypeName(descriptor)?.let { nsEnumTypeName -> auxiliaryDeclarations.add( - ObjCNativeEnum(nsEnumTypeName, descriptor.enumEntries.map { + ObjCNSEnum(nsEnumTypeName, descriptor.enumEntries.map { ObjcExportNativeEnumEntryName( objCName = namer.getEnumEntrySelector(it).replaceFirstChar { it.uppercaseChar() }, swiftName = namer.getEnumEntrySwiftName(it)) @@ -365,17 +365,18 @@ class ObjCExportTranslatorImpl( val generics = mapTypeConstructorParameters(descriptor) val superClassGenerics = superClassGenerics(genericExportScope) - return TranslatedClass(objCInterface( - name, - generics = generics, - descriptor = descriptor, - superClass = superName.objCName, - superClassGenerics = superClassGenerics, - superProtocols = superProtocols, - members = members, - attributes = attributes, - comment = objCCommentOrNull(mustBeDocumentedAttributeList(descriptor.annotations)), - ), auxiliaryDeclarations.toList()) + return TranslatedClass( + auxiliaryDeclarations = auxiliaryDeclarations.toList(), + objCInterface = objCInterface( + name, + generics = generics, + descriptor = descriptor, + superClass = superName.objCName, + superClassGenerics = superClassGenerics, + superProtocols = superProtocols, + members = members, + attributes = attributes, + comment = objCCommentOrNull(mustBeDocumentedAttributeList(descriptor.annotations)))) } internal fun createGenericExportScope(descriptor: ClassDescriptor): ObjCExportScope = if (objcGenerics) { @@ -800,7 +801,8 @@ class ObjCExportTranslatorImpl( } private val mustBeDocumentedAnnotationsStopList = - setOf(StandardNames.FqNames.deprecated, KonanFqNames.objCName, KonanFqNames.shouldRefineInSwift) + setOf(StandardNames.FqNames.deprecated, KonanFqNames.objCName, KonanFqNames.shouldRefineInSwift, + KonanFqNames.objCEnum) private fun mustBeDocumentedAnnotations(annotations: Annotations): List { return annotations.mapNotNull { it -> diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt index 81fedb7476573..94bf2d24f49f6 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt @@ -84,7 +84,7 @@ object StubRenderer { is ObjCProperty -> { +renderProperty(this) } - is ObjCNativeEnum -> { + is ObjCNSEnum -> { +renderNativeEnumType(this) } else -> throw IllegalArgumentException("unsupported stub: " + stub::class) @@ -122,7 +122,7 @@ object StubRenderer { append(';') } - private fun renderNativeEnumType(nativeEnum: ObjCNativeEnum): String = buildString { + private fun renderNativeEnumType(nativeEnum: ObjCNSEnum): String = buildString { append("typedef NS_ENUM(int32_t, ${nativeEnum.name}) {\n") for ((index, literal) in nativeEnum.literals.withIndex()) { append(" ${nativeEnum.name}${literal.objCName} NS_SWIFT_NAME(${literal.swiftName}) = $index,\n") diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt index b8fa5e0311502..8b219cba27e86 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt @@ -46,7 +46,7 @@ val ObjCExportStub.psiOrNull abstract class ObjCTopLevel : ObjCExportStub -class ObjCNativeEnum( +class ObjCNSEnum( override val name: String, val literals: List, ) : ObjCExportStub { @@ -54,7 +54,7 @@ class ObjCNativeEnum( get() = null override val origin: ObjCExportStubOrigin? get() = null - override val extras = mutableExtrasOf() + override val extras: Extras = emptyExtras() } sealed class ObjCClass : ObjCTopLevel() { From 0ccc11847ac0922b3d7d89fbc1e062accc05e608 Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Tue, 4 Nov 2025 14:20:26 +0100 Subject: [PATCH 14/20] Review comments addressed; mostly adding the missing AA impl --- .../konan/objcexport/ObjCExportCodeSpec.kt | 8 ++-- .../klib-public-api/kotlin-stdlib.api | 20 ++++++++++ .../kotlin-stdlib-runtime-merged.txt | 3 ++ .../framework/objcexport/nativeEnum.swift | 4 +- .../kotlin/objcexport/TranslatedClass.kt | 17 +++++++++ .../kotlin/objcexport/getNSEnumType.kt | 24 ++++++++++++ .../kotlin/objcexport/translateEnumMembers.kt | 2 +- .../kotlin/objcexport/translateToNSEnum.kt | 38 +++++++++++++++++++ .../kotlin/objcexport/translateToObjCClass.kt | 32 ++++++++++------ .../objcexport/translateToObjCExportStub.kt | 9 ++--- .../objcexport/translateToObjCHeader.kt | 6 ++- .../tests/ObjCExportStubOriginTest.kt | 6 +-- .../tests/TypeParametersTranslationTests.kt | 4 +- .../tests/mangling/ManglingConflictsTest.kt | 8 ++-- .../tests/mangling/MethodManglingTest.kt | 4 +- .../tests/mangling/PropertyManglingTest.kt | 4 +- .../konan/objcexport/ObjCExportTranslator.kt | 28 +++++++------- .../konan/objcexport/ObjCExportName.kt | 15 +++++--- .../backend/konan/objcexport/StubRenderer.kt | 38 +++++++++++++++++-- .../kotlin/backend/konan/objcexport/stubs.kt | 6 +-- .../!simpleEnumClassWithObjCEnum.h | 3 +- 21 files changed, 213 insertions(+), 66 deletions(-) create mode 100644 native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/TranslatedClass.kt create mode 100644 native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getNSEnumType.kt create mode 100644 native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToNSEnum.kt diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt index bcdec2f0a2311..e2297fb2e9da4 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt @@ -93,8 +93,7 @@ internal fun ObjCExportedInterface.createCodeSpec(symbolTable: SymbolTable): Obj val ordinalDescriptor = superClass.contributedMethods.first { it.name.asString() == "" } val symbol = symbolTable.descriptorExtension.referenceSimpleFunction(ordinalDescriptor) val bridge = mapper.bridgeMethod(ordinalDescriptor) - val method = ObjCMethodSpec.BaseMethod(symbol, bridge, "toNSEnum") - methods += ObjCGetterForNSEnumType(symbol, bridge, "toNSEnum", method) + methods += ObjCGetterForNSEnumType(symbol, bridge, "nsEnum") } descriptor.enumEntries.mapTo(methods) { @@ -179,7 +178,7 @@ internal fun ObjCExportCodeSpec.dumpSelectorToSignatureMapping(path: String) { is ObjCInitMethodForKotlinConstructor -> "$objcClass.${baseMethod.selector},${baseMethod.symbol.signature}" is ObjCKotlinThrowableAsErrorMethod -> null is ObjCMethodForKotlinMethod -> "$objcClass.${baseMethod.selector},${baseMethod.symbol.signature}" - is ObjCGetterForNSEnumType -> "$objcClass.${baseMethod.selector},${baseMethod.symbol.signature}" + is ObjCGetterForNSEnumType -> "$objcClass.$selector,${symbol.signature}" } out.println("\n# Instance methods mapping") for (type in types) { @@ -239,10 +238,9 @@ internal class ObjCGetterForNSEnumType( val symbol: IrSimpleFunctionSymbol, val bridge: MethodBridge, val selector: String, - val baseMethod: BaseMethod ) : ObjCMethodSpec() { override fun toString(): String = - "ObjC spec of ${baseMethod.selector} for ${baseMethod.symbol}" + "ObjC spec of $selector for $symbol" } diff --git a/libraries/tools/binary-compatibility-validator/klib-public-api/kotlin-stdlib.api b/libraries/tools/binary-compatibility-validator/klib-public-api/kotlin-stdlib.api index 036b12c19108d..6980b3cf0d3be 100644 --- a/libraries/tools/binary-compatibility-validator/klib-public-api/kotlin-stdlib.api +++ b/libraries/tools/binary-compatibility-validator/klib-public-api/kotlin-stdlib.api @@ -53,6 +53,10 @@ open annotation class kotlin.experimental/ExperimentalNativeApi : kotlin/Annotat constructor () // kotlin.experimental/ExperimentalNativeApi.|(){}[0] } +open annotation class kotlin.experimental/ExperimentalObjCEnum : kotlin/Annotation { // kotlin.experimental/ExperimentalObjCEnum|null[0] + constructor () // kotlin.experimental/ExperimentalObjCEnum.|(){}[0] +} + open annotation class kotlin.experimental/ExperimentalObjCName : kotlin/Annotation { // kotlin.experimental/ExperimentalObjCName|null[0] constructor () // kotlin.experimental/ExperimentalObjCName.|(){}[0] } @@ -9829,6 +9833,14 @@ open annotation class kotlin.native/NoInline : kotlin/Annotation { // kotlin.nat constructor () // kotlin.native/NoInline.|(){}[0] } +// Targets: [native] +open annotation class kotlin.native/ObjCEnum : kotlin/Annotation { // kotlin.native/ObjCEnum|null[0] + constructor (kotlin/String = ...) // kotlin.native/ObjCEnum.|(kotlin.String){}[0] + + final val name // kotlin.native/ObjCEnum.name|{}name[0] + final fun (): kotlin/String // kotlin.native/ObjCEnum.name.|(){}[0] +} + // Targets: [native] open annotation class kotlin.native/ObjCName : kotlin/Annotation { // kotlin.native/ObjCName|null[0] constructor (kotlin/String = ..., kotlin/String = ..., kotlin/Boolean = ...) // kotlin.native/ObjCName.|(kotlin.String;kotlin.String;kotlin.Boolean){}[0] @@ -13739,6 +13751,14 @@ open annotation class kotlin.native/HidesFromObjC : kotlin/Annotation { // kotli constructor () // kotlin.native/HidesFromObjC.|(){}[1] } +// Targets: [js, wasmJs, wasmWasi] +open annotation class kotlin.native/ObjCEnum : kotlin/Annotation { // kotlin.native/ObjCEnum|null[1] + constructor (kotlin/String = ...) // kotlin.native/ObjCEnum.|(kotlin.String){}[1] + + final val name // kotlin.native/ObjCEnum.name|{}name[1] + final fun (): kotlin/String // kotlin.native/ObjCEnum.name.|(){}[1] +} + // Targets: [js, wasmJs, wasmWasi] open annotation class kotlin.native/ObjCName : kotlin/Annotation { // kotlin.native/ObjCName|null[1] constructor (kotlin/String = ..., kotlin/String = ..., kotlin/Boolean = ...) // kotlin.native/ObjCName.|(kotlin.String;kotlin.String;kotlin.Boolean){}[1] diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index 20c895329e8a2..8c168a719cdb5 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -3257,6 +3257,9 @@ public abstract interface annotation class kotlin/experimental/ExpectRefinement public abstract interface annotation class kotlin/experimental/ExperimentalNativeApi : java/lang/annotation/Annotation { } +public abstract interface annotation class kotlin/experimental/ExperimentalObjCEnum : java/lang/annotation/Annotation { +} + public abstract interface annotation class kotlin/experimental/ExperimentalObjCName : java/lang/annotation/Annotation { } diff --git a/native/native.tests/testData/framework/objcexport/nativeEnum.swift b/native/native.tests/testData/framework/objcexport/nativeEnum.swift index 96bb09d4aa4b7..5ad500d60bcac 100644 --- a/native/native.tests/testData/framework/objcexport/nativeEnum.swift +++ b/native/native.tests/testData/framework/objcexport/nativeEnum.swift @@ -3,10 +3,10 @@ import Kt private func testNativeEnumValues() throws { let ktEnum = MyKotlinEnum.a - let nsEnum = ktEnum.toNSEnum() + let nsEnum = ktEnum.nsEnum switch(nsEnum) { - case .a: try assertEquals(actual: nsEnum, expected: ktEnum.toNSEnum()) + case .a: try assertEquals(actual: nsEnum, expected: ktEnum.nsEnum) case .b: try fail() case .c: try fail() } diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/TranslatedClass.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/TranslatedClass.kt new file mode 100644 index 0000000000000..dab840448b67f --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/TranslatedClass.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2010-2025 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.backend.konan.objcexport.ObjCClass +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCTopLevel + +class TranslatedClass( + val auxiliaryDeclarations: List, + val objCClass: ObjCClass, +) + +fun TranslatedClass(objCClass: ObjCClass?) = objCClass?.let { TranslatedClass(emptyList(), it) } + diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getNSEnumType.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getNSEnumType.kt new file mode 100644 index 0000000000000..67b0f76afb9f5 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getNSEnumType.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2010-2025 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.annotations.KaAnnotationValue +import org.jetbrains.kotlin.analysis.api.symbols.KaClassSymbol +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName + +/** Returns the NSEnum type for the given enum type if the corresponding annotation is set; null otherwise */ +fun ObjCExportContext.getNSEnumType(symbol: KaClassSymbol): String? { + val classId = ClassId(FqName("kotlin.native"), FqName("ObjCEnum"), false) + val annotation = symbol.annotations[classId].firstOrNull() + return if (annotation == null) { + null + } else if (annotation.arguments.isEmpty()) { + getObjCClassOrProtocolName(symbol).toString() + } else { + (annotation.arguments[0].expression as KaAnnotationValue.ConstantValue).value.value as String + } +} \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateEnumMembers.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateEnumMembers.kt index c2ec41e5d005f..a6e07dddd83af 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateEnumMembers.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateEnumMembers.kt @@ -78,7 +78,7 @@ private fun ObjCExportContext.getEnumEntriesProperty(symbol: KaClassSymbol): Obj /** * See K1 implementation as [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl.getEnumEntryName] */ -private fun ObjCExportContext.getEnumEntryName(symbol: KaEnumEntrySymbol, forSwift: Boolean): String { +internal fun ObjCExportContext.getEnumEntryName(symbol: KaEnumEntrySymbol, forSwift: Boolean): String { val name = getObjCPropertyName(symbol).run { when { diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToNSEnum.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToNSEnum.kt new file mode 100644 index 0000000000000..3c8b9fbad70ee --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToNSEnum.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2010-2025 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.symbols.KaClassSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KaEnumEntrySymbol +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCNSEnum +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProperty +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCRawType +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCTopLevel +import org.jetbrains.kotlin.backend.konan.objcexport.ObjcExportNativeEnumEntry + +internal fun ObjCExportContext.translateNSEnum(symbol: KaClassSymbol, nsEnumTypeName: String, auxiliaryDeclarations: MutableList): ObjCProperty { + auxiliaryDeclarations.add(ObjCNSEnum(nsEnumTypeName, getNSEnumEntries(symbol, nsEnumTypeName))) + return ObjCProperty( + "nsEnum", + null, + null, + ObjCRawType(nsEnumTypeName), + listOf("readonly") + ) +} + + +private fun ObjCExportContext.getNSEnumEntries(symbol: KaClassSymbol, typeName: String): List { + val staticMembers = with(analysisSession) { symbol.staticDeclaredMemberScope }.callables.toList() + return staticMembers.filterIsInstance().mapIndexed { index, entry -> + ObjcExportNativeEnumEntry( + getEnumEntryName(entry, false), + typeName + getEnumEntryName(entry, true).replaceFirstChar { it.uppercaseChar() }, + index + ) + } +} + 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 7232d036137d6..2c8c682c55d47 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 @@ -15,7 +15,8 @@ import org.jetbrains.kotlin.analysis.api.export.utilities.isThrowable import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC -fun ObjCExportContext.translateToObjCClass(symbol: KaClassSymbol): ObjCClass? = withClassifierContext(symbol) { + +fun ObjCExportContext.translateToObjCClass(symbol: KaClassSymbol): TranslatedClass? = withClassifierContext(symbol) { require( symbol.classKind == KaClassKind.CLASS || symbol.classKind == KaClassKind.ENUM_CLASS || @@ -37,6 +38,7 @@ fun ObjCExportContext.translateToObjCClass(symbol: KaClassSymbol): ObjCClass? = val superClass = translateSuperClass(symbol) val superProtocols: List = superProtocols(symbol) + val auxiliaryDeclarations = mutableListOf() val members = buildList { /* The order of members tries to replicate the K1 implementation explicitly */ @@ -51,6 +53,9 @@ fun ObjCExportContext.translateToObjCClass(symbol: KaClassSymbol): ObjCClass? = .flatMap { translateToObjCExportStub(it) } if (symbol.classKind == KaClassKind.ENUM_CLASS) { + getNSEnumType(symbol)?.let { + this += translateNSEnum(symbol, it, auxiliaryDeclarations) + } this += translateEnumMembers(symbol) } @@ -69,17 +74,20 @@ fun ObjCExportContext.translateToObjCClass(symbol: KaClassSymbol): ObjCClass? = ) } - 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 + TranslatedClass( + auxiliaryDeclarations.toList(), + 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/translateToObjCExportStub.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExportStub.kt index 9bf79d228769d..a134405f8d296 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExportStub.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExportStub.kt @@ -6,7 +6,6 @@ package org.jetbrains.kotlin.objcexport import org.jetbrains.kotlin.analysis.api.symbols.* -import org.jetbrains.kotlin.backend.konan.objcexport.ObjCClass import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportStub import org.jetbrains.kotlin.utils.addIfNotNull @@ -32,11 +31,11 @@ internal fun ObjCExportContext.translateToObjCExportStub(symbol: KaCallableSymbo return result } -internal fun ObjCExportContext.translateToObjCExportStub(symbol: KaClassSymbol): ObjCClass? = when (symbol.classKind) { - KaClassKind.INTERFACE -> translateToObjCProtocol(symbol) +internal fun ObjCExportContext.translateToObjCExportStub(symbol: KaClassSymbol): TranslatedClass? = when (symbol.classKind) { + KaClassKind.INTERFACE -> TranslatedClass(translateToObjCProtocol(symbol)) KaClassKind.CLASS -> translateToObjCClass(symbol) - KaClassKind.OBJECT -> translateToObjCObject(symbol) + KaClassKind.OBJECT -> TranslatedClass(translateToObjCObject(symbol)) KaClassKind.ENUM_CLASS -> translateToObjCClass(symbol) - KaClassKind.COMPANION_OBJECT -> translateToObjCObject(symbol) + KaClassKind.COMPANION_OBJECT -> TranslatedClass(translateToObjCObject(symbol)) else -> null } 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 2f1cc5ca24b32..2cf8cf074e2ae 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 @@ -147,7 +147,8 @@ private class KtObjCExportHeaderGenerator( * Translate: Note: Even if the result was 'null', the classId will still be marked as 'handled' by adding it * to the [objCStubsByClassId] index. */ - val objCClass = translateToObjCExportStub(symbol) + val translated = translateToObjCExportStub(symbol) + val objCClass = translated?.objCClass objCStubsByClassId[classId] = objCClass objCClass ?: return null @@ -170,6 +171,9 @@ private class KtObjCExportHeaderGenerator( } } + for (aux in translated.auxiliaryDeclarations) { + objCStubs.add(aux) + } /* 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()) diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/ObjCExportStubOriginTest.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/ObjCExportStubOriginTest.kt index 7f9c244a44e0b..6db02cfe41793 100644 --- a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/ObjCExportStubOriginTest.kt +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/ObjCExportStubOriginTest.kt @@ -31,7 +31,7 @@ class ObjCExportStubOriginTest( ) { file -> val classSymbols = file.classifierSymbols val objectSymbol = classSymbols.single() - val objectStub = translateToObjCExportStub(objectSymbol) + val objectStub = translateToObjCExportStub(objectSymbol)?.objCClass assertIs(objectStub, "Object stub should be an interface") val sharedProperty = objectStub.members.firstOrNull { @@ -85,10 +85,10 @@ class ObjCExportStubOriginTest( } assertNotNull(classSymbol, "Class symbol should be present") - val companionObjectStub = translateToObjCExportStub(companionObjectSymbol) + val companionObjectStub = translateToObjCExportStub(companionObjectSymbol)?.objCClass assertIs(companionObjectStub, "Object stub should be an interface") - val classStub = translateToObjCExportStub(classSymbol) + val classStub = translateToObjCExportStub(classSymbol)?.objCClass assertIs(classStub, "Class stub should be an interface") val sharedProperty = companionObjectStub.members.firstOrNull { diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/TypeParametersTranslationTests.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/TypeParametersTranslationTests.kt index 34dd6f67bc81c..9b7e91a19cceb 100644 --- a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/TypeParametersTranslationTests.kt +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/TypeParametersTranslationTests.kt @@ -192,8 +192,8 @@ class TypeParametersTranslationTests( val foo = analysisSession.getClassOrFail(file, "Foo") val bar = analysisSession.getClassOrFail(file, "Bar") - val fooObjC = translateToObjCClass(foo) - val barObjC = translateToObjCClass(bar) + val fooObjC = translateToObjCClass(foo)?.objCClass + val barObjC = translateToObjCClass(bar)?.objCClass val initFoo = fooObjC?.members?.first { it.name.startsWith("initWith") } as? ObjCMethod ?: error("no initWith constructor were translate for Foo") diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/mangling/ManglingConflictsTest.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/mangling/ManglingConflictsTest.kt index 21e2fb92ab671..d3ad224a47b6f 100644 --- a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/mangling/ManglingConflictsTest.kt +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/mangling/ManglingConflictsTest.kt @@ -28,7 +28,7 @@ class ManglingConflictsTest( } """.trimMargin() ) { foo -> - val members = translateToObjCExportStub(foo)?.members ?: error("no translated members") + val members = translateToObjCExportStub(foo)?.objCClass?.members ?: error("no translated members") assertTrue(members.hasPropertiesConflicts()) } } @@ -43,7 +43,7 @@ class ManglingConflictsTest( } """.trimMargin() ) { foo -> - val stub = translateToObjCExportStub(foo) + val stub = translateToObjCExportStub(foo)?.objCClass assertTrue(stub?.members?.hasPropertiesConflicts() == false) } } @@ -58,7 +58,7 @@ class ManglingConflictsTest( } """.trimMargin() ) { foo -> - val stub = translateToObjCExportStub(foo) + val stub = translateToObjCExportStub(foo)?.objCClass assertTrue(stub?.members?.hasMethodConflicts() == true) } } @@ -73,7 +73,7 @@ class ManglingConflictsTest( } """.trimMargin() ) { foo -> - val stub = translateToObjCExportStub(foo) + val stub = translateToObjCExportStub(foo)?.objCClass assertTrue(stub?.members?.hasPropertiesConflicts() == false) } } diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/mangling/MethodManglingTest.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/mangling/MethodManglingTest.kt index 30ca8dbc7b9a0..b0103737b58f3 100644 --- a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/mangling/MethodManglingTest.kt +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/mangling/MethodManglingTest.kt @@ -29,7 +29,7 @@ class MethodManglingTest( } """.trimMargin() ) { foo -> - val stub = translateToObjCExportStub(foo) + val stub = translateToObjCExportStub(foo)?.objCClass val methods = stub?.members ?: error("no translated members") val mangledMethods = mangleObjCMethods(methods, stub).filterIsInstance().filter { it.name.startsWith("bar") } @@ -49,7 +49,7 @@ class MethodManglingTest( } """.trimMargin() ) { foo -> - val stub = translateToObjCExportStub(foo) + val stub = translateToObjCExportStub(foo)?.objCClass val methods = stub?.members ?: error("no translated members") val mangledMethods = mangleObjCMethods(methods, stub).filterIsInstance().filter { it.name.startsWith("bar") } diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/mangling/PropertyManglingTest.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/mangling/PropertyManglingTest.kt index 997e66c981b8c..5f69caf3008d0 100644 --- a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/mangling/PropertyManglingTest.kt +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/mangling/PropertyManglingTest.kt @@ -28,7 +28,7 @@ class PropertyManglingTest( } """.trimMargin() ) { foo -> - val properties = translateToObjCExportStub(foo)?.members ?: error("no translated members") + val properties = translateToObjCExportStub(foo)?.objCClass?.members ?: error("no translated members") val property = mangleObjCProperties(properties).filterIsInstance().first() assertEquals("bar", property.name) @@ -46,7 +46,7 @@ class PropertyManglingTest( } """.trimMargin() ) { foo -> - val properties = translateToObjCExportStub(foo)?.members ?: error("no translated members") + val properties = translateToObjCExportStub(foo)?.objCClass?.members ?: error("no translated members") val property = mangleObjCProperties(properties).filterIsInstance().first() assertEquals("bar", property.name) diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt index e67dca5a364fd..4b147c040ec27 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt @@ -32,12 +32,13 @@ import org.jetbrains.kotlin.types.typeUtil.supertypes import org.jetbrains.kotlin.utils.addIfNotNull import kotlin.* - class TranslatedClass( - val auxiliaryDeclarations: List = emptyList(), + val auxiliaryDeclarations: List, val objCInterface: ObjCInterface, ) +fun TranslatedClass(objCInterface: ObjCInterface) = TranslatedClass(emptyList(), objCInterface) + interface ObjCExportTranslator { fun generateBaseDeclarations(): List fun getClassIfExtension(receiverType: KotlinType): ClassDescriptor? @@ -293,22 +294,21 @@ class ObjCExportTranslatorImpl( namer.getNSEnumFunctionTypeName(descriptor)?.let { nsEnumTypeName -> auxiliaryDeclarations.add( - ObjCNSEnum(nsEnumTypeName, descriptor.enumEntries.map { - ObjcExportNativeEnumEntryName( - objCName = namer.getEnumEntrySelector(it).replaceFirstChar { it.uppercaseChar() }, - swiftName = namer.getEnumEntrySwiftName(it)) - } )) + ObjCNSEnum(nsEnumTypeName, descriptor.enumEntries.mapIndexed { ordinal, entry -> + ObjcExportNativeEnumEntry( + objCName = nsEnumTypeName + namer.getEnumEntrySelector(entry).replaceFirstChar { it.uppercaseChar() }, + swiftName = namer.getEnumEntrySwiftName(entry), + value = ordinal) + })) add { - ObjCMethod( - null, + ObjCProperty( + "nsEnum", null, - true, ObjCRawType(nsEnumTypeName), - listOf("toNSEnum"), - emptyList(), - emptyList() - ) + listOf("readonly"), + declarationAttributes = emptyList(), + comment = null) } } diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportName.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportName.kt index d8d039b87a5f9..4c51571530594 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportName.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportName.kt @@ -22,7 +22,9 @@ interface ObjCExportFunctionName : ObjCExportName interface ObjCExportFileName : ObjCExportName -interface ObjcExportNativeEnumEntryName : ObjCExportName +interface ObjcExportNativeEnumEntry : ObjCExportName { + val value: Int +} fun ObjCExportClassOrProtocolName( swiftName: String, @@ -64,12 +66,14 @@ fun ObjCExportFileName( objCName = objCName ) -fun ObjcExportNativeEnumEntryName( +fun ObjcExportNativeEnumEntry( swiftName: String, objCName: String, -): ObjcExportNativeEnumEntryName = ObjcExportNativeEnumEntryNameImpl( + value: Int, +): ObjcExportNativeEnumEntry = ObjcExportNativeEnumEntryImpl( swiftName = swiftName, objCName = objCName, + value = value ) private data class ObjCExportPropertyNameImpl( @@ -88,10 +92,11 @@ private data class ObjCExportFileNameImpl( ) : ObjCExportFileName -private data class ObjcExportNativeEnumEntryNameImpl( +private data class ObjcExportNativeEnumEntryImpl( override val swiftName: String, override val objCName: String, -) : ObjcExportNativeEnumEntryName + override val value: Int, +) : ObjcExportNativeEnumEntry fun ObjCExportClassOrProtocolName.toNameAttributes(): List = listOfNotNull( diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt index 94bf2d24f49f6..5a0130d445838 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt @@ -123,11 +123,41 @@ object StubRenderer { } private fun renderNativeEnumType(nativeEnum: ObjCNSEnum): String = buildString { - append("typedef NS_ENUM(int32_t, ${nativeEnum.name}) {\n") - for ((index, literal) in nativeEnum.literals.withIndex()) { - append(" ${nativeEnum.name}${literal.objCName} NS_SWIFT_NAME(${literal.swiftName}) = $index,\n") + fun appendName() { + append("NS_ENUM(int32_t, ") + append(nativeEnum.name) + append(")") } - append("};") + + append("typedef ") + appendName() + append(" {\n") + for (entry in nativeEnum.entries) { + appendNativeEnumEntry(entry) + } + append("};\n") + } + + private fun Appendable.appendNativeEnumEntry(entry: ObjcExportNativeEnumEntry) { + fun appendName() { + append(entry.objCName) + } + + fun appendSwiftName() { + append(entry.swiftName) + } + + fun appendValue() { + append(entry.value.toString()) + } + + append(" ") + appendName() + append(" NS_SWIFT_NAME(") + appendSwiftName() + append(") = ") + appendValue() + append(",\n") } private fun renderMethod(method: ObjCMethod): String = buildString { diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt index 8b219cba27e86..c0008c5f9d664 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt @@ -10,12 +10,12 @@ package org.jetbrains.kotlin.backend.konan.objcexport import org.jetbrains.kotlin.tooling.core.Extras import org.jetbrains.kotlin.tooling.core.HasExtras import org.jetbrains.kotlin.tooling.core.emptyExtras -import org.jetbrains.kotlin.tooling.core.mutableExtrasOf @Deprecated("Use 'ObjCExportStub' instead", replaceWith = ReplaceWith("ObjCExportStub")) @Suppress("unused") typealias Stub<@Suppress("UNUSED_TYPEALIAS_PARAMETER") T> = ObjCExportStub + sealed interface ObjCExportStub : HasExtras { /** * The ObjC name of this entity; @@ -48,8 +48,8 @@ abstract class ObjCTopLevel : ObjCExportStub class ObjCNSEnum( override val name: String, - val literals: List, -) : ObjCExportStub { + val entries: List, +) : ObjCTopLevel() { override val comment: ObjCComment? get() = null override val origin: ObjCExportStubOrigin? diff --git a/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/!simpleEnumClassWithObjCEnum.h b/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/!simpleEnumClassWithObjCEnum.h index 0a2078a175662..ee9840b8f71ed 100644 --- a/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/!simpleEnumClassWithObjCEnum.h +++ b/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/!simpleEnumClassWithObjCEnum.h @@ -45,6 +45,7 @@ typedef NS_ENUM(int32_t, OBJCFoo) { }; + /** * @note annotations * kotlin.native.ObjCEnum(name="OBJCFoo") @@ -54,7 +55,7 @@ __attribute__((objc_subclassing_restricted)) + (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)); -- (OBJCFoo)toNSEnum; +@property (readonly) OBJCFoo nsEnum; @property (class, readonly) Foo *a __attribute__((swift_name("a"))); @property (class, readonly) Foo *b __attribute__((swift_name("b"))); @property (class, readonly) Foo *c __attribute__((swift_name("c"))); From 60cca8233c4328bbe6ee5075a0cb9eb2bc114951 Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Wed, 5 Nov 2025 16:27:34 +0100 Subject: [PATCH 15/20] Make the enum entry value / order more explicit / add a clarifying comment --- .../src/org/jetbrains/kotlin/objcexport/translateToNSEnum.kt | 5 +++-- .../kotlin/backend/konan/objcexport/ObjCExportTranslator.kt | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToNSEnum.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToNSEnum.kt index 3c8b9fbad70ee..084a4deb7a5f4 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToNSEnum.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToNSEnum.kt @@ -27,11 +27,12 @@ internal fun ObjCExportContext.translateNSEnum(symbol: KaClassSymbol, nsEnumType private fun ObjCExportContext.getNSEnumEntries(symbol: KaClassSymbol, typeName: String): List { val staticMembers = with(analysisSession) { symbol.staticDeclaredMemberScope }.callables.toList() - return staticMembers.filterIsInstance().mapIndexed { index, entry -> + // Map the enum entries in declaration order, preserving the ordinal + return staticMembers.filterIsInstance().mapIndexed { ordinal, entry -> ObjcExportNativeEnumEntry( getEnumEntryName(entry, false), typeName + getEnumEntryName(entry, true).replaceFirstChar { it.uppercaseChar() }, - index + ordinal ) } } diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt index 4b147c040ec27..53801dcc2e273 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt @@ -293,6 +293,7 @@ class ObjCExportTranslatorImpl( val type = mapType(descriptor.defaultType, ReferenceBridge, ObjCRootExportScope) namer.getNSEnumFunctionTypeName(descriptor)?.let { nsEnumTypeName -> + // Map the enum entries in declaration order, preserving the ordinal auxiliaryDeclarations.add( ObjCNSEnum(nsEnumTypeName, descriptor.enumEntries.mapIndexed { ordinal, entry -> ObjcExportNativeEnumEntry( From 8da8bb471f0037403817ccc25d1766bfb089e37c Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Mon, 10 Nov 2025 15:33:34 +0100 Subject: [PATCH 16/20] Naming consistency / fixes and test extensions / improvements --- .../main/kotlin/kotlin/native/Annotations.kt | 4 +- .../kotlin/annotations/NativeAnnotations.kt | 4 +- .../framework/objcexport/nativeEnum.kt | 2 +- .../framework/objcexport/nativeEnum.swift | 8 +- .../kotlin/objcexport/getNSEnumType.kt | 2 +- .../konan/objcexport/ObjCExportNamer.kt | 4 +- .../tests/ObjCExportHeaderGeneratorTest.kt | 9 +- .../!enumClassWithNamedObjCEnum.h} | 12 +-- .../Foo.kt | 2 +- .../!enumClassWithObjCEnum.h | 93 +++++++++++++++++++ .../headers/enumClassWithObjCEnum/Foo.kt | 9 ++ 11 files changed, 128 insertions(+), 21 deletions(-) rename native/objcexport-header-generator/testData/headers/{simpleEnumClassWithObjCEnum/!simpleEnumClassWithObjCEnum.h => enumClassWithNamedObjCEnum/!enumClassWithNamedObjCEnum.h} (91%) rename native/objcexport-header-generator/testData/headers/{simpleEnumClassWithObjCEnum => enumClassWithNamedObjCEnum}/Foo.kt (86%) create mode 100644 native/objcexport-header-generator/testData/headers/enumClassWithObjCEnum/!enumClassWithObjCEnum.h create mode 100644 native/objcexport-header-generator/testData/headers/enumClassWithObjCEnum/Foo.kt 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 b0f59be358f93..3d5178e19f8fb 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/Annotations.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/Annotations.kt @@ -119,9 +119,9 @@ public actual annotation class ObjCName(actual val name: String = "", actual val /** * Instructs the Kotlin compiler to generate a NS_ENUM typedef for the annotated enum class. The name of the generated type will - * be the name of the enum type with "Enum" appended. This name can be overridden with the "name" parameter, which is treated + * be the name of the enum type with "NSEnum" appended. This name can be overridden with the "name" parameter, which is treated * as an exact name. The enum literals will be prefixed with the type name, as they live in a global namespace. - * Swift naming will automatically remove these prefixes. + * Swift naming will automatically remove these prefixes. The native values are accessible via the "nsEnum" property. */ @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) diff --git a/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt b/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt index a68ac78cfb7e5..9cd696361d136 100644 --- a/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt +++ b/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt @@ -74,9 +74,9 @@ public expect annotation class ObjCName(val name: String = "", val swiftName: St /** * Instructs the Kotlin compiler to generate a NS_ENUM typedef for the annotated enum class. The name of the generated type will - * be the name of the enum type with "Enum" appended. This name can be overridden with the "name" parameter, which is treated + * be the name of the enum type with "NSEnum" appended. This name can be overridden with the "name" parameter, which is treated * as an exact name. The enum literals will be prefixed with the type name, as they live in a global namespace. - * Swift naming will automatically remove these disambiguation prefixes. + * Swift naming will automatically remove these disambiguation prefixes. The native values are accessible via the "nsEnum" property. */ @Target( AnnotationTarget.CLASS, diff --git a/native/native.tests/testData/framework/objcexport/nativeEnum.kt b/native/native.tests/testData/framework/objcexport/nativeEnum.kt index fd9445ace3229..dabc88d269bcc 100644 --- a/native/native.tests/testData/framework/objcexport/nativeEnum.kt +++ b/native/native.tests/testData/framework/objcexport/nativeEnum.kt @@ -6,5 +6,5 @@ import kotlin.experimental.ExperimentalObjCEnum @OptIn(kotlin.experimental.ExperimentalObjCEnum::class) @ObjCEnum("OBJCFoo") enum class MyKotlinEnum { - A, B, C + ALPHA, BAR_FOO, COPY } \ No newline at end of file diff --git a/native/native.tests/testData/framework/objcexport/nativeEnum.swift b/native/native.tests/testData/framework/objcexport/nativeEnum.swift index 5ad500d60bcac..de244f984e516 100644 --- a/native/native.tests/testData/framework/objcexport/nativeEnum.swift +++ b/native/native.tests/testData/framework/objcexport/nativeEnum.swift @@ -2,13 +2,13 @@ import Kt private func testNativeEnumValues() throws { - let ktEnum = MyKotlinEnum.a + let ktEnum = MyKotlinEnum.alpha let nsEnum = ktEnum.nsEnum switch(nsEnum) { - case .a: try assertEquals(actual: nsEnum, expected: ktEnum.nsEnum) - case .b: try fail() - case .c: try fail() + case .alpha: try assertEquals(actual: nsEnum, expected: ktEnum.nsEnum) + case .barFoo: try fail() + case .theCopy: try fail() } } diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getNSEnumType.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getNSEnumType.kt index 67b0f76afb9f5..8ee8067a193e1 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getNSEnumType.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getNSEnumType.kt @@ -17,7 +17,7 @@ fun ObjCExportContext.getNSEnumType(symbol: KaClassSymbol): String? { return if (annotation == null) { null } else if (annotation.arguments.isEmpty()) { - getObjCClassOrProtocolName(symbol).toString() + getObjCClassOrProtocolName(symbol).objCName + "NSEnum" } else { (annotation.arguments[0].expression as KaAnnotationValue.ConstantValue).value.value as String } diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt index e996a7b756c8c..154808dbd5762 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt @@ -101,9 +101,9 @@ interface ObjCExportNamer { // Null means no NSEnum function. fun getNSEnumFunctionTypeName(descriptor: ClassDescriptor): String? = - descriptor.annotations.findAnnotation(FqName("kotlin.native.ObjCEnum"))?.let { + descriptor.annotations.findAnnotation(KonanFqNames.objCEnum)?.let { val name = it.allValueArguments.entries.find { it.key.asString() == "name" }?.value?.value?.toString() - name ?: "${getClassOrProtocolName(descriptor).objCName}_Enum" + name ?: "${getClassOrProtocolName(descriptor).objCName}NSEnum" } companion object { 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 9887bc5f9914f..3a08507b44ea7 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 @@ -49,8 +49,13 @@ class ObjCExportHeaderGeneratorTest(private val generator: HeaderGenerator) { } @Test - fun `test - simpleEnumClassWithObjCEnum`() { - doTest(headersTestDataDir.resolve("simpleEnumClassWithObjCEnum")) + fun `test - enumClassWithNamedObjCEnum`() { + doTest(headersTestDataDir.resolve("enumClassWithNamedObjCEnum")) + } + + @Test + fun `test - enumClassWithObjCEnum`() { + doTest(headersTestDataDir.resolve("enumClassWithObjCEnum")) } @Test diff --git a/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/!simpleEnumClassWithObjCEnum.h b/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/!enumClassWithNamedObjCEnum.h similarity index 91% rename from native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/!simpleEnumClassWithObjCEnum.h rename to native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/!enumClassWithNamedObjCEnum.h index ee9840b8f71ed..14b10021adc6b 100644 --- a/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/!simpleEnumClassWithObjCEnum.h +++ b/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/!enumClassWithNamedObjCEnum.h @@ -39,9 +39,9 @@ NS_ASSUME_NONNULL_BEGIN @end typedef NS_ENUM(int32_t, OBJCFoo) { - OBJCFooA NS_SWIFT_NAME(a) = 0, - OBJCFooB NS_SWIFT_NAME(b) = 1, - OBJCFooC NS_SWIFT_NAME(c) = 2, + OBJCFooAlpha NS_SWIFT_NAME(alpha) = 0, + OBJCFooBarFoo NS_SWIFT_NAME(barFoo) = 1, + OBJCFooTheCopy NS_SWIFT_NAME(theCopy) = 2, }; @@ -56,9 +56,9 @@ __attribute__((objc_subclassing_restricted)) + (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)); @property (readonly) OBJCFoo nsEnum; -@property (class, readonly) Foo *a __attribute__((swift_name("a"))); -@property (class, readonly) Foo *b __attribute__((swift_name("b"))); -@property (class, readonly) Foo *c __attribute__((swift_name("c"))); +@property (class, readonly) Foo *alpha __attribute__((swift_name("alpha"))); +@property (class, readonly) Foo *barFoo __attribute__((swift_name("barFoo"))); +@property (class, readonly) Foo *theCopy __attribute__((swift_name("theCopy"))); + (KotlinArray *)values __attribute__((swift_name("values()"))); @property (class, readonly) NSArray *entries __attribute__((swift_name("entries"))); @end diff --git a/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/Foo.kt b/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/Foo.kt similarity index 86% rename from native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/Foo.kt rename to native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/Foo.kt index b6425d70d7323..f3576f3efcf63 100644 --- a/native/objcexport-header-generator/testData/headers/simpleEnumClassWithObjCEnum/Foo.kt +++ b/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/Foo.kt @@ -5,5 +5,5 @@ import kotlin.experimental.ExperimentalObjCEnum @ObjCEnum("OBJCFoo") enum class Foo { - A, B, C + ALPHA, BAR_FOO, COPY } \ No newline at end of file diff --git a/native/objcexport-header-generator/testData/headers/enumClassWithObjCEnum/!enumClassWithObjCEnum.h b/native/objcexport-header-generator/testData/headers/enumClassWithObjCEnum/!enumClassWithObjCEnum.h new file mode 100644 index 0000000000000..5d8b55cdf0e08 --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/enumClassWithObjCEnum/!enumClassWithObjCEnum.h @@ -0,0 +1,93 @@ +#import +#import +#import +#import +#import +#import +#import + +@class Foo, KotlinArray, KotlinEnum, 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 + +@protocol KotlinComparable +@required +- (int32_t)compareToOther:(id _Nullable)other __attribute__((swift_name("compareTo(other:)"))); +@end + +@interface KotlinEnum : Base +- (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 + +typedef NS_ENUM(int32_t, FooNSEnum) { + FooNSEnumAlpha NS_SWIFT_NAME(alpha) = 0, + FooNSEnumBarFoo NS_SWIFT_NAME(barFoo) = 1, + FooNSEnumTheCopy NS_SWIFT_NAME(theCopy) = 2, +}; + + + +/** + * @note annotations + * kotlin.native.ObjCEnum +*/ +__attribute__((objc_subclassing_restricted)) +@interface Foo : KotlinEnum ++ (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)); +@property (readonly) FooNSEnum nsEnum; +@property (class, readonly) Foo *alpha __attribute__((swift_name("alpha"))); +@property (class, readonly) Foo *barFoo __attribute__((swift_name("barFoo"))); +@property (class, readonly) Foo *theCopy __attribute__((swift_name("theCopy"))); ++ (KotlinArray *)values __attribute__((swift_name("values()"))); +@property (class, readonly) NSArray *entries __attribute__((swift_name("entries"))); +@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 : 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)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/enumClassWithObjCEnum/Foo.kt b/native/objcexport-header-generator/testData/headers/enumClassWithObjCEnum/Foo.kt new file mode 100644 index 0000000000000..e1fcd5599f1c8 --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/enumClassWithObjCEnum/Foo.kt @@ -0,0 +1,9 @@ +import kotlin.native.ObjCEnum +import kotlin.experimental.ExperimentalObjCEnum + +@file:OptIn(ExperimentalObjCEnum::class) + +@ObjCEnum +enum class Foo { + ALPHA, BAR_FOO, COPY +} \ No newline at end of file From 6a52203bbd98d07fcc512d1975346d8083a228eb Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Wed, 12 Nov 2025 13:29:27 +0100 Subject: [PATCH 17/20] Move ObjCEnum to the auto documentartion exclude list for AA, matching K1 --- .../analysisApiUtils/getObjCDocumentedAnnotations.kt | 3 ++- .../enumClassWithNamedObjCEnum/!enumClassWithNamedObjCEnum.h | 5 ----- .../headers/enumClassWithObjCEnum/!enumClassWithObjCEnum.h | 5 ----- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getObjCDocumentedAnnotations.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getObjCDocumentedAnnotations.kt index d165465de784e..91b16441fdc40 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getObjCDocumentedAnnotations.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getObjCDocumentedAnnotations.kt @@ -38,6 +38,7 @@ internal fun KaSession.getObjCDocumentedAnnotations(list: KaAnnotationList): Lis private val mustBeDocumentedAnnotationsStopList = setOf( StandardNames.FqNames.deprecated, + KonanFqNames.objCEnum, KonanFqNames.objCName, - KonanFqNames.shouldRefineInSwift + KonanFqNames.shouldRefineInSwift, ) \ No newline at end of file diff --git a/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/!enumClassWithNamedObjCEnum.h b/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/!enumClassWithNamedObjCEnum.h index 14b10021adc6b..116673c00eec5 100644 --- a/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/!enumClassWithNamedObjCEnum.h +++ b/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/!enumClassWithNamedObjCEnum.h @@ -45,11 +45,6 @@ typedef NS_ENUM(int32_t, OBJCFoo) { }; - -/** - * @note annotations - * kotlin.native.ObjCEnum(name="OBJCFoo") -*/ __attribute__((objc_subclassing_restricted)) @interface Foo : KotlinEnum + (instancetype)alloc __attribute__((unavailable)); diff --git a/native/objcexport-header-generator/testData/headers/enumClassWithObjCEnum/!enumClassWithObjCEnum.h b/native/objcexport-header-generator/testData/headers/enumClassWithObjCEnum/!enumClassWithObjCEnum.h index 5d8b55cdf0e08..c846e71c11c42 100644 --- a/native/objcexport-header-generator/testData/headers/enumClassWithObjCEnum/!enumClassWithObjCEnum.h +++ b/native/objcexport-header-generator/testData/headers/enumClassWithObjCEnum/!enumClassWithObjCEnum.h @@ -45,11 +45,6 @@ typedef NS_ENUM(int32_t, FooNSEnum) { }; - -/** - * @note annotations - * kotlin.native.ObjCEnum -*/ __attribute__((objc_subclassing_restricted)) @interface Foo : KotlinEnum + (instancetype)alloc __attribute__((unavailable)); From bfb69217018cc16ac3f6952c56df7796f20e905b Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Wed, 19 Nov 2025 20:15:48 +0100 Subject: [PATCH 18/20] first part of addressing 2nd round of review comments --- .../objcexport/ObjCExportCodeGenerator.kt | 5 ++-- .../konan/objcexport/ObjCExportCodeSpec.kt | 8 ++++--- .../kotlin/annotations/NativeAnnotations.kt | 6 ++--- .../experimental/ExperimentalObjCEnum.kt | 4 ++-- .../framework/objcexport/nativeEnum.kt | 2 +- ...dClass.kt => ObjCExportTranslatedClass.kt} | 4 ++-- .../kotlin/objcexport/ObjCPropertyNames.kt | 2 ++ ...{getNSEnumType.kt => getNSEnumTypeName.kt} | 9 ++++--- .../objcexport/resolveObjCNameAnnotation.kt | 2 +- .../kotlin/objcexport/translateToNSEnum.kt | 19 ++++++++------- .../kotlin/objcexport/translateToObjCClass.kt | 8 +++---- .../objcexport/translateToObjCExportStub.kt | 8 +++---- .../objcexport/translateToObjCHeader.kt | 13 +++++----- .../konan/objcexport/ObjCExportNamer.kt | 10 ++++---- .../konan/objcexport/ObjCExportTranslator.kt | 24 +++++++++---------- .../konan/objcexport/ObjCExportName.kt | 22 ----------------- .../backend/konan/objcexport/StubRenderer.kt | 20 ++++------------ .../kotlin/backend/konan/objcexport/stubs.kt | 11 ++++++--- .../!enumClassWithNamedObjCEnum.h | 6 ++--- .../headers/enumClassWithNamedObjCEnum/Foo.kt | 2 +- .../!enumClassWithObjCEnum.h | 6 ++--- .../headers/enumClassWithObjCEnum/Foo.kt | 2 +- 22 files changed, 86 insertions(+), 107 deletions(-) rename native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/{TranslatedClass.kt => ObjCExportTranslatedClass.kt} (75%) rename native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/{getNSEnumType.kt => getNSEnumTypeName.kt} (66%) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt index 552811b100b97..5d34d6f30be5a 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt @@ -20,7 +20,6 @@ import org.jetbrains.kotlin.backend.konan.llvm.objcexport.KotlinToObjCMethodAdap import org.jetbrains.kotlin.backend.konan.lower.getLoweredConstructorFunction import org.jetbrains.kotlin.backend.konan.lower.getObjectClassInstanceFunction import org.jetbrains.kotlin.backend.konan.objcexport.* -import org.jetbrains.kotlin.backend.konan.objcexport.ObjCMethodSpec.BaseMethod import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.incremental.components.NoLookupLocation @@ -1381,7 +1380,7 @@ private fun ObjCExportCodeGenerator.createNSEnumAdapter( methodBridge: MethodBridge, selectorName: String ): ObjCToKotlinMethodAdapter { - val imp = generateObjCImp(symbol.owner, symbol.owner.getLowered(), methodBridge) + val imp = generateObjCImp(symbol.owner.getLowered(), symbol.owner.getLowered(), methodBridge) return objCToKotlinMethodAdapter(selectorName, methodBridge, imp) } @@ -1441,7 +1440,7 @@ private fun ObjCExportCodeGenerator.createTypeAdapter( adapters += createConstructorAdapter(it.baseMethod) } is ObjCGetterForNSEnumType -> { - adapters += createNSEnumAdapter(it.symbol, it.bridge, it. selector) + adapters += createNSEnumAdapter(it.symbol, it.bridge, it.selector) } is ObjCFactoryMethodForKotlinArrayConstructor -> { classAdapters += createArrayConstructorAdapter(it.baseMethod) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt index e2297fb2e9da4..be769ab1ad544 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt @@ -9,6 +9,8 @@ import org.jetbrains.kotlin.backend.konan.descriptors.contributedMethods import org.jetbrains.kotlin.backend.konan.descriptors.enumEntries import org.jetbrains.kotlin.backend.konan.descriptors.isArray import org.jetbrains.kotlin.backend.konan.descriptors.isInterface +import org.jetbrains.kotlin.backend.konan.descriptors.propertyIfAccessor +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamer.Companion.nsEnumPropertyName import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI @@ -88,12 +90,12 @@ internal fun ObjCExportedInterface.createCodeSpec(symbolTable: SymbolTable): Obj } if (descriptor.kind == ClassKind.ENUM_CLASS) { - if (namer.getNSEnumFunctionTypeName(descriptor) != null) { + if (namer.getNSEnumTypeName(descriptor) != null) { val superClass = descriptor.getSuperClassNotAny()!! // ordinal is declared in KotlinEnum - val ordinalDescriptor = superClass.contributedMethods.first { it.name.asString() == "" } + val ordinalDescriptor = superClass.contributedMethods.first { it.propertyIfAccessor.name.asString() == "ordinal" } val symbol = symbolTable.descriptorExtension.referenceSimpleFunction(ordinalDescriptor) val bridge = mapper.bridgeMethod(ordinalDescriptor) - methods += ObjCGetterForNSEnumType(symbol, bridge, "nsEnum") + methods += ObjCGetterForNSEnumType(symbol, bridge, nsEnumPropertyName) } descriptor.enumEntries.mapTo(methods) { diff --git a/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt b/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt index 9cd696361d136..1303346856c1f 100644 --- a/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt +++ b/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2025 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. */ @@ -76,7 +76,7 @@ public expect annotation class ObjCName(val name: String = "", val swiftName: St * Instructs the Kotlin compiler to generate a NS_ENUM typedef for the annotated enum class. The name of the generated type will * be the name of the enum type with "NSEnum" appended. This name can be overridden with the "name" parameter, which is treated * as an exact name. The enum literals will be prefixed with the type name, as they live in a global namespace. - * Swift naming will automatically remove these disambiguation prefixes. The native values are accessible via the "nsEnum" property. + * Swift naming will automatically remove these disambiguation prefixes. The NSEnum values are accessible via the "nsEnum" property. */ @Target( AnnotationTarget.CLASS, @@ -85,7 +85,7 @@ public expect annotation class ObjCName(val name: String = "", val swiftName: St @MustBeDocumented @OptionalExpectation @ExperimentalObjCEnum -@SinceKotlin("2.2.21") +@SinceKotlin("2.3") public expect annotation class ObjCEnum(val name: String = "") /** diff --git a/libraries/stdlib/src/kotlin/experimental/ExperimentalObjCEnum.kt b/libraries/stdlib/src/kotlin/experimental/ExperimentalObjCEnum.kt index 0dc6afc89a4ac..d6da5fc65a3fb 100644 --- a/libraries/stdlib/src/kotlin/experimental/ExperimentalObjCEnum.kt +++ b/libraries/stdlib/src/kotlin/experimental/ExperimentalObjCEnum.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2025 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. */ @@ -12,5 +12,5 @@ package kotlin.experimental @Target(AnnotationTarget.ANNOTATION_CLASS) @Retention(AnnotationRetention.BINARY) @MustBeDocumented -@SinceKotlin("2.2.21") +@SinceKotlin("2.3") public annotation class ExperimentalObjCEnum diff --git a/native/native.tests/testData/framework/objcexport/nativeEnum.kt b/native/native.tests/testData/framework/objcexport/nativeEnum.kt index dabc88d269bcc..da206abf0db3b 100644 --- a/native/native.tests/testData/framework/objcexport/nativeEnum.kt +++ b/native/native.tests/testData/framework/objcexport/nativeEnum.kt @@ -6,5 +6,5 @@ import kotlin.experimental.ExperimentalObjCEnum @OptIn(kotlin.experimental.ExperimentalObjCEnum::class) @ObjCEnum("OBJCFoo") enum class MyKotlinEnum { - ALPHA, BAR_FOO, COPY + ALPHA, COPY, BAR_FOO } \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/TranslatedClass.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/ObjCExportTranslatedClass.kt similarity index 75% rename from native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/TranslatedClass.kt rename to native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/ObjCExportTranslatedClass.kt index dab840448b67f..1bb1d7607002f 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/TranslatedClass.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/ObjCExportTranslatedClass.kt @@ -8,10 +8,10 @@ package org.jetbrains.kotlin.objcexport import org.jetbrains.kotlin.backend.konan.objcexport.ObjCClass import org.jetbrains.kotlin.backend.konan.objcexport.ObjCTopLevel -class TranslatedClass( +class ObjCExportTranslatedClass( val auxiliaryDeclarations: List, val objCClass: ObjCClass, ) -fun TranslatedClass(objCClass: ObjCClass?) = objCClass?.let { TranslatedClass(emptyList(), it) } +fun ObjCExportTranslatedClass(objCClass: ObjCClass?) = objCClass?.let { ObjCExportTranslatedClass(emptyList(), it) } diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/ObjCPropertyNames.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/ObjCPropertyNames.kt index 335f46f66319e..d6f66d046979f 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/ObjCPropertyNames.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/ObjCPropertyNames.kt @@ -11,4 +11,6 @@ internal object ObjCPropertyNames { @Suppress("unused") const val companionObjectPropertyName: String = "companion" + + const val nsEnumPropertyName: String = "nsEnum" } \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getNSEnumType.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getNSEnumTypeName.kt similarity index 66% rename from native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getNSEnumType.kt rename to native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getNSEnumTypeName.kt index 8ee8067a193e1..0c256e1c270ee 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getNSEnumType.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getNSEnumTypeName.kt @@ -5,20 +5,19 @@ package org.jetbrains.kotlin.objcexport -import org.jetbrains.kotlin.analysis.api.annotations.KaAnnotationValue import org.jetbrains.kotlin.analysis.api.symbols.KaClassSymbol +import org.jetbrains.kotlin.backend.konan.KonanFqNames import org.jetbrains.kotlin.name.ClassId -import org.jetbrains.kotlin.name.FqName /** Returns the NSEnum type for the given enum type if the corresponding annotation is set; null otherwise */ -fun ObjCExportContext.getNSEnumType(symbol: KaClassSymbol): String? { - val classId = ClassId(FqName("kotlin.native"), FqName("ObjCEnum"), false) +fun ObjCExportContext.getNSEnumTypeName(symbol: KaClassSymbol): String? { + val classId = ClassId.topLevel(KonanFqNames.objCEnum) val annotation = symbol.annotations[classId].firstOrNull() return if (annotation == null) { null } else if (annotation.arguments.isEmpty()) { getObjCClassOrProtocolName(symbol).objCName + "NSEnum" } else { - (annotation.arguments[0].expression as KaAnnotationValue.ConstantValue).value.value as String + annotation.arguments.first().resolveStringConstantValue() } } \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/resolveObjCNameAnnotation.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/resolveObjCNameAnnotation.kt index c7331e26ce43e..6bcfb2fb0577f 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/resolveObjCNameAnnotation.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/resolveObjCNameAnnotation.kt @@ -57,7 +57,7 @@ private fun KaAnnotation.findArgument(name: String): KaNamedAnnotationValue? { return arguments.find { it.name.identifier == name } } -private fun KaNamedAnnotationValue.resolveStringConstantValue(): String? { +internal fun KaNamedAnnotationValue.resolveStringConstantValue(): String? { return expression.let { it as? KaAnnotationValue.ConstantValue }?.value ?.let { it as? StringValue } ?.value diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToNSEnum.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToNSEnum.kt index 084a4deb7a5f4..5d90a6d5f0008 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToNSEnum.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToNSEnum.kt @@ -11,12 +11,15 @@ import org.jetbrains.kotlin.backend.konan.objcexport.ObjCNSEnum import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProperty import org.jetbrains.kotlin.backend.konan.objcexport.ObjCRawType import org.jetbrains.kotlin.backend.konan.objcexport.ObjCTopLevel -import org.jetbrains.kotlin.backend.konan.objcexport.ObjcExportNativeEnumEntry +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNativeEnumEntry +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportStubOrigin -internal fun ObjCExportContext.translateNSEnum(symbol: KaClassSymbol, nsEnumTypeName: String, auxiliaryDeclarations: MutableList): ObjCProperty { - auxiliaryDeclarations.add(ObjCNSEnum(nsEnumTypeName, getNSEnumEntries(symbol, nsEnumTypeName))) +internal fun ObjCExportContext.translateNSEnum( + symbol: KaClassSymbol, origin: ObjCExportStubOrigin, nsEnumTypeName: String, auxiliaryDeclarations: MutableList +): ObjCProperty { + auxiliaryDeclarations.add(ObjCNSEnum(nsEnumTypeName, origin, getNSEnumEntries(symbol, nsEnumTypeName))) return ObjCProperty( - "nsEnum", + ObjCPropertyNames.nsEnumPropertyName, null, null, ObjCRawType(nsEnumTypeName), @@ -25,13 +28,13 @@ internal fun ObjCExportContext.translateNSEnum(symbol: KaClassSymbol, nsEnumType } -private fun ObjCExportContext.getNSEnumEntries(symbol: KaClassSymbol, typeName: String): List { +private fun ObjCExportContext.getNSEnumEntries(symbol: KaClassSymbol, typeName: String): List { val staticMembers = with(analysisSession) { symbol.staticDeclaredMemberScope }.callables.toList() // Map the enum entries in declaration order, preserving the ordinal return staticMembers.filterIsInstance().mapIndexed { ordinal, entry -> - ObjcExportNativeEnumEntry( - getEnumEntryName(entry, false), - typeName + getEnumEntryName(entry, true).replaceFirstChar { it.uppercaseChar() }, + ObjCExportNativeEnumEntry( + getEnumEntryName(entry, true), + typeName + getEnumEntryName(entry, false).replaceFirstChar { it.uppercaseChar() }, ordinal ) } 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 2c8c682c55d47..f910a2660b43e 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 @@ -16,7 +16,7 @@ import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC -fun ObjCExportContext.translateToObjCClass(symbol: KaClassSymbol): TranslatedClass? = withClassifierContext(symbol) { +fun ObjCExportContext.translateToObjCClass(symbol: KaClassSymbol): ObjCExportTranslatedClass? = withClassifierContext(symbol) { require( symbol.classKind == KaClassKind.CLASS || symbol.classKind == KaClassKind.ENUM_CLASS || @@ -53,8 +53,8 @@ fun ObjCExportContext.translateToObjCClass(symbol: KaClassSymbol): TranslatedCla .flatMap { translateToObjCExportStub(it) } if (symbol.classKind == KaClassKind.ENUM_CLASS) { - getNSEnumType(symbol)?.let { - this += translateNSEnum(symbol, it, auxiliaryDeclarations) + getNSEnumTypeName(symbol)?.let { + this += translateNSEnum(symbol, origin,it, auxiliaryDeclarations) } this += translateEnumMembers(symbol) } @@ -74,7 +74,7 @@ fun ObjCExportContext.translateToObjCClass(symbol: KaClassSymbol): TranslatedCla ) } - TranslatedClass( + ObjCExportTranslatedClass( auxiliaryDeclarations.toList(), ObjCInterfaceImpl( name = name.objCName, diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExportStub.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExportStub.kt index a134405f8d296..66e1e58edf372 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExportStub.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExportStub.kt @@ -31,11 +31,11 @@ internal fun ObjCExportContext.translateToObjCExportStub(symbol: KaCallableSymbo return result } -internal fun ObjCExportContext.translateToObjCExportStub(symbol: KaClassSymbol): TranslatedClass? = when (symbol.classKind) { - KaClassKind.INTERFACE -> TranslatedClass(translateToObjCProtocol(symbol)) +internal fun ObjCExportContext.translateToObjCExportStub(symbol: KaClassSymbol): ObjCExportTranslatedClass? = when (symbol.classKind) { + KaClassKind.INTERFACE -> ObjCExportTranslatedClass(translateToObjCProtocol(symbol)) KaClassKind.CLASS -> translateToObjCClass(symbol) - KaClassKind.OBJECT -> TranslatedClass(translateToObjCObject(symbol)) + KaClassKind.OBJECT -> ObjCExportTranslatedClass(translateToObjCObject(symbol)) KaClassKind.ENUM_CLASS -> translateToObjCClass(symbol) - KaClassKind.COMPANION_OBJECT -> TranslatedClass(translateToObjCObject(symbol)) + KaClassKind.COMPANION_OBJECT -> ObjCExportTranslatedClass(translateToObjCObject(symbol)) else -> null } 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 2cf8cf074e2ae..a8dd797e7dd3c 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 @@ -171,12 +171,8 @@ private class KtObjCExportHeaderGenerator( } } - for (aux in translated.auxiliaryDeclarations) { - objCStubs.add(aux) - } - /* 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()) + addObjCStubIfNotTranslated(objCClass, symbol.classId?.packageFqName?.asString(), translated.auxiliaryDeclarations) enqueueDependencyClasses(objCClass) return objCClass } @@ -289,11 +285,16 @@ private class KtObjCExportHeaderGenerator( * K1 also uses a dedicated hash map, but filtering out is spread across the translation traversal. * See the usage of [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportHeaderGenerator.generatedClasses]. */ - private fun addObjCStubIfNotTranslated(objCClass: ObjCClass, packageFqn: String? = "") { + private fun addObjCStubIfNotTranslated( + objCClass: ObjCClass, + packageFqn: String? = "", + auxiliaryDeclarations: List = emptyList() + ) { val key = ObjCClassKey(objCClass.name, packageFqn, (objCClass as? ObjCInterface)?.categoryName) val translatedClass = objCStubsByClassKey[key] if (translatedClass != null) return objCStubsByClassKey[key] = objCClass + objCStubs += auxiliaryDeclarations objCStubs += objCClass } } diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt index 154808dbd5762..4f110fb3c872f 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt @@ -22,7 +22,6 @@ import org.jetbrains.kotlin.library.shortName import org.jetbrains.kotlin.library.uniqueName import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils import org.jetbrains.kotlin.name.ClassId -import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType @@ -99,10 +98,10 @@ interface ObjCExportNamer { fun getCompanionObjectPropertySelector(descriptor: ClassDescriptor): String fun needsExplicitMethodFamily(name: String): Boolean - // Null means no NSEnum function. - fun getNSEnumFunctionTypeName(descriptor: ClassDescriptor): String? = + // Null means no NSEnum property. + fun getNSEnumTypeName(descriptor: ClassDescriptor): String? = descriptor.annotations.findAnnotation(KonanFqNames.objCEnum)?.let { - val name = it.allValueArguments.entries.find { it.key.asString() == "name" }?.value?.value?.toString() + val name = it.argumentValue("name")?.value as String? name ?: "${getClassOrProtocolName(descriptor).objCName}NSEnum" } @@ -115,6 +114,9 @@ interface ObjCExportNamer { @InternalKotlinNativeApi const val companionObjectPropertyName: String = "companion" + + @InternalKotlinNativeApi + const val nsEnumPropertyName: String = "nsEnum" } } diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt index 53801dcc2e273..50a9a6efc2f78 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt @@ -32,18 +32,16 @@ import org.jetbrains.kotlin.types.typeUtil.supertypes import org.jetbrains.kotlin.utils.addIfNotNull import kotlin.* -class TranslatedClass( - val auxiliaryDeclarations: List, +class ObjCExportTranslatedClass( + val auxiliaryDeclarations: List = emptyList(), val objCInterface: ObjCInterface, ) -fun TranslatedClass(objCInterface: ObjCInterface) = TranslatedClass(emptyList(), objCInterface) - interface ObjCExportTranslator { fun generateBaseDeclarations(): List fun getClassIfExtension(receiverType: KotlinType): ClassDescriptor? fun translateFile(file: SourceFile, declarations: List): ObjCInterface - fun translateClass(descriptor: ClassDescriptor): TranslatedClass + fun translateClass(descriptor: ClassDescriptor): ObjCExportTranslatedClass fun translateInterface(descriptor: ClassDescriptor): ObjCProtocol fun translateExtensions(classDescriptor: ClassDescriptor, declarations: List): ObjCInterface } @@ -179,10 +177,10 @@ class ObjCExportTranslatorImpl( ) } - override fun translateClass(descriptor: ClassDescriptor): TranslatedClass { + override fun translateClass(descriptor: ClassDescriptor): ObjCExportTranslatedClass { require(!descriptor.isInterface) if (!mapper.shouldBeExposed(descriptor)) { - return TranslatedClass(objCInterface = translateUnexposedClassAsUnavailableStub(descriptor)) + return ObjCExportTranslatedClass(objCInterface = translateUnexposedClassAsUnavailableStub(descriptor)) } val auxiliaryDeclarations = mutableListOf() @@ -292,11 +290,11 @@ class ObjCExportTranslatorImpl( ClassKind.ENUM_CLASS -> { val type = mapType(descriptor.defaultType, ReferenceBridge, ObjCRootExportScope) - namer.getNSEnumFunctionTypeName(descriptor)?.let { nsEnumTypeName -> + namer.getNSEnumTypeName(descriptor)?.let { nsEnumTypeName -> // Map the enum entries in declaration order, preserving the ordinal auxiliaryDeclarations.add( - ObjCNSEnum(nsEnumTypeName, descriptor.enumEntries.mapIndexed { ordinal, entry -> - ObjcExportNativeEnumEntry( + ObjCNSEnum(nsEnumTypeName, origin = ObjCExportStubOrigin(descriptor),descriptor.enumEntries.mapIndexed { ordinal, entry -> + ObjCExportNativeEnumEntry( objCName = nsEnumTypeName + namer.getEnumEntrySelector(entry).replaceFirstChar { it.uppercaseChar() }, swiftName = namer.getEnumEntrySwiftName(entry), value = ordinal) @@ -366,7 +364,7 @@ class ObjCExportTranslatorImpl( val generics = mapTypeConstructorParameters(descriptor) val superClassGenerics = superClassGenerics(genericExportScope) - return TranslatedClass( + return ObjCExportTranslatedClass( auxiliaryDeclarations = auxiliaryDeclarations.toList(), objCInterface = objCInterface( name, @@ -377,7 +375,9 @@ class ObjCExportTranslatorImpl( superProtocols = superProtocols, members = members, attributes = attributes, - comment = objCCommentOrNull(mustBeDocumentedAttributeList(descriptor.annotations)))) + comment = objCCommentOrNull(mustBeDocumentedAttributeList(descriptor.annotations)) + ) + ) } internal fun createGenericExportScope(descriptor: ClassDescriptor): ObjCExportScope = if (objcGenerics) { diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportName.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportName.kt index 4c51571530594..b9270cdea700e 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportName.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportName.kt @@ -22,10 +22,6 @@ interface ObjCExportFunctionName : ObjCExportName interface ObjCExportFileName : ObjCExportName -interface ObjcExportNativeEnumEntry : ObjCExportName { - val value: Int -} - fun ObjCExportClassOrProtocolName( swiftName: String, objCName: String, @@ -66,16 +62,6 @@ fun ObjCExportFileName( objCName = objCName ) -fun ObjcExportNativeEnumEntry( - swiftName: String, - objCName: String, - value: Int, -): ObjcExportNativeEnumEntry = ObjcExportNativeEnumEntryImpl( - swiftName = swiftName, - objCName = objCName, - value = value -) - private data class ObjCExportPropertyNameImpl( override val swiftName: String, override val objCName: String, @@ -91,14 +77,6 @@ private data class ObjCExportFileNameImpl( override val objCName: String, ) : ObjCExportFileName - -private data class ObjcExportNativeEnumEntryImpl( - override val swiftName: String, - override val objCName: String, - override val value: Int, -) : ObjcExportNativeEnumEntry - - fun ObjCExportClassOrProtocolName.toNameAttributes(): List = listOfNotNull( binaryName.takeIf { it != objCName }?.let { objcRuntimeNameAttribute(it) }, swiftName.takeIf { it != objCName }?.let { swiftNameAttribute(it) } diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt index 5a0130d445838..85b84ee1d1e1d 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt @@ -138,25 +138,13 @@ object StubRenderer { append("};\n") } - private fun Appendable.appendNativeEnumEntry(entry: ObjcExportNativeEnumEntry) { - fun appendName() { - append(entry.objCName) - } - - fun appendSwiftName() { - append(entry.swiftName) - } - - fun appendValue() { - append(entry.value.toString()) - } - + private fun Appendable.appendNativeEnumEntry(entry: ObjCExportNativeEnumEntry) { append(" ") - appendName() + append(entry.objCName) append(" NS_SWIFT_NAME(") - appendSwiftName() + append(entry.swiftName) append(") = ") - appendValue() + append(entry.value.toString()) append(",\n") } diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt index c0008c5f9d664..dc990f0de13ac 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt @@ -46,14 +46,19 @@ val ObjCExportStub.psiOrNull abstract class ObjCTopLevel : ObjCExportStub +class ObjCExportNativeEnumEntry( + val swiftName: String, + val objCName: String, + val value: Int +) + class ObjCNSEnum( override val name: String, - val entries: List, + override val origin: ObjCExportStubOrigin?, + val entries: List, ) : ObjCTopLevel() { override val comment: ObjCComment? get() = null - override val origin: ObjCExportStubOrigin? - get() = null override val extras: Extras = emptyExtras() } diff --git a/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/!enumClassWithNamedObjCEnum.h b/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/!enumClassWithNamedObjCEnum.h index 116673c00eec5..2e95c0ff398e5 100644 --- a/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/!enumClassWithNamedObjCEnum.h +++ b/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/!enumClassWithNamedObjCEnum.h @@ -40,8 +40,8 @@ NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(int32_t, OBJCFoo) { OBJCFooAlpha NS_SWIFT_NAME(alpha) = 0, - OBJCFooBarFoo NS_SWIFT_NAME(barFoo) = 1, - OBJCFooTheCopy NS_SWIFT_NAME(theCopy) = 2, + OBJCFooTheCopy NS_SWIFT_NAME(theCopy) = 1, + OBJCFooBarFoo NS_SWIFT_NAME(barFoo) = 2, }; @@ -52,8 +52,8 @@ __attribute__((objc_subclassing_restricted)) - (instancetype)initWithName:(NSString *)name ordinal:(int32_t)ordinal __attribute__((swift_name("init(name:ordinal:)"))) __attribute__((objc_designated_initializer)) __attribute__((unavailable)); @property (readonly) OBJCFoo nsEnum; @property (class, readonly) Foo *alpha __attribute__((swift_name("alpha"))); -@property (class, readonly) Foo *barFoo __attribute__((swift_name("barFoo"))); @property (class, readonly) Foo *theCopy __attribute__((swift_name("theCopy"))); +@property (class, readonly) Foo *barFoo __attribute__((swift_name("barFoo"))); + (KotlinArray *)values __attribute__((swift_name("values()"))); @property (class, readonly) NSArray *entries __attribute__((swift_name("entries"))); @end diff --git a/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/Foo.kt b/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/Foo.kt index f3576f3efcf63..0907f055bebd9 100644 --- a/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/Foo.kt +++ b/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/Foo.kt @@ -5,5 +5,5 @@ import kotlin.experimental.ExperimentalObjCEnum @ObjCEnum("OBJCFoo") enum class Foo { - ALPHA, BAR_FOO, COPY + ALPHA, COPY, BAR_FOO, } \ No newline at end of file diff --git a/native/objcexport-header-generator/testData/headers/enumClassWithObjCEnum/!enumClassWithObjCEnum.h b/native/objcexport-header-generator/testData/headers/enumClassWithObjCEnum/!enumClassWithObjCEnum.h index c846e71c11c42..d9cd6412f9d21 100644 --- a/native/objcexport-header-generator/testData/headers/enumClassWithObjCEnum/!enumClassWithObjCEnum.h +++ b/native/objcexport-header-generator/testData/headers/enumClassWithObjCEnum/!enumClassWithObjCEnum.h @@ -40,8 +40,8 @@ NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(int32_t, FooNSEnum) { FooNSEnumAlpha NS_SWIFT_NAME(alpha) = 0, - FooNSEnumBarFoo NS_SWIFT_NAME(barFoo) = 1, - FooNSEnumTheCopy NS_SWIFT_NAME(theCopy) = 2, + FooNSEnumTheCopy NS_SWIFT_NAME(theCopy) = 1, + FooNSEnumBarFoo NS_SWIFT_NAME(barFoo) = 2, }; @@ -52,8 +52,8 @@ __attribute__((objc_subclassing_restricted)) - (instancetype)initWithName:(NSString *)name ordinal:(int32_t)ordinal __attribute__((swift_name("init(name:ordinal:)"))) __attribute__((objc_designated_initializer)) __attribute__((unavailable)); @property (readonly) FooNSEnum nsEnum; @property (class, readonly) Foo *alpha __attribute__((swift_name("alpha"))); -@property (class, readonly) Foo *barFoo __attribute__((swift_name("barFoo"))); @property (class, readonly) Foo *theCopy __attribute__((swift_name("theCopy"))); +@property (class, readonly) Foo *barFoo __attribute__((swift_name("barFoo"))); + (KotlinArray *)values __attribute__((swift_name("values()"))); @property (class, readonly) NSArray *entries __attribute__((swift_name("entries"))); @end diff --git a/native/objcexport-header-generator/testData/headers/enumClassWithObjCEnum/Foo.kt b/native/objcexport-header-generator/testData/headers/enumClassWithObjCEnum/Foo.kt index e1fcd5599f1c8..a32df36750d3e 100644 --- a/native/objcexport-header-generator/testData/headers/enumClassWithObjCEnum/Foo.kt +++ b/native/objcexport-header-generator/testData/headers/enumClassWithObjCEnum/Foo.kt @@ -5,5 +5,5 @@ import kotlin.experimental.ExperimentalObjCEnum @ObjCEnum enum class Foo { - ALPHA, BAR_FOO, COPY + ALPHA, COPY, BAR_FOO } \ No newline at end of file From 51cf02b20734b78649b7dcf5eb32bf47d8860822 Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Thu, 20 Nov 2025 15:58:36 +0100 Subject: [PATCH 19/20] addressing review comments part 2 --- .../konan/objcexport/ObjCExportCodeSpec.kt | 2 +- .../main/kotlin/kotlin/native/Annotations.kt | 2 +- .../kotlin/annotations/NativeAnnotations.kt | 2 +- .../klib-public-api/kotlin-stdlib.api | 8 +++-- .../kotlin/objcexport/getNSEnumTypeName.kt | 17 +++++----- .../objcexport/resolveObjCNameAnnotation.kt | 2 +- .../kotlin/objcexport/translateToNSEnum.kt | 15 ++++++--- .../konan/objcexport/ObjCExportNamer.kt | 8 +++-- .../konan/objcexport/ObjCExportTranslator.kt | 31 +++++++++++++------ .../konan/objcexport/ObjCExportName.kt | 15 +++++++++ .../backend/konan/objcexport/StubRenderer.kt | 20 ++++++------ .../kotlin/backend/konan/objcexport/stubs.kt | 1 + .../!enumClassWithNamedObjCEnum.h | 2 +- .../headers/enumClassWithNamedObjCEnum/Foo.kt | 2 +- 14 files changed, 82 insertions(+), 45 deletions(-) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt index be769ab1ad544..d6fabe41039b9 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportCodeSpec.kt @@ -180,7 +180,7 @@ internal fun ObjCExportCodeSpec.dumpSelectorToSignatureMapping(path: String) { is ObjCInitMethodForKotlinConstructor -> "$objcClass.${baseMethod.selector},${baseMethod.symbol.signature}" is ObjCKotlinThrowableAsErrorMethod -> null is ObjCMethodForKotlinMethod -> "$objcClass.${baseMethod.selector},${baseMethod.symbol.signature}" - is ObjCGetterForNSEnumType -> "$objcClass.$selector,${symbol.signature}" + is ObjCGetterForNSEnumType -> null } out.println("\n# Instance methods mapping") for (type in types) { 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 3d5178e19f8fb..c63c84c66a0a1 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/Annotations.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/Annotations.kt @@ -128,7 +128,7 @@ public actual annotation class ObjCName(actual val name: String = "", actual val @MustBeDocumented @ExperimentalObjCEnum @SinceKotlin("2.3") -public actual annotation class ObjCEnum(actual val name: String = "") +public actual annotation class ObjCEnum(actual val name: String = "", actual val swiftName: String = "") /** * Meta-annotation that instructs the Kotlin compiler to remove the annotated class, function or property from the public Objective-C API. diff --git a/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt b/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt index 1303346856c1f..afae21a76157a 100644 --- a/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt +++ b/libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt @@ -86,7 +86,7 @@ public expect annotation class ObjCName(val name: String = "", val swiftName: St @OptionalExpectation @ExperimentalObjCEnum @SinceKotlin("2.3") -public expect annotation class ObjCEnum(val name: String = "") +public expect annotation class ObjCEnum(val name: String = "", val swiftName: String = "") /** * Meta-annotation that instructs the Kotlin compiler to remove the annotated class, function or property from the public Objective-C API. diff --git a/libraries/tools/binary-compatibility-validator/klib-public-api/kotlin-stdlib.api b/libraries/tools/binary-compatibility-validator/klib-public-api/kotlin-stdlib.api index 6980b3cf0d3be..a7fb6a3e14c33 100644 --- a/libraries/tools/binary-compatibility-validator/klib-public-api/kotlin-stdlib.api +++ b/libraries/tools/binary-compatibility-validator/klib-public-api/kotlin-stdlib.api @@ -9835,10 +9835,12 @@ open annotation class kotlin.native/NoInline : kotlin/Annotation { // kotlin.nat // Targets: [native] open annotation class kotlin.native/ObjCEnum : kotlin/Annotation { // kotlin.native/ObjCEnum|null[0] - constructor (kotlin/String = ...) // kotlin.native/ObjCEnum.|(kotlin.String){}[0] + constructor (kotlin/String = ..., kotlin/String = ...) // kotlin.native/ObjCEnum.|(kotlin.String;kotlin.String){}[0] final val name // kotlin.native/ObjCEnum.name|{}name[0] final fun (): kotlin/String // kotlin.native/ObjCEnum.name.|(){}[0] + final val swiftName // kotlin.native/ObjCEnum.swiftName|{}swiftName[0] + final fun (): kotlin/String // kotlin.native/ObjCEnum.swiftName.|(){}[0] } // Targets: [native] @@ -13753,10 +13755,12 @@ open annotation class kotlin.native/HidesFromObjC : kotlin/Annotation { // kotli // Targets: [js, wasmJs, wasmWasi] open annotation class kotlin.native/ObjCEnum : kotlin/Annotation { // kotlin.native/ObjCEnum|null[1] - constructor (kotlin/String = ...) // kotlin.native/ObjCEnum.|(kotlin.String){}[1] + constructor (kotlin/String = ..., kotlin/String = ...) // kotlin.native/ObjCEnum.|(kotlin.String;kotlin.String){}[1] final val name // kotlin.native/ObjCEnum.name|{}name[1] final fun (): kotlin/String // kotlin.native/ObjCEnum.name.|(){}[1] + final val swiftName // kotlin.native/ObjCEnum.swiftName|{}swiftName[1] + final fun (): kotlin/String // kotlin.native/ObjCEnum.swiftName.|(){}[1] } // Targets: [js, wasmJs, wasmWasi] diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getNSEnumTypeName.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getNSEnumTypeName.kt index 0c256e1c270ee..b137b7e368fbd 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getNSEnumTypeName.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getNSEnumTypeName.kt @@ -7,17 +7,16 @@ package org.jetbrains.kotlin.objcexport import org.jetbrains.kotlin.analysis.api.symbols.KaClassSymbol import org.jetbrains.kotlin.backend.konan.KonanFqNames +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNSEnumTypeName import org.jetbrains.kotlin.name.ClassId /** Returns the NSEnum type for the given enum type if the corresponding annotation is set; null otherwise */ -fun ObjCExportContext.getNSEnumTypeName(symbol: KaClassSymbol): String? { +fun ObjCExportContext.getNSEnumTypeName(symbol: KaClassSymbol): ObjCExportNSEnumTypeName? { val classId = ClassId.topLevel(KonanFqNames.objCEnum) - val annotation = symbol.annotations[classId].firstOrNull() - return if (annotation == null) { - null - } else if (annotation.arguments.isEmpty()) { - getObjCClassOrProtocolName(symbol).objCName + "NSEnum" - } else { - annotation.arguments.first().resolveStringConstantValue() - } + val annotation = symbol.annotations[classId].firstOrNull() ?: return null + + val name = annotation.findArgument("name")?.resolveStringConstantValue()?.ifEmpty { null } + ?: (getObjCClassOrProtocolName(symbol).objCName + "NSEnum") + val swiftName = annotation.findArgument("swiftName")?.resolveStringConstantValue()?.ifEmpty { null } ?: name + return ObjCExportNSEnumTypeName(swiftName = swiftName, objCName = name) } \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/resolveObjCNameAnnotation.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/resolveObjCNameAnnotation.kt index 6bcfb2fb0577f..3eb7adfbc4c85 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/resolveObjCNameAnnotation.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/resolveObjCNameAnnotation.kt @@ -53,7 +53,7 @@ internal fun KaAnnotatedSymbol.resolveObjCNameAnnotation(): ObjCExportObjCNameAn ) } -private fun KaAnnotation.findArgument(name: String): KaNamedAnnotationValue? { +internal fun KaAnnotation.findArgument(name: String): KaNamedAnnotationValue? { return arguments.find { it.name.identifier == name } } diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToNSEnum.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToNSEnum.kt index 5d90a6d5f0008..8c5125e6a06c7 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToNSEnum.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToNSEnum.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.objcexport import org.jetbrains.kotlin.analysis.api.symbols.KaClassSymbol import org.jetbrains.kotlin.analysis.api.symbols.KaEnumEntrySymbol +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNSEnumTypeName import org.jetbrains.kotlin.backend.konan.objcexport.ObjCNSEnum import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProperty import org.jetbrains.kotlin.backend.konan.objcexport.ObjCRawType @@ -15,26 +16,30 @@ import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNativeEnumEntry import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportStubOrigin internal fun ObjCExportContext.translateNSEnum( - symbol: KaClassSymbol, origin: ObjCExportStubOrigin, nsEnumTypeName: String, auxiliaryDeclarations: MutableList + symbol: KaClassSymbol, + origin: ObjCExportStubOrigin, + nsEnumTypeName: ObjCExportNSEnumTypeName, + auxiliaryDeclarations: MutableList ): ObjCProperty { - auxiliaryDeclarations.add(ObjCNSEnum(nsEnumTypeName, origin, getNSEnumEntries(symbol, nsEnumTypeName))) + auxiliaryDeclarations.add( + ObjCNSEnum(nsEnumTypeName.objCName, nsEnumTypeName.swiftName, origin, getNSEnumEntries(symbol, nsEnumTypeName.objCName))) return ObjCProperty( ObjCPropertyNames.nsEnumPropertyName, null, null, - ObjCRawType(nsEnumTypeName), + ObjCRawType(nsEnumTypeName.objCName), listOf("readonly") ) } -private fun ObjCExportContext.getNSEnumEntries(symbol: KaClassSymbol, typeName: String): List { +private fun ObjCExportContext.getNSEnumEntries(symbol: KaClassSymbol, objCTypeName: String): List { val staticMembers = with(analysisSession) { symbol.staticDeclaredMemberScope }.callables.toList() // Map the enum entries in declaration order, preserving the ordinal return staticMembers.filterIsInstance().mapIndexed { ordinal, entry -> ObjCExportNativeEnumEntry( getEnumEntryName(entry, true), - typeName + getEnumEntryName(entry, false).replaceFirstChar { it.uppercaseChar() }, + objCTypeName + getEnumEntryName(entry, false).replaceFirstChar { it.uppercaseChar() }, ordinal ) } diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt index 4f110fb3c872f..d7dc1487b864b 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt @@ -99,10 +99,12 @@ interface ObjCExportNamer { fun needsExplicitMethodFamily(name: String): Boolean // Null means no NSEnum property. - fun getNSEnumTypeName(descriptor: ClassDescriptor): String? = + fun getNSEnumTypeName(descriptor: ClassDescriptor): ObjCExportNSEnumTypeName? = descriptor.annotations.findAnnotation(KonanFqNames.objCEnum)?.let { - val name = it.argumentValue("name")?.value as String? - name ?: "${getClassOrProtocolName(descriptor).objCName}NSEnum" + val name = ((it.argumentValue("name")?.value as String?)?.ifEmpty { null }) + ?: "${getClassOrProtocolName(descriptor).objCName}NSEnum" + val swiftName = ((it.argumentValue("name")?.value as String?)?.ifEmpty { null }) ?: name + ObjCExportNSEnumTypeName(swiftName = swiftName, objCName = name) } companion object { diff --git a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt index 50a9a6efc2f78..39868b36682e1 100644 --- a/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt +++ b/native/objcexport-header-generator/impl/k1/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportTranslator.kt @@ -293,21 +293,31 @@ class ObjCExportTranslatorImpl( namer.getNSEnumTypeName(descriptor)?.let { nsEnumTypeName -> // Map the enum entries in declaration order, preserving the ordinal auxiliaryDeclarations.add( - ObjCNSEnum(nsEnumTypeName, origin = ObjCExportStubOrigin(descriptor),descriptor.enumEntries.mapIndexed { ordinal, entry -> - ObjCExportNativeEnumEntry( - objCName = nsEnumTypeName + namer.getEnumEntrySelector(entry).replaceFirstChar { it.uppercaseChar() }, - swiftName = namer.getEnumEntrySwiftName(entry), - value = ordinal) - })) + ObjCNSEnum( + name = nsEnumTypeName.objCName, + swiftName = nsEnumTypeName.swiftName, + origin = ObjCExportStubOrigin(descriptor), + entries = descriptor.enumEntries.mapIndexed { ordinal, entry -> + ObjCExportNativeEnumEntry( + objCName = nsEnumTypeName.objCName + namer.getEnumEntrySelector(entry) + .replaceFirstChar { it.uppercaseChar() }, + swiftName = namer.getEnumEntrySwiftName(entry), + value = ordinal + ) + } + ) + ) add { + // TODO(KT-82581): If an enum already has a property named nsEnum, we'll get a name conflict ObjCProperty( "nsEnum", null, - ObjCRawType(nsEnumTypeName), + ObjCRawType(nsEnumTypeName.objCName), listOf("readonly"), declarationAttributes = emptyList(), - comment = null) + comment = null + ) } } @@ -802,8 +812,9 @@ class ObjCExportTranslatorImpl( } private val mustBeDocumentedAnnotationsStopList = - setOf(StandardNames.FqNames.deprecated, KonanFqNames.objCName, KonanFqNames.shouldRefineInSwift, - KonanFqNames.objCEnum) + setOf( + StandardNames.FqNames.deprecated, KonanFqNames.objCName, KonanFqNames.shouldRefineInSwift, KonanFqNames.objCEnum + ) private fun mustBeDocumentedAnnotations(annotations: Annotations): List { return annotations.mapNotNull { it -> diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportName.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportName.kt index b9270cdea700e..e381a92b9c4af 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportName.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportName.kt @@ -22,6 +22,8 @@ interface ObjCExportFunctionName : ObjCExportName interface ObjCExportFileName : ObjCExportName +interface ObjCExportNSEnumTypeName : ObjCExportName + fun ObjCExportClassOrProtocolName( swiftName: String, objCName: String, @@ -62,6 +64,14 @@ fun ObjCExportFileName( objCName = objCName ) +fun ObjCExportNSEnumTypeName( + swiftName: String, + objCName: String, +): ObjCExportNSEnumTypeName = ObjCExportNSEnumTypeNameImpl( + swiftName = swiftName, + objCName = objCName +) + private data class ObjCExportPropertyNameImpl( override val swiftName: String, override val objCName: String, @@ -77,6 +87,11 @@ private data class ObjCExportFileNameImpl( override val objCName: String, ) : ObjCExportFileName +private data class ObjCExportNSEnumTypeNameImpl( + override val swiftName: String, + override val objCName: String, +) : ObjCExportNSEnumTypeName + fun ObjCExportClassOrProtocolName.toNameAttributes(): List = listOfNotNull( binaryName.takeIf { it != objCName }?.let { objcRuntimeNameAttribute(it) }, swiftName.takeIf { it != objCName }?.let { swiftNameAttribute(it) } diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt index 85b84ee1d1e1d..f8fcb8bc68334 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/StubRenderer.kt @@ -123,19 +123,19 @@ object StubRenderer { } private fun renderNativeEnumType(nativeEnum: ObjCNSEnum): String = buildString { - fun appendName() { - append("NS_ENUM(int32_t, ") - append(nativeEnum.name) - append(")") - } - - append("typedef ") - appendName() - append(" {\n") + append("typedef NS_ENUM(int32_t, ") + append(nativeEnum.name) + append(") {\n") for (entry in nativeEnum.entries) { appendNativeEnumEntry(entry) } - append("};\n") + append("}") + if (nativeEnum.name != nativeEnum.swiftName) { + append(" NS_SWIFT_NAME(") + append(nativeEnum.swiftName) + append(")") + } + append(";\n") } private fun Appendable.appendNativeEnumEntry(entry: ObjCExportNativeEnumEntry) { diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt index dc990f0de13ac..1549afb0f92d3 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/stubs.kt @@ -54,6 +54,7 @@ class ObjCExportNativeEnumEntry( class ObjCNSEnum( override val name: String, + val swiftName: String, override val origin: ObjCExportStubOrigin?, val entries: List, ) : ObjCTopLevel() { diff --git a/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/!enumClassWithNamedObjCEnum.h b/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/!enumClassWithNamedObjCEnum.h index 2e95c0ff398e5..98c9b2dc8e47f 100644 --- a/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/!enumClassWithNamedObjCEnum.h +++ b/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/!enumClassWithNamedObjCEnum.h @@ -42,7 +42,7 @@ typedef NS_ENUM(int32_t, OBJCFoo) { OBJCFooAlpha NS_SWIFT_NAME(alpha) = 0, OBJCFooTheCopy NS_SWIFT_NAME(theCopy) = 1, OBJCFooBarFoo NS_SWIFT_NAME(barFoo) = 2, -}; +} NS_SWIFT_NAME(SwiftFoo); __attribute__((objc_subclassing_restricted)) diff --git a/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/Foo.kt b/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/Foo.kt index 0907f055bebd9..cdc7ecf4fe997 100644 --- a/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/Foo.kt +++ b/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/Foo.kt @@ -3,7 +3,7 @@ import kotlin.experimental.ExperimentalObjCEnum @file:OptIn(ExperimentalObjCEnum::class) -@ObjCEnum("OBJCFoo") +@ObjCEnum(name = "OBJCFoo", swiftName = "SwiftFoo") enum class Foo { ALPHA, COPY, BAR_FOO, } \ No newline at end of file From 32645cb9cb2389c37941e99d0fad763c07191ae5 Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Thu, 20 Nov 2025 16:03:26 +0100 Subject: [PATCH 20/20] Add a comment about ordering --- .../testData/headers/enumClassWithNamedObjCEnum/Foo.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/Foo.kt b/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/Foo.kt index cdc7ecf4fe997..6f8290b9a36fb 100644 --- a/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/Foo.kt +++ b/native/objcexport-header-generator/testData/headers/enumClassWithNamedObjCEnum/Foo.kt @@ -5,5 +5,7 @@ import kotlin.experimental.ExperimentalObjCEnum @ObjCEnum(name = "OBJCFoo", swiftName = "SwiftFoo") enum class Foo { + // Note that the order is not alphabetic on purpose, ensuring that tests fail if we can't rely on the + // order to be preserved for the ordinal value. ALPHA, COPY, BAR_FOO, } \ No newline at end of file