Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2ab6493
make 'something' visible end-to-end
stefanhaustein Sep 29, 2025
b824f55
End-to-end wireup with test; method body missing
stefanhaustein Oct 2, 2025
e180ad4
current state (incomplete)
stefanhaustein Oct 3, 2025
6ab07ae
cleanup
stefanhaustein Oct 8, 2025
b925ff2
cleanup
stefanhaustein Oct 8, 2025
e7ec42e
cleanup
stefanhaustein Oct 8, 2025
7e4fb66
ordinal should be in KotlinEnum
stefanhaustein Oct 8, 2025
b713914
refactored to avoid ObjCMethodForKotlinMethod
stefanhaustein Oct 13, 2025
b126cd4
move symbol resolution from ObjCExportCodeSpec to ObjCExportCodeGener…
stefanhaustein Oct 17, 2025
f51d934
swift test compiles
stefanhaustein Oct 20, 2025
854371f
remove footgun
stefanhaustein Oct 20, 2025
4013372
objc/switft name distinction / adjustment
stefanhaustein Oct 24, 2025
c9de8d8
Merge branch 'JetBrains:master' into master
stefanhaustein Oct 24, 2025
200acce
Merge branch 'JetBrains:master' into master
stefanhaustein Oct 29, 2025
29e8031
first round of review comments / snapshot before changing to property
stefanhaustein Oct 31, 2025
de01982
Merge branch 'JetBrains:master' into master
stefanhaustein Nov 4, 2025
0ccc118
Review comments addressed; mostly adding the missing AA impl
stefanhaustein Nov 4, 2025
60cca82
Make the enum entry value / order more explicit / add a clarifying co…
stefanhaustein Nov 5, 2025
8da8bb4
Naming consistency / fixes and test extensions / improvements
stefanhaustein Nov 10, 2025
6a52203
Move ObjCEnum to the auto documentartion exclude list for AA, matchin…
stefanhaustein Nov 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import?

import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
Expand Down Expand Up @@ -1375,6 +1376,15 @@ private fun ObjCExportCodeGenerator.createArrayConstructorAdapter(
return objCToKotlinMethodAdapter(selectorName, methodBridge, imp)
}

private fun ObjCExportCodeGenerator.createNSEnumAdapter(
symbol: IrSimpleFunctionSymbol,
methodBridge: MethodBridge,
selectorName: String
): ObjCToKotlinMethodAdapter {
val imp = generateObjCImp(symbol.owner, symbol.owner.getLowered<IrSimpleFunction>(), methodBridge)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think both symbol.owner usages here should be getLowered. The first two arguments of generateObjCImp are supposed to be consistent – either the same function or two functions, one overriding another.

return objCToKotlinMethodAdapter(selectorName, methodBridge, imp)
}

