diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCKClassSupport.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCKClassSupport.kt new file mode 100644 index 00000000000..cf0f94290e0 --- /dev/null +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCKClassSupport.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +package kotlinx.cinterop + +import kotlin.native.internal.KClassImpl +import kotlin.reflect.KClass + +/** + * If [objCClass] is a class generated to Objective-C header for Kotlin class, + * returns [KClass] for that original Kotlin class. + * + * Otherwise returns `null`. + */ +fun getOriginalKotlinClass(objCClass: ObjCClass): KClass<*>? { + val typeInfo = getTypeInfoForClass(objCClass.objcPtr()) + if (typeInfo.isNull()) return null + + return KClassImpl(typeInfo) +} + +/** + * If [objCProtocol] is a protocol generated to Objective-C header for Kotlin class, + * returns [KClass] for that original Kotlin class. + * + * Otherwise returns `null`. + */ +fun getOriginalKotlinClass(objCProtocol: ObjCProtocol): KClass<*>? { + val typeInfo = getTypeInfoForProtocol(objCProtocol.objcPtr()) + if (typeInfo.isNull()) return null + + return KClassImpl(typeInfo) +} + +@SymbolName("Kotlin_ObjCInterop_getTypeInfoForClass") +private external fun getTypeInfoForClass(ptr: NativePtr): NativePtr + +@SymbolName("Kotlin_ObjCInterop_getTypeInfoForProtocol") +private external fun getTypeInfoForProtocol(ptr: NativePtr): NativePtr \ No newline at end of file diff --git a/backend.native/tests/framework/values/expectedLazy.h b/backend.native/tests/framework/values/expectedLazy.h index 21615d539fc..89caabb7e12 100644 --- a/backend.native/tests/framework/values/expectedLazy.h +++ b/backend.native/tests/framework/values/expectedLazy.h @@ -526,6 +526,22 @@ __attribute__((swift_name("MyAbstractList"))) @interface ValuesMyAbstractList : NSObject @end; +__attribute__((objc_subclassing_restricted)) +__attribute__((swift_name("TestKClass"))) +@interface ValuesTestKClass : KotlinBase +- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer)); ++ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead"))); +- (id _Nullable)getKotlinClassClazz:(Class)clazz __attribute__((swift_name("getKotlinClass(clazz:)"))); +- (id _Nullable)getKotlinClassProtocol:(Protocol *)protocol __attribute__((swift_name("getKotlinClass(protocol:)"))); +- (BOOL)isTestKClassKClass:(id)kClass __attribute__((swift_name("isTestKClass(kClass:)"))); +- (BOOL)isIKClass:(id)kClass __attribute__((swift_name("isI(kClass:)"))); +@end; + +__attribute__((swift_name("TestKClassI"))) +@protocol ValuesTestKClassI +@required +@end; + @interface ValuesEnumeration (ValuesKt) - (ValuesEnumeration *)getAnswer __attribute__((swift_name("getAnswer()"))); @end; diff --git a/backend.native/tests/framework/values/values.kt b/backend.native/tests/framework/values/values.kt index 99ae5bc146e..b36eee6866c 100644 --- a/backend.native/tests/framework/values/values.kt +++ b/backend.native/tests/framework/values/values.kt @@ -11,7 +11,9 @@ package conversions import kotlin.native.concurrent.freeze import kotlin.native.concurrent.isFrozen import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KClass import kotlin.reflect.KProperty +import kotlinx.cinterop.* // Constants const val dbl: Double = 3.14 @@ -426,3 +428,13 @@ abstract class MyAbstractList : List fun takeForwardDeclaredClass(obj: objcnames.classes.ForwardDeclaredClass) {} fun takeForwardDeclaredProtocol(obj: objcnames.protocols.ForwardDeclaredProtocol) {} + +class TestKClass { + fun getKotlinClass(clazz: ObjCClass) = getOriginalKotlinClass(clazz) + fun getKotlinClass(protocol: ObjCProtocol) = getOriginalKotlinClass(protocol) + + fun isTestKClass(kClass: KClass<*>): Boolean = (kClass == TestKClass::class) + fun isI(kClass: KClass<*>): Boolean = (kClass == TestKClass.I::class) + + interface I +} diff --git a/backend.native/tests/framework/values/values.swift b/backend.native/tests/framework/values/values.swift index 8c9469ee4cd..f5304e126f9 100644 --- a/backend.native/tests/framework/values/values.swift +++ b/backend.native/tests/framework/values/values.swift @@ -583,6 +583,27 @@ func testGH2959() throws { try assertEquals(actual: GH2959().getI(id: 2959)[0].id, expected: 2959) } +func testKClass() throws { + let test = TestKClass() + + let testKClass = test.getKotlinClass(clazz: TestKClass.self)! + try assertTrue(test.isTestKClass(kClass: testKClass)) + try assertFalse(test.isI(kClass: testKClass)) + try assertEquals(actual: testKClass.simpleName, expected: "TestKClass") + + let iKClass = test.getKotlinClass(protocol: TestKClassI.self)! + try assertFalse(test.isTestKClass(kClass: iKClass)) + try assertTrue(test.isI(kClass: iKClass)) + try assertEquals(actual: iKClass.simpleName, expected: "I") + + try assertTrue(test.getKotlinClass(clazz: NSObject.self) == nil) + try assertTrue(test.getKotlinClass(clazz: PureSwiftClass.self) == nil) + try assertTrue(test.getKotlinClass(clazz: PureSwiftKotlinInterfaceImpl.self) == nil) + try assertTrue(test.getKotlinClass(clazz: Base123.self) == nil) + + try assertTrue(test.getKotlinClass(protocol: NSObjectProtocol.self) == nil) +} + // See https://github.com/JetBrains/kotlin-native/issues/2931 func testGH2931() throws { for i in 0..<50000 { @@ -646,6 +667,7 @@ class ValuesTests : TestProvider { TestCase(name: "TestGH2945", method: withAutorelease(testGH2945)), TestCase(name: "TestGH2830", method: withAutorelease(testGH2830)), TestCase(name: "TestGH2959", method: withAutorelease(testGH2959)), + TestCase(name: "TestKClass", method: withAutorelease(testKClass)), TestCase(name: "TestGH2931", method: withAutorelease(testGH2931)), ] } diff --git a/runtime/src/main/cpp/ObjCExport.mm b/runtime/src/main/cpp/ObjCExport.mm index 491aaf4f1a8..40835a726ca 100644 --- a/runtime/src/main/cpp/ObjCExport.mm +++ b/runtime/src/main/cpp/ObjCExport.mm @@ -350,6 +350,22 @@ static void addProtocolForInterface(Class clazz, const TypeInfo* interfaceInfo) } } +extern "C" const TypeInfo* Kotlin_ObjCInterop_getTypeInfoForClass(Class clazz) { + const TypeInfo* candidate = getAssociatedTypeInfo(clazz); + + if (candidate != nullptr && (candidate->flags_ & TF_OBJC_DYNAMIC) == 0) { + return candidate; + } else { + return nullptr; + } +} + +extern "C" const TypeInfo* Kotlin_ObjCInterop_getTypeInfoForProtocol(Protocol* protocol) { + const ObjCTypeAdapter* typeAdapter = findProtocolAdapter(protocol); + + return (typeAdapter != nullptr) ? typeAdapter->kotlinTypeInfo : nullptr; +} + static const TypeInfo* getOrCreateTypeInfo(Class clazz); static void initializeClass(Class clazz) { @@ -714,7 +730,7 @@ static const TypeInfo* createTypeInfo( TypeInfo* result = (TypeInfo*)konanAllocMemory(sizeof(TypeInfo) + vtable.size() * sizeof(void*)); result->typeInfo_ = result; - result->flags_ = 0; + result->flags_ = TF_OBJC_DYNAMIC; result->instanceSize_ = superType->instanceSize_; result->superType_ = superType; diff --git a/runtime/src/main/cpp/TypeInfo.h b/runtime/src/main/cpp/TypeInfo.h index f6d66203fec..04f0737b90d 100644 --- a/runtime/src/main/cpp/TypeInfo.h +++ b/runtime/src/main/cpp/TypeInfo.h @@ -55,7 +55,8 @@ enum Konan_RuntimeType { enum Konan_TypeFlags { TF_IMMUTABLE = 1 << 0, TF_ACYCLIC = 1 << 1, - TF_INTERFACE = 1 << 2 + TF_INTERFACE = 1 << 2, + TF_OBJC_DYNAMIC = 1 << 3 }; enum Konan_MetaFlags { diff --git a/runtime/src/main/kotlin/kotlin/native/internal/NativePtr.kt b/runtime/src/main/kotlin/kotlin/native/internal/NativePtr.kt index e4e951916e1..b073866184b 100644 --- a/runtime/src/main/kotlin/kotlin/native/internal/NativePtr.kt +++ b/runtime/src/main/kotlin/kotlin/native/internal/NativePtr.kt @@ -26,6 +26,8 @@ class NativePtr @PublishedApi internal constructor(private val value: NonNullNat override fun hashCode() = this.toLong().hashCode() override fun toString() = "0x${this.toLong().toString(16)}" + + internal fun isNull(): Boolean = (value == null) } @PublishedApi