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 2cd06a06151..7752fcef14e 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 @@ -1229,6 +1229,9 @@ private fun ObjCExportCodeGenerator.createTypeAdapter( is ObjCGetterForKotlinEnumEntry -> { classAdapters += createEnumEntryAdapter(it.irEnumEntrySymbol.owner) } + is ObjCClassMethodForKotlinEnumValues -> { + classAdapters += createEnumValuesAdapter(it.valuesFunctionSymbol.owner, it.selector) + } is ObjCMethodForKotlinMethod -> {} // Handled below. }.let {} // Force exhaustive. } @@ -1473,6 +1476,21 @@ private fun ObjCExportCodeGenerator.createEnumEntryAdapter( } } +private fun ObjCExportCodeGenerator.createEnumValuesAdapter( + valuesFunction: IrFunction, + selector: String +): ObjCExportCodeGenerator.ObjCToKotlinMethodAdapter { + val methodBridge = MethodBridge( + returnBridge = MethodBridge.ReturnValue.Mapped(ReferenceBridge), + receiver = MethodBridgeReceiver.Static, + valueParameters = emptyList() + ) + + val imp = generateObjCImp(valuesFunction, valuesFunction, methodBridge, isVirtual = false) + + return objCToKotlinMethodAdapter(selector, methodBridge, imp) +} + private fun List.toMethods(): List = this.flatMap { when (it) { is PropertyDescriptor -> listOfNotNull(it.getter, it.setter) 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 376bc9e7c3e..842db0ed5b2 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 @@ -10,10 +10,7 @@ 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.descriptors.* -import org.jetbrains.kotlin.ir.symbols.IrClassSymbol -import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol -import org.jetbrains.kotlin.ir.symbols.IrEnumEntrySymbol -import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol +import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.util.SymbolTable import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny @@ -67,6 +64,13 @@ internal fun ObjCExportedInterface.createCodeSpec(symbolTable: SymbolTable): Obj descriptor.enumEntries.mapTo(methods) { ObjCGetterForKotlinEnumEntry(symbolTable.referenceEnumEntry(it)) } + + descriptor.getEnumValuesFunctionDescriptor()?.let { + methods += ObjCClassMethodForKotlinEnumValues( + symbolTable.referenceSimpleFunction(it), + namer.getEnumValuesSelector(it) + ) + } } val categoryMethods = categoryMembers[descriptor].orEmpty().toObjCMethods() @@ -100,6 +104,11 @@ internal class ObjCFactoryMethodForKotlinArrayConstructor( internal class ObjCGetterForKotlinEnumEntry(val irEnumEntrySymbol: IrEnumEntrySymbol) : ObjCMethodSpec() +internal class ObjCClassMethodForKotlinEnumValues( + val valuesFunctionSymbol: IrFunctionSymbol, + val selector: String +) : ObjCMethodSpec() + internal sealed class ObjCTypeSpec(val binaryName: String) internal sealed class ObjCTypeForKotlinType( diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt index bf1bdb44f2b..59216a2be7e 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt @@ -365,6 +365,14 @@ internal class ObjCExportTranslatorImpl( +ObjCProperty(entryName, it, type, listOf("class", "readonly"), declarationAttributes = listOf(swiftNameAttribute(entryName))) } + + // Note: it is possible to support this function through a common machinery, + // but it is very special (static and synthetic), so apparently it is much easier + // to keep this ad hoc here than to add special cases to the most complicated parts + // of the machinery. + descriptor.getEnumValuesFunctionDescriptor()?.let { enumValues -> + +buildEnumValuesMethod(enumValues, genericExportScope) + } } else -> { // Nothing special. @@ -404,6 +412,21 @@ internal class ObjCExportTranslatorImpl( ) } + private fun buildEnumValuesMethod( + enumValues: SimpleFunctionDescriptor, + genericExportScope: ObjCExportScope + ): ObjCMethod { + val selector = namer.getEnumValuesSelector(enumValues) + return ObjCMethod( + enumValues, + isInstanceMethod = false, + returnType = mapReferenceType(enumValues.returnType!!, genericExportScope), + selectors = splitSelector(selector), + parameters = emptyList(), + attributes = listOf(swiftNameAttribute("$selector()")) + ) + } + private fun ClassDescriptor.getExposedMembers(): List = this.unsubstitutedMemberScope.getContributedDescriptors() .asSequence() diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt index 0b5991c0dce..fa96217dcda 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt @@ -13,6 +13,7 @@ import org.jetbrains.kotlin.backend.konan.descriptors.isArray import org.jetbrains.kotlin.backend.konan.descriptors.isInterface import org.jetbrains.kotlin.builtins.* import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.resolve.deprecation.Deprecation import org.jetbrains.kotlin.resolve.deprecation.DeprecationLevelValue @@ -181,6 +182,15 @@ internal fun ObjCExportMapper.isTopLevel(descriptor: CallableMemberDescriptor): internal fun ObjCExportMapper.isObjCProperty(property: PropertyDescriptor): Boolean = property.extensionReceiverParameter == null || getClassIfCategory(property) != null +internal fun ClassDescriptor.getEnumValuesFunctionDescriptor(): SimpleFunctionDescriptor? { + require(this.kind == ClassKind.ENUM_CLASS) + + return this.staticScope.getContributedFunctions( + StandardNames.ENUM_VALUES, + NoLookupLocation.FROM_BACKEND + ).singleOrNull { it.extensionReceiverParameter == null && it.valueParameters.size == 0 } +} + internal fun ObjCExportMapper.doesThrow(method: FunctionDescriptor): Boolean = method.allOverriddenDescriptors.any { it.overriddenDescriptors.isEmpty() && it.annotations.hasAnnotation(KonanFqNames.throws) } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt index 3e06c627ed5..c7827f5d090 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.backend.konan.descriptors.isArray import org.jetbrains.kotlin.backend.konan.descriptors.isInterface import org.jetbrains.kotlin.descriptors.konan.isNativeStdlib import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.konan.* import org.jetbrains.kotlin.incremental.components.NoLookupLocation @@ -56,6 +57,7 @@ interface ObjCExportNamer { fun getPropertyName(property: PropertyDescriptor): String fun getObjectInstanceSelector(descriptor: ClassDescriptor): String fun getEnumEntrySelector(descriptor: ClassDescriptor): String + fun getEnumValuesSelector(descriptor: FunctionDescriptor): String fun getTypeParameterName(typeParameterDescriptor: TypeParameterDescriptor): String fun numberBoxName(classId: ClassId): ClassOrProtocolName @@ -328,7 +330,7 @@ internal class ObjCExportNamerImpl( private val genericTypeParameterNameMapping = GenericTypeParameterNameMapping() - private abstract inner class ClassPropertyNameMapping : Mapping() { + private abstract inner class ClassSelectorNameMapping : Mapping() { // Try to avoid clashing with NSObject class methods: @@ -343,12 +345,12 @@ internal class ObjCExportNamerImpl( override fun reserved(name: String) = (name in reserved) || (name in cKeywords) } - private val objectInstanceSelectors = object : ClassPropertyNameMapping() { + private val objectInstanceSelectors = object : ClassSelectorNameMapping() { override fun conflict(first: ClassDescriptor, second: ClassDescriptor) = false } - private val enumEntrySelectors = object : ClassPropertyNameMapping() { - override fun conflict(first: ClassDescriptor, second: ClassDescriptor) = + private val enumClassSelectors = object : ClassSelectorNameMapping() { + override fun conflict(first: DeclarationDescriptor, second: DeclarationDescriptor) = first.containingDeclaration == second.containingDeclaration } @@ -558,7 +560,7 @@ internal class ObjCExportNamerImpl( override fun getEnumEntrySelector(descriptor: ClassDescriptor): String { assert(descriptor.kind == ClassKind.ENUM_ENTRY) - return enumEntrySelectors.getOrPut(descriptor) { + return enumClassSelectors.getOrPut(descriptor) { // FOO_BAR_BAZ -> fooBarBaz: val name = descriptor.name.asString().split('_').mapIndexed { index, s -> val lower = s.toLowerCase() @@ -569,6 +571,19 @@ internal class ObjCExportNamerImpl( } } + override fun getEnumValuesSelector(descriptor: FunctionDescriptor): String { + val containingDeclaration = descriptor.containingDeclaration + require(containingDeclaration is ClassDescriptor && containingDeclaration.kind == ClassKind.ENUM_CLASS) + require(descriptor.name == StandardNames.ENUM_VALUES) + require(descriptor.dispatchReceiverParameter == null) { "must be static" } + require(descriptor.extensionReceiverParameter == null) { "must be static" } + require(descriptor.valueParameters.isEmpty()) + + return enumClassSelectors.getOrPut(descriptor) { + StringBuilder(descriptor.name.asString()).mangledBySuffixUnderscores() + } + } + override fun getTypeParameterName(typeParameterDescriptor: TypeParameterDescriptor): String { return genericTypeParameterNameMapping.getOrPut(typeParameterDescriptor) { StringBuilder().apply { diff --git a/kotlin-native/backend.native/tests/objcexport/enumValues.kt b/kotlin-native/backend.native/tests/objcexport/enumValues.kt new file mode 100644 index 00000000000..87eea28cd89 --- /dev/null +++ b/kotlin-native/backend.native/tests/objcexport/enumValues.kt @@ -0,0 +1,16 @@ +package enumValues + +enum class EnumLeftRightUpDown { + LEFT, RIGHT, UP, DOWN +} + +enum class EnumOneTwoThreeValues { + ONE, TWO, THREE, VALUES +} + +enum class EnumValuesValues_ { + VALUES, VALUES_ +} + +enum class EmptyEnum { +} diff --git a/kotlin-native/backend.native/tests/objcexport/enumValues.swift b/kotlin-native/backend.native/tests/objcexport/enumValues.swift new file mode 100644 index 00000000000..a19a3f87078 --- /dev/null +++ b/kotlin-native/backend.native/tests/objcexport/enumValues.swift @@ -0,0 +1,47 @@ +import Kt + +private func testEnumValues() throws { + let values = EnumLeftRightUpDown.values() + + try assertEquals(actual: values.size, expected: 4) + + try assertSame(actual: values.get(index: 0), expected: EnumLeftRightUpDown.left) + try assertSame(actual: values.get(index: 1), expected: EnumLeftRightUpDown.right) + try assertSame(actual: values.get(index: 2), expected: EnumLeftRightUpDown.up) + try assertSame(actual: values.get(index: 3), expected: EnumLeftRightUpDown.down) +} + +private func testEnumValuesMangled() throws { + let values = EnumOneTwoThreeValues.values_() + + try assertEquals(actual: values.size, expected: 4) + + try assertSame(actual: values.get(index: 0), expected: EnumOneTwoThreeValues.one) + try assertSame(actual: values.get(index: 1), expected: EnumOneTwoThreeValues.two) + try assertSame(actual: values.get(index: 2), expected: EnumOneTwoThreeValues.three) + try assertSame(actual: values.get(index: 3), expected: EnumOneTwoThreeValues.values) +} + +private func testEnumValuesMangledTwice() throws { + let values = EnumValuesValues_.values__() + + try assertEquals(actual: values.size, expected: 2) + + try assertSame(actual: values.get(index: 0), expected: EnumValuesValues_.values) + try assertSame(actual: values.get(index: 1), expected: EnumValuesValues_.values_) +} + +private func testEnumValuesEmpty() throws { + try assertEquals(actual: EmptyEnum.values().size, expected: 0) +} + +class EnumValuesTests : SimpleTestProvider { + override init() { + super.init() + + test("TestEnumValues", testEnumValues) + test("TestEnumValuesMangled", testEnumValuesMangled) + test("TestEnumValuesMangledTwice", testEnumValuesMangledTwice) + test("TestEnumValuesEmpty", testEnumValuesEmpty) + } +} diff --git a/kotlin-native/backend.native/tests/objcexport/expectedLazy.h b/kotlin-native/backend.native/tests/objcexport/expectedLazy.h index c96ee87923e..85973717d55 100644 --- a/kotlin-native/backend.native/tests/objcexport/expectedLazy.h +++ b/kotlin-native/backend.native/tests/objcexport/expectedLazy.h @@ -331,6 +331,52 @@ __attribute__((swift_name("DeallocRetainKt"))) + (KtKotlinWeakReference *)createWeakReferenceValue:(id)value __attribute__((swift_name("createWeakReference(value:)"))); @end; +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("EnumLeftRightUpDown"))) +@interface KtEnumLeftRightUpDown : KtKotlinEnum ++ (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) KtEnumLeftRightUpDown *left __attribute__((swift_name("left"))); +@property (class, readonly) KtEnumLeftRightUpDown *right __attribute__((swift_name("right"))); +@property (class, readonly) KtEnumLeftRightUpDown *up __attribute__((swift_name("up"))); +@property (class, readonly) KtEnumLeftRightUpDown *down __attribute__((swift_name("down"))); ++ (KtKotlinArray *)values __attribute__((swift_name("values()"))); +@end; + +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("EnumOneTwoThreeValues"))) +@interface KtEnumOneTwoThreeValues : KtKotlinEnum ++ (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) KtEnumOneTwoThreeValues *one __attribute__((swift_name("one"))); +@property (class, readonly) KtEnumOneTwoThreeValues *two __attribute__((swift_name("two"))); +@property (class, readonly) KtEnumOneTwoThreeValues *three __attribute__((swift_name("three"))); +@property (class, readonly) KtEnumOneTwoThreeValues *values __attribute__((swift_name("values"))); ++ (KtKotlinArray *)values __attribute__((swift_name("values()"))); +@end; + +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("EnumValuesValues_"))) +@interface KtEnumValuesValues_ : KtKotlinEnum ++ (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) KtEnumValuesValues_ *values __attribute__((swift_name("values"))); +@property (class, readonly) KtEnumValuesValues_ *values __attribute__((swift_name("values"))); ++ (KtKotlinArray *)values __attribute__((swift_name("values()"))); +@end; + +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("EmptyEnum"))) +@interface KtEmptyEnum : KtKotlinEnum ++ (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)); ++ (KtKotlinArray *)values __attribute__((swift_name("values()"))); +@end; + __attribute__((swift_name("FHolder"))) @interface KtFHolder : KtBase - (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer)); @@ -696,6 +742,7 @@ __attribute__((swift_name("Enumeration"))) @property (class, readonly) KtEnumeration *answer __attribute__((swift_name("answer"))); @property (class, readonly) KtEnumeration *year __attribute__((swift_name("year"))); @property (class, readonly) KtEnumeration *temperature __attribute__((swift_name("temperature"))); ++ (KtKotlinArray *)values __attribute__((swift_name("values()"))); @property (readonly) int32_t enumValue __attribute__((swift_name("enumValue"))); @end; @@ -1407,6 +1454,7 @@ __attribute__((swift_name("TestInvalidIdentifiers.E"))) @property (class, readonly) KtTestInvalidIdentifiersE *_5_ __attribute__((swift_name("_5_"))); @property (class, readonly) KtTestInvalidIdentifiersE *__ __attribute__((swift_name("__"))); @property (class, readonly) KtTestInvalidIdentifiersE *__ __attribute__((swift_name("__"))); ++ (KtKotlinArray *)values __attribute__((swift_name("values()"))); @property (readonly) int32_t value __attribute__((swift_name("value"))); @end;