[KN] CInterop: __attribute__((objc_direct)) support

Merge-request: KT-MR-8828
Merged-by: Gleb Lukianets <Gleb.Lukianets@jetbrains.com>
This commit is contained in:
Gleb Lukianets
2023-03-02 10:36:53 +00:00
committed by Space Team
parent ae07d0e9ce
commit fc96eb6d8d
17 changed files with 319 additions and 36 deletions
@@ -1150,14 +1150,15 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole
}
return ObjCMethod(
selector, encoding, parameters, returnType,
isVariadic = clang_Cursor_isVariadic(cursor) != 0,
isClass = isClass,
nsConsumesSelf = clang_Cursor_isObjCConsumingSelfMethod(cursor) != 0,
nsReturnsRetained = clang_Cursor_isObjCReturningRetainedMethod(cursor) != 0,
isOptional = (clang_Cursor_isObjCOptional(cursor) != 0),
isInit = (clang_Cursor_isObjCInitMethod(cursor) != 0),
isExplicitlyDesignatedInitializer = hasAttribute(cursor, OBJC_DESGINATED_INITIALIZER)
selector, encoding, parameters, returnType,
isVariadic = clang_Cursor_isVariadic(cursor) != 0,
isClass = isClass,
nsConsumesSelf = clang_Cursor_isObjCConsumingSelfMethod(cursor) != 0,
nsReturnsRetained = clang_Cursor_isObjCReturningRetainedMethod(cursor) != 0,
isOptional = (clang_Cursor_isObjCOptional(cursor) != 0),
isInit = (clang_Cursor_isObjCInitMethod(cursor) != 0),
isExplicitlyDesignatedInitializer = hasAttribute(cursor, OBJC_DESIGNATED_INITIALIZER),
isDirect = hasAttribute(cursor, OBJC_DIRECT),
)
}
@@ -1201,7 +1202,8 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole
}
private val NS_CONSUMED = "ns_consumed"
private val OBJC_DESGINATED_INITIALIZER = "objc_designated_initializer"
private val OBJC_DESIGNATED_INITIALIZER = "objc_designated_initializer"
private val OBJC_DIRECT = "objc_direct"
private fun hasAttribute(cursor: CValue<CXCursor>, name: String): Boolean {
var result = false
@@ -257,7 +257,7 @@ sealed class ObjCClassOrProtocol(val name: String) : ObjCContainer(), TypeDeclar
data class ObjCMethod(
val selector: String, val encoding: String, val parameters: List<Parameter>, private val returnType: Type,
val isVariadic: Boolean, val isClass: Boolean, val nsConsumesSelf: Boolean, val nsReturnsRetained: Boolean,
val isOptional: Boolean, val isInit: Boolean, val isExplicitlyDesignatedInitializer: Boolean
val isOptional: Boolean, val isInit: Boolean, val isExplicitlyDesignatedInitializer: Boolean, val isDirect: Boolean
) {
fun returnsInstancetype(): Boolean = returnType is ObjCInstanceType
@@ -112,6 +112,10 @@ annotation class ExternalObjCClass(val protocolGetter: String = "", val binaryNa
@Retention(AnnotationRetention.BINARY)
annotation class ObjCMethod(val selector: String, val encoding: String, val isStret: Boolean = false)
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.BINARY)
annotation class ObjCDirect(val symbol: String)
@Target(AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.BINARY)
annotation class ObjCConstructor(val initSelector: String, val designated: Boolean)
@@ -132,7 +132,7 @@ private class ObjCMethodStubBuilder(
kotlinMethodParameters = method.getKotlinParameters(context, forConstructorOrFactory = false)
external = (container !is ObjCProtocol)
modality = when (container) {
is ObjCClass -> MemberStubModality.OPEN
is ObjCClass -> if (method.isDirect) MemberStubModality.FINAL else MemberStubModality.OPEN
is ObjCProtocol -> if (method.isOptional) MemberStubModality.OPEN else MemberStubModality.ABSTRACT
is ObjCCategory -> MemberStubModality.FINAL
}
@@ -145,7 +145,17 @@ private class ObjCMethodStubBuilder(
private fun buildObjCMethodAnnotations(main: AnnotationStub): List<AnnotationStub> = listOfNotNull(
main,
AnnotationStub.ObjC.ConsumesReceiver.takeIf { method.nsConsumesSelf },
AnnotationStub.ObjC.ReturnsRetained.takeIf { method.nsReturnsRetained }
AnnotationStub.ObjC.ReturnsRetained.takeIf { method.nsReturnsRetained },
if (method.isDirect) {
when (container) {
is ObjCClass -> container.name
is ObjCCategory -> container.clazz.name
is ObjCProtocol -> null
}?.let {
val prefix = if (method.isClass) '+' else '-'
AnnotationStub.ObjC.Direct("$prefix[$it ${method.selector}]")
}
} else { null },
)
fun isDefaultConstructor(): Boolean =
@@ -185,6 +185,9 @@ sealed class AnnotationStub(val classifier: Classifier) {
class Method(val selector: String, val encoding: String, val isStret: Boolean = false) :
ObjC(Classifier.topLevel(cinteropPackage, "ObjCMethod"))
class Direct(val symbol: String) :
ObjC(Classifier.topLevel(cinteropPackage, "ObjCDirect"))
class Factory(val selector: String, val encoding: String, val isStret: Boolean = false) :
ObjC(Classifier.topLevel(cinteropPackage, "ObjCFactory"))
@@ -394,6 +394,9 @@ private class MappingExtensions(
("encoding" to encoding).asAnnotationArgument(),
("isStret" to KmAnnotationArgument.BooleanValue(isStret))
)
is AnnotationStub.ObjC.Direct -> mapOfNotNull(
("symbol" to symbol).asAnnotationArgument(),
)
is AnnotationStub.ObjC.Factory -> mapOfNotNull(
("selector" to selector).asAnnotationArgument(),
("encoding" to encoding).asAnnotationArgument(),
@@ -458,6 +458,7 @@ class StubIrTextEmitter(
val encoding = annotationStub.encoding.quoteAsKotlinLiteral()
"@ObjCMethod($selector, $encoding$stret)"
}
is AnnotationStub.ObjC.Direct -> "@ObjCDirect(${annotationStub.symbol.quoteAsKotlinLiteral()})"
is AnnotationStub.ObjC.Factory -> {
val stret = if (annotationStub.isStret) ", true" else ""
val selector = annotationStub.selector.quoteAsKotlinLiteral()
@@ -0,0 +1,60 @@
/*
* 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 org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.native.interop.indexer.buildNativeIndex
import org.junit.Assume
import org.junit.BeforeClass
import org.junit.Test
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class ObjCMethodSignaturesTest : InteropTestsBase() {
companion object {
@BeforeClass
@JvmStatic
fun assumeMacOS() {
Assume.assumeTrue(HostManager.hostIsMac)
}
}
@Test
fun `ObjC Direct`() {
val files = TempFiles("ObjCDirect")
files.file("header.h", """
#import <Foundation/Foundation.h>
@interface Foo : NSObject
+ (void)direct __attribute__((objc_direct));
- (void)direct __attribute__((objc_direct));
@end
@interface Foo(Ext)
+ (void)directExt __attribute__((objc_direct));
- (void)directExt __attribute__((objc_direct));
@end
""".trimIndent())
val defFile = files.file("direct.def", """
language = Objective-C
headers = header.h
""".trimIndent())
val library = buildNativeLibraryFrom(defFile, files.directory)
val index = buildNativeIndex(library, false).index
index.objCClasses.find { it.name == "Foo" }.let { cls ->
assertNotNull(cls, "Class 'Foo' not found in native library $library")
assertNotNull(cls.methods.find { it.selector == "direct" && it.isClass && it.isDirect })
assertNotNull(cls.methods.find { it.selector == "direct" && !it.isClass && it.isDirect })
}
index.objCCategories.find { it.name == "Ext" && it.clazz.name == "Foo" }.let { cat ->
assertNotNull(cat, "Category 'Foo(Ext)' not found in native library $library")
assertNotNull(cat.methods.find { it.selector == "directExt" && it.isClass && it.isDirect })
assertNotNull(cat.methods.find { it.selector == "directExt" && !it.isClass && it.isDirect })
}
}
}