private fun ObjCExportCodeGenerator.vtableIndex(irFunction: IrSimpleFunction): Int? {
assert(irFunction.isOverridable)
val irClass = irFunction.parentAsClass
Expand Down Expand Up @@ -1430,6 +1440,9 @@ private fun ObjCExportCodeGenerator.createTypeAdapter(
is ObjCInitMethodForKotlinConstructor -> {
adapters += createConstructorAdapter(it.baseMethod)
}
is ObjCGetterForNSEnumType -> {
adapters += createNSEnumAdapter(it.symbol, it.bridge, it. selector)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant space in it. selector.

}
is ObjCFactoryMethodForKotlinArrayConstructor -> {
classAdapters += createArrayConstructorAdapter(it.baseMethod)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
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
Expand Down Expand Up @@ -89,6 +88,14 @@ internal fun ObjCExportedInterface.createCodeSpec(symbolTable: SymbolTable): Obj
}

if (descriptor.kind == ClassKind.ENUM_CLASS) {
if (namer.getNSEnumFunctionTypeName(descriptor) != null) {
val superClass = descriptor.getSuperClassNotAny()!! // ordinal is declared in KotlinEnum
val ordinalDescriptor = superClass.contributedMethods.first { it.name.asString() == "<get-ordinal>" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little bit less magical way to do that would be:

superClass.contributedMethods.first { it.propertyIfAccessor.name.asString() == "ordinal" }

or

superClass.unsubstitutedMemberScope.getContributedVariables(Name.identifier("ordinal"), NoLookupLocation.FROM_BACKEND).single().getter

val symbol = symbolTable.descriptorExtension.referenceSimpleFunction(ordinalDescriptor)
val bridge = mapper.bridgeMethod(ordinalDescriptor)
methods += ObjCGetterForNSEnumType(symbol, bridge, "nsEnum")
}

descriptor.enumEntries.mapTo(methods) {
ObjCGetterForKotlinEnumEntry(symbolTable.descriptorExtension.referenceEnumEntry(it), namer.getEnumEntrySelector(it))
}
Expand Down Expand Up @@ -160,6 +167,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) {
Expand All @@ -170,6 +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.$selector,${symbol.signature}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, that's a bit tricky.
IIRC, the dump generated here is used for some additional DCE: if the Objective-C/Swift code doesn't use certain generated method, using this dump we can find the original Kotlin declaration that doesn't need to be exposed then.

The problem with this implementation is therefore the following: if the generated nsEnum property of a class is not used, the compiler will be instructed to hide Enum.ordinal, which is not exactly intended.

See these changes for more details:

}
out.println("\n# Instance methods mapping")
for (type in types) {
Expand Down Expand Up @@ -224,6 +233,17 @@ internal class ObjCGetterForKotlinEnumEntry(
"ObjC spec of getter `$selector` for `$irEnumEntrySymbol`"
}


internal class ObjCGetterForNSEnumType(
val symbol: IrSimpleFunctionSymbol,
val bridge: MethodBridge,
val selector: String,
) : ObjCMethodSpec() {
override fun toString(): String =
"ObjC spec of $selector for $symbol"
}


internal class ObjCClassMethodForKotlinEnumValuesOrEntries(
val valuesFunctionSymbol: IrFunctionSymbol,
val selector: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package kotlin.native

import kotlin.experimental.ExperimentalNativeApi
import kotlin.experimental.ExperimentalObjCEnum
import kotlin.experimental.ExperimentalObjCName
import kotlin.experimental.ExperimentalObjCRefinement

Expand Down Expand Up @@ -116,6 +117,19 @@ public actual annotation class CName(actual val externName: String = "", actual
@SinceKotlin("1.8")
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 "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. The native values are accessible via the "nsEnum" property.
*/
@Target(AnnotationTarget.CLASS)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, we should check that the annotation is not applied to non-enum classes.
A new frontend checker is needed for that. See an example here:

object FirNativeThreadLocalChecker : FirBasicDeclarationChecker(MppCheckerKind.Platform) {
.

To avoid stalling this PR, this can be done separately.

@Retention(AnnotationRetention.BINARY)
@MustBeDocumented
@ExperimentalObjCEnum
@SinceKotlin("2.3")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This version differs from the one for the expect class. They should match.

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.
*
Expand Down
17 changes: 17 additions & 0 deletions libraries/stdlib/src/kotlin/annotations/NativeAnnotations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package kotlin.native

import kotlin.experimental.ExperimentalNativeApi
import kotlin.experimental.ExperimentalObjCEnum
import kotlin.experimental.ExperimentalObjCName
import kotlin.experimental.ExperimentalObjCRefinement

Expand Down Expand Up @@ -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 "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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"The native values" might be confusing: "native" is a very ambiguous term, and it is mentioned for the first time in this comment.

*/
@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.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The year is a bit old.

* 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
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ open annotation class kotlin.experimental/ExperimentalNativeApi : kotlin/Annotat
constructor <init>() // kotlin.experimental/ExperimentalNativeApi.<init>|<init>(){}[0]
}

open annotation class kotlin.experimental/ExperimentalObjCEnum : kotlin/Annotation { // kotlin.experimental/ExperimentalObjCEnum|null[0]
constructor <init>() // kotlin.experimental/ExperimentalObjCEnum.<init>|<init>(){}[0]
}

open annotation class kotlin.experimental/ExperimentalObjCName : kotlin/Annotation { // kotlin.experimental/ExperimentalObjCName|null[0]
constructor <init>() // kotlin.experimental/ExperimentalObjCName.<init>|<init>(){}[0]
}
Expand Down Expand Up @@ -9829,6 +9833,14 @@ open annotation class kotlin.native/NoInline : kotlin/Annotation { // kotlin.nat
constructor <init>() // kotlin.native/NoInline.<init>|<init>(){}[0]
}

// Targets: [native]
open annotation class kotlin.native/ObjCEnum : kotlin/Annotation { // kotlin.native/ObjCEnum|null[0]
constructor <init>(kotlin/String = ...) // kotlin.native/ObjCEnum.<init>|<init>(kotlin.String){}[0]

final val name // kotlin.native/ObjCEnum.name|{}name[0]
final fun <get-name>(): kotlin/String // kotlin.native/ObjCEnum.name.<get-name>|<get-name>(){}[0]
}

// Targets: [native]
open annotation class kotlin.native/ObjCName : kotlin/Annotation { // kotlin.native/ObjCName|null[0]
constructor <init>(kotlin/String = ..., kotlin/String = ..., kotlin/Boolean = ...) // kotlin.native/ObjCName.<init>|<init>(kotlin.String;kotlin.String;kotlin.Boolean){}[0]
Expand Down Expand Up @@ -13739,6 +13751,14 @@ open annotation class kotlin.native/HidesFromObjC : kotlin/Annotation { // kotli
constructor <init>() // kotlin.native/HidesFromObjC.<init>|<init>(){}[1]
}

// Targets: [js, wasmJs, wasmWasi]
open annotation class kotlin.native/ObjCEnum : kotlin/Annotation { // kotlin.native/ObjCEnum|null[1]
constructor <init>(kotlin/String = ...) // kotlin.native/ObjCEnum.<init>|<init>(kotlin.String){}[1]

final val name // kotlin.native/ObjCEnum.name|{}name[1]
final fun <get-name>(): kotlin/String // kotlin.native/ObjCEnum.name.<get-name>|<get-name>(){}[1]
}

// Targets: [js, wasmJs, wasmWasi]
open annotation class kotlin.native/ObjCName : kotlin/Annotation { // kotlin.native/ObjCName|null[1]
constructor <init>(kotlin/String = ..., kotlin/String = ..., kotlin/Boolean = ...) // kotlin.native/ObjCName.<init>|<init>(kotlin.String;kotlin.String;kotlin.Boolean){}[1]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
10 changes: 10 additions & 0 deletions native/native.tests/testData/framework/objcexport/nativeEnum.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package nativeEnum

import kotlin.native.ObjCEnum
import kotlin.experimental.ExperimentalObjCEnum

@OptIn(kotlin.experimental.ExperimentalObjCEnum::class)
@ObjCEnum("OBJCFoo")
enum class MyKotlinEnum {
ALPHA, BAR_FOO, COPY
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Kt


private func testNativeEnumValues() throws {
let ktEnum = MyKotlinEnum.alpha
let nsEnum = ktEnum.nsEnum

switch(nsEnum) {
case .alpha: try assertEquals(actual: nsEnum, expected: ktEnum.nsEnum)
case .barFoo: try fail()
case .theCopy: try fail()
}
}

class NativeEnumTests : SimpleTestProvider {
override init() {
super.init()

test("TestNativeEnumValues", testNativeEnumValues)
}
}
Original file line number Diff line number Diff line change
@@ -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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name is kind of too ambiguous.
Most classes in ObjCExport use the name prefix. Let's name this class accordingly, like ObjCExportTranslatedClass?
Same in the K1 impl.

val auxiliaryDeclarations: List<ObjCTopLevel>,
val objCClass: ObjCClass,
)

fun TranslatedClass(objCClass: ObjCClass?) = objCClass?.let { TranslatedClass(emptyList(), it) }

Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Original file line number Diff line number Diff line change
@@ -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? {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function returns not a type but a type name. Let's name it accordingly? Like getNSEnumTypeName.

val classId = ClassId(FqName("kotlin.native"), FqName("ObjCEnum"), false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be written this way

val classId = ClassId.topLevel(KonanFqNames.objCEnum)

to reuse the existing constant and keep things clearer.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveStringConstantValue can be reused instead.

objCName = annotation.findArgument("name")?.resolveStringConstantValue(),

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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<ObjCTopLevel>): 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<ObjcExportNativeEnumEntry> {
val staticMembers = with(analysisSession) { symbol.staticDeclaredMemberScope }.callables.toList()
// Map the enum entries in declaration order, preserving the ordinal
return staticMembers.filterIsInstance<KaEnumEntrySymbol>().mapIndexed { ordinal, entry ->
ObjcExportNativeEnumEntry(
getEnumEntryName(entry, false),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this code passes swiftName = getEnumEntryName(entry, forSwift = false))? I mean, it seems to accidentally use an Objective-C name as a Swift name.

typeName + getEnumEntryName(entry, true).replaceFirstChar { it.uppercaseChar() },
ordinal
)
}
}

Loading